From b1fe88f8c666ac3b9664ad0a285d6e3e3f53b0f5 Mon Sep 17 00:00:00 2001 From: jstenar <> Date: Wed, 18 Oct 2006 21:25:49 +0000 Subject: [PATCH] pyreadline-refactor: Fixing clipboard bugs, adding some editing functions --- pyreadline/keysyms/keysyms.py | 4 +- pyreadline/lineeditor/lineobj.py | 102 ++++++++++++++++++++++--------- pyreadline/logger.py | 2 +- pyreadline/modes/basemode.py | 49 ++++++++++++++- pyreadline/modes/emacs.py | 32 ++-------- pyreadline/rlmain.py | 15 ++--- 6 files changed, 134 insertions(+), 70 deletions(-) diff --git a/pyreadline/keysyms/keysyms.py b/pyreadline/keysyms/keysyms.py index 2328001..6d62de7 100644 --- a/pyreadline/keysyms/keysyms.py +++ b/pyreadline/keysyms/keysyms.py @@ -115,8 +115,10 @@ def make_KeyPress(char,state,keycode): control = (state & (4+8)) != 0 meta = (state & (1+2)) != 0 shift = (state & 0x10) != 0 - if control: + if control and char !="\x00": char = chr(VkKeyScan(ord(char)) & 0xff) + elif control: + char=chr(keycode) try: keyname=code2sym_map[keycode] except KeyError: diff --git a/pyreadline/lineeditor/lineobj.py b/pyreadline/lineeditor/lineobj.py index 40635a2..3f189d3 100644 --- a/pyreadline/lineeditor/lineobj.py +++ b/pyreadline/lineeditor/lineobj.py @@ -9,7 +9,7 @@ import re,operator import wordmatcher import pyreadline.clipboard as clipboard - +from pyreadline.logger import log,log_sock class NotAWordError(IndexError): pass @@ -292,7 +292,7 @@ class TextLine(object): stop=key.stop(self) else: stop=key.stop - return TextLine(self.line_buffer[start:stop],point=0) + return self.__class__(self.line_buffer[start:stop],point=0) elif isinstance(key,LinePositioner): return self.line_buffer[key(self)] elif isinstance(key,tuple): @@ -337,15 +337,15 @@ class TextLine(object): if isinstance(key,slice): start=key.start stop=key.stop - prev=self.line_buffer[:start] - rest=self.line_buffer[stop:] elif isinstance(key,LinePositioner): start=key(self) stop=start+1 else: start=key stop=key+1 - value=TextLine(value).line_buffer + prev=self.line_buffer[:start] + value=self.__class__(value).line_buffer + rest=self.line_buffer[stop:] out=prev+value+rest if len(out)>=len(self): self.point=len(self) @@ -356,10 +356,16 @@ class TextLine(object): def upper(self): self.line_buffer=[x.upper() for x in self.line_buffer] + return self def lower(self): self.line_buffer=[x.lower() for x in self.line_buffer] - + return self + + def capitalize(self): + self.set_line(self.get_line_text().capitalize(),self.point) + return self + def startswith(self,txt): return self.get_line_text().startswith(txt) @@ -390,12 +396,16 @@ class ReadLineTextBuffer(TextLine): def __repr__(self): return 'ReadLineTextBuffer("%s",point=%s,mark=%s,selection_mark=%s)'%(self.line_buffer,self.point,self.mark,self.selection_mark) - + def insert_text(self,char): self.delete_selection() self.selection_mark=-1 self._insert_text(char) + def to_clipboard(self): + if self.enable_win32_clipboard: + clipboard.set_clipboard_text(self.get_line_text()) + ######### Movement def beginning_of_line(self): @@ -473,6 +483,7 @@ class ReadLineTextBuffer(TextLine): return True else: return False + self.selection_mark=-1 def delete_char(self): if not self.delete_selection(): @@ -492,93 +503,128 @@ class ReadLineTextBuffer(TextLine): del self[PrevWordStart:Point] self.selection_mark=-1 + def forward_delete_word(self): + if not self.delete_selection(): + #del self[PrevWordEnd:Point] + del self[Point:NextWordStart] + self.selection_mark=-1 + def delete_current_word(self): if not self.delete_selection(): del self[CurrentWord] self.selection_mark=-1 def delete_horizontal_space(self): - if not self.delete_selection(): - pass + if self[Point] in " \t": + del self[PrevWordEnd:NextWordStart] self.selection_mark=-1 ######### Case def upcase_word(self): + p=self.point try: - self[CurrentWord]=self[CurrentWord].line_buffer.upper() + self[CurrentWord]=self[CurrentWord].upper() + self.point=p except NotAWordError: pass def downcase_word(self): + p=self.point try: - self[CurrentWord]=self[CurrentWord].line_buffer.lower() + self[CurrentWord]=self[CurrentWord].lower() + self.point=p except NotAWordError: pass - def capitalize_word(self): + def capitalize_word(self): + p=self.point try: - self[CurrentWord]=self[CurrentWord].line_buffer.capitalize() + self[CurrentWord]=self[CurrentWord].capitalize() + self.point=p except NotAWordError: pass ########### Transpose def transpose_chars(self): - pass + p2=Point(self) + if p2==0: + return + elif p2==len(self): + p2=p2-1 + p1=p2-1 + self[p2],self[p1]=self[p1],self[p2] + self.point=p2+1 def transpose_words(self): - pass + word1=TextLine(self) + word2=TextLine(self) + if self.point==len(self): + word2.point=PrevWordStart + word1.point=PrevWordStart(word2) + else: + word1.point=PrevWordStart + word2.point=NextWordStart + stop1=NextWordEnd(word1) + stop2=NextWordEnd(word2) + start1=word1.point + start2=word2.point + self[start2:stop2]=word1[Point:NextWordEnd] + self[start1:stop1]=word2[Point:NextWordEnd] + self.point=stop2 + ############ Kill def kill_line(self): - if self.enable_win32_clipboard: - toclipboard="".join(self.line_buffer[self.point:]) - clipboard.set_clipboard_text(toclipboard) + self[self.point:].to_clipboard() del self.line_buffer[self.point:] def kill_whole_line(self): - clipboard.set_clipboard_text() + self[:].to_clipboard() del self[:] def backward_kill_line(self): - clipboard.set_clipboard_text() + self[StartOfLine:Point].to_clipboard() del self[StartOfLine:Point] def unix_line_discard(self): - clipboard.set_clipboard_text(self[StartOfLine:Point]) + del self[StartOfLine:Point] pass def kill_word(self): """Kills to next word ending""" - clipboard.set_clipboard_text(self[Point:NextWordEnd]) + self[Point:NextWordEnd].to_clipboard() del self[Point:NextWordEnd] def backward_kill_word(self): """Kills to next word ending""" - clipboard.set_clipboard_text(self[PrevWordStart:Point]) + self[PrevWordStart:Point].to_clipboard() if not self.delete_selection(): del self[PrevWordStart:Point] self.selection_mark=-1 + def forward_kill_word(self): + """Kills to next word ending""" + self[Point:NextWordEnd].to_clipboard() + if not self.delete_selection(): + del self[Point:NextWordEnd] + self.selection_mark=-1 + def unix_word_rubout(self): - clipboard.set_clipboard_text(self[PrevSpace:Point]) + self[PrevSpace:Point].to_clipboard() if not self.delete_selection(): del self[PrevSpace:Point] self.selection_mark=-1 def kill_region(self): - clipboard.set_clipboard_text() pass def copy_region_as_kill(self): - clipboard.set_clipboard_text() pass def copy_backward_word(self): - clipboard.set_clipboard_text() pass def copy_forward_word(self): - clipboard.set_clipboard_text() pass diff --git a/pyreadline/logger.py b/pyreadline/logger.py index f11aac9..9878e6c 100644 --- a/pyreadline/logger.py +++ b/pyreadline/logger.py @@ -26,7 +26,7 @@ host="localhost" port=8081 logsocket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) -show_event=["keypress","bound_function"] +show_event=["keypress","bound_function","bind_key"] show_event=["bound_function"] def log_sock(s,event_type=None): diff --git a/pyreadline/modes/basemode.py b/pyreadline/modes/basemode.py index 55b9b00..dc64678 100644 --- a/pyreadline/modes/basemode.py +++ b/pyreadline/modes/basemode.py @@ -84,6 +84,7 @@ class BaseMode(object): 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))) keyinfo = make_KeyPress_from_keydescr(key.lower()).tuple() + log(">>>%s -> %s<<<"%(keyinfo,func.__name__)) self.key_dispatch[keyinfo] = func def _bind_exit_key(self, key): @@ -282,6 +283,21 @@ class BaseMode(object): self.l_buffer.backward_word_extend_selection() + 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.''' + 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.''' + 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.''' + self.l_buffer.capitalize_word() + def clear_screen(self, e): # (C-l) @@ -312,11 +328,20 @@ class BaseMode(object): to kill the characters instead of deleting them.''' self.l_buffer.backward_delete_char() - def backward_delete_word(self, e): # (Rubout) + 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.''' self.l_buffer.backward_delete_word() + 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.''' + self.l_buffer.forward_delete_word() + + def delete_horizontal_space(self, e): # () + '''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. ''' if ord(e.char)!=0: #don't insert null character in buffer, can happen with dead keys. @@ -326,13 +351,17 @@ class BaseMode(object): # Paste from clipboard def paste(self,e): - '''Paste windows clipboard''' + '''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") + log("paste: >%s<"%map(ord,txt)) self.insert_text(txt) def paste_mulitline_code(self,e): - '''Paste windows clipboard''' + '''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) @@ -372,6 +401,20 @@ class BaseMode(object): '''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 + 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.''' + print + txt="\n".join(self.rl_settings_to_string()) + print txt + self._print_prompt() + + + + def commonprefix(m): "Given a list of pathnames, returns the longest common leading component" if not m: return '' diff --git a/pyreadline/modes/emacs.py b/pyreadline/modes/emacs.py index d771961..2882e94 100644 --- a/pyreadline/modes/emacs.py +++ b/pyreadline/modes/emacs.py @@ -275,21 +275,6 @@ class EmacsMode(basemode.BaseMode): of the line, this transposes the last two words on the line.''' self.l_buffer.transpose_words() - 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.''' - 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.''' - 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.''' - self.l_buffer.capitalize_word() - def overwrite_mode(self, e): # () '''Toggle overwrite mode. With an explicit positive numeric argument, switches to overwrite mode. With an explicit non-positive @@ -324,7 +309,8 @@ class EmacsMode(basemode.BaseMode): words, to the end of the next word. Word boundaries are the same as forward-word.''' self.l_buffer.kill_word() - + forward_kill_word=kill_word + def backward_kill_word(self, e): # (M-DEL) '''Kill the word behind point. Word boundaries are the same as backward-word. ''' @@ -335,10 +321,6 @@ class EmacsMode(basemode.BaseMode): boundary. The killed text is saved on the kill-ring.''' self.l_buffer.unix_word_rubout() - def delete_horizontal_space(self, e): # () - '''Delete all spaces and tabs around point. By default, this is unbound. ''' - pass - def kill_region(self, e): # () '''Kill the text in the current region. By default, this command is unbound. ''' pass @@ -476,13 +458,6 @@ class EmacsMode(basemode.BaseMode): case, the line is accepted as if a newline had been typed.''' pass - def dump_functions(self, e): # () - '''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.''' - pass - def dump_variables(self, e): # () '''Print all of the settable variables and their values to the Readline output stream. If a numeric argument is supplied, the @@ -516,7 +491,7 @@ class EmacsMode(basemode.BaseMode): self._bind_key('Right', self.forward_char) self._bind_key('Control-f', self.forward_char) self._bind_key('BackSpace', self.backward_delete_char) - self._bind_key('Control-BackSpace', self.backward_delete_word) + self._bind_key('Control-BackSpace', self.backward_delete_word) self._bind_key('Home', self.beginning_of_line) self._bind_key('End', self.end_of_line) @@ -576,6 +551,7 @@ class EmacsMode(basemode.BaseMode): self._bind_key("multiply", self.self_insert) self._bind_key("divide", self.self_insert) self._bind_key("vk_decimal", self.self_insert) + log("RUNNING INIT EMACS") # make it case insensitive def commonprefix(m): diff --git a/pyreadline/rlmain.py b/pyreadline/rlmain.py index 2f11238..a502b54 100644 --- a/pyreadline/rlmain.py +++ b/pyreadline/rlmain.py @@ -19,7 +19,7 @@ import operator import exceptions import clipboard,logger,console -from logger import log +from logger import log,log_sock from error import ReadlineError,GetSetError from pyreadline.keysyms.common import make_KeyPress_from_keydescr @@ -241,7 +241,7 @@ class Readline(object): ## Internal functions - def rl_settings_to_string(self,e=None): + def rl_settings_to_string(self): out=["%-20s: %s"%("show all if ambigous",self.show_all_if_ambiguous)] out.append("%-20s: %s"%("mark_directories",self.mark_directories)) out.append("%-20s: %s"%("bell_style",self.bell_style)) @@ -250,15 +250,9 @@ class Readline(object): tablepat="%-7s %-7s %-7s %-15s %-15s " out.append(tablepat%("Control","Meta","Shift","Keycode/char","Function")) bindings=[(k[0],k[1],k[2],k[3],v.__name__) for k,v in self.mode.key_dispatch.iteritems()] - #print self.mode.key_dispatch - #bindings=[str(v) for k,v in self.mode.key_dispatch.iteritems()] bindings.sort() for key in bindings: - pass - # out.append(str(key)) out.append(tablepat%(key)) - if e: - print "\n".join(out) return out def _bell(self): @@ -334,10 +328,13 @@ class Readline(object): def setmode(name): self.mode=modes[name] def bind_key(key,name): - # print "bind",key,name + log("bind %s %s"%(key,name)) + log_sock("bindkey: %s %s"%(key,name),"bind_key") if hasattr(modes[mode],name): # print "can bind",key,name modes[mode]._bind_key(key,getattr(modes[mode],name)) + else: + print "Trying to bind unknown command '%s' to key '%s'"%(name,key) def un_bind_key(key): keyinfo = make_KeyPress_from_keydescr(key).tuple() if keyinfo in modes[mode].key_dispatch: