1219 lines
40 KiB
Python
1219 lines
40 KiB
Python
# -*- coding: utf-8 -*-
|
|
#*****************************************************************************
|
|
# Copyright (C) 2003-2006 Gary Bishop.
|
|
# Copyright (C) 2006 Michael Graz. <mgraz@plan10.com>
|
|
# Copyright (C) 2006 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
|
|
#
|
|
# Distributed under the terms of the BSD License. The full license is in
|
|
# the file COPYING, distributed as part of this software.
|
|
#*****************************************************************************
|
|
import os
|
|
import pyreadline.logger as logger
|
|
from pyreadline.logger import log,log_sock
|
|
import pyreadline.lineeditor.lineobj as lineobj
|
|
import pyreadline.lineeditor.history as history
|
|
import basemode
|
|
|
|
class ViMode(basemode.BaseMode):
|
|
mode="vi"
|
|
def __init__(self,rlobj):
|
|
super(ViMode,self).__init__(rlobj)
|
|
self.__vi_insert_mode = None
|
|
|
|
def __repr__(self):
|
|
return "<ViMode>"
|
|
|
|
def _readline_from_keyboard(self):
|
|
c=self.console
|
|
while 1:
|
|
self._update_line()
|
|
event = c.getkeypress()
|
|
if self.next_meta:
|
|
self.next_meta = False
|
|
control, meta, shift, code = event.keyinfo
|
|
event.keyinfo = (control, True, shift, code)
|
|
|
|
#Process exit keys. Only exit on empty line
|
|
if event.keyinfo in self.exit_dispatch:
|
|
if lineobj.EndOfLine(self.l_buffer) == 0:
|
|
raise EOFError
|
|
|
|
dispatch_func = self.key_dispatch.get(event.keyinfo.tuple(),self.vi_key)
|
|
log("readline from keyboard:%s->%s"%(event.keyinfo.tuple(),dispatch_func))
|
|
r = None
|
|
if dispatch_func:
|
|
r = dispatch_func(event)
|
|
self.l_buffer.push_undo()
|
|
|
|
self.previous_func = dispatch_func
|
|
if r:
|
|
self._update_line()
|
|
break
|
|
|
|
def readline(self, prompt=''):
|
|
'''Try to act like GNU readline.'''
|
|
# handle startup_hook
|
|
if self.first_prompt:
|
|
self.first_prompt = False
|
|
if self.startup_hook:
|
|
try:
|
|
self.startup_hook()
|
|
except:
|
|
print 'startup hook failed'
|
|
traceback.print_exc()
|
|
|
|
c = self.console
|
|
self.l_buffer.reset_line()
|
|
self.prompt = prompt
|
|
self._print_prompt()
|
|
|
|
if self.pre_input_hook:
|
|
try:
|
|
self.pre_input_hook()
|
|
except:
|
|
print 'pre_input_hook failed'
|
|
traceback.print_exc()
|
|
self.pre_input_hook = None
|
|
|
|
log("in readline: %s"%self.paste_line_buffer)
|
|
if len(self.paste_line_buffer)>0:
|
|
self.l_buffer=lineobj.ReadlineTextBuffer(self.paste_line_buffer[0])
|
|
self._update_line()
|
|
self.paste_line_buffer=self.paste_line_buffer[1:]
|
|
c.write('\r\n')
|
|
else:
|
|
self._readline_from_keyboard()
|
|
c.write('\r\n')
|
|
|
|
self.add_history(self.l_buffer.copy())
|
|
|
|
log('returning(%s)' % self.l_buffer.get_line_text())
|
|
return self.l_buffer.get_line_text() + '\n'
|
|
|
|
### Methods below here are bindable emacs functions
|
|
|
|
def init_editing_mode(self, e): # (M-C-j)
|
|
'''Initialize vi editingmode'''
|
|
self.show_all_if_ambiguous = 'on'
|
|
self.key_dispatch = {}
|
|
self.__vi_insert_mode = None
|
|
self._vi_command = None
|
|
self._vi_command_edit = None
|
|
self._vi_key_find_char = None
|
|
self._vi_key_find_direction = True
|
|
self._vi_yank_buffer = None
|
|
self._vi_multiplier1 = ''
|
|
self._vi_multiplier2 = ''
|
|
self._vi_undo_stack = []
|
|
self._vi_undo_cursor = -1
|
|
self._vi_current = None
|
|
self._vi_search_text = ''
|
|
self.vi_save_line ()
|
|
self.vi_set_insert_mode (True)
|
|
# make ' ' to ~ self insert
|
|
for c in range(ord(' '), 127):
|
|
self._bind_key('%s' % chr(c), self.vi_key)
|
|
self._bind_key('BackSpace', self.vi_backspace)
|
|
self._bind_key('Escape', self.vi_escape)
|
|
self._bind_key('Return', self.vi_accept_line)
|
|
|
|
self._bind_key('Left', self.backward_char)
|
|
self._bind_key('Right', self.forward_char)
|
|
self._bind_key('Home', self.beginning_of_line)
|
|
self._bind_key('End', self.end_of_line)
|
|
self._bind_key('Delete', self.delete_char)
|
|
|
|
self._bind_key('Control-d', self.vi_eof)
|
|
self._bind_key('Control-z', self.vi_eof)
|
|
self._bind_key('Control-r', self.vi_redo)
|
|
self._bind_key('Up', self.vi_arrow_up)
|
|
self._bind_key('Control-p', self.vi_up)
|
|
self._bind_key('Down', self.vi_arrow_down)
|
|
self._bind_key('Control-n', self.vi_down)
|
|
self._bind_key('Tab', self.vi_complete)
|
|
# self._bind_key('Control-e', self.emacs)
|
|
|
|
def vi_key (self, e):
|
|
if not self._vi_command:
|
|
self._vi_command = ViCommand (self)
|
|
elif self._vi_command.is_end:
|
|
if self._vi_command.is_edit:
|
|
self._vi_command_edit = self._vi_command
|
|
self._vi_command = ViCommand (self)
|
|
self._vi_command.add_char (e.char)
|
|
|
|
def vi_error (self):
|
|
self._bell ()
|
|
|
|
def vi_get_is_insert_mode (self):
|
|
return self.__vi_insert_mode
|
|
vi_is_insert_mode = property (vi_get_is_insert_mode)
|
|
|
|
def vi_escape (self, e):
|
|
if self.vi_is_insert_mode:
|
|
if self._vi_command:
|
|
self._vi_command.add_char (e.char)
|
|
else:
|
|
self._vi_command = ViCommand (self)
|
|
self.vi_set_insert_mode (False)
|
|
# if self.line_cursor > 0:
|
|
# self.line_cursor -= 1
|
|
self.l_buffer.point=lineobj.PrevChar
|
|
elif self._vi_command and self._vi_command.is_replace_one:
|
|
self._vi_command.add_char (e.char)
|
|
else:
|
|
self.vi_error ()
|
|
|
|
def vi_backspace (self, e):
|
|
if self._vi_command:
|
|
self._vi_command.add_char (e.char)
|
|
else:
|
|
self._vi_do_backspace (self._vi_command)
|
|
|
|
def _vi_do_backspace (self, vi_cmd):
|
|
if self.vi_is_insert_mode or (self._vi_command and self._vi_command.is_search):
|
|
if self.l_buffer.point > 0:
|
|
self.l_buffer.point -= 1
|
|
if self.l_buffer.overwrite:
|
|
try:
|
|
prev = self._vi_undo_stack [self._vi_undo_cursor][1][self.l_buffer.point ]
|
|
self.l_buffer.line_buffer [self.l_buffer.point] = prev
|
|
except IndexError:
|
|
del self.l_buffer.line_buffer [self.l_buffer.point ]
|
|
else:
|
|
self.vi_save_line ()
|
|
del self.l_buffer.line_buffer [self.l_buffer.point ]
|
|
|
|
def vi_accept_line (self, e):
|
|
if self._vi_command and self._vi_command.is_search:
|
|
self._vi_command.do_search ()
|
|
return False
|
|
self._vi_command = None
|
|
self.vi_set_insert_mode (True)
|
|
self._vi_undo_stack = []
|
|
self._vi_undo_cursor = -1
|
|
self._vi_current = None
|
|
return self.accept_line (e)
|
|
|
|
def vi_eof (self, e):
|
|
raise EOFError
|
|
|
|
def vi_set_insert_mode (self, value):
|
|
if self.__vi_insert_mode == value:
|
|
return
|
|
self.__vi_insert_mode = value
|
|
if value:
|
|
self.vi_save_line ()
|
|
self.console.cursor (size=25)
|
|
else:
|
|
self.console.cursor (size=100)
|
|
|
|
def vi_undo_restart (self):
|
|
tpl_undo = (self.l_buffer.point, self.l_buffer.line_buffer[:], )
|
|
self._vi_undo_stack = [tpl_undo]
|
|
self._vi_undo_cursor = 0
|
|
|
|
def vi_save_line (self):
|
|
if self._vi_undo_stack and self._vi_undo_cursor >= 0:
|
|
del self._vi_undo_stack [self._vi_undo_cursor + 1 : ]
|
|
# tpl_undo = (self.l_buffer.point, self.l_buffer[:], )
|
|
tpl_undo = (self.l_buffer.point, self.l_buffer.line_buffer[:], )
|
|
if not self._vi_undo_stack or self._vi_undo_stack[self._vi_undo_cursor][1] != tpl_undo[1]:
|
|
self._vi_undo_stack.append (tpl_undo)
|
|
self._vi_undo_cursor += 1
|
|
|
|
def vi_undo_prepare (self):
|
|
if self._vi_undo_cursor == len(self._vi_undo_stack)-1:
|
|
self.vi_save_line ()
|
|
|
|
def vi_undo (self, do_pop=True):
|
|
self.vi_undo_prepare ()
|
|
if not self._vi_undo_stack or self._vi_undo_cursor <= 0:
|
|
self.vi_error ()
|
|
return
|
|
self._vi_undo_cursor -= 1
|
|
self.vi_undo_assign ()
|
|
|
|
def vi_undo_all (self):
|
|
self.vi_undo_prepare ()
|
|
if self._vi_undo_cursor > 0:
|
|
self._vi_undo_cursor = 0
|
|
self.vi_undo_assign ()
|
|
else:
|
|
self.vi_error ()
|
|
|
|
def vi_undo_assign (self):
|
|
tpl_undo = self._vi_undo_stack [self._vi_undo_cursor]
|
|
self.l_buffer.line_buffer = tpl_undo [1][:]
|
|
self.l_buffer.point = tpl_undo [0]
|
|
|
|
def vi_redo (self, e):
|
|
if self._vi_undo_cursor >= len(self._vi_undo_stack)-1:
|
|
self.vi_error ()
|
|
return
|
|
self._vi_undo_cursor += 1
|
|
self.vi_undo_assign ()
|
|
|
|
def vi_search (self, rng):
|
|
for i in rng:
|
|
line_history = self._history.history [i]
|
|
pos = line_history.get_line_text().find (self._vi_search_text)
|
|
if pos >= 0:
|
|
self._history.history_cursor = i
|
|
self.l_buffer.line_buffer = list (line_history.line_buffer)
|
|
self.l_buffer.point = pos
|
|
self.vi_undo_restart ()
|
|
return True
|
|
self._bell ()
|
|
return False
|
|
|
|
def vi_search_first (self):
|
|
text = ''.join (self.l_buffer.line_buffer [1:])
|
|
if text:
|
|
self._vi_search_text = text
|
|
position = len (self._history.history) - 1
|
|
elif self._vi_search_text:
|
|
position = self._history.history_cursor - 1
|
|
else:
|
|
self.vi_error ()
|
|
self.vi_undo ()
|
|
return
|
|
if not self.vi_search (range (position, -1, -1)):
|
|
# Here: search text not found
|
|
self.vi_undo ()
|
|
|
|
def vi_search_again_backward (self):
|
|
self.vi_search (range (self._history.history_cursor-1, -1, -1))
|
|
|
|
def vi_search_again_forward (self):
|
|
self.vi_search (range (self._history.history_cursor+1, len(self._history.history)))
|
|
|
|
def vi_up (self, e):
|
|
if self._history.history_cursor == len(self._history.history):
|
|
self._vi_current = self.l_buffer.line_buffer [:]
|
|
# self._history.previous_history (e)
|
|
self._history.previous_history (self.l_buffer)
|
|
if self.vi_is_insert_mode:
|
|
self.end_of_line (e)
|
|
else:
|
|
self.beginning_of_line (e)
|
|
self.vi_undo_restart ()
|
|
|
|
def vi_down (self, e):
|
|
if self._history.history_cursor >= len(self._history.history):
|
|
self.vi_error ()
|
|
return
|
|
if self._history.history_cursor < len(self._history.history) - 1:
|
|
# self._history.next_history (e)
|
|
self._history.next_history (self.l_buffer)
|
|
if self.vi_is_insert_mode:
|
|
self.end_of_line (e)
|
|
else:
|
|
self.beginning_of_line (e)
|
|
self.vi_undo_restart ()
|
|
elif self._vi_current is not None:
|
|
self._history.history_cursor = len(self._history.history)
|
|
self.l_buffer.line_buffer = self._vi_current
|
|
self.end_of_line (e)
|
|
if not self.vi_is_insert_mode and self.l_buffer.point > 0:
|
|
self.l_buffer.point -= 1
|
|
self._vi_current = None
|
|
else:
|
|
self.vi_error ()
|
|
return
|
|
|
|
def vi_arrow_up (self, e):
|
|
self.vi_set_insert_mode (True)
|
|
self.vi_up (e)
|
|
self.vi_save_line ()
|
|
|
|
def vi_arrow_down (self, e):
|
|
self.vi_set_insert_mode (True)
|
|
self.vi_down (e)
|
|
self.vi_save_line ()
|
|
|
|
def vi_complete (self, e):
|
|
text = self.l_buffer.get_line_text ()
|
|
if text and not text.isspace ():
|
|
return self.complete (e)
|
|
else:
|
|
return self.vi_key (e)
|
|
|
|
# vi input states
|
|
# sequence of possible states are in the order below
|
|
_VI_BEGIN = 'vi_begin'
|
|
_VI_MULTI1 = 'vi_multi1'
|
|
_VI_ACTION = 'vi_action'
|
|
_VI_MULTI2 = 'vi_multi2'
|
|
_VI_MOTION = 'vi_motion'
|
|
_VI_MOTION_ARGUMENT = 'vi_motion_argument'
|
|
_VI_REPLACE_ONE = 'vi_replace_one'
|
|
_VI_TEXT = 'vi_text'
|
|
_VI_SEARCH = 'vi_search'
|
|
_VI_END = 'vi_end'
|
|
|
|
# vi helper class
|
|
class ViCommand:
|
|
def __init__ (self, readline):
|
|
self.readline = readline
|
|
self.lst_char = []
|
|
self.state = _VI_BEGIN
|
|
self.action = self.movement
|
|
self.motion = None
|
|
self.motion_argument = None
|
|
self.text = None
|
|
self.pos_motion = None
|
|
self.is_edit = False
|
|
self.is_overwrite = False
|
|
self.is_error = False
|
|
self.is_star = False
|
|
self.delete_left = 0
|
|
self.delete_right = 0
|
|
self.readline._vi_multiplier1 = ''
|
|
self.readline._vi_multiplier2 = ''
|
|
self.set_override_multiplier (0)
|
|
self.skip_multipler = False
|
|
self.dct_fcn = {
|
|
ord('$') : self.key_dollar,
|
|
ord('^') : self.key_hat,
|
|
ord(';') : self.key_semicolon,
|
|
ord(',') : self.key_comma,
|
|
ord('%') : self.key_percent,
|
|
ord('.') : self.key_dot,
|
|
ord('/') : self.key_slash,
|
|
ord('*') : self.key_star,
|
|
ord('|') : self.key_bar,
|
|
ord('~') : self.key_tilde,
|
|
8 : self.key_backspace,
|
|
}
|
|
|
|
def add_char (self, char):
|
|
self.lst_char.append (char)
|
|
if self.state == _VI_BEGIN and self.readline.vi_is_insert_mode:
|
|
self.readline.vi_save_line ()
|
|
self.state = _VI_TEXT
|
|
if self.state == _VI_SEARCH:
|
|
if char == '\x08': # backspace
|
|
self.key_backspace (char)
|
|
else:
|
|
self.set_text (char)
|
|
return
|
|
if self.state == _VI_TEXT:
|
|
if char == '\x1b': # escape
|
|
self.escape (char)
|
|
elif char == '\x09': # tab
|
|
ts = self.readline.tabstop
|
|
ws = ' ' * (ts - (self.readline.l_buffer.point%ts))
|
|
self.set_text (ws)
|
|
elif char == '\x08': # backspace
|
|
self.key_backspace (char)
|
|
else:
|
|
self.set_text (char)
|
|
return
|
|
if self.state == _VI_MOTION_ARGUMENT:
|
|
self.set_motion_argument (char)
|
|
return
|
|
if self.state == _VI_REPLACE_ONE:
|
|
self.replace_one (char)
|
|
return
|
|
try:
|
|
fcn_instance = self.dct_fcn [ord(char)]
|
|
except:
|
|
fcn_instance = getattr (self, 'key_%s' % char, None)
|
|
if fcn_instance:
|
|
fcn_instance (char)
|
|
return
|
|
if char.isdigit ():
|
|
self.key_digit (char)
|
|
return
|
|
# Here: could not process key
|
|
self.error ()
|
|
|
|
def set_text (self, text):
|
|
if self.text is None:
|
|
self.text = text
|
|
else:
|
|
self.text += text
|
|
self.set_buffer (text)
|
|
|
|
def set_buffer (self, text):
|
|
for char in text:
|
|
if not self.char_isprint (char):
|
|
continue
|
|
# self.readline.l_buffer.insert_text(char)
|
|
# continue
|
|
# #overwrite in l_buffer obj
|
|
if self.is_overwrite:
|
|
if self.readline.l_buffer.point < len (self.readline.l_buffer.line_buffer):
|
|
# self.readline.l_buffer[self.l_buffer.point]=char
|
|
self.readline.l_buffer.line_buffer [self.readline.l_buffer.point] = char
|
|
else:
|
|
# self.readline.l_buffer.insert_text(char)
|
|
self.readline.l_buffer.line_buffer.append (char)
|
|
else:
|
|
# self.readline.l_buffer.insert_text(char)
|
|
self.readline.l_buffer.line_buffer.insert (self.readline.l_buffer.point, char)
|
|
self.readline.l_buffer.point += 1
|
|
|
|
def replace_one (self, char):
|
|
if char == '\x1b': # escape
|
|
self.end ()
|
|
return
|
|
self.is_edit = True
|
|
self.readline.vi_save_line ()
|
|
times = self.get_multiplier ()
|
|
cursor = self.readline.l_buffer.point
|
|
self.readline.l_buffer.line_buffer [cursor : cursor + times] = char * times
|
|
if times > 1:
|
|
self.readline.l_buffer.point += (times - 1)
|
|
self.end ()
|
|
|
|
def char_isprint (self, char):
|
|
return ord(char) >= ord(' ') and ord(char) <= ord('~')
|
|
|
|
def key_dollar (self, char):
|
|
self.motion = self.motion_end_in_line
|
|
self.delete_right = 1
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_hat (self, char):
|
|
self.motion = self.motion_beginning_of_line
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_0 (self, char):
|
|
if self.state in [_VI_BEGIN, _VI_ACTION]:
|
|
self.key_hat (char)
|
|
else:
|
|
self.key_digit (char)
|
|
|
|
def key_digit (self, char):
|
|
if self.state in [_VI_BEGIN, _VI_MULTI1]:
|
|
self.readline._vi_multiplier1 += char
|
|
self.readline._vi_multiplier2 = ''
|
|
self.state = _VI_MULTI1
|
|
elif self.state in [_VI_ACTION, _VI_MULTI2]:
|
|
self.readline._vi_multiplier2 += char
|
|
self.state = _VI_MULTI2
|
|
|
|
def key_w (self, char):
|
|
if self.action == self.change:
|
|
self.key_e (char)
|
|
return
|
|
self.motion = self.motion_word_short
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_W (self, char):
|
|
if self.action == self.change:
|
|
self.key_E (char)
|
|
return
|
|
self.motion = self.motion_word_long
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_e (self, char):
|
|
self.motion = self.motion_end_short
|
|
self.state = _VI_MOTION
|
|
self.delete_right = 1
|
|
self.apply ()
|
|
|
|
def key_E (self, char):
|
|
self.motion = self.motion_end_long
|
|
self.state = _VI_MOTION
|
|
self.delete_right = 1
|
|
self.apply ()
|
|
|
|
def key_b (self, char):
|
|
self.motion = self.motion_back_short
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_B (self, char):
|
|
self.motion = self.motion_back_long
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_f (self, char):
|
|
self.readline._vi_key_find_direction = True
|
|
self.motion = self.motion_find_char_forward
|
|
self.delete_right = 1
|
|
self.state = _VI_MOTION_ARGUMENT
|
|
|
|
def key_F (self, char):
|
|
self.readline._vi_key_find_direction = False
|
|
self.motion = self.motion_find_char_backward
|
|
self.delete_left = 1
|
|
self.state = _VI_MOTION_ARGUMENT
|
|
|
|
def key_t (self, char):
|
|
self.motion = self.motion_to_char_forward
|
|
self.delete_right = 1
|
|
self.state = _VI_MOTION_ARGUMENT
|
|
|
|
def key_T (self, char):
|
|
self.motion = self.motion_to_char_backward
|
|
self.state = _VI_MOTION_ARGUMENT
|
|
|
|
def key_j (self, char):
|
|
self.readline.vi_down (ViEvent (char))
|
|
self.state = _VI_END
|
|
|
|
def key_k (self, char):
|
|
self.readline.vi_up (ViEvent (char))
|
|
self.state = _VI_END
|
|
|
|
def key_semicolon (self, char):
|
|
if self.readline._vi_key_find_char is None:
|
|
self.error ()
|
|
return
|
|
if self.readline._vi_key_find_direction:
|
|
self.motion = self.motion_find_char_forward
|
|
else:
|
|
self.motion = self.motion_find_char_backward
|
|
self.set_motion_argument (self.readline._vi_key_find_char)
|
|
|
|
def key_comma (self, char):
|
|
if self.readline._vi_key_find_char is None:
|
|
self.error ()
|
|
return
|
|
if self.readline._vi_key_find_direction:
|
|
self.motion = self.motion_find_char_backward
|
|
else:
|
|
self.motion = self.motion_find_char_forward
|
|
self.set_motion_argument (self.readline._vi_key_find_char)
|
|
|
|
def key_percent (self, char):
|
|
'''find matching <([{}])>'''
|
|
self.motion = self.motion_matching
|
|
self.delete_right = 1
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_dot (self, char):
|
|
vi_cmd_edit = self.readline._vi_command_edit
|
|
if not vi_cmd_edit:
|
|
return
|
|
if vi_cmd_edit.is_star:
|
|
self.key_star (char)
|
|
return
|
|
if self.has_multiplier ():
|
|
count = self.get_multiplier ()
|
|
else:
|
|
count = 0
|
|
# Create the ViCommand object after getting multipler from self
|
|
# Side effect of the ViCommand creation is resetting of global multipliers
|
|
vi_cmd = ViCommand (self.readline)
|
|
if count >= 1:
|
|
vi_cmd.set_override_multiplier (count)
|
|
vi_cmd_edit.set_override_multiplier (count)
|
|
elif vi_cmd_edit.override_multiplier:
|
|
vi_cmd.set_override_multiplier (vi_cmd_edit.override_multiplier)
|
|
for char in vi_cmd_edit.lst_char:
|
|
vi_cmd.add_char (char)
|
|
if vi_cmd_edit.is_overwrite and self.readline.l_buffer.point > 0:
|
|
self.readline.l_buffer.point -= 1
|
|
self.readline.vi_set_insert_mode (False)
|
|
self.end ()
|
|
|
|
def key_slash (self, char):
|
|
self.readline.vi_save_line ()
|
|
self.readline.l_buffer.line_buffer=['/']
|
|
self.readline.l_buffer.point= 1
|
|
self.state = _VI_SEARCH
|
|
|
|
def key_star (self, char):
|
|
self.is_star = True
|
|
self.is_edit = True
|
|
self.readline.vi_save_line ()
|
|
completions = self.readline._get_completions()
|
|
if completions:
|
|
text = ' '.join (completions) + ' '
|
|
self.readline.l_buffer.line_buffer [self.readline.begidx : self.readline.endidx + 1] = list (text)
|
|
prefix_len = self.readline.endidx - self.readline.begidx
|
|
self.readline.l_buffer.point += len(text) - prefix_len
|
|
self.readline.vi_set_insert_mode (True)
|
|
else:
|
|
self.error ()
|
|
self.state = _VI_TEXT
|
|
|
|
def key_bar (self, char):
|
|
self.motion = self.motion_column
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_tilde (self, char):
|
|
self.is_edit = True
|
|
self.readline.vi_save_line ()
|
|
for i in range (self.get_multiplier()):
|
|
try:
|
|
c = self.readline.l_buffer.line_buffer [self.readline.l_buffer.point]
|
|
if c.isupper ():
|
|
self.readline.l_buffer.line_buffer [self.readline.l_buffer.point] = c.lower()
|
|
elif c.islower ():
|
|
self.readline.l_buffer.line_buffer [self.readline.l_buffer.point] = c.upper()
|
|
self.readline.l_buffer.point += 1
|
|
except IndexError:
|
|
break
|
|
self.end ()
|
|
|
|
def key_h (self, char):
|
|
self.motion = self.motion_left
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_backspace (self, char):
|
|
if self.state in [_VI_TEXT, _VI_SEARCH]:
|
|
if self.text and len(self.text):
|
|
self.text = self.text [:-1]
|
|
try:
|
|
# Remove backspaces for potential dot command
|
|
self.lst_char.pop ()
|
|
self.lst_char.pop ()
|
|
except IndexError:
|
|
pass
|
|
else:
|
|
self.key_h (char)
|
|
self.readline._vi_do_backspace (self)
|
|
if self.state == _VI_SEARCH and not (self.readline.l_buffer.line_buffer):
|
|
self.state = _VI_BEGIN
|
|
|
|
def key_l (self, char):
|
|
self.motion = self.motion_right
|
|
self.state = _VI_MOTION
|
|
self.apply ()
|
|
|
|
def key_i (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_TEXT
|
|
self.readline.vi_set_insert_mode (True)
|
|
|
|
def key_I (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_TEXT
|
|
self.readline.vi_set_insert_mode (True)
|
|
self.readline.l_buffer.point = 0
|
|
|
|
def key_a (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_TEXT
|
|
self.readline.vi_set_insert_mode (True)
|
|
if len (self.readline.l_buffer.line_buffer):
|
|
self.readline.l_buffer.point += 1
|
|
|
|
def key_A (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_TEXT
|
|
self.readline.vi_set_insert_mode (True)
|
|
self.readline.l_buffer.point = len (self.readline.l_buffer.line_buffer)
|
|
|
|
def key_d (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_ACTION
|
|
self.action = self.delete
|
|
|
|
def key_D (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_ACTION
|
|
self.action = self.delete_end_of_line
|
|
self.apply ()
|
|
|
|
def key_x (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_ACTION
|
|
self.action = self.delete_char
|
|
self.apply ()
|
|
|
|
def key_X (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_ACTION
|
|
self.action = self.delete_prev_char
|
|
self.apply ()
|
|
|
|
def key_s (self, char):
|
|
self.is_edit = True
|
|
i1 = self.readline.l_buffer.point
|
|
i2 = self.readline.l_buffer.point + self.get_multiplier ()
|
|
self.skip_multipler = True
|
|
self.readline.vi_set_insert_mode (True)
|
|
del self.readline.l_buffer.line_buffer [i1 : i2]
|
|
self.state = _VI_TEXT
|
|
|
|
def key_S (self, char):
|
|
self.is_edit = True
|
|
self.readline.vi_set_insert_mode (True)
|
|
self.readline.l_buffer.line_buffer = []
|
|
self.readline.l_buffer.point = 0
|
|
self.state = _VI_TEXT
|
|
|
|
def key_c (self, char):
|
|
self.is_edit = True
|
|
self.state = _VI_ACTION
|
|
self.action = self.change
|
|
|
|
def key_C (self, char):
|
|
self.is_edit = True
|
|
self.readline.vi_set_insert_mode (True)
|
|
del self.readline.l_buffer.line_buffer [self.readline.l_buffer.point : ]
|
|
self.state = _VI_TEXT
|
|
|
|
def key_r (self, char):
|
|
self.state = _VI_REPLACE_ONE
|
|
|
|
def key_R (self, char):
|
|
self.is_edit = True
|
|
self.is_overwrite = True
|
|
self.readline.l_buffer.overwrite=True
|
|
self.readline.vi_set_insert_mode (True)
|
|
self.state = _VI_TEXT
|
|
|
|
def key_y (self, char):
|
|
self._state = _VI_ACTION
|
|
self.action = self.yank
|
|
|
|
def key_Y (self, char):
|
|
self.readline._vi_yank_buffer = self.readline.l_buffer.get_line_text()
|
|
self.end ()
|
|
|
|
def key_p (self, char):
|
|
if not self.readline._vi_yank_buffer:
|
|
return
|
|
self.is_edit = True
|
|
self.readline.vi_save_line ()
|
|
self.readline.l_buffer.point += 1
|
|
self.readline.l_buffer.insert_text (self.readline._vi_yank_buffer * self.get_multiplier ())
|
|
self.readline.l_buffer.point -= 1
|
|
self.state = _VI_END
|
|
|
|
def key_P (self, char):
|
|
if not self.readline._vi_yank_buffer:
|
|
return
|
|
self.is_edit = True
|
|
self.readline.vi_save_line ()
|
|
self.readline.l_buffer.insert_text (self.readline._vi_yank_buffer * self.get_multiplier ())
|
|
self.readline.l_buffer.point -= 1
|
|
self.state = _VI_END
|
|
|
|
def key_u (self, char):
|
|
self.readline.vi_undo ()
|
|
self.state = _VI_END
|
|
|
|
def key_U (self, char):
|
|
self.readline.vi_undo_all ()
|
|
self.state = _VI_END
|
|
|
|
def key_v (self, char):
|
|
editor = ViExternalEditor (self.readline.l_buffer.line_buffer)
|
|
self.readline.l_buffer.line_buffer = list (editor.result)
|
|
self.readline.l_buffer.point = 0
|
|
self.is_edit = True
|
|
self.state = _VI_END
|
|
|
|
def error (self):
|
|
self.readline._bell ()
|
|
self.is_error = True
|
|
|
|
def state_is_end (self):
|
|
return self.state == _VI_END
|
|
is_end = property (state_is_end)
|
|
|
|
def state_is_search (self):
|
|
return self.state == _VI_SEARCH
|
|
is_search = property (state_is_search)
|
|
|
|
def state_is_replace_one (self):
|
|
return self.state == _VI_REPLACE_ONE
|
|
is_replace_one = property (state_is_replace_one)
|
|
|
|
def do_search (self):
|
|
self.readline.vi_search_first ()
|
|
self.state = _VI_END
|
|
|
|
def key_n (self, char):
|
|
self.readline.vi_search_again_backward ()
|
|
self.state = _VI_END
|
|
|
|
def key_N (self, char):
|
|
self.readline.vi_search_again_forward ()
|
|
self.state = _VI_END
|
|
|
|
def motion_beginning_of_line (self, line, index=0, count=1, **kw):
|
|
return 0
|
|
|
|
def motion_end_in_line (self, line, index=0, count=1, **kw):
|
|
return max (0, len (self.readline.l_buffer.line_buffer)-1)
|
|
|
|
def motion_word_short (self, line, index=0, count=1, **kw):
|
|
return vi_pos_word_short (line, index, count)
|
|
|
|
def motion_word_long (self, line, index=0, count=1, **kw):
|
|
return vi_pos_word_long (line, index, count)
|
|
|
|
def motion_end_short (self, line, index=0, count=1, **kw):
|
|
return vi_pos_end_short (line, index, count)
|
|
|
|
def motion_end_long (self, line, index=0, count=1, **kw):
|
|
return vi_pos_end_long (line, index, count)
|
|
|
|
def motion_back_short (self, line, index=0, count=1, **kw):
|
|
return vi_pos_back_short (line, index, count)
|
|
|
|
def motion_back_long (self, line, index=0, count=1, **kw):
|
|
return vi_pos_back_long (line, index, count)
|
|
|
|
def motion_find_char_forward (self, line, index=0, count=1, char=None):
|
|
self.readline._vi_key_find_char = char
|
|
return vi_pos_find_char_forward (line, char, index, count)
|
|
|
|
def motion_find_char_backward (self, line, index=0, count=1, char=None):
|
|
self.readline._vi_key_find_char = char
|
|
return vi_pos_find_char_backward (line, char, index, count)
|
|
|
|
def motion_to_char_forward (self, line, index=0, count=1, char=None):
|
|
return vi_pos_to_char_forward (line, char, index, count)
|
|
|
|
def motion_to_char_backward (self, line, index=0, count=1, char=None):
|
|
return vi_pos_to_char_backward (line, char, index, count)
|
|
|
|
def motion_left (self, line, index=0, count=1, char=None):
|
|
return max (0, index - count)
|
|
|
|
def motion_right (self, line, index=0, count=1, char=None):
|
|
return min (len(line), index + count)
|
|
|
|
def motion_matching (self, line, index=0, count=1, char=None):
|
|
return vi_pos_matching (line, index)
|
|
|
|
def motion_column (self, line, index=0, count=1, char=None):
|
|
return max (0, count-1)
|
|
|
|
def has_multiplier (self):
|
|
return self.override_multiplier or self.readline._vi_multiplier1 or self.readline._vi_multiplier2
|
|
|
|
def get_multiplier (self):
|
|
if self.override_multiplier:
|
|
return int (self.override_multiplier)
|
|
if self.readline._vi_multiplier1 == '': m1 = 1
|
|
else: m1 = int(self.readline._vi_multiplier1)
|
|
if self.readline._vi_multiplier2 == '': m2 = 1
|
|
else: m2 = int(self.readline._vi_multiplier2)
|
|
return m1 * m2
|
|
|
|
def set_override_multiplier (self, count):
|
|
self.override_multiplier = count
|
|
|
|
def apply (self):
|
|
if self.motion:
|
|
self.pos_motion = self.motion (self.readline.l_buffer.line_buffer, self.readline.l_buffer.point,
|
|
self.get_multiplier(), char=self.motion_argument)
|
|
if self.pos_motion < 0:
|
|
self.error ()
|
|
return
|
|
self.action ()
|
|
if self.state != _VI_TEXT:
|
|
self.end ()
|
|
|
|
def movement (self):
|
|
if self.pos_motion <= len(self.readline.l_buffer.line_buffer):
|
|
self.readline.l_buffer.point = self.pos_motion
|
|
else:
|
|
self.readline.l_buffer.point = len(self.readline.l_buffer.line_buffer) - 1
|
|
|
|
def yank (self):
|
|
if self.pos_motion > self.readline.l_buffer.point:
|
|
s = self.readline.l_buffer.line_buffer [self.readline.l_buffer.point : self.pos_motion + self.delete_right]
|
|
else:
|
|
index = max (0, self.pos_motion - self.delete_left)
|
|
s = self.readline.l_buffer.line_buffer [index : self.readline.l_buffer.point + self.delete_right]
|
|
self.readline._vi_yank_buffer = s
|
|
|
|
def delete (self):
|
|
self.readline.vi_save_line ()
|
|
self.yank ()
|
|
# point=lineobj.Point(self.readline.l_buffer)
|
|
# pm=self.pos_motion
|
|
# del self.readline.l_buffer[point:pm]
|
|
# return
|
|
if self.pos_motion > self.readline.l_buffer.point:
|
|
del self.readline.l_buffer.line_buffer [self.readline.l_buffer.point : self.pos_motion + self.delete_right]
|
|
if self.readline.l_buffer.point > len (self.readline.l_buffer.line_buffer):
|
|
self.readline.l_buffer.point = len (self.readline.l_buffer.line_buffer)
|
|
else:
|
|
index = max (0, self.pos_motion - self.delete_left)
|
|
del self.readline.l_buffer.line_buffer [index : self.readline.l_buffer.point + self.delete_right]
|
|
self.readline.l_buffer.point = index
|
|
|
|
def delete_end_of_line (self):
|
|
self.readline.vi_save_line ()
|
|
# del self.readline.l_buffer [self.readline.l_buffer.point : ]
|
|
line_text = self.readline.l_buffer.get_line_text ()
|
|
line_text = line_text [ : self.readline.l_buffer.point]
|
|
self.readline.l_buffer.set_line (line_text)
|
|
if self.readline.l_buffer.point > 0:
|
|
self.readline.l_buffer.point -= 1
|
|
|
|
def delete_char (self):
|
|
# point=lineobj.Point(self.readline.l_buffer)
|
|
# del self.readline.l_buffer[point:point+self.get_multiplier ()]
|
|
# return
|
|
self.pos_motion = self.readline.l_buffer.point + self.get_multiplier ()
|
|
self.delete ()
|
|
end = max (0, len (self.readline.l_buffer) - 1)
|
|
if self.readline.l_buffer.point > end:
|
|
self.readline.l_buffer.point = end
|
|
|
|
def delete_prev_char (self):
|
|
self.pos_motion = self.readline.l_buffer.point - self.get_multiplier ()
|
|
self.delete ()
|
|
|
|
def change (self):
|
|
self.readline.vi_set_insert_mode (True)
|
|
self.delete ()
|
|
self.skip_multipler = True
|
|
self.state = _VI_TEXT
|
|
|
|
def escape (self, char):
|
|
if self.state == _VI_TEXT:
|
|
if not self.skip_multipler:
|
|
times = self.get_multiplier ()
|
|
if times > 1 and self.text:
|
|
extra = self.text * (times - 1)
|
|
self.set_buffer (extra)
|
|
self.state = _VI_END
|
|
|
|
def set_motion_argument (self, char):
|
|
self.motion_argument = char
|
|
self.apply ()
|
|
|
|
def end (self):
|
|
self.state = _VI_END
|
|
if self.readline.l_buffer.point >= len(self.readline.l_buffer.line_buffer):
|
|
self.readline.l_buffer.point = max (0, len(self.readline.l_buffer.line_buffer) - 1)
|
|
|
|
class ViExternalEditor:
|
|
def __init__ (self, line):
|
|
if type(line) is type([]):
|
|
line = ''.join (line)
|
|
file_tmp = self.get_tempfile ()
|
|
fp_tmp = self.file_open (file_tmp, 'w')
|
|
fp_tmp.write (line)
|
|
fp_tmp.close ()
|
|
self.run_editor (file_tmp)
|
|
fp_tmp = self.file_open (file_tmp, 'r')
|
|
self.result = fp_tmp.read ()
|
|
fp_tmp.close ()
|
|
self.file_remove (file_tmp)
|
|
|
|
def get_tempfile (self):
|
|
import tempfile
|
|
return tempfile.mktemp (prefix='readline-', suffix='.py')
|
|
|
|
def file_open (self, filename, mode):
|
|
return file (filename, mode)
|
|
|
|
def file_remove (self, filename):
|
|
os.remove (filename)
|
|
|
|
def get_editor (self):
|
|
try:
|
|
return os.environ ['EDITOR']
|
|
except KeyError:
|
|
return 'notepad' # ouch
|
|
|
|
def run_editor (self, filename):
|
|
cmd = '%s %s' % (self.get_editor(), filename, )
|
|
self.run_command (cmd)
|
|
|
|
def run_command (self, command):
|
|
os.system (command)
|
|
|
|
class ViEvent:
|
|
def __init__ (self, char):
|
|
self.char = char
|
|
|
|
# vi standalone functions
|
|
def vi_is_word (char):
|
|
log ('xx vi_is_word: type(%s), %s' % (type(char), char, ))
|
|
return char.isalpha() or char.isdigit() or char == '_'
|
|
|
|
def vi_is_space (char):
|
|
return char.isspace ()
|
|
|
|
def vi_is_word_or_space (char):
|
|
return vi_is_word (char) or vi_is_space (char)
|
|
|
|
def vi_pos_word_short (line, index=0, count=1):
|
|
try:
|
|
for i in range(count):
|
|
in_word = vi_is_word (line[index])
|
|
if not in_word:
|
|
while not vi_is_word (line[index]):
|
|
index += 1
|
|
else:
|
|
while vi_is_word (line[index]):
|
|
index += 1
|
|
while vi_is_space (line[index]):
|
|
index += 1
|
|
return index
|
|
except IndexError:
|
|
return len(line)
|
|
|
|
def vi_pos_word_long (line, index=0, count=1):
|
|
try:
|
|
for i in range(count):
|
|
in_space = vi_is_space (line[index])
|
|
if not in_space:
|
|
while not vi_is_space (line[index]):
|
|
index += 1
|
|
while vi_is_space (line[index]):
|
|
index += 1
|
|
return index
|
|
except IndexError:
|
|
return len(line)
|
|
|
|
def vi_pos_end_short (line, index=0, count=1):
|
|
try:
|
|
for i in range(count):
|
|
index += 1
|
|
while vi_is_space (line[index]):
|
|
index += 1
|
|
in_word = vi_is_word (line[index])
|
|
if not in_word:
|
|
while not vi_is_word_or_space (line[index]):
|
|
index += 1
|
|
else:
|
|
while vi_is_word (line[index]):
|
|
index += 1
|
|
return index - 1
|
|
except IndexError:
|
|
return max (0, len(line)-1)
|
|
|
|
def vi_pos_end_long (line, index=0, count=1):
|
|
try:
|
|
for i in range(count):
|
|
index += 1
|
|
while vi_is_space (line[index]):
|
|
index += 1
|
|
while not vi_is_space (line[index]):
|
|
index += 1
|
|
return index - 1
|
|
except IndexError:
|
|
return max (0, len(line)-1)
|
|
|
|
class vi_list (list):
|
|
'''This is a list that cannot have a negative index'''
|
|
def __getitem__ (self, key):
|
|
try:
|
|
if int(key) < 0:
|
|
raise IndexError
|
|
except ValueError:
|
|
pass
|
|
return list.__getitem__ (self, key)
|
|
|
|
def vi_pos_back_short (line, index=0, count=1):
|
|
line = vi_list (line)
|
|
try:
|
|
for i in range(count):
|
|
index -= 1
|
|
while vi_is_space (line[index]):
|
|
index -= 1
|
|
in_word = vi_is_word (line[index])
|
|
if in_word:
|
|
while vi_is_word (line[index]):
|
|
index -= 1
|
|
else:
|
|
while not vi_is_word_or_space (line[index]):
|
|
index -= 1
|
|
return index + 1
|
|
except IndexError:
|
|
return 0
|
|
|
|
def vi_pos_back_long (line, index=0, count=1):
|
|
line = vi_list (line)
|
|
try:
|
|
for i in range(count):
|
|
index -= 1
|
|
while vi_is_space (line[index]):
|
|
index -= 1
|
|
while not vi_is_space (line[index]):
|
|
index -= 1
|
|
return index + 1
|
|
except IndexError:
|
|
return 0
|
|
|
|
def vi_pos_find_char_forward (line, char, index=0, count=1):
|
|
try:
|
|
for i in range(count):
|
|
index += 1
|
|
while line [index] != char:
|
|
index += 1
|
|
return index
|
|
except IndexError:
|
|
return -1
|
|
|
|
def vi_pos_find_char_backward (line, char, index=0, count=1):
|
|
try:
|
|
for i in range(count):
|
|
index -= 1
|
|
while 1:
|
|
if index < 0:
|
|
return -1
|
|
if line[index] == char:
|
|
break
|
|
index -= 1
|
|
return index
|
|
except IndexError:
|
|
return -1
|
|
|
|
def vi_pos_to_char_forward (line, char, index=0, count=1):
|
|
index = vi_pos_find_char_forward (line, char, index, count)
|
|
if index > 0:
|
|
return index - 1
|
|
return index
|
|
|
|
def vi_pos_to_char_backward (line, char, index=0, count=1):
|
|
index = vi_pos_find_char_backward (line, char, index, count)
|
|
if index >= 0:
|
|
return index + 1
|
|
return index
|
|
|
|
_vi_dct_matching = {
|
|
'<': ('>', +1), '>': ('<', -1),
|
|
'(': (')', +1), ')': ('(', -1),
|
|
'[': (']', +1), ']': ('[', -1),
|
|
'{': ('}', +1), '}': ('{', -1),
|
|
}
|
|
|
|
def vi_pos_matching (line, index=0):
|
|
'''find matching <([{}])>'''
|
|
anchor = None
|
|
target = None
|
|
delta = 1
|
|
count = 0
|
|
try:
|
|
while 1:
|
|
if anchor is None:
|
|
# first find anchor
|
|
try:
|
|
target, delta = _vi_dct_matching [line [index]]
|
|
anchor = line [index]
|
|
count = 1
|
|
except KeyError:
|
|
index += 1
|
|
continue
|
|
else:
|
|
# Here the anchor has been found
|
|
# Need to get corresponding target
|
|
if index < 0:
|
|
return -1
|
|
if line [index] == anchor:
|
|
count += 1
|
|
elif line [index] == target:
|
|
count -= 1
|
|
if count == 0:
|
|
return index
|
|
index += delta
|
|
except IndexError:
|
|
return -1
|
|
|