diff --git a/pyreadline/keysyms/keysyms.py b/pyreadline/keysyms/keysyms.py index 6d62de7..f07e1f5 100644 --- a/pyreadline/keysyms/keysyms.py +++ b/pyreadline/keysyms/keysyms.py @@ -117,6 +117,8 @@ def make_KeyPress(char,state,keycode): shift = (state & 0x10) != 0 if control and char !="\x00": char = chr(VkKeyScan(ord(char)) & 0xff) + elif control and meta and char !="\x00": + char = chr(VkKeyScan(ord(char)) & 0xff) elif control: char=chr(keycode) try: diff --git a/pyreadline/lineeditor/history.py b/pyreadline/lineeditor/history.py index 10012a5..a433f55 100644 --- a/pyreadline/lineeditor/history.py +++ b/pyreadline/lineeditor/history.py @@ -143,50 +143,6 @@ class LineHistory(object): return res[0][1].get_line_text() return "" - def _non_i_search(self, direction, current): - c = pyreadline.rl.console - line = current.get_line_text() - query = '' - while 1: - c.pos(*pyreadline.rl.prompt_end_pos) - scroll = c.write_scrolling(":%s" % query) - pyreadline.rl._update_prompt_pos(scroll) - pyreadline.rl._clear_after() - - event = c.getkeypress() - - if event.keyinfo.keyname == 'backspace': - if len(query) > 0: - query = query[:-1] - else: - break - elif event.char in string.letters + string.digits + string.punctuation + ' ': - query += event.char - elif event.keyinfo.keyname == 'return': - break - else: - pyreadline.rl._bell() - res="" - if query: - if direction==-1: - res=self.reverse_search_history(query) - - else: - res=self.forward_search_history(query) - return lineobj.ReadLineTextBuffer(res,point=0) - - def non_incremental_reverse_search_history(self,current): # (M-p) - '''Search backward starting at the current line and moving up - through the history as necessary using a non-incremental search for - a string supplied by the user.''' - return self._non_i_search(-1,current) - - def non_incremental_forward_search_history(self,current): # (M-n) - '''Search forward starting at the current line and moving down - through the the history as necessary using a non-incremental search - for a string supplied by the user.''' - return self._non_i_search(1,current) - def _search(self, direction, partial): try: if (self.lastcommand != self.history_search_forward and diff --git a/pyreadline/modes/basemode.py b/pyreadline/modes/basemode.py index dfaa449..d3c84f1 100644 --- a/pyreadline/modes/basemode.py +++ b/pyreadline/modes/basemode.py @@ -51,6 +51,9 @@ class BaseMode(object): self.paste_line_buffer=[] + self._sub_modes=[] + + def __repr__(self): return "" @@ -144,7 +147,7 @@ class BaseMode(object): def _bind_key(self, key, func): - '''setup the mapping from key to call the function.''' + """setup the mapping from key to call the function.""" if type(func) != type(self._bind_key): print "Trying to bind non method to keystroke:%s,%s"%(key,func) raise PyreadlineError("Trying to bind non method to keystroke:%s,%s,%s,%s"%(key,func,type(func),type(self._bind_key))) @@ -153,20 +156,20 @@ class BaseMode(object): self.key_dispatch[keyinfo] = func def _bind_exit_key(self, key): - '''setup the mapping from key to call the function.''' + """setup the mapping from key to call the function.""" keyinfo = make_KeyPress_from_keydescr(key.lower()).tuple() 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.''' + """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.''' + """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 @@ -235,9 +238,9 @@ class BaseMode(object): def complete(self, e): # (TAB) - '''Attempt to perform completion on the text before point. The + """Attempt to perform completion on the text before point. The actual completion performed is application-specific. The default is - filename completion.''' + filename completion.""" completions = self._get_completions() if completions: cprefix = commonprefix(completions) @@ -254,13 +257,13 @@ class BaseMode(object): self._bell() def possible_completions(self, e): # (M-?) - '''List the possible completions of the text before point. ''' + """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.''' + """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 @@ -273,7 +276,7 @@ class BaseMode(object): self.line_cursor = b def menu_complete(self, e): # () - '''Similar to complete, but replaces the word to be completed with a + """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 @@ -281,154 +284,153 @@ class BaseMode(object): 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.''' + be bound to TAB, but is unbound by default.""" pass ### Methods below here are bindable emacs functions def insert_text(self, string): - '''Insert text into the command line.''' + """Insert text into the command line.""" self.l_buffer.insert_text(string) def beginning_of_line(self, e): # (C-a) - '''Move to the start of the current line. ''' + """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. ''' + """Move to the end of the line. """ self.l_buffer.end_of_line() def forward_char(self, e): # (C-f) - '''Move forward a character. ''' + """Move forward a character. """ self.l_buffer.forward_char(self.argument_reset) def backward_char(self, e): # (C-b) - '''Move back a character. ''' + """Move back a character. """ self.l_buffer.backward_char(self.argument_reset) def forward_word(self, e): # (M-f) - '''Move forward to the end of the next word. Words are composed of - letters and digits.''' + """Move forward to the end of the next word. Words are composed of + letters and digits.""" self.l_buffer.forward_word(self.argument_reset) 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.''' + """Move back to the start of the current or previous word. Words are + composed of letters and digits.""" self.l_buffer.backward_word(self.argument_reset) def forward_word_end(self, e): # () - '''Move forward to the end of the next word. Words are composed of - letters and digits.''' + """Move forward to the end of the next word. Words are composed of + letters and digits.""" self.l_buffer.forward_word_end(self.argument_reset) def backward_word_end(self, e): # () - '''Move forward to the end of the next word. Words are composed of - letters and digits.''' + """Move forward to the end of the next word. Words are composed of + letters and digits.""" self.l_buffer.backward_word_end(self.argument_reset) ### Movement with extend selection def beginning_of_line_extend_selection(self, e): # - '''Move to the start of the current line. ''' + """Move to the start of the current line. """ self.l_buffer.beginning_of_line_extend_selection() def end_of_line_extend_selection(self, e): # - '''Move to the end of the line. ''' + """Move to the end of the line. """ self.l_buffer.end_of_line_extend_selection() def forward_char_extend_selection(self, e): # - '''Move forward a character. ''' + """Move forward a character. """ self.l_buffer.forward_char_extend_selection(self.argument_reset) def backward_char_extend_selection(self, e): # - '''Move back a character. ''' + """Move back a character. """ self.l_buffer.backward_char_extend_selection(self.argument_reset) def forward_word_extend_selection(self, e): # - '''Move forward to the end of the next word. Words are composed of - letters and digits.''' + """Move forward to the end of the next word. Words are composed of + letters and digits.""" self.l_buffer.forward_word_extend_selection(self.argument_reset) def backward_word_extend_selection(self, e): # - '''Move back to the start of the current or previous word. Words are - composed of letters and digits.''' + """Move back to the start of the current or previous word. Words are + composed of letters and digits.""" self.l_buffer.backward_word_extend_selection(self.argument_reset) def forward_word_end_extend_selection(self, e): # - '''Move forward to the end of the next word. Words are composed of - letters and digits.''' + """Move forward to the end of the next word. Words are composed of + letters and digits.""" self.l_buffer.forward_word_end_extend_selection(self.argument_reset) def backward_word_end_extend_selection(self, e): # - '''Move forward to the end of the next word. Words are composed of - letters and digits.''' + """Move forward to the end of the next word. Words are composed of + letters and digits.""" self.l_buffer.forward_word_end_extend_selection(self.argument_reset) ######## Change case def upcase_word(self, e): # (M-u) - '''Uppercase the current (or following) word. With a negative - argument, uppercase the previous word, but do not move the cursor.''' + """Uppercase the current (or following) word. With a negative + argument, uppercase the previous word, but do not move the cursor.""" self.l_buffer.upcase_word() def downcase_word(self, e): # (M-l) - '''Lowercase the current (or following) word. With a negative - argument, lowercase the previous word, but do not move the cursor.''' + """Lowercase the current (or following) word. With a negative + argument, lowercase the previous word, but do not move the cursor.""" self.l_buffer.downcase_word() def capitalize_word(self, e): # (M-c) - '''Capitalize the current (or following) word. With a negative - argument, capitalize the previous word, but do not move the cursor.''' + """Capitalize the current (or following) word. With a negative + argument, capitalize the previous word, but do not move the cursor.""" self.l_buffer.capitalize_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.''' + """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.''' + """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 + """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.''' + 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 + """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.''' + character typed was not bound to delete-char, then return EOF.""" self.l_buffer.delete_char(self.argument_reset) 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.''' + """Delete the character behind the cursor. A numeric argument means + to kill the characters instead of deleting them.""" self.l_buffer.backward_delete_char(self.argument_reset) def backward_delete_word(self, e): # (Control-Rubout) - '''Delete the character behind the cursor. A numeric argument means - to kill the characters instead of deleting them.''' + """Delete the character behind the cursor. A numeric argument means + to kill the characters instead of deleting them.""" self.l_buffer.backward_delete_word(self.argument_reset) def forward_delete_word(self, e): # (Control-Delete) - '''Delete the character behind the cursor. A numeric argument means - to kill the characters instead of deleting them.''' + """Delete the character behind the cursor. A numeric argument means + to kill the characters instead of deleting them.""" self.l_buffer.forward_delete_word(self.argument_reset) def delete_horizontal_space(self, e): # () - '''Delete all spaces and tabs around point. By default, this is unbound. ''' + """Delete all spaces and tabs around point. By default, this is unbound. """ self.l_buffer.delete_horizontal_space() def self_insert(self, e): # (a, b, A, 1, !, ...) - '''Insert yourself. ''' - + """Insert yourself. """ if e.char and ord(e.char)!=0: #don't insert null character in buffer, can happen with dead keys. self.insert_text(e.char) @@ -436,8 +438,8 @@ class BaseMode(object): # Paste from clipboard def paste(self,e): - '''Paste windows clipboard. - Assume single line strip other lines and end of line markers and trailing spaces''' #(Control-v) + """Paste windows clipboard. + Assume single line strip other lines and end of line markers and trailing spaces""" #(Control-v) if self.enable_win32_clipboard: txt=clipboard.get_clipboard_text_and_convert(False) txt=txt.split("\n")[0].strip("\r").strip("\n") @@ -445,8 +447,8 @@ class BaseMode(object): self.insert_text(txt) def paste_mulitline_code(self,e): - '''Paste windows clipboard as multiline code. - Removes any empty lines in the code''' + """Paste windows clipboard as multiline code. + Removes any empty lines in the code""" reg=re.compile("\r?\n") if self.enable_win32_clipboard: txt=clipboard.get_clipboard_text_and_convert(False) @@ -462,10 +464,10 @@ class BaseMode(object): return False def ipython_paste(self,e): - '''Paste windows clipboard. If enable_ipython_paste_list_of_lists is + """Paste windows clipboard. If enable_ipython_paste_list_of_lists is True then try to convert tabseparated data to repr of list of lists or repr of array. - If enable_ipython_paste_for_paths==True then change \\ to / and spaces to \space''' + If enable_ipython_paste_for_paths==True then change \\ to / and spaces to \space""" if self.enable_win32_clipboard: txt=clipboard.get_clipboard_text_and_convert( self.enable_ipython_paste_list_of_lists) @@ -476,22 +478,22 @@ class BaseMode(object): def copy_region_to_clipboard(self, e): # () - '''Copy the text in the region to the windows clipboard.''' + """Copy the text in the region to the windows clipboard.""" self.l_buffer.copy_region_to_clipboard() def copy_selection_to_clipboard(self, e): # () - '''Copy the text in the region to the windows clipboard.''' + """Copy the text in the region to the windows clipboard.""" self.l_buffer.copy_selection_to_clipboard() def cut_selection_to_clipboard(self, e): # () - '''Copy the text in the region to the windows clipboard.''' + """Copy the text in the region to the windows clipboard.""" self.l_buffer.cut_selection_to_clipboard() def dump_functions(self, e): # () - '''Print all of the functions and their key bindings to the Readline + """Print all of the functions and their key bindings to the Readline output stream. If a numeric argument is supplied, the output is formatted in such a way that it can be made part of an inputrc - file. This command is unbound by default.''' + file. This command is unbound by default.""" print txt="\n".join(self.rl_settings_to_string()) print txt diff --git a/pyreadline/modes/emacs.py b/pyreadline/modes/emacs.py index b9c82a5..221e68c 100644 --- a/pyreadline/modes/emacs.py +++ b/pyreadline/modes/emacs.py @@ -25,17 +25,138 @@ def format(keyinfo): return "(%s,%s,%s,%s,%x)"%k in_ironpython="IronPython" in sys.version +class IncrementalSearchPromptMode(object): + def __init__(self, rlobj): + pass + + def _process_incremental_search_keyevent(self, keyinfo): + keytuple=keyinfo.tuple() + log_sock("%s %s"%(keyinfo,keytuple)) + if keyinfo.keyname == 'backspace': + self.subsearch_query = self.subsearch_query[:-1] + if len(self.subsearch_query) > 0: + self.line=self.subsearch_fun(self.subsearch_query) + else: + self._bell() + self.line="" #empty query means no search result + elif keyinfo.keyname in ['return', 'escape']: + self._bell() + self.prompt=self.subsearch_oldprompt + self.process_keyevent_queue=self.process_keyevent_queue[:-1] + self._history.history_cursor=len(self._history.history) + if keyinfo.keyname == 'escape': + self.l_buffer.set_line(self.subsearch_old_line) + return False + elif keyinfo.keyname: + pass + elif keytuple==self.subsearch_init_event: + self._history.history_cursor += self.subsearch_direction + self.line=self.subsearch_fun(self.subsearch_query) + elif keyinfo.control==False and keyinfo.meta==False : + self.subsearch_query += keyinfo.char + self.line=self.subsearch_fun(self.subsearch_query) + else: + pass + self.prompt=self.subsearch_prompt%self.subsearch_query + self.l_buffer.set_line(self.line) -class EmacsMode(basemode.BaseMode): + def _init_incremental_search(self, searchfun, direction, init_event): + """Initialize search prompt + """ + self.subsearch_init_event=init_event.tuple() + self.subsearch_direction=direction + self.subsearch_query = '' + self.subsearch_fun = searchfun + self.subsearch_old_line = self.l_buffer.get_line_text() + + self.process_keyevent_queue.append(self._process_incremental_search_keyevent) + + self.subsearch_oldprompt=self.prompt + + if (self.previous_func != self.history_search_forward and + self.previous_func != self.history_search_backward): + self.subsearch_query = ''.join(self.l_buffer[0:Point].get_line_text()) + + + if self.subsearch_direction < 0: + self.subsearch_prompt = "reverse-i-search`%s': " + else: + self.subsearch_prompt = "forward-i-search`%s': " + self.prompt=self.subsearch_prompt%"" + if self.subsearch_query: + self.line=self._process_search_keyevent(init_event) + else: + self.line="" + +class SearchPromptMode(object): + def __init__(self, rlobj): + pass + + def _process_non_incremental_search_keyevent(self, keyinfo): + keytuple=keyinfo.tuple() + log_sock("%s %s"%(keyinfo,keytuple)) + + if keyinfo.keyname == 'backspace': + self.non_inc_query = self.non_inc_query[:-1] + elif keyinfo.keyname in ['return', 'escape']: + if self.non_inc_query: + if self.non_inc_direction==-1: + res=self._history.reverse_search_history(self.non_inc_query) + else: + res=self._history.forward_search_history(self.non_inc_query) + + self._bell() + self.prompt=self.non_inc_oldprompt + self.process_keyevent_queue=self.process_keyevent_queue[:-1] + self._history.history_cursor=len(self._history.history) + if keyinfo.keyname == 'escape': + self.l_buffer=self.non_inc_oldline + else: + self.l_buffer.set_line(res) + return False + elif keyinfo.keyname: + pass + elif keyinfo.control==False and keyinfo.meta==False : + self.non_inc_query += keyinfo.char + else: + pass + self.prompt=self.non_inc_oldprompt+":"+self.non_inc_query + + def _init_non_i_search(self, direction): + self.non_inc_direction = direction + self.non_inc_query = "" + self.non_inc_oldprompt=self.prompt + self.non_inc_oldline=self.l_buffer.copy() + self.l_buffer.reset_line() + self.prompt=self.non_inc_oldprompt+":" + self.process_keyevent_queue.append(self._process_non_incremental_search_keyevent) + + def non_incremental_reverse_search_history(self, e): # (M-p) + '''Search backward starting at the current line and moving up + through the history as necessary using a non-incremental search for + a string supplied by the user.''' + return self._init_non_i_search(-1) + + def non_incremental_forward_search_history(self, e): # (M-n) + '''Search forward starting at the current line and moving down + through the the history as necessary using a non-incremental search + for a string supplied by the user.''' + return self._init_non_i_search(1) + +class EmacsMode(IncrementalSearchPromptMode, SearchPromptMode, basemode.BaseMode): mode="emacs" - def __init__(self,rlobj): - super(EmacsMode,self).__init__(rlobj) + def __init__(self, rlobj): + basemode.BaseMode.__init__(self, rlobj) + IncrementalSearchPromptMode.__init__(self, rlobj) + SearchPromptMode.__init__(self, rlobj) self._keylog=(lambda x,y: None) self.previous_func=None self.prompt=">>>" self._insert_verbatim=False self.next_meta = False # True to force meta on next character + self.process_keyevent_queue=[self._process_keyevent] + def __repr__(self): return "" @@ -44,6 +165,13 @@ class EmacsMode(basemode.BaseMode): self._keylog=logfun def process_keyevent(self, keyinfo): + r=self.process_keyevent_queue[-1](keyinfo) + if r: + self.add_history(self.l_buffer.copy()) + return True + return False + + def _process_keyevent(self, keyinfo): """return True when line is final """ #Process exit keys. Only exit on empty line @@ -71,6 +199,7 @@ class EmacsMode(basemode.BaseMode): log("readline from keyboard:%s,%s"%(keytuple, dispatch_func)) log_sock((u"%s|%s"%(ensure_unicode(format(keytuple)),dispatch_func.__name__)),"bound_function") + r = None if dispatch_func: r = dispatch_func(keyinfo) @@ -78,10 +207,8 @@ class EmacsMode(basemode.BaseMode): self.l_buffer.push_undo() self.previous_func = dispatch_func - if r: - self.add_history(self.l_buffer.copy()) - return True - return False + return r + ######### History commands def previous_history(self, e): # (C-p) @@ -102,77 +229,16 @@ class EmacsMode(basemode.BaseMode): being entered.''' self._history.end_of_history(self.l_buffer) - def _i_search(self, searchfun, direction, init_event): - c = self.console - line = self.l_buffer.get_line_text() - query = '' - if (self.previous_func != self.history_search_forward and - self.previous_func != self.history_search_backward): - self.query = ''.join(self.l_buffer[0:Point].get_line_text()) - hc_start = self._history.history_cursor #+ direction - while 1: - x, y = self.prompt_end_pos - c.pos(0, y) - if direction < 0: - prompt = 'reverse-i-search' - else: - prompt = 'forward-i-search' - - scroll = c.write_scrolling("%s`%s': %s" % (prompt, query, line)) - self._update_prompt_pos(scroll) - self._clear_after() - - event = c.getkeypress().keyinfo - if event.keyname == 'backspace': - query = query[:-1] - if len(query) > 0: - #self._history.history_cursor = hc_start #forces search to restart when search empty - line=searchfun(query) - else: - self._bell() - line="" #empty query means no search result - elif event.char in string.letters + string.digits + string.punctuation + ' ': - #self._history.history_cursor = hc_start - query += event.char - line=searchfun(query) - elif event== init_event: - self._history.history_cursor += direction - line=searchfun(query) - else: - if event.keyname != 'return': - self._bell() - break - - px, py = self.prompt_begin_pos - c.pos(0, py) - self.l_buffer.set_line(line) - self._print_prompt() - self._history.history_cursor=len(self._history.history) def reverse_search_history(self, e): # (C-r) '''Search backward starting at the current line and moving up through the history as necessary. This is an incremental search.''' - self._i_search(self._history.reverse_search_history, -1, e) + self._init_incremental_search(self._history.reverse_search_history, -1, e) def forward_search_history(self, e): # (C-s) '''Search forward starting at the current line and moving down through the the history as necessary. This is an incremental search.''' - self._i_search(self._history.forward_search_history, 1, e) - - - def non_incremental_reverse_search_history(self, e): # (M-p) - '''Search backward starting at the current line and moving up - through the history as necessary using a non-incremental search for - a string supplied by the user.''' - q=self._history.non_incremental_reverse_search_history(self.l_buffer) - self.l_buffer=q - - def non_incremental_forward_search_history(self, e): # (M-n) - '''Search forward starting at the current line and moving down - through the the history as necessary using a non-incremental search - for a string supplied by the user.''' - q=self._history.non_incremental_reverse_search_history(self.l_buffer) - self.l_buffer=q + self._init_incremental_search(self._history.forward_search_history, 1, e) def history_search_forward(self, e): # () '''Search forward through the history for the string of characters diff --git a/pyreadline/rlmain.py b/pyreadline/rlmain.py index cf2bcd6..026022d 100644 --- a/pyreadline/rlmain.py +++ b/pyreadline/rlmain.py @@ -422,7 +422,9 @@ class Readline(BaseReadline): c=self.console l_buffer=self.mode.l_buffer c.cursor(0) #Hide cursor avoiding flicking - c.pos(*self.prompt_end_pos) + #c.pos(*self.prompt_end_pos) + c.pos(*self.prompt_begin_pos) + self._print_prompt() ltext = l_buffer.quoted_text() if l_buffer.enable_selection and l_buffer.selection_mark>=0: start=len(l_buffer[:l_buffer.selection_mark].quoted_text()) @@ -490,7 +492,6 @@ class Readline(BaseReadline): event = c.getkeypress() except KeyboardInterrupt: event=self.handle_ctrl_c() - result=self.mode.process_keyevent(event.keyinfo) self._update_line() return result @@ -529,10 +530,6 @@ class Readline(BaseReadline): - - - - # create a Readline object to contain the state rl = Readline()