mirror of
https://github.com/wassname/pyreadline.git
synced 2026-07-20 12:30:38 +08:00
pyreadline refactor: Refactored line_buffer and history_buffer
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
2006-02-14 Jörgen Stenarson <jorgen.stenarson -at- bostream.nu>
|
||||
|
||||
* Started Refactor branch
|
||||
* Refactored line_buffer to separate class
|
||||
* Refactored history buffer to separate class
|
||||
*
|
||||
|
||||
2006-01-31 Jörgen Stenarson <jorgen.stenarson -at- bostream.nu>
|
||||
|
||||
* Added experimental mulitline paste. Thís version ALWAYS adds
|
||||
|
||||
@@ -12,7 +12,7 @@ This was modeled after the C extension of the same name by Fredrik Lundh.
|
||||
'''
|
||||
|
||||
# primitive debug printing that won't interfere with the screen
|
||||
if 0:
|
||||
if 1:
|
||||
fp = open('debug.txt', 'w')
|
||||
def log(s):
|
||||
print >>fp, s
|
||||
@@ -468,6 +468,7 @@ class Console(object):
|
||||
'''Return next key press event from the queue, ignoring others.'''
|
||||
while 1:
|
||||
e = self.get()
|
||||
log("getKeypress:%s,%s,%s"%(e.keyinfo,e.keycode,e.type))
|
||||
if e.type == 'KeyPress' and e.keycode not in key_modifiers:
|
||||
log(e)
|
||||
if e.keysym == 'Next':
|
||||
@@ -476,7 +477,9 @@ class Console(object):
|
||||
self.scroll_window(-12)
|
||||
else:
|
||||
return e
|
||||
|
||||
elif e.type == 'KeyRelease' and e.keyinfo==(True, False, False, 83):
|
||||
return e
|
||||
|
||||
def getchar(self):
|
||||
'''Get next character from queue.'''
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
FOREGROUND_BLUE = 0x0001
|
||||
FOREGROUND_GREEN = 0x0002
|
||||
FOREGROUND_RED = 0x0004
|
||||
FOREGROUND_INTENSITY = 0x0008
|
||||
BACKGROUND_BLUE = 0x0010
|
||||
BACKGROUND_GREEN = 0x0020
|
||||
BACKGROUND_RED = 0x0040
|
||||
BACKGROUND_INTENSITY = 0x0080
|
||||
COMMON_LVB_LEADING_BYTE = 0x0100
|
||||
COMMON_LVB_TRAILING_BYTE = 0x0200
|
||||
COMMON_LVB_GRID_HORIZONTAL= 0x0400
|
||||
COMMON_LVB_GRID_LVERTICAL = 0x0800
|
||||
COMMON_LVB_GRID_RVERTICAL = 0x1000
|
||||
COMMON_LVB_REVERSE_VIDEO = 0x2000
|
||||
COMMON_LVB_UNDERSCORE = 0x4000
|
||||
@@ -0,0 +1,18 @@
|
||||
import sys,textwrap
|
||||
|
||||
rlmain=sys.modules["pyreadline.rlmain"]
|
||||
rl=rlmain.rl
|
||||
|
||||
def get_doc(rl):
|
||||
methods=[(x,getattr(rl,x)) for x in dir(rl) if callable(getattr(rl,x))]
|
||||
return [ (x,m.__doc__ )for x,m in methods if m.__doc__]
|
||||
|
||||
|
||||
def get_rest(rl):
|
||||
q=get_doc(rl)
|
||||
out=[]
|
||||
for funcname,doc in q:
|
||||
out.append(funcname)
|
||||
out.append("\n".join(textwrap.wrap(doc,80,initial_indent=" ")))
|
||||
out.append("")
|
||||
return out
|
||||
@@ -0,0 +1,190 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#*****************************************************************************
|
||||
# Copyright (C) 2006 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#*****************************************************************************
|
||||
import re,operator,string,sys
|
||||
|
||||
#import wordmatcher
|
||||
#import pyreadline.clipboard as clipboard
|
||||
if "pyreadline" in sys.modules:
|
||||
pyreadline= sys.modules["pyreadline"]
|
||||
else:
|
||||
import pyreadline
|
||||
|
||||
import lineobj
|
||||
|
||||
import exceptions
|
||||
|
||||
class EscapeHistory(exceptions.Exception):
|
||||
pass
|
||||
|
||||
class LineHistory(object):
|
||||
def __init__(self):
|
||||
self.history=[]
|
||||
self.history_length=-1
|
||||
self.history_cursor=0
|
||||
|
||||
def get_history_length(self):
|
||||
return self.history_length
|
||||
|
||||
def set_history_length(self,value):
|
||||
self.history_length=value
|
||||
|
||||
def read_history_file(self, filename):
|
||||
'''Load a readline history file.'''
|
||||
try:
|
||||
for line in open(filename, 'rt'):
|
||||
self.add_history(lineobj.ReadLineTextBuffer(line.rstrip()))
|
||||
except IOError:
|
||||
self.history = []
|
||||
self.history_cursor = 0
|
||||
raise IOError
|
||||
|
||||
def write_history_file(self, filename):
|
||||
'''Save a readline history file.'''
|
||||
fp = open(filename, 'wb')
|
||||
for line in self.history:
|
||||
fp.write(line.get_line_text())
|
||||
fp.write('\n')
|
||||
fp.close()
|
||||
|
||||
|
||||
def add_history(self, line):
|
||||
'''Append a line to the history buffer, as if it was the last line typed.'''
|
||||
if not line.get_line_text():
|
||||
pass
|
||||
elif len(self.history) > 0 and self.history[-1].get_line_text() == line.get_line_text():
|
||||
pass
|
||||
else:
|
||||
self.history.append(line)
|
||||
if self.history_length > 0 and len(self.history) > self.history_length:
|
||||
self.history = self.history[-self.history_length:]
|
||||
self.history_cursor = len(self.history)
|
||||
|
||||
def previous_history(self,current): # (C-p)
|
||||
'''Move back through the history list, fetching the previous command. '''
|
||||
if self.history_cursor==len(self.history):
|
||||
self.history.append(current.copy()) #do not use add_history since we do not want to increment cursor
|
||||
|
||||
if self.history_cursor > 0:
|
||||
self.history_cursor -= 1
|
||||
current.set_line(self.history[self.history_cursor].get_line_text())
|
||||
|
||||
def next_history(self,current): # (C-n)
|
||||
'''Move forward through the history list, fetching the next command. '''
|
||||
if self.history_cursor < len(self.history)-1:
|
||||
self.history_cursor += 1
|
||||
current.set_line(self.history[self.history_cursor].get_line_text())
|
||||
|
||||
def beginning_of_history(self): # (M-<)
|
||||
'''Move to the first line in the history.'''
|
||||
self.history_cursor = 0
|
||||
if len(self.history) > 0:
|
||||
self.l_buffer = self.history[0]
|
||||
|
||||
def end_of_history(self,current): # (M->)
|
||||
'''Move to the end of the input history, i.e., the line currently
|
||||
being entered.'''
|
||||
self.history_cursor=len(self.history)
|
||||
current.set_line(self.history[-1].get_line_text())
|
||||
|
||||
def reverse_search_history(self,searchfor):
|
||||
res=[(idx,line) for idx,line in enumerate(self.history[self.history_cursor:0:-1]) if searchfor in line]
|
||||
if res:
|
||||
self.history_cursor-=res[0][0]
|
||||
return res[0][1].get_line_text()
|
||||
return ""
|
||||
|
||||
def forward_search_history(self,searchfor):
|
||||
res=[(idx,line) for idx,line in enumerate(self.history[self.history_cursor:]) if searchfor in line]
|
||||
if res:
|
||||
self.history_cursor+=res[0][0]
|
||||
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.keysym == '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.keysym == 'Return':
|
||||
break
|
||||
else:
|
||||
pyreadline.rl._bell()
|
||||
|
||||
if query:
|
||||
hc = self.history_cursor - 1
|
||||
while (direction < 0 and hc >= 0) or (direction > 0 and hc < len(self.history)):
|
||||
if self.history[hc].startswith(query) >= 0:
|
||||
current=self.history[hc]
|
||||
self.history_cursor = hc
|
||||
return
|
||||
hc += direction
|
||||
else:
|
||||
pyreadline.rl._bell()
|
||||
|
||||
|
||||
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.'''
|
||||
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.'''
|
||||
self._non_i_search(1,current)
|
||||
|
||||
def _search(self, direction,partial):
|
||||
query = partial[0:partial.point].get_line_text()
|
||||
hc = self.history_cursor + direction
|
||||
while (direction < 0 and hc >= 0) or (direction > 0 and hc < len(self.history)):
|
||||
h = self.history[hc]
|
||||
if not query:
|
||||
self.history_cursor = hc
|
||||
result=lineobj.ReadLineTextBuffer(h,point=partial.point)
|
||||
return result
|
||||
elif h.get_line_text().startswith(query) and h != partial.get_line_text():
|
||||
self.history_cursor = hc
|
||||
result=lineobj.ReadLineTextBuffer(h,point=partial.point)
|
||||
return result
|
||||
hc += direction
|
||||
else:
|
||||
return lineobj.ReadLineTextBuffer(query,point=partial.point)
|
||||
|
||||
def history_search_forward(self,partial): # ()
|
||||
'''Search forward through the history for the string of characters
|
||||
between the start of the current line and the point. This is a
|
||||
non-incremental search. By default, this command is unbound.'''
|
||||
return self._search(1,partial)
|
||||
|
||||
def history_search_backward(self,partial): # ()
|
||||
'''Search backward through the history for the string of characters
|
||||
between the start of the current line and the point. This is a
|
||||
non-incremental search. By default, this command is unbound.'''
|
||||
return self._search(-1,partial)
|
||||
if __name__=="__main__":
|
||||
q=LineHistory()
|
||||
RL=lineobj.ReadLineTextBuffer
|
||||
q.add_history(RL("apan"))
|
||||
q.add_history(RL("apbn"))
|
||||
q.add_history(RL("apcn"))
|
||||
q.add_history(RL("apdn"))
|
||||
q.add_history(RL("apen"))
|
||||
@@ -0,0 +1,526 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#*****************************************************************************
|
||||
# Copyright (C) 2006 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#*****************************************************************************
|
||||
import re,operator
|
||||
|
||||
import wordmatcher
|
||||
import pyreadline.clipboard as clipboard
|
||||
|
||||
class NotAWordError(IndexError):
|
||||
pass
|
||||
|
||||
def quote_char(c):
|
||||
if ord(c)>0:
|
||||
return c
|
||||
|
||||
|
||||
|
||||
############## Line positioner ########################
|
||||
|
||||
class LinePositioner(object):
|
||||
def __call__(self,line):
|
||||
NotImplementedError("Base class !!!")
|
||||
|
||||
class NextChar(LinePositioner):
|
||||
def __call__(self,line):
|
||||
if line.point<len(line.line_buffer):
|
||||
return line.point+1
|
||||
else:
|
||||
return line.point
|
||||
NextChar=NextChar()
|
||||
|
||||
class PrevChar(LinePositioner):
|
||||
def __call__(self,line):
|
||||
if line.point>0:
|
||||
return line.point-1
|
||||
else:
|
||||
return line.point
|
||||
PrevChar=PrevChar()
|
||||
|
||||
class NextWordStart(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return line.next_start_segment(line.line_buffer,line.is_word_token)[line.point]
|
||||
NextWordStart=NextWordStart()
|
||||
|
||||
class NextWordEnd(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return line.next_end_segment(line.line_buffer,line.is_word_token)[line.point]
|
||||
NextWordEnd=NextWordEnd()
|
||||
|
||||
class PrevWordStart(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return line.prev_start_segment(line.line_buffer,line.is_word_token)[line.point]
|
||||
PrevWordStart=PrevWordStart()
|
||||
|
||||
|
||||
class WordStart(LinePositioner):
|
||||
def __call__(self,line):
|
||||
if line.is_word_token(line.get_line_text()[Point(line):Point(line)+1]):
|
||||
if Point(line)>0 and line.is_word_token(line.get_line_text()[Point(line)-1:Point(line)]):
|
||||
return PrevWordStart(line)
|
||||
else:
|
||||
return line.point
|
||||
else:
|
||||
raise NotInWord
|
||||
WordStart=WordStart()
|
||||
|
||||
class WordEnd(LinePositioner):
|
||||
def __call__(self,line):
|
||||
if line.is_word_token(line.get_line_text()[Point(line):Point(line)+1]):
|
||||
if line.is_word_token(line.get_line_text()[Point(line)+1:Point(line)+2]):
|
||||
return NextWordEnd(line)
|
||||
else:
|
||||
return line.point
|
||||
else:
|
||||
raise NotAWordError("Point is not in a word")
|
||||
WordEnd=WordEnd()
|
||||
|
||||
class PrevWordEnd(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return line.prev_end_segment(line.line_buffer,line.is_word_token)[line.point]
|
||||
PrevWordEnd=PrevWordEnd()
|
||||
|
||||
class StartOfLine(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return 0
|
||||
StartOfLine=StartOfLine()
|
||||
|
||||
class EndOfLine(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return len(line.line_buffer)
|
||||
EndOfLine=EndOfLine()
|
||||
|
||||
class Point(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return line.point
|
||||
Point=Point()
|
||||
|
||||
class Mark(LinePositioner):
|
||||
def __call__(self,line):
|
||||
return line.mark
|
||||
Mark=Mark()
|
||||
|
||||
all_positioners=[(value.__class__.__name__,value) for key,value in globals().items() if isinstance(value,LinePositioner)]
|
||||
all_positioners.sort()
|
||||
|
||||
############### LineSlice #################
|
||||
|
||||
class LineSlice(object):
|
||||
def __call__(self,line):
|
||||
NotImplementedError("Base class !!!")
|
||||
|
||||
|
||||
class CurrentWord(LineSlice):
|
||||
def __call__(self,line):
|
||||
return slice(WordStart(line),WordEnd(line),None)
|
||||
CurrentWord=CurrentWord()
|
||||
|
||||
class NextWord(LineSlice):
|
||||
def __call__(self,line):
|
||||
work=TextLine(line)
|
||||
work.point=NextWordStart
|
||||
start=work.point
|
||||
stop=NextWordEnd(work)
|
||||
return slice(start,stop)
|
||||
NextWord=NextWord()
|
||||
|
||||
class PrevWord(LineSlice):
|
||||
def __call__(self,line):
|
||||
work=TextLine(line)
|
||||
work.point=PrevWordEnd
|
||||
stop=work.point
|
||||
start=PrevWordStart(work)
|
||||
return slice(start,stop)
|
||||
PrevWord=PrevWord()
|
||||
|
||||
class PointSlice(LineSlice):
|
||||
def __call__(self,line):
|
||||
return slice(Point(line),Point(line)+1,None)
|
||||
PointSlice=PointSlice()
|
||||
|
||||
|
||||
############### TextLine ######################
|
||||
|
||||
class TextLine(object):
|
||||
def __init__(self,txtstr,point=None,mark=None):
|
||||
self.line_buffer=[]
|
||||
self._point=0
|
||||
self.mark=-1
|
||||
self.undo_stack=[]
|
||||
if isinstance(txtstr,TextLine): #copy
|
||||
if point is None:
|
||||
self.point=txtstr.point
|
||||
else:
|
||||
self.point=point
|
||||
if mark is None:
|
||||
self.mark=txtstr.mark
|
||||
else:
|
||||
self.mark=mark
|
||||
self.line_buffer=txtstr.line_buffer[:]
|
||||
else:
|
||||
self._insert_text(txtstr)
|
||||
if point is None:
|
||||
self.point=0
|
||||
else:
|
||||
self.point=point
|
||||
if mark is None:
|
||||
self.mark=-1
|
||||
else:
|
||||
self.mark=mark
|
||||
|
||||
self.is_word_token=wordmatcher.is_word_token
|
||||
self.next_start_segment=wordmatcher.next_start_segment
|
||||
self.next_end_segment=wordmatcher.next_end_segment
|
||||
self.prev_start_segment=wordmatcher.prev_start_segment
|
||||
self.prev_end_segment=wordmatcher.prev_end_segment
|
||||
|
||||
def push_undo(self):
|
||||
ltext = self.get_line_text()
|
||||
if self.undo_stack and ltext == self.undo_stack[-1].get_line_text():
|
||||
self.undo_stack[-1].point = self.point
|
||||
else:
|
||||
self.undo_stack.append(self.copy())
|
||||
|
||||
def pop_undo(self):
|
||||
if len(self.undo_stack) >= 2:
|
||||
self.undo_stack.pop()
|
||||
self.set_top_undo()
|
||||
self.undo_stack.pop()
|
||||
else:
|
||||
self.reset_line()
|
||||
self.undo_stack = []
|
||||
|
||||
def set_top_undo(self):
|
||||
if self.undo_stack:
|
||||
undo=self.undo_stack[-1]
|
||||
self.line_buffer=undo.line_buffer
|
||||
self.point=undo.point
|
||||
self.mark=undo.mark
|
||||
else:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return 'TextLine("%s",point=%s,mark=%s)'%(self.line_buffer,self.point,self.mark)
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
def set_point(self,value):
|
||||
if isinstance(value,LinePositioner):
|
||||
self._point=value(self)
|
||||
else:
|
||||
self._point=value
|
||||
def get_point(self):
|
||||
return self._point
|
||||
point=property(get_point,set_point)
|
||||
|
||||
|
||||
def visible_line_width(self,position=Point):
|
||||
"""Return the visible width of the text in line buffer up to position."""
|
||||
return len(self[:position].quoted_text())
|
||||
|
||||
def quoted_text(self):
|
||||
quoted = [ quote_char(c) for c in self.line_buffer ]
|
||||
self.line_char_width = [ len(c) for c in quoted ]
|
||||
return ''.join(quoted)
|
||||
|
||||
def get_line_text(self):
|
||||
return ''.join(self.line_buffer)
|
||||
|
||||
def set_line(self, text, cursor=None):
|
||||
self.line_buffer = [ c for c in str(text) ]
|
||||
if cursor is None:
|
||||
self.point = len(self.line_buffer)
|
||||
else:
|
||||
self.point = cursor
|
||||
|
||||
def reset_line(self):
|
||||
self.line_buffer = []
|
||||
self.point = 0
|
||||
|
||||
def _insert_text(self, text):
|
||||
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
|
||||
#and continue processing
|
||||
if isinstance(key,LineSlice):
|
||||
key=key(self)
|
||||
if isinstance(key,slice):
|
||||
if key.step is None:
|
||||
pass
|
||||
else:
|
||||
raise Error
|
||||
if key.start is None:
|
||||
start=StartOfLine(self)
|
||||
elif isinstance(key.start,LinePositioner):
|
||||
start=key.start(self)
|
||||
else:
|
||||
start=key.start
|
||||
if key.stop is None:
|
||||
stop=EndOfLine(self)
|
||||
elif isinstance(key.stop,LinePositioner):
|
||||
stop=key.stop(self)
|
||||
else:
|
||||
stop=key.stop
|
||||
return TextLine(self.line_buffer[start:stop])
|
||||
elif isinstance(key,LinePositioner):
|
||||
return self.line_buffer[key(self)]
|
||||
elif isinstance(key,tuple):
|
||||
raise IndexError("Cannot use step in line buffer indexing") #Multiple slice not allowed
|
||||
else:
|
||||
return TextLine(self.line_buffer[key])
|
||||
|
||||
def __delitem__(self,key):
|
||||
if isinstance(key,LineSlice):
|
||||
key=key(self)
|
||||
if isinstance(key,slice):
|
||||
start=key.start
|
||||
stop=key.stop
|
||||
if isinstance(start,LinePositioner):
|
||||
start=start(self)
|
||||
if isinstance(stop,LinePositioner):
|
||||
stop=stop(self)
|
||||
elif isinstance(key,LinePositioner):
|
||||
start=key(self)
|
||||
stop=start+1
|
||||
else:
|
||||
start=key
|
||||
stop=key+1
|
||||
if self.point>stop:
|
||||
self.point=self.point-(stop-start)
|
||||
elif self.point>=start and self.point <=stop:
|
||||
self.point=start
|
||||
prev=self.line_buffer[:start]
|
||||
rest=self.line_buffer[stop:]
|
||||
self.line_buffer=prev+rest
|
||||
|
||||
def __setitem__(self,key,value):
|
||||
if isinstance(key,LineSlice):
|
||||
key=key(self)
|
||||
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
|
||||
self.line_buffer=prev+value+rest
|
||||
|
||||
def upper(self):
|
||||
self.line_buffer=self.line_buffer.upper()
|
||||
|
||||
def lower(self):
|
||||
self.line_buffer=self.line_buffer.lower()
|
||||
|
||||
def startswith(self,txt):
|
||||
return self.get_line_text().startswith(txt)
|
||||
|
||||
def endswith(self,txt):
|
||||
return self.get_line_text().endswith(txt)
|
||||
|
||||
def __contains__(self,txt):
|
||||
return txt in self.get_line_text()
|
||||
|
||||
|
||||
lines=[TextLine("abc"),
|
||||
TextLine("abc def"),
|
||||
TextLine("abc def ghi"),
|
||||
TextLine(" abc def "),
|
||||
]
|
||||
l=lines[2]
|
||||
l.point=5
|
||||
|
||||
|
||||
|
||||
class ReadLineTextBuffer(TextLine):
|
||||
def __init__(self,txtstr,point=None,mark=None):
|
||||
super(ReadLineTextBuffer,self).__init__(txtstr,point,mark)
|
||||
self.enable_win32_clipboard=True
|
||||
|
||||
def insert_text(self,char):
|
||||
self._insert_text(char)
|
||||
|
||||
######### Movement
|
||||
|
||||
def beginning_of_line(self):
|
||||
self.point=StartOfLine
|
||||
|
||||
def end_of_line(self):
|
||||
self.point=EndOfLine
|
||||
|
||||
def forward_char(self):
|
||||
self.point=NextChar
|
||||
|
||||
def backward_char(self):
|
||||
self.point=PrevChar
|
||||
|
||||
def forward_word(self):
|
||||
self.point=NextWordStart
|
||||
|
||||
def backward_word(self):
|
||||
self.point=PrevWordStart
|
||||
|
||||
######### delete
|
||||
|
||||
def delete_char(self):
|
||||
del self[Point]
|
||||
|
||||
def backward_delete_char(self):
|
||||
self.backward_char()
|
||||
self.delete_char()
|
||||
|
||||
def delete_current_word(self):
|
||||
del self[CurrentWord]
|
||||
|
||||
def delete_horizontal_space(self):
|
||||
pass
|
||||
######### Case
|
||||
|
||||
def upcase_word(self):
|
||||
try:
|
||||
self[CurrentWord]=self[CurrentWord].line_buffer.upper()
|
||||
except NotAWordError:
|
||||
pass
|
||||
|
||||
def downcase_word(self):
|
||||
try:
|
||||
self[CurrentWord]=self[CurrentWord].line_buffer.lower()
|
||||
except NotAWordError:
|
||||
pass
|
||||
|
||||
def capitalize_word(self):
|
||||
try:
|
||||
self[CurrentWord]=self[CurrentWord].line_buffer.capitalize()
|
||||
except NotAWordError:
|
||||
pass
|
||||
########### Transpose
|
||||
def transpose_chars(self):
|
||||
pass
|
||||
|
||||
def transpose_words(self):
|
||||
pass
|
||||
|
||||
############ Kill
|
||||
|
||||
def kill_line(self):
|
||||
if self.enable_win32_clipboard:
|
||||
toclipboard="".join(self.line_buffer[self.point:])
|
||||
clipboard.set_clipboard_text(toclipboard)
|
||||
self.line_buffer[self.point:] = []
|
||||
|
||||
def backward_kill_line(self):
|
||||
del self[StartOfLine:Point]
|
||||
|
||||
def unix_line_discard(self):
|
||||
pass
|
||||
|
||||
def kill_word(self):
|
||||
"""Kills to next word ending"""
|
||||
del self[Point:NextWordEnd]
|
||||
|
||||
def backward_kill_word(self):
|
||||
"""Kills to next word ending"""
|
||||
pass
|
||||
|
||||
def unix_word_rubout(self):
|
||||
pass
|
||||
|
||||
def kill_region(self):
|
||||
pass
|
||||
|
||||
def copy_region_as_kill(self):
|
||||
pass
|
||||
|
||||
def copy_backward_word(self):
|
||||
pass
|
||||
|
||||
def copy_forward_word(self):
|
||||
pass
|
||||
|
||||
|
||||
def yank(self):
|
||||
pass
|
||||
|
||||
def yank_pop(self):
|
||||
pass
|
||||
|
||||
|
||||
############## Mark
|
||||
|
||||
def set_mark(self):
|
||||
self.mark=self.point
|
||||
|
||||
def exchange_point_and_mark(self):
|
||||
pass
|
||||
|
||||
|
||||
def copy_region_to_clipboard(self): # ()
|
||||
'''Copy the text in the region to the windows clipboard.'''
|
||||
if self.enable_win32_clipboard:
|
||||
mark=min(self.mark,len(self.line_buffer))
|
||||
cursor=min(self.point,len(self.line_buffer))
|
||||
if self.mark==-1:
|
||||
return
|
||||
begin=min(cursor,mark)
|
||||
end=max(cursor,mark)
|
||||
toclipboard="".join(self.line_buffer[begin:end])
|
||||
clipboard.SetClipboardText(str(toclipboard))
|
||||
|
||||
############## Paste
|
||||
|
||||
|
||||
##################################################################
|
||||
q=ReadLineTextBuffer("asff asFArw ewrWErhg",point=8)
|
||||
q=TextLine("asff asFArw ewrWErhg",point=8)
|
||||
|
||||
def show_pos(buff,pos,chr="."):
|
||||
l=len(buff.line_buffer)
|
||||
def choice(bool):
|
||||
if bool:
|
||||
return chr
|
||||
else:
|
||||
return " "
|
||||
return "".join([choice(pos==idx) for idx in range(l+1)])
|
||||
|
||||
|
||||
def test_positioner(buff,points,positioner):
|
||||
print (" %s "%positioner.__class__.__name__).center(40,"-")
|
||||
buffstr=buff.line_buffer
|
||||
|
||||
print '"%s"'%(buffstr)
|
||||
for point in points:
|
||||
b=TextLine(buff,point=point)
|
||||
out=[" "]*(len(buffstr)+1)
|
||||
pos=positioner(b)
|
||||
if pos==point:
|
||||
out[pos]="&"
|
||||
else:
|
||||
out[point]="."
|
||||
out[pos]="^"
|
||||
print '"%s"'%("".join(out))
|
||||
|
||||
if __name__=="__main__":
|
||||
|
||||
|
||||
print '%-15s "%s"'%("Position",q.get_line_text())
|
||||
print '%-15s "%s"'%("Point",show_pos(q,q.point))
|
||||
|
||||
|
||||
for name,positioner in all_positioners:
|
||||
pos=positioner(q)
|
||||
[]
|
||||
print '%-15s "%s"'%(name,show_pos(q,pos,"^"))
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#*****************************************************************************
|
||||
# Copyright (C) 2006 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
#*****************************************************************************
|
||||
|
||||
import re,operator
|
||||
|
||||
|
||||
def str_find_all(str,ch):
|
||||
result=[]
|
||||
index=0
|
||||
while index>=0:
|
||||
index=str.find(ch,index)
|
||||
if index>=0:
|
||||
result.append(index)
|
||||
index+=1
|
||||
return result
|
||||
|
||||
|
||||
word_pattern=re.compile("(x*)")
|
||||
|
||||
def markwords(str,iswordfun):
|
||||
markers={True:"x",False:"o"}
|
||||
return "".join([markers[iswordfun(ch)] for ch in str])
|
||||
|
||||
def split_words(str,iswordfun):
|
||||
return [x for x in word_pattern.split(markwords(str,iswordfun)) if x !=""]
|
||||
|
||||
def mark_start_segment(str,is_segment):
|
||||
def mark_start(s):
|
||||
if s[0:1]=="x":
|
||||
return "s"+s[1:]
|
||||
else:
|
||||
return s
|
||||
return "".join(map(mark_start,split_words(str,is_segment)))
|
||||
|
||||
def mark_end_segment(str,is_segment):
|
||||
def mark_start(s):
|
||||
if s[0:1]=="x":
|
||||
return s[:-1]+"s"
|
||||
else:
|
||||
return s
|
||||
return "".join(map(mark_start,split_words(str,is_segment)))
|
||||
|
||||
def mark_start_segment_index(str,is_segment):
|
||||
return str_find_all(mark_start_segment(str,is_segment),"s")
|
||||
|
||||
def mark_end_segment_index(str,is_segment):
|
||||
return [x+1 for x in str_find_all(mark_end_segment(str,is_segment),"s")]
|
||||
|
||||
|
||||
################ Following are used in lineobj ###########################
|
||||
|
||||
def is_word_token(str):
|
||||
return not is_non_word_token(str)
|
||||
|
||||
def is_non_word_token(str):
|
||||
assert(len(str)==1)
|
||||
if str in " \t\n":
|
||||
return True
|
||||
|
||||
def next_start_segment(str,is_segment):
|
||||
str="".join(str)
|
||||
result=[]
|
||||
for start in mark_start_segment_index(str,is_segment):
|
||||
result[len(result):start]=[start for x in range(start-len(result))]
|
||||
result[len(result):len(str)]=[len(str) for x in range(len(str)-len(result)+1)]
|
||||
return result
|
||||
|
||||
def next_end_segment(str,is_segment):
|
||||
str="".join(str)
|
||||
result=[]
|
||||
for start in mark_end_segment_index(str,is_segment):
|
||||
result[len(result):start]=[start for x in range(start-len(result))]
|
||||
result[len(result):len(str)]=[len(str) for x in range(len(str)-len(result)+1)]
|
||||
return result
|
||||
|
||||
|
||||
def prev_start_segment(str,is_segment):
|
||||
str="".join(str)
|
||||
result=[]
|
||||
prev=0
|
||||
for start in mark_start_segment_index(str,is_segment):
|
||||
result[len(result):start+1]=[prev for x in range(start-len(result)+1)]
|
||||
prev=start
|
||||
result[len(result):len(str)]=[prev for x in range(len(str)-len(result)+1)]
|
||||
return result
|
||||
|
||||
def prev_end_segment(str,is_segment):
|
||||
str="".join(str)
|
||||
result=[]
|
||||
prev=0
|
||||
for start in mark_end_segment_index(str,is_segment):
|
||||
result[len(result):start+1]=[prev for x in range(start-len(result)+1)]
|
||||
prev=start
|
||||
result[len(result):len(str)]=[len(str) for x in range(len(str)-len(result)+1)]
|
||||
return result
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ It is not complete and does not necessarily strive for complete
|
||||
compatibilty but rather convenience in use on the windows platform. It has been
|
||||
tested for use with windows 2000 and windows xp.
|
||||
|
||||
There are currently no released version, only subversion access is possible.
|
||||
use: easy_install pyreadline==dev
|
||||
|
||||
features:
|
||||
- Copy and paste using the clipboard
|
||||
- Smart paste for convenient use with ipython. Converting tab separated data to
|
||||
@@ -44,7 +47,7 @@ features:
|
||||
The latest development version is always available at the IPython subversion
|
||||
repository_.
|
||||
|
||||
.. _repository: http://ipython.scipy.org/svn/ipython/pyreadline/trunk#egg=ipython-dev
|
||||
.. _repository: http://ipython.scipy.org/svn/ipython/pyreadline/trunk#egg=pyreadline-dev
|
||||
"""
|
||||
|
||||
license = 'BSD'
|
||||
|
||||
+114
-280
@@ -25,7 +25,8 @@ import clipboard
|
||||
from console import log
|
||||
from keysyms import key_text_to_keyinfo
|
||||
|
||||
|
||||
import lineeditor.lineobj as lineobj
|
||||
import lineeditor.history as history
|
||||
|
||||
|
||||
def quote_char(c):
|
||||
@@ -38,19 +39,16 @@ class ReadlineError(exceptions.Exception):
|
||||
def inword(buffer,point):
|
||||
return buffer[point:point+1] in [A-Za-z0-9]
|
||||
|
||||
class GetSetError(ReadlineError):
|
||||
pass
|
||||
|
||||
class Readline:
|
||||
class Readline(object):
|
||||
def __init__(self):
|
||||
self.startup_hook = None
|
||||
self.pre_input_hook = None
|
||||
self.completer = None
|
||||
self.completer_delims = " \t\n\"\\'`@$><=;|&{("
|
||||
self.history_length = -1
|
||||
self.history = [] # strings for previous commands
|
||||
self.history_cursor = 0
|
||||
self.undo_stack = [] # each entry is a tuple with cursor_position and line_text
|
||||
self.line_buffer = []
|
||||
self.line_cursor = 0
|
||||
|
||||
self.console = console.Console()
|
||||
self.size = self.console.size()
|
||||
self.prompt_color = None
|
||||
@@ -85,6 +83,24 @@ class Readline:
|
||||
|
||||
self.paste_line_buffer=[]
|
||||
|
||||
# self.line_buffer = []
|
||||
# self.line_cursor = 0
|
||||
self.l_buffer=lineobj.ReadLineTextBuffer("")
|
||||
self._history=history.LineHistory()
|
||||
|
||||
def _g(x):
|
||||
def g(self):
|
||||
raise GetSetError("GET %s"%x)
|
||||
def s(self,q):
|
||||
raise GetSetError("SET %s"%x)
|
||||
return g,s
|
||||
line_buffer=property(*_g("line_buffer"))
|
||||
line_cursor=property(*_g("line_buffer"))
|
||||
undo_stack =property(*_g("undo_stack")) # each entry is a tuple with cursor_position and line_text
|
||||
history_length =property(*_g("history_length")) # each entry is a tuple with cursor_position and line_text
|
||||
history =property(*_g("history")) # each entry is a tuple with cursor_position and line_text
|
||||
history_cursor =property(*_g("history_cursor")) # each entry is a tuple with cursor_position and line_text
|
||||
|
||||
|
||||
def rl_settings_to_string(self):
|
||||
out=["%-20s: %s"%("show all if ambigous",self.show_all_if_ambiguous)]
|
||||
@@ -110,26 +126,6 @@ class Readline:
|
||||
else:
|
||||
raise ReadlineError("Bellstyle %s unknown."%self.bell_style)
|
||||
|
||||
def _quoted_text(self):
|
||||
quoted = [ quote_char(c) for c in self.line_buffer ]
|
||||
self.line_char_width = [ len(c) for c in quoted ]
|
||||
return ''.join(quoted)
|
||||
|
||||
def _line_text(self):
|
||||
return ''.join(self.line_buffer)
|
||||
|
||||
def _set_line(self, text, cursor=None):
|
||||
self.line_buffer = [ c for c in str(text) ]
|
||||
if cursor is None:
|
||||
self.line_cursor = len(self.line_buffer)
|
||||
else:
|
||||
self.line_cursor = cursor
|
||||
|
||||
def _reset_line(self):
|
||||
self.line_buffer = []
|
||||
self.line_cursor = 0
|
||||
self.undo_stack = []
|
||||
|
||||
def _clear_after(self):
|
||||
c = self.console
|
||||
x, y = c.pos()
|
||||
@@ -141,7 +137,7 @@ class Readline:
|
||||
c = self.console
|
||||
xc, yc = self.prompt_end_pos
|
||||
w, h = c.size()
|
||||
xc += reduce(operator.add, self.line_char_width[0:self.line_cursor], 0)
|
||||
xc += self.l_buffer.visible_line_width()
|
||||
while(xc > w):
|
||||
xc -= w
|
||||
yc += 1
|
||||
@@ -166,7 +162,7 @@ class Readline:
|
||||
def _update_line(self):
|
||||
c=self.console
|
||||
c.pos(*self.prompt_end_pos)
|
||||
ltext = self._quoted_text()
|
||||
ltext = self.l_buffer.quoted_text()
|
||||
n = c.write_scrolling(ltext, self.command_color)
|
||||
self._update_prompt_pos(n)
|
||||
self._clear_after()
|
||||
@@ -185,35 +181,23 @@ class Readline:
|
||||
|
||||
#Process exit keys. Only exit on empty line
|
||||
if event.keyinfo in self.exit_dispatch:
|
||||
if len(self.line_buffer) == 0:
|
||||
if lineobj.EndOfLine(self.l_buffer) == 0:
|
||||
raise EOFError
|
||||
try:
|
||||
dispatch_func = self.key_dispatch[event.keyinfo]
|
||||
except KeyError:
|
||||
# unknown? try printing it anyway
|
||||
#if event.keyinfo[0]!=True:
|
||||
self.self_insert(event) #insert only if ctrl is not pressed
|
||||
#c.bell()
|
||||
continue
|
||||
|
||||
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)
|
||||
ltext = self._line_text()
|
||||
if self.undo_stack and ltext == self.undo_stack[-1][1]:
|
||||
self.undo_stack[-1][0] = self.line_cursor
|
||||
else:
|
||||
self.undo_stack.append([self.line_cursor, ltext])
|
||||
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
|
||||
@@ -225,7 +209,7 @@ class Readline:
|
||||
traceback.print_exc()
|
||||
|
||||
c = self.console
|
||||
self._reset_line()
|
||||
self.l_buffer.reset_line()
|
||||
self.prompt = prompt
|
||||
self._print_prompt()
|
||||
|
||||
@@ -239,25 +223,18 @@ class Readline:
|
||||
|
||||
log("in readline: %s"%self.paste_line_buffer)
|
||||
if len(self.paste_line_buffer)>0:
|
||||
#self.console.bell()
|
||||
self._set_line(self.paste_line_buffer[0])
|
||||
c.pos(*self.prompt_end_pos)
|
||||
ltext = self._quoted_text()
|
||||
n = c.write_scrolling(ltext, self.command_color)
|
||||
self._update_prompt_pos(n)
|
||||
self._clear_after()
|
||||
self._set_cursor()
|
||||
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')
|
||||
|
||||
rtext = self._line_text()
|
||||
self.add_history(rtext)
|
||||
self.add_history(self.l_buffer.copy())
|
||||
|
||||
log('returning(%s)' % rtext)
|
||||
return rtext + '\n'
|
||||
log('returning(%s)' % self.l_buffer.get_line_text())
|
||||
return self.l_buffer.get_line_text() + '\n'
|
||||
|
||||
def parse_and_bind(self, string):
|
||||
'''Parse and execute single line of a readline init file.'''
|
||||
@@ -295,41 +272,29 @@ class Readline:
|
||||
|
||||
def get_line_buffer(self):
|
||||
'''Return the current contents of the line buffer.'''
|
||||
return "".join(self.line_buffer)
|
||||
return self.l_buffer.get_line_text()
|
||||
|
||||
def insert_text(self, string):
|
||||
'''Insert text into the command line.'''
|
||||
for c in string:
|
||||
self.line_buffer.insert(self.line_cursor, c)
|
||||
self.line_cursor += 1
|
||||
|
||||
self.l_buffer.insert_text(string)
|
||||
|
||||
def read_init_file(self, filename=None):
|
||||
'''Parse a readline initialization file. The default filename is the last filename used.'''
|
||||
log('read_init_file("%s")' % filename)
|
||||
|
||||
def read_history_file(self, filename=os.path.expanduser('~/.history')):
|
||||
'''Load a readline history file. The default filename is ~/.history.'''
|
||||
try:
|
||||
for line in open(filename, 'rt'):
|
||||
self.add_history(line.rstrip())
|
||||
except IOError:
|
||||
self.history = []
|
||||
self.history_cursor = 0
|
||||
raise IOError
|
||||
self._history.read_history_file(filename)
|
||||
|
||||
def write_history_file(self, filename=os.path.expanduser('~/.history')):
|
||||
'''Save a readline history file. The default filename is ~/.history.'''
|
||||
fp = open(filename, 'wb')
|
||||
for line in self.history:
|
||||
fp.write(line)
|
||||
fp.write('\n')
|
||||
fp.close()
|
||||
self._history.write_history_file(filename)
|
||||
|
||||
def get_history_length(self, ):
|
||||
'''Return the desired length of the history file.
|
||||
|
||||
Negative values imply unlimited history file size.'''
|
||||
return self.history_length
|
||||
return self._history.get_history_length()
|
||||
|
||||
def set_history_length(self, length):
|
||||
'''Set the number of lines to save in the history file.
|
||||
@@ -337,7 +302,7 @@ class Readline:
|
||||
write_history_file() uses this value to truncate the history file
|
||||
when saving. Negative values imply unlimited history file size.
|
||||
'''
|
||||
self.history_length = length
|
||||
self._history.set_history_length(length)
|
||||
|
||||
def set_startup_hook(self, function=None):
|
||||
'''Set or remove the startup_hook function.
|
||||
@@ -400,58 +365,35 @@ class Readline:
|
||||
|
||||
def add_history(self, line):
|
||||
'''Append a line to the history buffer, as if it was the last line typed.'''
|
||||
if not line:
|
||||
pass
|
||||
elif len(self.history) > 0 and self.history[-1] == line:
|
||||
pass
|
||||
else:
|
||||
self.history.append(line)
|
||||
if self.history_length > 0 and len(self.history) > self.history_length:
|
||||
self.history = self.history[-self.history_length:]
|
||||
self.history_cursor = len(self.history)
|
||||
self._history.add_history(line)
|
||||
|
||||
### Methods below here are bindable functions
|
||||
|
||||
def beginning_of_line(self, e): # (C-a)
|
||||
'''Move to the start of the current line. '''
|
||||
self.line_cursor = 0
|
||||
self.l_buffer.beginning_of_line()
|
||||
|
||||
def end_of_line(self, e): # (C-e)
|
||||
'''Move to the end of the line. '''
|
||||
self.line_cursor = len(self.line_buffer)
|
||||
self.l_buffer.end_of_line()
|
||||
|
||||
def forward_char(self, e): # (C-f)
|
||||
'''Move forward a character. '''
|
||||
if self.line_cursor < len(self.line_buffer):
|
||||
self.line_cursor += 1
|
||||
else:
|
||||
self._bell()
|
||||
self.l_buffer.forward_char()
|
||||
|
||||
def backward_char(self, e): # (C-b)
|
||||
'''Move back a character. '''
|
||||
if self.line_cursor > 0:
|
||||
self.line_cursor -= 1
|
||||
else:
|
||||
self._bell()
|
||||
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.'''
|
||||
L = len(self.line_buffer)
|
||||
while self.line_cursor < L:
|
||||
self.line_cursor += 1
|
||||
if self.line_cursor == L:
|
||||
break
|
||||
if self.line_buffer[self.line_cursor] not in string.letters + string.digits:
|
||||
break
|
||||
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.'''
|
||||
while self.line_cursor > 0:
|
||||
self.line_cursor -= 1
|
||||
if self.line_buffer[self.line_cursor] not in string.letters + string.digits:
|
||||
break
|
||||
self.l_buffer.backward_word()
|
||||
|
||||
def clear_screen(self, e): # (C-l)
|
||||
'''Clear the screen and redraw the current line, leaving the current
|
||||
@@ -468,51 +410,29 @@ class Readline:
|
||||
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. '''
|
||||
if self.history_cursor > 0:
|
||||
self.history_cursor -= 1
|
||||
line = self.history[self.history_cursor]
|
||||
self._set_line(line)
|
||||
else:
|
||||
self._bell()
|
||||
self._history.previous_history(self.l_buffer)
|
||||
|
||||
def next_history(self, e): # (C-n)
|
||||
'''Move forward through the history list, fetching the next command. '''
|
||||
if self.history_cursor < len(self.history) - 1:
|
||||
self.history_cursor += 1
|
||||
line = self.history[self.history_cursor]
|
||||
self._set_line(line)
|
||||
elif self.undo_stack:
|
||||
cursor, text = self.undo_stack[-1]
|
||||
self._set_line(text, cursor)
|
||||
else:
|
||||
self._bell()
|
||||
self._history.next_history(self.l_buffer)
|
||||
|
||||
def beginning_of_history(self, e): # (M-<)
|
||||
'''Move to the first line in the history.'''
|
||||
self.history_cursor = 0
|
||||
if len(self.history) > 0:
|
||||
self._set_line(self.history[0])
|
||||
else:
|
||||
self._bell()
|
||||
self._history.beginning_of_history()
|
||||
|
||||
def end_of_history(self, e): # (M->)
|
||||
'''Move to the end of the input history, i.e., the line currently
|
||||
being entered.'''
|
||||
if self.undo_stack:
|
||||
cursor, text = self.undo_stack[-1]
|
||||
self._set_line(text, cursor)
|
||||
else:
|
||||
self._bell()
|
||||
self._history.end_of_history(self.l_buffer)
|
||||
|
||||
def _i_search(self, direction, init_event):
|
||||
def _i_search(self, searchfun, direction, init_event):
|
||||
c = self.console
|
||||
line = self._line_text()
|
||||
line = self.get_line_buffer()
|
||||
query = ''
|
||||
hc_start = self.history_cursor + direction
|
||||
hc = hc_start
|
||||
hc_start = self._history.history_cursor #+ direction
|
||||
while 1:
|
||||
x, y = self.prompt_end_pos
|
||||
c.pos(0, y)
|
||||
@@ -529,123 +449,66 @@ class Readline:
|
||||
if event.keysym == 'BackSpace':
|
||||
if len(query) > 0:
|
||||
query = query[:-1]
|
||||
hc = hc_start
|
||||
self._history.history_cursor = hc_start
|
||||
else:
|
||||
self._bell()
|
||||
elif event.char in string.letters + string.digits + string.punctuation + ' ':
|
||||
self._history.history_cursor = hc_start
|
||||
query += event.char
|
||||
hc = hc_start
|
||||
elif event.keyinfo == init_event.keyinfo:
|
||||
hc += direction
|
||||
self._history.history_cursor += direction
|
||||
line=searchfun(query)
|
||||
pass
|
||||
else:
|
||||
if event.keysym != 'Return':
|
||||
self._bell()
|
||||
break
|
||||
|
||||
while (direction < 0 and hc >= 0) or (direction > 0 and hc < len(self.history)):
|
||||
if self.history[hc].find(query) >= 0:
|
||||
break
|
||||
hc += direction
|
||||
else:
|
||||
self._bell()
|
||||
continue
|
||||
line = self.history[hc]
|
||||
line=searchfun(query)
|
||||
|
||||
px, py = self.prompt_begin_pos
|
||||
c.pos(0, py)
|
||||
self._set_line(line)
|
||||
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(-1, e)
|
||||
# print "HEJ"
|
||||
# self.console.bell()
|
||||
self._i_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(1, e)
|
||||
|
||||
def _non_i_search(self, direction):
|
||||
c = self.console
|
||||
line = self._line_text()
|
||||
query = ''
|
||||
while 1:
|
||||
c.pos(*self.prompt_end_pos)
|
||||
scroll = c.write_scrolling(":%s" % query)
|
||||
self._update_prompt_pos(scroll)
|
||||
self._clear_after()
|
||||
|
||||
event = c.getkeypress()
|
||||
if event.keysym == '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.keysym == 'Return':
|
||||
break
|
||||
else:
|
||||
self._bell()
|
||||
|
||||
if query:
|
||||
hc = self.history_cursor - 1
|
||||
while (direction < 0 and hc >= 0) or (direction > 0 and hc < len(self.history)):
|
||||
if self.history[hc].find(query) >= 0:
|
||||
self._set_line(self.history[hc])
|
||||
self.history_cursor = hc
|
||||
return
|
||||
hc += direction
|
||||
else:
|
||||
self._bell()
|
||||
# print "HEJ"
|
||||
# self.console.bell()
|
||||
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.'''
|
||||
self._non_i_search(-1)
|
||||
self._history.non_incremental_reverse_search_history(self.l_buffer)
|
||||
|
||||
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.'''
|
||||
self._non_i_search(1)
|
||||
|
||||
def _search(self, direction):
|
||||
c = self.console
|
||||
|
||||
if (self.previous_func != self.history_search_forward and
|
||||
self.previous_func != self.history_search_backward):
|
||||
self.query = ''.join(self.line_buffer[0:self.line_cursor])
|
||||
hc = self.history_cursor + direction
|
||||
while (direction < 0 and hc >= 0) or (direction > 0 and hc < len(self.history)):
|
||||
h = self.history[hc]
|
||||
if not self.query:
|
||||
self._set_line(h)
|
||||
self.history_cursor = hc
|
||||
return
|
||||
elif h.startswith(self.query) and h != self._line_text:
|
||||
self._set_line(h, len(self.query))
|
||||
self.history_cursor = hc
|
||||
return
|
||||
hc += direction
|
||||
else:
|
||||
self._set_line(self.query)
|
||||
self._bell()
|
||||
self._history.non_incremental_reverse_search_history(self.l_buffer)
|
||||
|
||||
def history_search_forward(self, e): # ()
|
||||
'''Search forward through the history for the string of characters
|
||||
between the start of the current line and the point. This is a
|
||||
non-incremental search. By default, this command is unbound.'''
|
||||
self._search(1)
|
||||
self.l_buffer=self._history.history_search_forward(self.l_buffer)
|
||||
|
||||
def history_search_backward(self, e): # ()
|
||||
'''Search backward through the history for the string of characters
|
||||
between the start of the current line and the point. This is a
|
||||
non-incremental search. By default, this command is unbound.'''
|
||||
self._search(-1)
|
||||
self.l_buffer=self._history.history_search_backward(self.l_buffer)
|
||||
|
||||
def yank_nth_arg(self, e): # (M-C-y)
|
||||
'''Insert the first argument to the previous command (usually the
|
||||
@@ -666,17 +529,12 @@ class Readline:
|
||||
'''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.'''
|
||||
if self.line_cursor < len(self.line_buffer):
|
||||
del self.line_buffer[self.line_cursor]
|
||||
else:
|
||||
self._bell()
|
||||
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.'''
|
||||
if self.line_cursor > 0:
|
||||
del self.line_buffer[self.line_cursor-1]
|
||||
self.line_cursor -= 1
|
||||
self.l_buffer.backward_delete_char()
|
||||
|
||||
def forward_backward_delete_char(self, e): # ()
|
||||
'''Delete the character under the cursor, unless the cursor is at
|
||||
@@ -688,8 +546,7 @@ class Readline:
|
||||
'''Add the next character typed to the line verbatim. This is how to
|
||||
insert key sequences like C-q, for example.'''
|
||||
e = self.console.getkeypress()
|
||||
self.line_buffer.insert(self.line_cursor, e.char)
|
||||
self.line_cursor += 1
|
||||
self.insert_text(e.char)
|
||||
|
||||
def tab_insert(self, e): # (M-TAB)
|
||||
'''Insert a tab character. '''
|
||||
@@ -699,36 +556,35 @@ class Readline:
|
||||
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.line_buffer.insert(self.line_cursor, e.char)
|
||||
self.line_cursor += 1
|
||||
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
|
||||
point is at the end of the line, then this transposes the last two
|
||||
characters of the line. Negative arguments have no effect.'''
|
||||
pass
|
||||
self.l_buffer.transpose_chars()
|
||||
|
||||
def transpose_words(self, e): # (M-t)
|
||||
'''Drag the word before point past the word after point, moving
|
||||
point past that word as well. If the insertion point is at the end
|
||||
of the line, this transposes the last two words on the line.'''
|
||||
pass
|
||||
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.'''
|
||||
pass
|
||||
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.'''
|
||||
pass
|
||||
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.'''
|
||||
pass
|
||||
self.l_buffer.capitalize_word()
|
||||
|
||||
def overwrite_mode(self, e): # ()
|
||||
'''Toggle overwrite mode. With an explicit positive numeric
|
||||
@@ -740,56 +596,40 @@ class Readline:
|
||||
the text to the right. Characters bound to backward-delete-char
|
||||
replace the character before point with a space.'''
|
||||
pass
|
||||
|
||||
|
||||
def kill_line(self, e): # (C-k)
|
||||
'''Kill the text from point to the end of the line. '''
|
||||
if self.enable_win32_clipboard:
|
||||
toclipboard="".join(self.line_buffer[self.line_cursor:])
|
||||
clipboard.set_clipboard_text(toclipboard)
|
||||
self.line_buffer[self.line_cursor:] = []
|
||||
|
||||
self.l_buffer.kill_line()
|
||||
|
||||
def backward_kill_line(self, e): # (C-x Rubout)
|
||||
'''Kill backward to the beginning of the line. '''
|
||||
self.line_buffer[:self.line_cursor] = []
|
||||
self.line_cursor = 0
|
||||
self.l_buffer.backward_kill_line()
|
||||
|
||||
def unix_line_discard(self, e): # (C-u)
|
||||
'''Kill backward from the cursor to the beginning of the current line. '''
|
||||
# how is this different from backward_kill_line?
|
||||
self.line_buffer[:self.line_cursor] = []
|
||||
self.line_cursor = 0
|
||||
self.l_buffer.unix_line_discard()
|
||||
|
||||
def kill_whole_line(self, e): # ()
|
||||
'''Kill all characters on the current line, no matter where point
|
||||
is. By default, this is unbound.'''
|
||||
self.line_buffer=self.line_buffer[:0]
|
||||
self.line_cursor=0
|
||||
self.l_buffer.kill_whole_line()
|
||||
|
||||
def kill_word(self, e): # (M-d)
|
||||
'''Kill from point to the end of the current word, or if between
|
||||
words, to the end of the next word. Word boundaries are the same as
|
||||
forward-word.'''
|
||||
begin = self.line_cursor
|
||||
self.forward_word(e)
|
||||
self.line_buffer[begin:self.line_cursor] = []
|
||||
self.line_cursor = begin
|
||||
self.l_buffer.kill_word()
|
||||
|
||||
def backward_kill_word(self, e): # (M-DEL)
|
||||
'''Kill the word behind point. Word boundaries are the same as
|
||||
backward-word. '''
|
||||
begin = self.line_cursor
|
||||
self.backward_word(e)
|
||||
self.line_buffer[self.line_cursor:begin] = []
|
||||
self.l_buffer.backward_kill_word()
|
||||
|
||||
def unix_word_rubout(self, e): # (C-w)
|
||||
'''Kill the word behind point, using white space as a word
|
||||
boundary. The killed text is saved on the kill-ring.'''
|
||||
begin = self.line_cursor
|
||||
while self.line_cursor > 0:
|
||||
self.line_cursor -= 1
|
||||
if self.line_buffer[self.line_cursor] == ' ':
|
||||
break
|
||||
self.line_buffer[self.line_cursor:begin] = []
|
||||
self.l_buffer.unix_word_rubout()
|
||||
|
||||
def delete_horizontal_space(self, e): # ()
|
||||
'''Delete all spaces and tabs around point. By default, this is unbound. '''
|
||||
@@ -807,13 +647,13 @@ class Readline:
|
||||
def copy_region_to_clipboard(self, e): # ()
|
||||
'''Copy the text in the region to the windows clipboard.'''
|
||||
if self.enable_win32_clipboard:
|
||||
mark=min(self.mark,len(self.line_buffer))
|
||||
cursor=min(self.line_cursor,len(self.line_buffer))
|
||||
if self.mark==-1:
|
||||
mark=min(self.l_buffer.mark,len(self.l_buffer.line_buffer))
|
||||
cursor=min(self.l_buffer.point,len(self.l_buffer.line_buffer))
|
||||
if self.l_buffer.mark==-1:
|
||||
return
|
||||
begin=min(cursor,mark)
|
||||
end=max(cursor,mark)
|
||||
toclipboard="".join(self.line_buffer[begin:end])
|
||||
toclipboard="".join(self.l_buffer.line_buffer[begin:end])
|
||||
clipboard.SetClipboardText(str(toclipboard))
|
||||
|
||||
def copy_backward_word(self, e): # ()
|
||||
@@ -843,6 +683,7 @@ class Readline:
|
||||
t=[row for row in t if row.strip()!=""] #remove empty lines
|
||||
if t!=[""]:
|
||||
self.insert_text(t[0])
|
||||
self.add_history(self.l_buffer.copy())
|
||||
self.paste_line_buffer=t[1:]
|
||||
log("multi: %s"%self.paste_line_buffer)
|
||||
return True
|
||||
@@ -896,16 +737,17 @@ class Readline:
|
||||
|
||||
Also set begidx and endidx in the process.'''
|
||||
completions = []
|
||||
self.begidx = self.line_cursor
|
||||
self.endidx = self.line_cursor
|
||||
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 self.line_buffer[self.begidx] in self.completer_delims:
|
||||
if buf[self.begidx] in self.completer_delims:
|
||||
self.begidx += 1
|
||||
break
|
||||
text = ''.join(self.line_buffer[self.begidx:self.endidx])
|
||||
text = ''.join(buf[self.begidx:self.endidx])
|
||||
log('complete text="%s"' % text)
|
||||
i = 0
|
||||
while 1:
|
||||
@@ -923,10 +765,10 @@ class Readline:
|
||||
# get the filename to complete
|
||||
while self.begidx > 0:
|
||||
self.begidx -= 1
|
||||
if self.line_buffer[self.begidx] in ' \t\n':
|
||||
if buf[self.begidx] in ' \t\n':
|
||||
self.begidx += 1
|
||||
break
|
||||
text = ''.join(self.line_buffer[self.begidx:self.endidx])
|
||||
text = ''.join(buf[self.begidx:self.endidx])
|
||||
log('file complete text="%s"' % text)
|
||||
completions = glob(os.path.expanduser(text) + '*')
|
||||
if self.mark_directories == 'on':
|
||||
@@ -965,8 +807,8 @@ class Readline:
|
||||
if completions:
|
||||
cprefix = commonprefix(completions)
|
||||
rep = [ c for c in cprefix ]
|
||||
self.line_buffer[self.begidx:self.endidx] = rep
|
||||
self.line_cursor += len(rep) - (self.endidx - self.begidx)
|
||||
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)
|
||||
@@ -989,10 +831,10 @@ class Readline:
|
||||
for comp in completions:
|
||||
rep = [ c for c in comp ]
|
||||
rep.append(' ')
|
||||
self.line_buffer[b:e] = rep
|
||||
self.l_buffer[b:e] = rep
|
||||
b += len(rep)
|
||||
e = b
|
||||
self.line_cursor = b
|
||||
self.line_cursor = b
|
||||
|
||||
def menu_complete(self, e): # ()
|
||||
'''Similar to complete, but replaces the word to be completed with a
|
||||
@@ -1049,15 +891,7 @@ class Readline:
|
||||
|
||||
def undo(self, e): # (C-_ or C-x C-u)
|
||||
'''Incremental undo, separately remembered for each line.'''
|
||||
log(self.undo_stack)
|
||||
if len(self.undo_stack) >= 2:
|
||||
self.undo_stack.pop()
|
||||
cursor, text = self.undo_stack.pop()
|
||||
else:
|
||||
cursor = 0
|
||||
text = ''
|
||||
self.undo_stack = []
|
||||
self._set_line(text, cursor)
|
||||
self.l_buffer.pop_undo()
|
||||
|
||||
def revert_line(self, e): # (M-r)
|
||||
'''Undo all changes made to this line. This is like executing the
|
||||
@@ -1071,7 +905,7 @@ class Readline:
|
||||
def set_mark(self, e): # (C-@)
|
||||
'''Set the mark to the point. If a numeric argument is supplied, the
|
||||
mark is set to that position.'''
|
||||
self.mark=self.line_cursor
|
||||
self.l_buffer.set_mark()
|
||||
|
||||
def exchange_point_and_mark(self, e): # (C-x C-x)
|
||||
'''Swap the point with the mark. The current cursor position is set
|
||||
@@ -1284,4 +1118,4 @@ if __name__ == '__main__':
|
||||
else:
|
||||
#import wingdbstub
|
||||
console.install_readline(rl.readline)
|
||||
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
This is the refactor branch of pyreadline.
|
||||
|
||||
Goals:
|
||||
- Decouple console from readline. In order to simplify future handling of terminals onother platforms than win32
|
||||
- Decouple keyboard shortcut logic from lineditor functions
|
||||
- Add unittests
|
||||
- Improve cut paste logic to be more like other windows apps
|
||||
- Make all subfunctionality like console, clipboard etc pluggable to simplify moving to other platforms.
|
||||
|
||||
pyreadline
|
||||
clipboard.py - clipboard functions
|
||||
console.py - terminal functions, read keyboard write to screen
|
||||
keysyms.py - translate descriptions of keypresses to actual keyinfo
|
||||
release.py - release info, description, copyright
|
||||
rlmain.py - contains readline class, this is the entry point to readline functionality
|
||||
lineditor
|
||||
history.py - history buffer
|
||||
lineobj.py - Contains linebuffer object, contains all editing functionality
|
||||
wordmatcher - Contains functions that match word and non-words
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user