diff --git a/doc/ChangeLog b/doc/ChangeLog index 7d70fa9..06e97ce 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,6 @@ +2006-03-16 Jörgen Stenarson + * Added vi patch + 2006-03-16 Jörgen Stenarson * Refactored emacs mode diff --git a/pyreadline/console.py b/pyreadline/console.py index a5c8a4e..11df2d8 100644 --- a/pyreadline/console.py +++ b/pyreadline/console.py @@ -522,11 +522,14 @@ class Console(object): else: return (info.dwSize.X, info.dwSize.Y) - def cursor(self, visible): + def cursor(self, visible=None, size=None): '''Set cursor on or off.''' info = CONSOLE_CURSOR_INFO() if self.GetConsoleCursorInfo(self.hout, byref(info)): - info.bVisible = visible + if visible is not None: + info.bVisible = visible + if size is not None: + info.dwSize = size self.SetConsoleCursorInfo(self.hout, byref(info)) def bell(self): diff --git a/pyreadline/lineeditor/lineobj.py b/pyreadline/lineeditor/lineobj.py index b32ceb3..e3bfc89 100644 --- a/pyreadline/lineeditor/lineobj.py +++ b/pyreadline/lineeditor/lineobj.py @@ -151,6 +151,7 @@ class TextLine(object): self._point=0 self.mark=-1 self.undo_stack=[] + self.overwrite=False if isinstance(txtstr,TextLine): #copy if point is None: self.point=txtstr.point @@ -243,9 +244,15 @@ class TextLine(object): self.point = 0 def _insert_text(self, text): - for c in text: - self.line_buffer.insert(self.point, c) - self.point += 1 + if self.overwrite: + for c in text: + #if self.point: + self.line_buffer[self.point]= c + self.point += 1 + else: + for c in text: + self.line_buffer.insert(self.point, c) + self.point += 1 def __getitem__(self,key): #Check if key is LineSlice, convert to regular slice @@ -318,11 +325,14 @@ class TextLine(object): value=TextLine(value).line_buffer self.line_buffer=prev+value+rest + def __len__(self): + return len(self.line_buffer) + def upper(self): - self.line_buffer=self.line_buffer.upper() + self.line_buffer=[x.upper() for x in self.line_buffer] def lower(self): - self.line_buffer=self.line_buffer.lower() + self.line_buffer=[x.lower() for x in self.line_buffer] def startswith(self,txt): return self.get_line_text().startswith(txt) @@ -523,4 +533,4 @@ if __name__=="__main__": [] print '%-15s "%s"'%(name,show_pos(q,pos,"^")) - + l=TextLine("kjjk") diff --git a/pyreadline/modes/__init__.py b/pyreadline/modes/__init__.py index b75cb40..b4d6544 100644 --- a/pyreadline/modes/__init__.py +++ b/pyreadline/modes/__init__.py @@ -1,5 +1,5 @@ -__all__=["emacs","notemacs"] -import emacs,notemacs -editingmodes=[emacs.EmacsMode,notemacs.NotEmacsMode] +__all__=["emacs","notemacs","vi"] +import emacs,notemacs,vi +editingmodes=[emacs.EmacsMode,notemacs.NotEmacsMode,vi.ViMode] #add check to ensure all modes have unique mode names \ No newline at end of file diff --git a/pyreadline/modes/basemode.py b/pyreadline/modes/basemode.py new file mode 100644 index 0000000..4cfb6b0 --- /dev/null +++ b/pyreadline/modes/basemode.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +#***************************************************************************** +# Copyright (C) 2003-2006 Gary Bishop. +# Copyright (C) 2006 Jorgen Stenarson. +# +# 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 +from pyreadline.keysyms import key_text_to_keyinfo +import pyreadline.lineeditor.lineobj as lineobj +import pyreadline.lineeditor.history as history + +class BaseMode(object): + mode="base" + def __init__(self,rlobj): + self.rlobj=rlobj + self.exit_dispatch = {} + self.key_dispatch = {} + self.startup_hook=None + self.pre_input_hook=None + + def __repr__(self): + return "" + + def _gs(x): + def g(self): + return getattr(self.rlobj,x) + def s(self,q): + setattr(self.rlobj,x,q) + return g,s + + def _g(x): + def g(self): + return getattr(self.rlobj,x) + return g + + l_buffer=property(*_gs("l_buffer")) + next_meta=property(*_gs("next_meta")) + first_prompt=property(*_gs("first_prompt")) + prompt=property(*_gs("prompt")) + console=property(_g("console")) + insert_text=property(_g("insert_text")) + _print_prompt=property(_g("_print_prompt")) + _update_line=property(_g("_update_line")) + paste_line_buffer=property(_g("paste_line_buffer")) + add_history=property(_g("add_history")) + _bell=property(_g("_bell")) + _clear_after=property(_g("_clear_after")) + _set_cursor=property(_g("_set_cursor")) + _print_prompt=property(_g("_print_prompt")) + _update_prompt_pos=property(_g("_update_prompt_pos")) + _update_line=property(_g("_update_line")) + enable_win32_clipboard=property(_g("enable_win32_clipboard")) + _bell=property(_g("_bell")) + _history=property(_g("_history")) + + def _readline_from_keyboard(self): + raise NotImplementedError + + def readline(self, prompt=''): + raise NotImplementedError + + #Create key bindings: + + def _bind_key(self, key, func): + '''setup the mapping from key to call the function.''' + keyinfo = key_text_to_keyinfo(key) +# print key,keyinfo,func.__name__ + self.key_dispatch[keyinfo] = func + + def _bind_exit_key(self, key): + '''setup the mapping from key to call the function.''' + keyinfo = key_text_to_keyinfo(key) + self.exit_dispatch[keyinfo] = None + + def init_editing_mode(self, e): # (C-e) + '''When in vi command mode, this causes a switch to emacs editing + mode.''' + + raise NotImplementedError +#completion commands + + def _get_completions(self): + + '''Return a list of possible completions for the string ending at the point. + + Also set begidx and endidx in the process.''' + completions = [] + self.begidx = self.l_buffer.point + self.endidx = self.l_buffer.point + buf=self.l_buffer.line_buffer + if self.completer: + # get the string to complete + while self.begidx > 0: + self.begidx -= 1 + if buf[self.begidx] in self.completer_delims: + self.begidx += 1 + break + text = ''.join(buf[self.begidx:self.endidx]) + log('complete text="%s"' % text) + i = 0 + while 1: + try: + r = self.completer(text, i) + except: + break + i += 1 + if r and r not in completions: + completions.append(r) + else: + break + log('text completions=%s' % completions) + if not completions: + # get the filename to complete + while self.begidx > 0: + self.begidx -= 1 + if buf[self.begidx] in ' \t\n': + self.begidx += 1 + break + text = ''.join(buf[self.begidx:self.endidx]) + log('file complete text="%s"' % text) + completions = glob(os.path.expanduser(text) + '*') + if self.mark_directories == 'on': + mc = [] + for f in completions: + if os.path.isdir(f): + mc.append(f + os.sep) + else: + mc.append(f) + completions = mc + log('fnames=%s' % completions) + return completions + + + def _display_completions(self, completions): + if not completions: + return + self.console.write('\n') + wmax = max(map(len, completions)) + w, h = self.console.size() + cols = max(1, int((w-1) / (wmax+1))) + rows = int(math.ceil(float(len(completions)) / cols)) + for row in range(rows): + s = '' + for col in range(cols): + i = col*rows + row + if i < len(completions): + self.console.write(completions[i].ljust(wmax+1)) + self.console.write('\n') + self._print_prompt() + + def complete(self, e): # (TAB) + '''Attempt to perform completion on the text before point. The + actual completion performed is application-specific. The default is + filename completion.''' + completions = self._get_completions() + if completions: + cprefix = commonprefix(completions) + rep = [ c for c in cprefix ] + self.l_buffer[self.begidx:self.endidx] = rep + self.l_buffer.point += len(rep) - (self.endidx - self.begidx) + if len(completions) > 1: + if self.show_all_if_ambiguous == 'on': + self._display_completions(completions) + else: + self._bell() + else: + self._bell() + + def possible_completions(self, e): # (M-?) + '''List the possible completions of the text before point. ''' + completions = self._get_completions() + self._display_completions(completions) + + def insert_completions(self, e): # (M-*) + '''Insert all completions of the text before point that would have + been generated by possible-completions.''' + completions = self._get_completions() + b = self.begidx + e = self.endidx + for comp in completions: + rep = [ c for c in comp ] + rep.append(' ') + self.l_buffer[b:e] = rep + b += len(rep) + e = b + self.line_cursor = b + + def menu_complete(self, e): # () + '''Similar to complete, but replaces the word to be completed with a + single match from the list of possible completions. Repeated + execution of menu-complete steps through the list of possible + completions, inserting each match in turn. At the end of the list of + completions, the bell is rung (subject to the setting of bell-style) + and the original text is restored. An argument of n moves n + positions forward in the list of matches; a negative argument may be + used to move backward through the list. This command is intended to + be bound to TAB, but is unbound by default.''' + pass + + ### Methods below here are bindable emacs functions + + def beginning_of_line(self, e): # (C-a) + '''Move to the start of the current line. ''' + self.l_buffer.beginning_of_line() + + def end_of_line(self, e): # (C-e) + '''Move to the end of the line. ''' + self.l_buffer.end_of_line() + + def forward_char(self, e): # (C-f) + '''Move forward a character. ''' + self.l_buffer.forward_char() + + def backward_char(self, e): # (C-b) + '''Move back a character. ''' + self.l_buffer.backward_char() + + def forward_word(self, e): # (M-f) + '''Move forward to the end of the next word. Words are composed of + letters and digits.''' + self.l_buffer.forward_word() + + def backward_word(self, e): # (M-b) + '''Move back to the start of the current or previous word. Words are + composed of letters and digits.''' + self.l_buffer.backward_word() + + def clear_screen(self, e): # (C-l) + '''Clear the screen and redraw the current line, leaving the current + line at the top of the screen.''' + self.console.page() + + def redraw_current_line(self, e): # () + '''Refresh the current line. By default, this is unbound.''' + pass + + def accept_line(self, e): # (Newline or Return) + '''Accept the line regardless of where the cursor is. If this line + is non-empty, it may be added to the history list for future recall + with add_history(). If this line is a modified history line, the + history line is restored to its original state.''' + return True + + + def delete_char(self, e): # (C-d) + '''Delete the character at point. If point is at the beginning of + the line, there are no characters in the line, and the last + character typed was not bound to delete-char, then return EOF.''' + self.l_buffer.delete_char() + + def backward_delete_char(self, e): # (Rubout) + '''Delete the character behind the cursor. A numeric argument means + to kill the characters instead of deleting them.''' + self.l_buffer.backward_delete_char() + + def self_insert(self, e): # (a, b, A, 1, !, ...) + '''Insert yourself. ''' + if ord(e.char)!=0: #don't insert null character in buffer, can happen with dead keys. + self.insert_text(e.char) + diff --git a/pyreadline/modes/emacs.py b/pyreadline/modes/emacs.py index c2506ec..aead4f4 100644 --- a/pyreadline/modes/emacs.py +++ b/pyreadline/modes/emacs.py @@ -12,51 +12,16 @@ from pyreadline.logger import log from pyreadline.keysyms import key_text_to_keyinfo import pyreadline.lineeditor.lineobj as lineobj import pyreadline.lineeditor.history as history +import basemode -class EmacsMode(object): +class EmacsMode(basemode.BaseMode): mode="emacs" def __init__(self,rlobj): - self.rlobj=rlobj - self.exit_dispatch = {} - self.key_dispatch = {} - self.startup_hook=None - self.pre_input_hook=None - + super(EmacsMode,self).__init__(rlobj) + def __repr__(self): return "" - def _gs(x): - def g(self): - return getattr(self.rlobj,x) - def s(self,q): - setattr(self.rlobj,x,q) - return g,s - - def _g(x): - def g(self): - return getattr(self.rlobj,x) - return g - - l_buffer=property(*_gs("l_buffer")) - next_meta=property(*_gs("next_meta")) - first_prompt=property(*_gs("first_prompt")) - prompt=property(*_gs("prompt")) - console=property(_g("console")) - insert_text=property(_g("insert_text")) - _print_prompt=property(_g("_print_prompt")) - _update_line=property(_g("_update_line")) - paste_line_buffer=property(_g("paste_line_buffer")) - add_history=property(_g("add_history")) - _bell=property(_g("_bell")) - _clear_after=property(_g("_clear_after")) - _set_cursor=property(_g("_set_cursor")) - _print_prompt=property(_g("_print_prompt")) - _update_prompt_pos=property(_g("_update_prompt_pos")) - _update_line=property(_g("_update_line")) - enable_win32_clipboard=property(_g("enable_win32_clipboard")) - _bell=property(_g("_bell")) - _history=property(_g("_history")) - def _readline_from_keyboard(self): c=self.console while 1: @@ -124,50 +89,6 @@ class EmacsMode(object): 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 beginning_of_line(self, e): # (C-a) - '''Move to the start of the current line. ''' - self.l_buffer.beginning_of_line() - - def end_of_line(self, e): # (C-e) - '''Move to the end of the line. ''' - self.l_buffer.end_of_line() - - def forward_char(self, e): # (C-f) - '''Move forward a character. ''' - self.l_buffer.forward_char() - - def backward_char(self, e): # (C-b) - '''Move back a character. ''' - self.l_buffer.backward_char() - - def forward_word(self, e): # (M-f) - '''Move forward to the end of the next word. Words are composed of - letters and digits.''' - self.l_buffer.forward_word() - - def backward_word(self, e): # (M-b) - '''Move back to the start of the current or previous word. Words are - composed of letters and digits.''' - self.l_buffer.backward_word() - - def clear_screen(self, e): # (C-l) - '''Clear the screen and redraw the current line, leaving the current - line at the top of the screen.''' - self.console.page() - - def redraw_current_line(self, e): # () - '''Refresh the current line. By default, this is unbound.''' - pass - - def accept_line(self, e): # (Newline or Return) - '''Accept the line regardless of where the cursor is. If this line - is non-empty, it may be added to the history list for future recall - with add_history(). If this line is a modified history line, the - history line is restored to its original state.''' - return True - ######### History commands def previous_history(self, e): # (C-p) '''Move back through the history list, fetching the previous command. ''' @@ -283,17 +204,6 @@ class EmacsMode(object): the history list, inserting the last argument of each line in turn.''' pass - def delete_char(self, e): # (C-d) - '''Delete the character at point. If point is at the beginning of - the line, there are no characters in the line, and the last - character typed was not bound to delete-char, then return EOF.''' - self.l_buffer.delete_char() - - def backward_delete_char(self, e): # (Rubout) - '''Delete the character behind the cursor. A numeric argument means - to kill the characters instead of deleting them.''' - self.l_buffer.backward_delete_char() - def forward_backward_delete_char(self, e): # () '''Delete the character under the cursor, unless the cursor is at the end of the line, in which case the character behind the cursor @@ -311,11 +221,6 @@ class EmacsMode(object): ws = ' ' * (self.tabstop - (self.line_cursor%self.tabstop)) self.insert_text(ws) - def self_insert(self, e): # (a, b, A, 1, !, ...) - '''Insert yourself. ''' - if ord(e.char)!=0: #don't insert null character in buffer, can happen with dead keys. - self.insert_text(e.char) - def transpose_chars(self, e): # (C-t) '''Drag the character before the cursor forward over the character at the cursor, moving the cursor forward as well. If the insertion @@ -489,123 +394,6 @@ class EmacsMode(object): default, this is not bound to a key.''' pass - - def _get_completions(self): - '''Return a list of possible completions for the string ending at the point. - - Also set begidx and endidx in the process.''' - completions = [] - self.begidx = self.l_buffer.point - self.endidx = self.l_buffer.point - buf=self.l_buffer.line_buffer - if self.completer: - # get the string to complete - while self.begidx > 0: - self.begidx -= 1 - if buf[self.begidx] in self.completer_delims: - self.begidx += 1 - break - text = ''.join(buf[self.begidx:self.endidx]) - log('complete text="%s"' % text) - i = 0 - while 1: - try: - r = self.completer(text, i) - except: - break - i += 1 - if r and r not in completions: - completions.append(r) - else: - break - log('text completions=%s' % completions) - if not completions: - # get the filename to complete - while self.begidx > 0: - self.begidx -= 1 - if buf[self.begidx] in ' \t\n': - self.begidx += 1 - break - text = ''.join(buf[self.begidx:self.endidx]) - log('file complete text="%s"' % text) - completions = glob(os.path.expanduser(text) + '*') - if self.mark_directories == 'on': - mc = [] - for f in completions: - if os.path.isdir(f): - mc.append(f + os.sep) - else: - mc.append(f) - completions = mc - log('fnames=%s' % completions) - return completions - - def _display_completions(self, completions): - if not completions: - return - self.console.write('\n') - wmax = max(map(len, completions)) - w, h = self.console.size() - cols = max(1, int((w-1) / (wmax+1))) - rows = int(math.ceil(float(len(completions)) / cols)) - for row in range(rows): - s = '' - for col in range(cols): - i = col*rows + row - if i < len(completions): - self.console.write(completions[i].ljust(wmax+1)) - self.console.write('\n') - self._print_prompt() - - def complete(self, e): # (TAB) - '''Attempt to perform completion on the text before point. The - actual completion performed is application-specific. The default is - filename completion.''' - completions = self._get_completions() - if completions: - cprefix = commonprefix(completions) - rep = [ c for c in cprefix ] - self.l_buffer[self.begidx:self.endidx] = rep - self.l_buffer.point += len(rep) - (self.endidx - self.begidx) - if len(completions) > 1: - if self.show_all_if_ambiguous == 'on': - self._display_completions(completions) - else: - self._bell() - else: - self._bell() - - def possible_completions(self, e): # (M-?) - '''List the possible completions of the text before point. ''' - completions = self._get_completions() - self._display_completions(completions) - - def insert_completions(self, e): # (M-*) - '''Insert all completions of the text before point that would have - been generated by possible-completions.''' - completions = self._get_completions() - b = self.begidx - e = self.endidx - for comp in completions: - rep = [ c for c in comp ] - rep.append(' ') - self.l_buffer[b:e] = rep - b += len(rep) - e = b - self.line_cursor = b - - def menu_complete(self, e): # () - '''Similar to complete, but replaces the word to be completed with a - single match from the list of possible completions. Repeated - execution of menu-complete steps through the list of possible - completions, inserting each match in turn. At the end of the list of - completions, the bell is rung (subject to the setting of bell-style) - and the original text is restored. An argument of n moves n - positions forward in the list of matches; a negative argument may be - used to move backward through the list. This command is intended to - be bound to TAB, but is unbound by default.''' - pass - def delete_char_or_list(self, e): # () '''Deletes the character under the cursor if not at the beginning or end of the line (like delete-char). If at the end of the line, diff --git a/pyreadline/modes/notemacs.py b/pyreadline/modes/notemacs.py index 2ebe2e7..a6d5b0a 100644 --- a/pyreadline/modes/notemacs.py +++ b/pyreadline/modes/notemacs.py @@ -12,53 +12,16 @@ from pyreadline.logger import log from pyreadline.keysyms import key_text_to_keyinfo import pyreadline.lineeditor.lineobj as lineobj import pyreadline.lineeditor.history as history +import basemode -class NotEmacsMode(object): +class NotEmacsMode(basemode.BaseMode): mode="notemacs" def __init__(self,rlobj): - self.rlobj=rlobj - self.exit_dispatch = {} - self.key_dispatch = {} - self.startup_hook=None - self.pre_input_hook=None - + super(NotEmacsMode,self).__init__(rlobj) + def __repr__(self): return "" - def _gs(x): - def g(self): - return getattr(self.rlobj,x) - def s(self,q): - setattr(self.rlobj,x,q) - return g,s - - def _g(x): - def g(self): - return getattr(self.rlobj,x) - return g - - l_buffer=property(*_gs("l_buffer")) - next_meta=property(*_gs("next_meta")) - first_prompt=property(*_gs("first_prompt")) - prompt=property(*_gs("prompt")) - console=property(_g("console")) - insert_text=property(_g("insert_text")) - _print_prompt=property(_g("_print_prompt")) - _update_line=property(_g("_update_line")) - paste_line_buffer=property(_g("paste_line_buffer")) - add_history=property(_g("add_history")) - _bell=property(_g("_bell")) - _clear_after=property(_g("_clear_after")) - _set_cursor=property(_g("_set_cursor")) - _print_prompt=property(_g("_print_prompt")) - _update_prompt_pos=property(_g("_update_prompt_pos")) - _update_line=property(_g("_update_line")) - enable_win32_clipboard=property(_g("enable_win32_clipboard")) - _bell=property(_g("_bell")) - - - - def _readline_from_keyboard(self): c=self.console while 1: @@ -491,123 +454,6 @@ class NotEmacsMode(object): default, this is not bound to a key.''' pass - - def _get_completions(self): - '''Return a list of possible completions for the string ending at the point. - - Also set begidx and endidx in the process.''' - completions = [] - self.begidx = self.l_buffer.point - self.endidx = self.l_buffer.point - buf=self.l_buffer.line_buffer - if self.completer: - # get the string to complete - while self.begidx > 0: - self.begidx -= 1 - if buf[self.begidx] in self.completer_delims: - self.begidx += 1 - break - text = ''.join(buf[self.begidx:self.endidx]) - log('complete text="%s"' % text) - i = 0 - while 1: - try: - r = self.completer(text, i) - except: - break - i += 1 - if r and r not in completions: - completions.append(r) - else: - break - log('text completions=%s' % completions) - if not completions: - # get the filename to complete - while self.begidx > 0: - self.begidx -= 1 - if buf[self.begidx] in ' \t\n': - self.begidx += 1 - break - text = ''.join(buf[self.begidx:self.endidx]) - log('file complete text="%s"' % text) - completions = glob(os.path.expanduser(text) + '*') - if self.mark_directories == 'on': - mc = [] - for f in completions: - if os.path.isdir(f): - mc.append(f + os.sep) - else: - mc.append(f) - completions = mc - log('fnames=%s' % completions) - return completions - - def _display_completions(self, completions): - if not completions: - return - self.console.write('\n') - wmax = max(map(len, completions)) - w, h = self.console.size() - cols = max(1, int((w-1) / (wmax+1))) - rows = int(math.ceil(float(len(completions)) / cols)) - for row in range(rows): - s = '' - for col in range(cols): - i = col*rows + row - if i < len(completions): - self.console.write(completions[i].ljust(wmax+1)) - self.console.write('\n') - self._print_prompt() - - def complete(self, e): # (TAB) - '''Attempt to perform completion on the text before point. The - actual completion performed is application-specific. The default is - filename completion.''' - completions = self._get_completions() - if completions: - cprefix = commonprefix(completions) - rep = [ c for c in cprefix ] - self.l_buffer[self.begidx:self.endidx] = rep - self.l_buffer.point += len(rep) - (self.endidx - self.begidx) - if len(completions) > 1: - if self.show_all_if_ambiguous == 'on': - self._display_completions(completions) - else: - self._bell() - else: - self._bell() - - def possible_completions(self, e): # (M-?) - '''List the possible completions of the text before point. ''' - completions = self._get_completions() - self._display_completions(completions) - - def insert_completions(self, e): # (M-*) - '''Insert all completions of the text before point that would have - been generated by possible-completions.''' - completions = self._get_completions() - b = self.begidx - e = self.endidx - for comp in completions: - rep = [ c for c in comp ] - rep.append(' ') - self.l_buffer[b:e] = rep - b += len(rep) - e = b - self.line_cursor = b - - def menu_complete(self, e): # () - '''Similar to complete, but replaces the word to be completed with a - single match from the list of possible completions. Repeated - execution of menu-complete steps through the list of possible - completions, inserting each match in turn. At the end of the list of - completions, the bell is rung (subject to the setting of bell-style) - and the original text is restored. An argument of n moves n - positions forward in the list of matches; a negative argument may be - used to move backward through the list. This command is intended to - be bound to TAB, but is unbound by default.''' - pass - def delete_char_or_list(self, e): # () '''Deletes the character under the cursor if not at the beginning or end of the line (like delete-char). If at the end of the line, diff --git a/pyreadline/modes/vi.py b/pyreadline/modes/vi.py new file mode 100644 index 0000000..03afbac --- /dev/null +++ b/pyreadline/modes/vi.py @@ -0,0 +1,1215 @@ +# -*- coding: utf-8 -*- +#***************************************************************************** +# Copyright (C) 2003-2006 Gary Bishop. +# Copyright (C) 2006 Jorgen Stenarson. +# +# 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 +from pyreadline.keysyms import key_text_to_keyinfo +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 "" + + 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,self.self_insert) + log("readline from keyboard:%s"%(event.keyinfo,)) + 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''' + print "initing vi" + 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 [self.l_buffer.point] = prev + except IndexError: + del self.l_buffer[self.l_buffer.point ] + else: + self.vi_save_line () + del self.l_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.line_cursor, self.line_buffer[:], ) + self._vi_undo_stack = [tpl_undo] + self._vi_undo_cursor = 0 + + def vi_save_line (self): + return + if self._vi_undo_stack and self._vi_undo_cursor >= 0: + del self._vi_undo_stack [self._vi_undo_cursor + 1 : ] + tpl_undo = (self.line_cursor, self.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.line_cursor = tpl_undo [0] + self.line_buffer = tpl_undo [1][:] + + 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 [i] + pos = line_history.find (self._vi_search_text) + if pos >= 0: + self.history_cursor = i + self.line_buffer = list (line_history) + self.line_cursor = pos + self.vi_undo_restart () + return True + self._bell () + return False + + def vi_search_first (self): + text = ''.join (self.line_buffer [1:]) + if text: + self._vi_search_text = text + position = len (self.history) - 1 + elif self._vi_search_text: + position = self.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_cursor-1, -1, -1)) + + def vi_search_again_forward (self): + self.vi_search (range (self.history_cursor+1, len(self.history))) + + def vi_up (self, e): + if self.history_cursor == len(self.history): + self._vi_current = self.line_buffer [:] + self.previous_history (e) + 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_cursor >= len(self.history): + self.vi_error () + return + if self.history_cursor < len(self.history) - 1: + self.next_history (e) + 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_cursor = len(self.history) + self.line_buffer = self._vi_current + self.end_of_line (e) + if not self.vi_is_insert_mode and self.line_cursor > 0: + self.line_cursor -= 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 = ''.join (self.line_buffer) + 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.line_cursor%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) + return + #overwrite in l_buffer obj + if self.is_overwrite: + if self.readline.line_cursor < len (self.readline.l_buffer): + self.readline.l_buffer[self.l_buffer.point]=char + #self.readline.line_buffer [self.readline.line_cursor] = char + else: + self.readline.l_buffer.insert_text(char) + #self.readline.line_buffer.append (char) + else: + self.readline.l_buffer.insert_text(char) + #self.readline.line_buffer.insert (self.readline.line_cursor, char) + #self.readline.line_cursor += 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.line_cursor + self.readline.line_buffer [cursor : cursor + times] = char * times + if times > 1: + self.readline.line_cursor += (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.line_cursor > 0: + self.readline.line_cursor -= 1 + self.readline.vi_set_insert_mode (False) + self.end () + + def key_slash (self, char): + self.readline.vi_save_line () + self.readline.line_cursor, self.readline.line_buffer = 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.line_buffer [self.readline.begidx : self.readline.endidx + 1] = list (text) + prefix_len = self.readline.endidx - self.readline.begidx + self.readline.line_cursor += 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.line_buffer [self.readline.line_cursor] + if c.isupper (): + self.readline.line_buffer [self.readline.line_cursor] = c.lower() + elif c.islower (): + self.readline.line_buffer [self.readline.line_cursor] = c.upper() + self.readline.line_cursor += 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.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.line_cursor = 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.line_buffer): + self.readline.line_cursor += 1 + + def key_A (self, char): + self.is_edit = True + self.state = _VI_TEXT + self.readline.vi_set_insert_mode (True) + self.readline.line_cursor = len (self.readline.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.line_cursor + i2 = self.readline.line_cursor + self.get_multiplier () + self.skip_multipler = True + self.readline.vi_set_insert_mode (True) + del self.readline.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.line_buffer = [] + self.readline.line_cursor = 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.line_buffer [self.readline.line_cursor : ] + 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 = ''.join (self.readline.line_buffer) + 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.line_cursor += 1 + self.readline.insert_text (self.readline._vi_yank_buffer * self.get_multiplier ()) + self.readline.line_cursor -= 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.insert_text (self.readline._vi_yank_buffer * self.get_multiplier ()) + self.readline.line_cursor -= 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.line_buffer) + self.readline.line_buffer = list (editor.result) + self.readline.line_cursor = 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.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, 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): + self.readline.l_buffer.point = self.pos_motion + else: + self.readline.l_buffer.point = len(self.readline.l_buffer) - 1 + + def yank (self): + if self.pos_motion > self.readline.line_cursor: + s = self.readline.line_buffer [self.readline.line_cursor : self.pos_motion + self.delete_right] + else: + index = max (0, self.pos_motion - self.delete_left) + s = self.readline.line_buffer [index : self.readline.line_cursor + 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.line_cursor: + del self.readline.line_buffer [self.readline.line_cursor : self.pos_motion + self.delete_right] + if self.readline.line_cursor > len (self.readline.line_buffer): + self.readline.line_cursor = len (self.readline.line_buffer) + else: + index = max (0, self.pos_motion - self.delete_left) + del self.readline.line_buffer [index : self.readline.line_cursor + self.delete_right] + self.readline.line_cursor = index + + def delete_end_of_line (self): + self.readline.vi_save_line () + del self.readline.line_buffer [self.readline.line_cursor : ] + if self.readline.line_cursor > 0: + self.readline.line_cursor -= 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.line_cursor + self.get_multiplier () + self.delete () + end = max (0, len (self.readline.line_buffer) - 1) + if self.readline.line_cursor > end: + self.readline.line_cursor = end + + def delete_prev_char (self): + self.pos_motion = self.readline.line_cursor - 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.line_cursor >= len(self.readline.line_buffer): + # self.readline.line_cursor = max (0, len(self.readline.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): + 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 + diff --git a/pyreadline/release.py b/pyreadline/release.py index 432f35d..df18c1f 100644 --- a/pyreadline/release.py +++ b/pyreadline/release.py @@ -20,6 +20,7 @@ name = 'pyreadline' # because bdist_rpm does not accept dashes (an RPM) convention, and # bdist_deb does not accept underscores (a Debian convention). +branch = 'refactor' version = 'refactor' diff --git a/pyreadline/rlmain.py b/pyreadline/rlmain.py index 6e741b6..bd781c5 100644 --- a/pyreadline/rlmain.py +++ b/pyreadline/rlmain.py @@ -26,6 +26,7 @@ from keysyms import key_text_to_keyinfo import pyreadline.lineeditor.lineobj as lineobj import pyreadline.lineeditor.history as history +import release from modes import editingmodes @@ -59,9 +60,10 @@ class Readline(object): self.tabstop = 4 self.editingmodes=[mode(self) for mode in editingmodes] + for mode in self.editingmodes: + mode.init_editing_mode(None) self.mode=self.editingmodes[0] - self.mode.init_editing_mode(None) self.begidx = 0 self.endidx = 0 @@ -351,7 +353,9 @@ class Readline(object): def debug_output(on,filename="pyreadline_debug_log.txt"): #Not implemented yet logger.start_log(on,filename) logger.log("STARTING LOG") - loc={"mode":mode, +# print release.branch + loc={"branch":release.branch, + "mode":mode, "modes":modes, "set_mode":setmode, "bind_key":bind_key,