Continued refactoring of keyboard handling

This commit is contained in:
Jorgen Stenarson
2008-06-30 21:56:49 +02:00
5 changed files with 214 additions and 191 deletions
+2
View File
@@ -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:
-44
View File
@@ -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
+73 -71
View File
@@ -51,6 +51,9 @@ class BaseMode(object):
self.paste_line_buffer=[]
self._sub_modes=[]
def __repr__(self):
return "<BaseMode>"
@@ -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
+136 -70
View File
@@ -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 "<EmacsMode>"
@@ -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
+3 -6
View File
@@ -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()