Permalink
Browse files

Fixed four space indent

Added ChangeLog
Fixed bug that crashes readline on my machine. Needs os.path.expanduser on '~/.history'
  • Loading branch information...
1 parent 005422f commit 15e3bd1255e50d88aca1a139f39223a848fc6fde jstenar committed Jan 21, 2006
Showing with 1,729 additions and 1,722 deletions.
  1. +1 −0 ChangeLog
  2. +6 −0 doc/ChangeLog
  3. +589 −589 readline/Console.py
  4. +1,061 −1,061 readline/PyReadline.py
  5. +72 −72 readline/keysyms.py
View
1 ChangeLog
@@ -0,0 +1 @@
+link doc/ChangeLog
View
6 doc/ChangeLog
@@ -0,0 +1,6 @@
+2006-01-21 Jörgen Stenarson <jorgen.stenarson -at- bostream.nu>
+
+ * Changed all python files to conform to 4 space indent.
+ * Added changelog
+ * Added os.path.expanduser to expand out ~/.history paths
+
View
1,178 readline/Console.py
@@ -5,26 +5,26 @@
# primitive debug printing that won't interfere with the screen
if 0:
- fp = open('debug.txt', 'w')
- def log(s):
- print >>fp, s
- fp.flush()
+ fp = open('debug.txt', 'w')
+ def log(s):
+ print >>fp, s
+ fp.flush()
else:
- def log(s):
- pass
+ def log(s):
+ pass
import sys
import traceback
import re
try:
- # I developed this with ctypes 0.6
- from ctypes import *
- from _ctypes import call_function
+ # I developed this with ctypes 0.6
+ from ctypes import *
+ from _ctypes import call_function
except ImportError:
- print 'you need the ctypes module to run this code'
- print 'http://starship.python.net/crew/theller/ctypes/'
- raise
+ print 'you need the ctypes module to run this code'
+ print 'http://starship.python.net/crew/theller/ctypes/'
+ raise
# my code
from keysyms import make_keysym, make_keyinfo
@@ -52,565 +52,565 @@ def log(s):
# Windows structures we'll need later
class COORD(Structure):
- _fields_ = [("X", c_short),
- ("Y", c_short)]
+ _fields_ = [("X", c_short),
+ ("Y", c_short)]
class SMALL_RECT(Structure):
- _fields_ = [("Left", c_short),
- ("Top", c_short),
- ("Right", c_short),
- ("Bottom", c_short)]
-
+ _fields_ = [("Left", c_short),
+ ("Top", c_short),
+ ("Right", c_short),
+ ("Bottom", c_short)]
+
class CONSOLE_SCREEN_BUFFER_INFO(Structure):
- _fields_ = [("dwSize", COORD),
- ("dwCursorPosition", COORD),
- ("wAttributes", c_short),
- ("srWindow", SMALL_RECT),
- ("dwMaximumWindowSize", COORD)]
+ _fields_ = [("dwSize", COORD),
+ ("dwCursorPosition", COORD),
+ ("wAttributes", c_short),
+ ("srWindow", SMALL_RECT),
+ ("dwMaximumWindowSize", COORD)]
class CHAR_UNION(Union):
- _fields_ = [("UnicodeChar", c_short),
- ("AsciiChar", c_char)]
+ _fields_ = [("UnicodeChar", c_short),
+ ("AsciiChar", c_char)]
class CHAR_INFO(Structure):
- _fields_ = [("Char", CHAR_UNION),
- ("Attributes", c_short)]
+ _fields_ = [("Char", CHAR_UNION),
+ ("Attributes", c_short)]
class KEY_EVENT_RECORD(Structure):
- _fields_ = [("bKeyDown", c_byte),
- ("pad2", c_byte),
- ('pad1', c_short),
- ("wRepeatCount", c_short),
- ("wVirtualKeyCode", c_short),
- ("wVirtualScanCode", c_short),
- ("uChar", CHAR_UNION),
- ("dwControlKeyState", c_int)]
+ _fields_ = [("bKeyDown", c_byte),
+ ("pad2", c_byte),
+ ('pad1', c_short),
+ ("wRepeatCount", c_short),
+ ("wVirtualKeyCode", c_short),
+ ("wVirtualScanCode", c_short),
+ ("uChar", CHAR_UNION),
+ ("dwControlKeyState", c_int)]
class MOUSE_EVENT_RECORD(Structure):
- _fields_ = [("dwMousePosition", COORD),
- ("dwButtonState", c_int),
- ("dwControlKeyState", c_int),
- ("dwEventFlags", c_int)]
+ _fields_ = [("dwMousePosition", COORD),
+ ("dwButtonState", c_int),
+ ("dwControlKeyState", c_int),
+ ("dwEventFlags", c_int)]
class WINDOW_BUFFER_SIZE_RECORD(Structure):
- _fields_ = [("dwSize", COORD)]
+ _fields_ = [("dwSize", COORD)]
class MENU_EVENT_RECORD(Structure):
- _fields_ = [("dwCommandId", c_uint)]
+ _fields_ = [("dwCommandId", c_uint)]
class FOCUS_EVENT_RECORD(Structure):
- _fields_ = [("bSetFocus", c_byte)]
+ _fields_ = [("bSetFocus", c_byte)]
class INPUT_UNION(Union):
- _fields_ = [("KeyEvent", KEY_EVENT_RECORD),
- ("MouseEvent", MOUSE_EVENT_RECORD),
- ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
- ("MenuEvent", MENU_EVENT_RECORD),
- ("FocusEvent", FOCUS_EVENT_RECORD)]
+ _fields_ = [("KeyEvent", KEY_EVENT_RECORD),
+ ("MouseEvent", MOUSE_EVENT_RECORD),
+ ("WindowBufferSizeEvent", WINDOW_BUFFER_SIZE_RECORD),
+ ("MenuEvent", MENU_EVENT_RECORD),
+ ("FocusEvent", FOCUS_EVENT_RECORD)]
class INPUT_RECORD(Structure):
- _fields_ = [("EventType", c_short),
- ("Event", INPUT_UNION)]
+ _fields_ = [("EventType", c_short),
+ ("Event", INPUT_UNION)]
class CONSOLE_CURSOR_INFO(Structure):
- _fields_ = [("dwSize", c_int),
- ("bVisible", c_byte)]
+ _fields_ = [("dwSize", c_int),
+ ("bVisible", c_byte)]
# I didn't want to have to individually import these so I made a list, they are
# added to the Console class later in this file.
funcs = [
- 'AllocConsole',
- 'CreateConsoleScreenBuffer',
- 'FillConsoleOutputAttribute',
- 'FillConsoleOutputCharacterA',
- 'FreeConsole',
- 'GetConsoleCursorInfo',
- 'GetConsoleMode',
- 'GetConsoleScreenBufferInfo',
- 'GetConsoleTitleA',
- 'GetProcAddress',
- 'GetStdHandle',
- 'PeekConsoleInputA',
- 'ReadConsoleInputA',
- 'ScrollConsoleScreenBufferA',
- 'SetConsoleActiveScreenBuffer',
- 'SetConsoleCursorInfo',
- 'SetConsoleCursorPosition',
- 'SetConsoleMode',
- 'SetConsoleScreenBufferSize',
- 'SetConsoleTextAttribute',
- 'SetConsoleTitleA',
- 'SetConsoleWindowInfo',
- 'WriteConsoleA',
- 'WriteConsoleOutputCharacterA',
- ]
+ 'AllocConsole',
+ 'CreateConsoleScreenBuffer',
+ 'FillConsoleOutputAttribute',
+ 'FillConsoleOutputCharacterA',
+ 'FreeConsole',
+ 'GetConsoleCursorInfo',
+ 'GetConsoleMode',
+ 'GetConsoleScreenBufferInfo',
+ 'GetConsoleTitleA',
+ 'GetProcAddress',
+ 'GetStdHandle',
+ 'PeekConsoleInputA',
+ 'ReadConsoleInputA',
+ 'ScrollConsoleScreenBufferA',
+ 'SetConsoleActiveScreenBuffer',
+ 'SetConsoleCursorInfo',
+ 'SetConsoleCursorPosition',
+ 'SetConsoleMode',
+ 'SetConsoleScreenBufferSize',
+ 'SetConsoleTextAttribute',
+ 'SetConsoleTitleA',
+ 'SetConsoleWindowInfo',
+ 'WriteConsoleA',
+ 'WriteConsoleOutputCharacterA',
+ ]
# I don't want events for these keys, they are just a bother for my application
key_modifiers = { VK_SHIFT:1,
VK_CONTROL:1,
VK_MENU:1, # alt key
0x5b:1, # windows key
- }
+ }
class Console(object):
- '''Console driver for Windows.
-
- '''
-
- def __init__(self, newbuffer=0):
- '''Initialize the Console object.
+ '''Console driver for Windows.
- newbuffer=1 will allocate a new buffer so the old content will be restored
- on exit.
'''
- #Do I need the following line? It causes a console to be created whenever
- #readline is imported into a pythonw application which seems wrong. Things
- #seem to work without it...
- #self.AllocConsole()
-
- if newbuffer:
- self.hout = self.CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
- 0, None, 1, None)
- self.SetConsoleActiveScreenBuffer(self.hout)
- else:
- self.hout = self.GetStdHandle(STD_OUTPUT_HANDLE)
-
- self.hin = self.GetStdHandle(STD_INPUT_HANDLE)
- self.inmode = c_int(0)
- self.GetConsoleMode(self.hin, byref(self.inmode))
- self.SetConsoleMode(self.hin, 0xf)
- info = CONSOLE_SCREEN_BUFFER_INFO()
- self.GetConsoleScreenBufferInfo(self.hout, byref(info))
- self.attr = info.wAttributes # remember the initial colors
- background = self.attr & 0xf0
- for escape in self.escape_to_color:
- if self.escape_to_color[escape] is not None:
- self.escape_to_color[escape] |= background
- log('initial attr=%x' % self.attr)
- self.softspace = 0 # this is for using it as a file-like object
- self.serial = 0
-
- self.pythondll = CDLL('python%s%s' % (sys.version[0], sys.version[2]))
- self.inputHookPtr = c_int.from_address(addressof(self.pythondll.PyOS_InputHook)).value
- setattr(Console, 'PyMem_Malloc', self.pythondll.PyMem_Malloc)
-
- def __del__(self):
- '''Cleanup the console when finished.'''
- # I don't think this ever gets called
- self.SetConsoleTextAttribute(self.hout, self.saveattr)
- self.SetConsoleMode(self.hin, self.inmode)
- self.FreeConsole()
-
- def fixcoord(self, x, y):
- '''Return a long with x and y packed inside, also handle negative x and y.'''
- if x < 0 or y < 0:
- info = CONSOLE_SCREEN_BUFFER_INFO()
- self.GetConsoleScreenBufferInfo(self.hout, byref(info))
- if x < 0:
- x = info.srWindow.Right - x
- y = info.srWindow.Bottom + y
-
- # this is a hack! ctypes won't pass structures but COORD is just like a
- # long, so this works.
- return c_int(y << 16 | x)
-
- def pos(self, x=None, y=None):
- '''Move or query the window cursor.'''
- if x is None:
- info = CONSOLE_SCREEN_BUFFER_INFO()
- self.GetConsoleScreenBufferInfo(self.hout, byref(info))
- return (info.dwCursorPosition.X, info.dwCursorPosition.Y)
- else:
- return self.SetConsoleCursorPosition(self.hout, self.fixcoord(x, y))
- def home(self):
- '''Move to home.'''
- self.pos(0,0)
+ def __init__(self, newbuffer=0):
+ '''Initialize the Console object.
+
+ newbuffer=1 will allocate a new buffer so the old content will be restored
+ on exit.
+ '''
+ #Do I need the following line? It causes a console to be created whenever
+ #readline is imported into a pythonw application which seems wrong. Things
+ #seem to work without it...
+ #self.AllocConsole()
+
+ if newbuffer:
+ self.hout = self.CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
+ 0, None, 1, None)
+ self.SetConsoleActiveScreenBuffer(self.hout)
+ else:
+ self.hout = self.GetStdHandle(STD_OUTPUT_HANDLE)
+
+ self.hin = self.GetStdHandle(STD_INPUT_HANDLE)
+ self.inmode = c_int(0)
+ self.GetConsoleMode(self.hin, byref(self.inmode))
+ self.SetConsoleMode(self.hin, 0xf)
+ info = CONSOLE_SCREEN_BUFFER_INFO()
+ self.GetConsoleScreenBufferInfo(self.hout, byref(info))
+ self.attr = info.wAttributes # remember the initial colors
+ background = self.attr & 0xf0
+ for escape in self.escape_to_color:
+ if self.escape_to_color[escape] is not None:
+ self.escape_to_color[escape] |= background
+ log('initial attr=%x' % self.attr)
+ self.softspace = 0 # this is for using it as a file-like object
+ self.serial = 0
+
+ self.pythondll = CDLL('python%s%s' % (sys.version[0], sys.version[2]))
+ self.inputHookPtr = c_int.from_address(addressof(self.pythondll.PyOS_InputHook)).value
+ setattr(Console, 'PyMem_Malloc', self.pythondll.PyMem_Malloc)
+
+ def __del__(self):
+ '''Cleanup the console when finished.'''
+ # I don't think this ever gets called
+ self.SetConsoleTextAttribute(self.hout, self.saveattr)
+ self.SetConsoleMode(self.hin, self.inmode)
+ self.FreeConsole()
+
+ def fixcoord(self, x, y):
+ '''Return a long with x and y packed inside, also handle negative x and y.'''
+ if x < 0 or y < 0:
+ info = CONSOLE_SCREEN_BUFFER_INFO()
+ self.GetConsoleScreenBufferInfo(self.hout, byref(info))
+ if x < 0:
+ x = info.srWindow.Right - x
+ y = info.srWindow.Bottom + y
+
+ # this is a hack! ctypes won't pass structures but COORD is just like a
+ # long, so this works.
+ return c_int(y << 16 | x)
+
+ def pos(self, x=None, y=None):
+ '''Move or query the window cursor.'''
+ if x is None:
+ info = CONSOLE_SCREEN_BUFFER_INFO()
+ self.GetConsoleScreenBufferInfo(self.hout, byref(info))
+ return (info.dwCursorPosition.X, info.dwCursorPosition.Y)
+ else:
+ return self.SetConsoleCursorPosition(self.hout, self.fixcoord(x, y))
-# Map ANSI color escape sequences into Windows Console Attributes
+ def home(self):
+ '''Move to home.'''
+ self.pos(0,0)
- terminal_escape = re.compile('(\001?\033\\[[0-9;]+m\002?)')
- escape_parts = re.compile('\001?\033\\[([0-9;]+)m\002?')
- escape_to_color = { '0;30': 0x0, #black
- '0;31': 0x4, #red
- '0;32': 0x2, #green
- '0;33': 0x4+0x2, #brown?
- '0;34': 0x1, #blue
- '0;35': 0x1+0x4, #purple
- '0;36': 0x2+0x4, #cyan
- '0;37': 0x1+0x2+0x4, #grey
- '1;30': 0x1+0x2+0x4, #dark gray
- '1;31': 0x4+0x8, #red
- '1;32': 0x2+0x8, #light green
- '1;33': 0x4+0x2+0x8, #yellow
- '1;34': 0x1+0x8, #light blue
- '1;35': 0x1+0x4+0x8, #light purple
- '1;36': 0x1+0x2+0x8, #light cyan
- '1;37': 0x1+0x2+0x4+0x8, #white
- '0': None,
- }
-
- # This pattern should match all characters that change the cursor position differently
- # than a normal character.
- motion_char_re = re.compile('([\n\r\t\010\007])')
-
- def write_scrolling(self, text, attr=None):
- '''write text at current cursor position while watching for scrolling.
-
- If the window scrolls because you are at the bottom of the screen
- buffer, all positions that you are storing will be shifted by the
- scroll amount. For example, I remember the cursor position of the
- prompt so that I can redraw the line but if the window scrolls,
- the remembered position is off.
-
- This variant of write tries to keep track of the cursor position
- so that it will know when the screen buffer is scrolled. It
- returns the number of lines that the buffer scrolled.
+# Map ANSI color escape sequences into Windows Console Attributes
- '''
- x, y = self.pos()
- w, h = self.size()
- scroll = 0 # the result
-
- # split the string into ordinary characters and funny characters
- chunks = self.motion_char_re.split(text)
- for chunk in chunks:
- log('C:'+chunk)
- n = self.write_color(chunk, attr)
- if len(chunk) == 1: # the funny characters will be alone
- if chunk[0] == '\n': # newline
- x = 0
- y += 1
- elif chunk[0] == '\r': # carriage return
- x = 0
- elif chunk[0] == '\t': # tab
- x = 8*(int(x/8)+1)
- if x > w: # newline
- x -= w
- y += 1
- elif chunk[0] == '\007': # bell
- pass
- elif chunk[0] == '\010':
- x -= 1
- if x < 0:
- y -= 1 # backed up 1 line
- else: # ordinary character
- x += 1
- if x == w: # wrap
- x = 0
- y += 1
- if y == h: # scroll
- scroll += 1
- y = h - 1
- else: # chunk of ordinary characters
- x += n
- l = int(x / w) # lines we advanced
- x = x % w # new x value
- y += l
- if y >= h: # scroll
- scroll += y - h + 1
- y = h - 1
- return scroll
-
- def write_color(self, text, attr=None):
- '''write text at current cursor position and interpret color escapes.
-
- return the number of characters written.
- '''
- log('write_color("%s", %s)' % (text, attr))
- chunks = self.terminal_escape.split(text)
- log('chunks=%s' % repr(chunks))
- junk = c_int(0)
- n = 0 # count the characters we actually write, omitting the escapes
- for chunk in chunks:
- m = self.escape_parts.match(chunk)
- if m:
- attr = self.escape_to_color[m.group(1)]
- continue
- n += len(chunk)
- log('attr=%s' % attr)
- if attr is None:
- attr = self.attr
- self.SetConsoleTextAttribute(self.hout, attr)
- self.WriteConsoleA(self.hout, chunk, len(chunk), byref(junk), None)
- return n
-
- def write_plain(self, text, attr=None):
- '''write text at current cursor position.'''
- log('write("%s", %s)' %(text,attr))
- if attr is None:
- attr = self.attr
- n = c_int(0)
- self.SetConsoleTextAttribute(self.hout, attr)
- self.WriteConsoleA(self.hout, text, len(text), byref(n), None)
- return len(text)
-
- # make this class look like a file object
- def write(self, text):
- log('write("%s")' % text)
- return self.write_color(text)
-
- #write = write_scrolling
-
- def isatty(self):
- return True
-
- def flush(self):
- pass
-
- def page(self, attr=None, fill=' '):
- '''Fill the entire screen.'''
- if attr is None:
- attr = self.attr
- if len(fill) != 1:
- raise ValueError
- info = CONSOLE_SCREEN_BUFFER_INFO()
- self.GetConsoleScreenBufferInfo(self.hout, byref(info))
- if info.dwCursorPosition.X != 0 or info.dwCursorPosition.Y != 0:
- self.SetConsoleCursorPosition(self.hout, self.fixcoord(0, 0))
-
- w = info.dwSize.X
- n = c_int(0)
- for y in range(info.dwSize.Y):
- self.FillConsoleOutputAttribute(self.hout, attr, w, self.fixcoord(0, y), byref(n))
- self.FillConsoleOutputCharacterA(self.hout, ord(fill[0]), w, self.fixcoord(0, y), byref(n))
-
- self.attr = attr
-
- def text(self, x, y, text, attr=None):
- '''Write text at the given position.'''
- if attr is None:
- attr = self.attr
-
- pos = self.fixcoord(x, y)
- n = c_int(0)
- self.WriteConsoleOutputCharacterA(self.hout, text, len(text), pos, byref(n))
- self.FillConsoleOutputAttribute(self.hout, attr, n, pos, byref(n))
-
- def rectangle(self, rect, attr=None, fill=' '):
- '''Fill Rectangle.'''
- x0, y0, x1, y1 = rect
- n = c_int(0)
- if attr is None:
- attr = self.attr
- for y in range(y0, y1):
- pos = self.fixcoord(x0, y)
- self.FillConsoleOutputAttribute(self.hout, attr, x1-x0, pos, byref(n))
- self.FillConsoleOutputCharacterA(self.hout, ord(fill[0]), x1-x0, pos, byref(n))
-
- def scroll(self, rect, dx, dy, attr=None, fill=' '):
- '''Scroll a rectangle.'''
- if attr is None:
- attr = self.attr
-
- x0, y0, x1, y1 = rect
- source = SMALL_RECT(x0, y0, x1-1, y1-1)
- dest = self.fixcoord(x0+dx, y0+dy)
- style = CHAR_INFO()
- style.Char.AsciiChar = fill[0]
- style.Attributes = attr
-
- return self.ScrollConsoleScreenBufferA(self.hout, byref(source), byref(source),
- dest, byref(style))
-
- def scroll_window(self, lines):
- '''Scroll the window by the indicated number of lines.'''
- info = CONSOLE_SCREEN_BUFFER_INFO()
- self.GetConsoleScreenBufferInfo(self.hout, byref(info))
- rect = info.srWindow
- log('sw: rtop=%d rbot=%d' % (rect.Top, rect.Bottom))
- top = rect.Top + lines
- bot = rect.Bottom + lines
- h = bot - top
- maxbot = info.dwSize.Y-1
- log('sw: lines=%d mb=%d top=%d bot=%d' % (lines,maxbot,top,bot))
- if top < 0:
- top = 0
- bot = h
- if bot > maxbot:
- bot = maxbot
- top = bot - h
-
- nrect = SMALL_RECT()
- nrect.Top = top
- nrect.Bottom = bot
- nrect.Left = rect.Left
- nrect.Right = rect.Right
- log('sn: top=%d bot=%d' % (top,bot))
- r=self.SetConsoleWindowInfo(self.hout, True, byref(nrect))
- log('r=%d' % r)
-
- def get(self):
- '''Get next event from queue.'''
- inputHookFunc = c_int.from_address(self.inputHookPtr).value
-
- Cevent = INPUT_RECORD()
- count = c_int(0)
- while 1:
- if inputHookFunc:
- call_function(inputHookFunc, ())
- status = self.ReadConsoleInputA(self.hin, byref(Cevent), 1, byref(count))
- if status and count.value == 1:
- e = event(self, Cevent)
- return e
-
- def getkeypress(self):
- '''Return next key press event from the queue, ignoring others.'''
- while 1:
- e = self.get()
- if e.type == 'KeyPress' and e.keycode not in key_modifiers:
- log(e)
- if e.keysym == 'Next':
- self.scroll_window(12)
- elif e.keysym == 'Prior':
- self.scroll_window(-12)
+ terminal_escape = re.compile('(\001?\033\\[[0-9;]+m\002?)')
+ escape_parts = re.compile('\001?\033\\[([0-9;]+)m\002?')
+ escape_to_color = { '0;30': 0x0, #black
+ '0;31': 0x4, #red
+ '0;32': 0x2, #green
+ '0;33': 0x4+0x2, #brown?
+ '0;34': 0x1, #blue
+ '0;35': 0x1+0x4, #purple
+ '0;36': 0x2+0x4, #cyan
+ '0;37': 0x1+0x2+0x4, #grey
+ '1;30': 0x1+0x2+0x4, #dark gray
+ '1;31': 0x4+0x8, #red
+ '1;32': 0x2+0x8, #light green
+ '1;33': 0x4+0x2+0x8, #yellow
+ '1;34': 0x1+0x8, #light blue
+ '1;35': 0x1+0x4+0x8, #light purple
+ '1;36': 0x1+0x2+0x8, #light cyan
+ '1;37': 0x1+0x2+0x4+0x8, #white
+ '0': None,
+ }
+
+ # This pattern should match all characters that change the cursor position differently
+ # than a normal character.
+ motion_char_re = re.compile('([\n\r\t\010\007])')
+
+ def write_scrolling(self, text, attr=None):
+ '''write text at current cursor position while watching for scrolling.
+
+ If the window scrolls because you are at the bottom of the screen
+ buffer, all positions that you are storing will be shifted by the
+ scroll amount. For example, I remember the cursor position of the
+ prompt so that I can redraw the line but if the window scrolls,
+ the remembered position is off.
+
+ This variant of write tries to keep track of the cursor position
+ so that it will know when the screen buffer is scrolled. It
+ returns the number of lines that the buffer scrolled.
+
+ '''
+ x, y = self.pos()
+ w, h = self.size()
+ scroll = 0 # the result
+
+ # split the string into ordinary characters and funny characters
+ chunks = self.motion_char_re.split(text)
+ for chunk in chunks:
+ log('C:'+chunk)
+ n = self.write_color(chunk, attr)
+ if len(chunk) == 1: # the funny characters will be alone
+ if chunk[0] == '\n': # newline
+ x = 0
+ y += 1
+ elif chunk[0] == '\r': # carriage return
+ x = 0
+ elif chunk[0] == '\t': # tab
+ x = 8*(int(x/8)+1)
+ if x > w: # newline
+ x -= w
+ y += 1
+ elif chunk[0] == '\007': # bell
+ pass
+ elif chunk[0] == '\010':
+ x -= 1
+ if x < 0:
+ y -= 1 # backed up 1 line
+ else: # ordinary character
+ x += 1
+ if x == w: # wrap
+ x = 0
+ y += 1
+ if y == h: # scroll
+ scroll += 1
+ y = h - 1
+ else: # chunk of ordinary characters
+ x += n
+ l = int(x / w) # lines we advanced
+ x = x % w # new x value
+ y += l
+ if y >= h: # scroll
+ scroll += y - h + 1
+ y = h - 1
+ return scroll
+
+ def write_color(self, text, attr=None):
+ '''write text at current cursor position and interpret color escapes.
+
+ return the number of characters written.
+ '''
+ log('write_color("%s", %s)' % (text, attr))
+ chunks = self.terminal_escape.split(text)
+ log('chunks=%s' % repr(chunks))
+ junk = c_int(0)
+ n = 0 # count the characters we actually write, omitting the escapes
+ for chunk in chunks:
+ m = self.escape_parts.match(chunk)
+ if m:
+ attr = self.escape_to_color[m.group(1)]
+ continue
+ n += len(chunk)
+ log('attr=%s' % attr)
+ if attr is None:
+ attr = self.attr
+ self.SetConsoleTextAttribute(self.hout, attr)
+ self.WriteConsoleA(self.hout, chunk, len(chunk), byref(junk), None)
+ return n
+
+ def write_plain(self, text, attr=None):
+ '''write text at current cursor position.'''
+ log('write("%s", %s)' %(text,attr))
+ if attr is None:
+ attr = self.attr
+ n = c_int(0)
+ self.SetConsoleTextAttribute(self.hout, attr)
+ self.WriteConsoleA(self.hout, text, len(text), byref(n), None)
+ return len(text)
+
+ # make this class look like a file object
+ def write(self, text):
+ log('write("%s")' % text)
+ return self.write_color(text)
+
+ #write = write_scrolling
+
+ def isatty(self):
+ return True
+
+ def flush(self):
+ pass
+
+ def page(self, attr=None, fill=' '):
+ '''Fill the entire screen.'''
+ if attr is None:
+ attr = self.attr
+ if len(fill) != 1:
+ raise ValueError
+ info = CONSOLE_SCREEN_BUFFER_INFO()
+ self.GetConsoleScreenBufferInfo(self.hout, byref(info))
+ if info.dwCursorPosition.X != 0 or info.dwCursorPosition.Y != 0:
+ self.SetConsoleCursorPosition(self.hout, self.fixcoord(0, 0))
+
+ w = info.dwSize.X
+ n = c_int(0)
+ for y in range(info.dwSize.Y):
+ self.FillConsoleOutputAttribute(self.hout, attr, w, self.fixcoord(0, y), byref(n))
+ self.FillConsoleOutputCharacterA(self.hout, ord(fill[0]), w, self.fixcoord(0, y), byref(n))
+
+ self.attr = attr
+
+ def text(self, x, y, text, attr=None):
+ '''Write text at the given position.'''
+ if attr is None:
+ attr = self.attr
+
+ pos = self.fixcoord(x, y)
+ n = c_int(0)
+ self.WriteConsoleOutputCharacterA(self.hout, text, len(text), pos, byref(n))
+ self.FillConsoleOutputAttribute(self.hout, attr, n, pos, byref(n))
+
+ def rectangle(self, rect, attr=None, fill=' '):
+ '''Fill Rectangle.'''
+ x0, y0, x1, y1 = rect
+ n = c_int(0)
+ if attr is None:
+ attr = self.attr
+ for y in range(y0, y1):
+ pos = self.fixcoord(x0, y)
+ self.FillConsoleOutputAttribute(self.hout, attr, x1-x0, pos, byref(n))
+ self.FillConsoleOutputCharacterA(self.hout, ord(fill[0]), x1-x0, pos, byref(n))
+
+ def scroll(self, rect, dx, dy, attr=None, fill=' '):
+ '''Scroll a rectangle.'''
+ if attr is None:
+ attr = self.attr
+
+ x0, y0, x1, y1 = rect
+ source = SMALL_RECT(x0, y0, x1-1, y1-1)
+ dest = self.fixcoord(x0+dx, y0+dy)
+ style = CHAR_INFO()
+ style.Char.AsciiChar = fill[0]
+ style.Attributes = attr
+
+ return self.ScrollConsoleScreenBufferA(self.hout, byref(source), byref(source),
+ dest, byref(style))
+
+ def scroll_window(self, lines):
+ '''Scroll the window by the indicated number of lines.'''
+ info = CONSOLE_SCREEN_BUFFER_INFO()
+ self.GetConsoleScreenBufferInfo(self.hout, byref(info))
+ rect = info.srWindow
+ log('sw: rtop=%d rbot=%d' % (rect.Top, rect.Bottom))
+ top = rect.Top + lines
+ bot = rect.Bottom + lines
+ h = bot - top
+ maxbot = info.dwSize.Y-1
+ log('sw: lines=%d mb=%d top=%d bot=%d' % (lines,maxbot,top,bot))
+ if top < 0:
+ top = 0
+ bot = h
+ if bot > maxbot:
+ bot = maxbot
+ top = bot - h
+
+ nrect = SMALL_RECT()
+ nrect.Top = top
+ nrect.Bottom = bot
+ nrect.Left = rect.Left
+ nrect.Right = rect.Right
+ log('sn: top=%d bot=%d' % (top,bot))
+ r=self.SetConsoleWindowInfo(self.hout, True, byref(nrect))
+ log('r=%d' % r)
+
+ def get(self):
+ '''Get next event from queue.'''
+ inputHookFunc = c_int.from_address(self.inputHookPtr).value
+
+ Cevent = INPUT_RECORD()
+ count = c_int(0)
+ while 1:
+ if inputHookFunc:
+ call_function(inputHookFunc, ())
+ status = self.ReadConsoleInputA(self.hin, byref(Cevent), 1, byref(count))
+ if status and count.value == 1:
+ e = event(self, Cevent)
+ return e
+
+ def getkeypress(self):
+ '''Return next key press event from the queue, ignoring others.'''
+ while 1:
+ e = self.get()
+ if e.type == 'KeyPress' and e.keycode not in key_modifiers:
+ log(e)
+ if e.keysym == 'Next':
+ self.scroll_window(12)
+ elif e.keysym == 'Prior':
+ self.scroll_window(-12)
+ else:
+ return e
+
+ def getchar(self):
+ '''Get next character from queue.'''
+
+ Cevent = INPUT_RECORD()
+ count = c_int(0)
+ while 1:
+ status = self.ReadConsoleInputA(self.hin, byref(Cevent), 1, byref(count))
+ if (status and count.value==1 and Cevent.EventType == 1 and
+ Cevent.Event.KeyEvent.bKeyDown):
+ sym = keysym(Cevent.Event.KeyEvent.wVirtualKeyCode)
+ if len(sym) == 0:
+ sym = Cevent.Event.KeyEvent.uChar.AsciiChar
+ return sym
+
+ def peek(self):
+ '''Check event queue.'''
+ Cevent = INPUT_RECORD()
+ count = c_int(0)
+ status = PeekConsoleInput(self.hin, byref(Cevent), 1, byref(count))
+ if status and count == 1:
+ return event(self, Cevent)
+
+ def title(self, txt=None):
+ '''Set/get title.'''
+ if txt:
+ self.SetConsoleTitleA(txt)
else:
- return e
-
- def getchar(self):
- '''Get next character from queue.'''
-
- Cevent = INPUT_RECORD()
- count = c_int(0)
- while 1:
- status = self.ReadConsoleInputA(self.hin, byref(Cevent), 1, byref(count))
- if (status and count.value==1 and Cevent.EventType == 1 and
- Cevent.Event.KeyEvent.bKeyDown):
- sym = keysym(Cevent.Event.KeyEvent.wVirtualKeyCode)
- if len(sym) == 0:
- sym = Cevent.Event.KeyEvent.uChar.AsciiChar
- return sym
-
- def peek(self):
- '''Check event queue.'''
- Cevent = INPUT_RECORD()
- count = c_int(0)
- status = PeekConsoleInput(self.hin, byref(Cevent), 1, byref(count))
- if status and count == 1:
- return event(self, Cevent)
-
- def title(self, txt=None):
- '''Set/get title.'''
- if txt:
- self.SetConsoleTitleA(txt)
- else:
- buffer = c_buffer(200)
- n = self.GetConsoleTitleA(buffer, 200)
- if n > 0:
- return buffer.value[:n]
-
- def size(self, width=None, height=None):
- '''Set/get window size.'''
- info = CONSOLE_SCREEN_BUFFER_INFO()
- status = self.GetConsoleScreenBufferInfo(self.hout, byref(info))
- if not status:
- return None
- if width is not None and height is not None:
- wmin = info.srWindow.Right - info.srWindow.Left + 1
- hmin = info.srWindow.Bottom - info.srWindow.Top + 1
- #print wmin, hmin
- width = max(width, wmin)
- height = max(height, hmin)
- #print width, height
- self.SetConsoleScreenBufferSize(self.hout, self.fixcoord(width, height))
- else:
- return (info.dwSize.X, info.dwSize.Y)
-
- def cursor(self, visible):
- '''Set cursor on or off.'''
- info = CONSOLE_CURSOR_INFO()
- if self.GetConsoleCursorInfo(self.hout, byref(info)):
- info.bVisible = visible
- self.SetConsoleCursorInfo(self.hout, byref(info))
-
- def bell(self):
- self.write('\007')
-
- def next_serial(self):
- '''Get next event serial number.'''
- self.serial += 1
- return self.serial
-
+ buffer = c_buffer(200)
+ n = self.GetConsoleTitleA(buffer, 200)
+ if n > 0:
+ return buffer.value[:n]
+
+ def size(self, width=None, height=None):
+ '''Set/get window size.'''
+ info = CONSOLE_SCREEN_BUFFER_INFO()
+ status = self.GetConsoleScreenBufferInfo(self.hout, byref(info))
+ if not status:
+ return None
+ if width is not None and height is not None:
+ wmin = info.srWindow.Right - info.srWindow.Left + 1
+ hmin = info.srWindow.Bottom - info.srWindow.Top + 1
+ #print wmin, hmin
+ width = max(width, wmin)
+ height = max(height, hmin)
+ #print width, height
+ self.SetConsoleScreenBufferSize(self.hout, self.fixcoord(width, height))
+ else:
+ return (info.dwSize.X, info.dwSize.Y)
+
+ def cursor(self, visible):
+ '''Set cursor on or off.'''
+ info = CONSOLE_CURSOR_INFO()
+ if self.GetConsoleCursorInfo(self.hout, byref(info)):
+ info.bVisible = visible
+ self.SetConsoleCursorInfo(self.hout, byref(info))
+
+ def bell(self):
+ self.write('\007')
+
+ def next_serial(self):
+ '''Get next event serial number.'''
+ self.serial += 1
+ return self.serial
+
# add the functions from the dll to the class
for func in funcs:
- setattr(Console, func, getattr(windll.kernel32, func))
+ setattr(Console, func, getattr(windll.kernel32, func))
class event(object):
- '''Represent events from the console.'''
- def __init__(self, console, input):
- '''Initialize an event from the Windows input structure.'''
- self.type = '??'
- self.serial = console.next_serial()
- self.width = 0
- self.height = 0
- self.x = 0
- self.y = 0
- self.char = ''
- self.keycode = 0
- self.keysym = '??'
- self.keyinfo = '' # a tuple with (control, meta, shift, keycode) for dispatch
- self.width = None
-
- if input.EventType == KEY_EVENT:
- if input.Event.KeyEvent.bKeyDown:
- self.type = "KeyPress"
- else:
- self.type = "KeyRelease"
- self.char = input.Event.KeyEvent.uChar.AsciiChar
- self.keycode = input.Event.KeyEvent.wVirtualKeyCode
- self.state = input.Event.KeyEvent.dwControlKeyState
- self.keysym = make_keysym(self.keycode)
- self.keyinfo = make_keyinfo(self.keycode, self.state)
- elif input.EventType == MOUSE_EVENT:
- if input.Event.MouseEvent.dwEventFlags & MOUSE_MOVED:
- self.type = "Motion"
- else:
- self.type = "Button"
- self.x = input.Event.MouseEvent.dwMousePosition.X
- self.y = input.Event.MouseEvent.dwMousePosition.Y
- self.state = input.Event.MouseEvent.dwButtonState
- elif input.EventType == WINDOW_BUFFER_SIZE_EVENT:
- self.type = "Configure"
- self.width = input.Event.WindowBufferSizeEvent.dwSize.X
- self.height = input.Event.WindowBufferSizeEvent.dwSize.Y
- elif input.EventType == FOCUS_EVENT:
- if input.Event.FocusEvent.bSetFocus:
- self.type = "FocusIn"
- else:
- self.type = "FocusOut"
- elif input.EventType == MENU_EVENT:
- self.type = "Menu"
- self.state = input.Event.MenuEvent.dwCommandId
-
- def __repr__(self):
- '''Display an event for debugging.'''
- if self.type in ['KeyPress', 'KeyRelease']:
- s = "%s char='%s'%d keysym='%s' keycode=%d:%x state=%x keyinfo=%s" % \
- (self.type, self.char, ord(self.char), self.keysym, self.keycode, self.keycode,
- self.state, self.keyinfo)
- elif self.type in ['Motion', 'Button']:
- s = '%s x=%d y=%d state=%x' % (self.type, self.x, self.y, self.state)
- elif self.type == 'Configure':
- s = '%s w=%d h=%d' % (self.type, self.width, self.height)
- elif self.type in ['FocusIn', 'FocusOut']:
- s = self.type
- elif self.type == 'Menu':
- s = '%s state=%x' % (self.type, self.state)
- else:
- s = 'unknown event type'
- return s
+ '''Represent events from the console.'''
+ def __init__(self, console, input):
+ '''Initialize an event from the Windows input structure.'''
+ self.type = '??'
+ self.serial = console.next_serial()
+ self.width = 0
+ self.height = 0
+ self.x = 0
+ self.y = 0
+ self.char = ''
+ self.keycode = 0
+ self.keysym = '??'
+ self.keyinfo = '' # a tuple with (control, meta, shift, keycode) for dispatch
+ self.width = None
+
+ if input.EventType == KEY_EVENT:
+ if input.Event.KeyEvent.bKeyDown:
+ self.type = "KeyPress"
+ else:
+ self.type = "KeyRelease"
+ self.char = input.Event.KeyEvent.uChar.AsciiChar
+ self.keycode = input.Event.KeyEvent.wVirtualKeyCode
+ self.state = input.Event.KeyEvent.dwControlKeyState
+ self.keysym = make_keysym(self.keycode)
+ self.keyinfo = make_keyinfo(self.keycode, self.state)
+ elif input.EventType == MOUSE_EVENT:
+ if input.Event.MouseEvent.dwEventFlags & MOUSE_MOVED:
+ self.type = "Motion"
+ else:
+ self.type = "Button"
+ self.x = input.Event.MouseEvent.dwMousePosition.X
+ self.y = input.Event.MouseEvent.dwMousePosition.Y
+ self.state = input.Event.MouseEvent.dwButtonState
+ elif input.EventType == WINDOW_BUFFER_SIZE_EVENT:
+ self.type = "Configure"
+ self.width = input.Event.WindowBufferSizeEvent.dwSize.X
+ self.height = input.Event.WindowBufferSizeEvent.dwSize.Y
+ elif input.EventType == FOCUS_EVENT:
+ if input.Event.FocusEvent.bSetFocus:
+ self.type = "FocusIn"
+ else:
+ self.type = "FocusOut"
+ elif input.EventType == MENU_EVENT:
+ self.type = "Menu"
+ self.state = input.Event.MenuEvent.dwCommandId
+
+ def __repr__(self):
+ '''Display an event for debugging.'''
+ if self.type in ['KeyPress', 'KeyRelease']:
+ s = "%s char='%s'%d keysym='%s' keycode=%d:%x state=%x keyinfo=%s" % \
+ (self.type, self.char, ord(self.char), self.keysym, self.keycode, self.keycode,
+ self.state, self.keyinfo)
+ elif self.type in ['Motion', 'Button']:
+ s = '%s x=%d y=%d state=%x' % (self.type, self.x, self.y, self.state)
+ elif self.type == 'Configure':
+ s = '%s w=%d h=%d' % (self.type, self.width, self.height)
+ elif self.type in ['FocusIn', 'FocusOut']:
+ s = self.type
+ elif self.type == 'Menu':
+ s = '%s state=%x' % (self.type, self.state)
+ else:
+ s = 'unknown event type'
+ return s
def getconsole(buffer=1):
- """Get a console handle.
+ """Get a console handle.
- If buffer is non-zero, a new console buffer is allocated and
- installed. Otherwise, this returns a handle to the current
- console buffer"""
+ If buffer is non-zero, a new console buffer is allocated and
+ installed. Otherwise, this returns a handle to the current
+ console buffer"""
- c = Console(buffer)
+ c = Console(buffer)
- return c
+ return c
# The following code uses ctypes to allow a Python callable to
# substitute for GNU readline within the Python interpreter. Calling
@@ -635,78 +635,78 @@ def getconsole(buffer=1):
readline_ref = None # this holds a reference to the c-callable to keep it alive
def hook_wrapper_23(stdin, stdout, prompt):
- '''Wrap a Python readline so it behaves like GNU readline.'''
- try:
- # call the Python hook
- res = readline_hook(prompt)
- # make sure it returned the right sort of thing
- if res and not isinstance(res, str):
- raise TypeError, 'readline must return a string.'
- except KeyboardInterrupt:
- # GNU readline returns 0 on keyboard interrupt
- return 0
- except EOFError:
- # It returns an empty string on EOF
- res = ''
- except:
- print >>sys.stderr, 'Readline internal error'
- traceback.print_exc()
- res = '\n'
- # we have to make a copy because the caller expects to free the result
- n = len(res)
- p = Console.PyMem_Malloc(n+1)
- cdll.msvcrt.strncpy(p, res, n+1)
- return p
-
+ '''Wrap a Python readline so it behaves like GNU readline.'''
+ try:
+ # call the Python hook
+ res = readline_hook(prompt)
+ # make sure it returned the right sort of thing
+ if res and not isinstance(res, str):
+ raise TypeError, 'readline must return a string.'
+ except KeyboardInterrupt:
+ # GNU readline returns 0 on keyboard interrupt
+ return 0
+ except EOFError:
+ # It returns an empty string on EOF
+ res = ''
+ except:
+ print >>sys.stderr, 'Readline internal error'
+ traceback.print_exc()
+ res = '\n'
+ # we have to make a copy because the caller expects to free the result
+ n = len(res)
+ p = Console.PyMem_Malloc(n+1)
+ cdll.msvcrt.strncpy(p, res, n+1)
+ return p
+
def hook_wrapper(prompt):
- '''Wrap a Python readline so it behaves like GNU readline.'''
- try:
- # call the Python hook
- res = readline_hook(prompt)
- # make sure it returned the right sort of thing
- if res and not isinstance(res, str):
- raise TypeError, 'readline must return a string.'
- except KeyboardInterrupt:
- # GNU readline returns 0 on keyboard interrupt
- return 0
- except EOFError:
- # It returns an empty string on EOF
- res = ''
- except:
- print >>sys.stderr, 'Readline internal error'
- traceback.print_exc()
- res = '\n'
- # we have to make a copy because the caller expects to free the result
- p = cdll.msvcrt._strdup(res)
- return p
+ '''Wrap a Python readline so it behaves like GNU readline.'''
+ try:
+ # call the Python hook
+ res = readline_hook(prompt)
+ # make sure it returned the right sort of thing
+ if res and not isinstance(res, str):
+ raise TypeError, 'readline must return a string.'
+ except KeyboardInterrupt:
+ # GNU readline returns 0 on keyboard interrupt
+ return 0
+ except EOFError:
+ # It returns an empty string on EOF
+ res = ''
+ except:
+ print >>sys.stderr, 'Readline internal error'
+ traceback.print_exc()
+ res = '\n'
+ # we have to make a copy because the caller expects to free the result
+ p = cdll.msvcrt._strdup(res)
+ return p
def install_readline(hook):
- '''Set up things for the interpreter to call our function like GNU readline.'''
- global readline_hook, readline_ref
- # save the hook so the wrapper can call it
- readline_hook = hook
- # get the address of PyOS_ReadlineFunctionPointer so we can update it
- PyOS_RFP = c_int.from_address(Console.GetProcAddress(sys.dllhandle,
- "PyOS_ReadlineFunctionPointer"))
- # save a reference to the generated C-callable so it doesn't go away
- if sys.version < '2.3':
- readline_ref = HOOKFUNC22(hook_wrapper)
- else:
- readline_ref = HOOKFUNC23(hook_wrapper_23)
- # get the address of the function
- func_start = c_int.from_address(addressof(readline_ref)).value
- # write the function address into PyOS_ReadlineFunctionPointer
- PyOS_RFP.value = func_start
+ '''Set up things for the interpreter to call our function like GNU readline.'''
+ global readline_hook, readline_ref
+ # save the hook so the wrapper can call it
+ readline_hook = hook
+ # get the address of PyOS_ReadlineFunctionPointer so we can update it
+ PyOS_RFP = c_int.from_address(Console.GetProcAddress(sys.dllhandle,
+ "PyOS_ReadlineFunctionPointer"))
+ # save a reference to the generated C-callable so it doesn't go away
+ if sys.version < '2.3':
+ readline_ref = HOOKFUNC22(hook_wrapper)
+ else:
+ readline_ref = HOOKFUNC23(hook_wrapper_23)
+ # get the address of the function
+ func_start = c_int.from_address(addressof(readline_ref)).value
+ # write the function address into PyOS_ReadlineFunctionPointer
+ PyOS_RFP.value = func_start
if __name__ == '__main__':
- import time, sys
- c = Console(0)
- sys.stdout = c
- sys.stderr = c
- c.page()
- c.pos(5, 10)
- c.write('hi there')
- print 'some printed output'
- for i in range(10):
- c.getkeypress()
- del c
+ import time, sys
+ c = Console(0)
+ sys.stdout = c
+ sys.stderr = c
+ c.page()
+ c.pos(5, 10)
+ c.write('hi there')
+ print 'some printed output'
+ for i in range(10):
+ c.getkeypress()
+ del c
View
2,122 readline/PyReadline.py
@@ -16,1083 +16,1083 @@
from keysyms import key_text_to_keyinfo
def quote_char(c):
- if ' ' <= c <= '~':
- return c
- else:
- return repr(c)[1:-1]
-
-class Readline:
- 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
- self.command_color = None
- self.key_dispatch = {}
- self.previous_func = None
- self.first_prompt = True
- self.next_meta = False # True to force meta on next character
- self.tabstop = 4
-
- self.emacs_editing_mode(None)
- self.begidx = 0
- self.endidx = 0
-
- # variables you can control with parse_and_bind
- self.show_all_if_ambiguous = 'off'
- self.mark_directories = 'on'
- self.bell_style = 'none'
-
- def _bell(self):
- '''ring the bell if requested.'''
- if self.bell_style == 'none':
- self.console.bell()
-
- 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)
+ if ' ' <= c <= '~':
+ return c
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()
- w, h = c.size()
- c.rectangle((x, y, w, y+1))
- c.rectangle((0, y+1, w, min(y+3,h)))
-
- def _set_cursor(self):
- 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)
- while(xc > w):
- xc -= w
- yc += 1
- c.pos(xc, yc)
-
- def _print_prompt(self):
- c = self.console
- log('prompt="%s"' % repr(self.prompt))
- x, y = c.pos()
- n = c.write_scrolling(self.prompt, self.prompt_color)
- self.prompt_begin_pos = (x, y - n)
- self.prompt_end_pos = c.pos()
- self.size = c.size()
-
- def _update_prompt_pos(self, n):
- if n != 0:
- bx, by = self.prompt_begin_pos
- ex, ey = self.prompt_end_pos
- self.prompt_begin_pos = (bx, by - n)
- self.prompt_end_pos = (ex, ey - n)
-
- def readline(self, prompt=''):
- '''Try to act like GNU readline.'''
-
- # handle startup_hook
- if self.first_prompt:
- self.first_prompt = False
- if self.startup_hook:
+ return repr(c)[1:-1]
+
+class Readline:
+ 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
+ self.command_color = None
+ self.key_dispatch = {}
+ self.previous_func = None
+ self.first_prompt = True
+ self.next_meta = False # True to force meta on next character
+ self.tabstop = 4
+
+ self.emacs_editing_mode(None)
+ self.begidx = 0
+ self.endidx = 0
+
+ # variables you can control with parse_and_bind
+ self.show_all_if_ambiguous = 'off'
+ self.mark_directories = 'on'
+ self.bell_style = 'none'
+
+ def _bell(self):
+ '''ring the bell if requested.'''
+ if self.bell_style == 'none':
+ self.console.bell()
+
+ 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()
+ w, h = c.size()
+ c.rectangle((x, y, w, y+1))
+ c.rectangle((0, y+1, w, min(y+3,h)))
+
+ def _set_cursor(self):
+ 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)
+ while(xc > w):
+ xc -= w
+ yc += 1
+ c.pos(xc, yc)
+
+ def _print_prompt(self):
+ c = self.console
+ log('prompt="%s"' % repr(self.prompt))
+ x, y = c.pos()
+ n = c.write_scrolling(self.prompt, self.prompt_color)
+ self.prompt_begin_pos = (x, y - n)
+ self.prompt_end_pos = c.pos()
+ self.size = c.size()
+
+ def _update_prompt_pos(self, n):
+ if n != 0:
+ bx, by = self.prompt_begin_pos
+ ex, ey = self.prompt_end_pos
+ self.prompt_begin_pos = (bx, by - n)
+ self.prompt_end_pos = (ex, ey - n)
+
+ def readline(self, prompt=''):
+ '''Try to act like GNU readline.'''
+
+ # handle startup_hook
+ if self.first_prompt:
+ self.first_prompt = False
+ if self.startup_hook:
+ try:
+ self.startup_hook()
+ except:
+ print 'startup hook failed'
+ traceback.print_exc()
+
+ c = self.console
+ self._reset_line()
+ self.prompt = prompt
+ self._print_prompt()
+
+ if self.pre_input_hook:
+ try:
+ self.pre_input_hook()
+ except:
+ print 'pre_input_hook failed'
+ traceback.print_exc()
+ self.pre_input_hook = None
+
+ while 1:
+ 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()
+
+ event = c.getkeypress()
+ if self.next_meta:
+ self.next_meta = False
+ control, meta, shift, code = event.keyinfo
+ event.keyinfo = (control, True, shift, code)
+
+ try:
+ dispatch_func = self.key_dispatch[event.keyinfo]
+ except KeyError:
+ c.bell()
+ continue
+ 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.previous_func = dispatch_func
+ if r:
+ break
+
+ c.write('\r\n')
+
+ rtext = self._line_text()
+ self.add_history(rtext)
+
+ log('returning(%s)' % rtext)
+ return rtext + '\n'
+
+ def parse_and_bind(self, string):
+ '''Parse and execute single line of a readline init file.'''
try:
- self.startup_hook()
+ log('parse_and_bind("%s")' % string)
+ if string.startswith('#'):
+ return
+ if string.startswith('set'):
+ m = re.compile(r'set\s+([-a-zA-Z0-9]+)\s+(.+)\s*$').match(string)
+ if m:
+ var_name = m.group(1)
+ val = m.group(2)
+ try:
+ setattr(self, var_name.replace('-','_'), val)
+ except AttributeError:
+ log('unknown var="%s" val="%s"' % (var_name, val))
+ else:
+ log('bad set "%s"' % string)
+ return
+ log('before')
+ m = re.compile(r'\s*(.+)\s*:\s*([-a-zA-Z]+)\s*$').match(string)
+ log('here')
+ if m:
+ key = m.group(1)
+ func_name = m.group(2)
+ py_name = func_name.replace('-', '_')
+ try:
+ func = getattr(self, py_name)
+ except AttributeError:
+ log('unknown func key="%s" func="%s"' % (key, func_name))
+ print 'unknown function to bind: "%s"' % func_name
+ self._bind_key(key, func)
except:
- print 'startup hook failed'
- traceback.print_exc()
-
- c = self.console
- self._reset_line()
- self.prompt = prompt
- self._print_prompt()
-
- if self.pre_input_hook:
- try:
- self.pre_input_hook()
- except:
- print 'pre_input_hook failed'
- traceback.print_exc()
- self.pre_input_hook = None
-
- while 1:
- 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()
-
- event = c.getkeypress()
- if self.next_meta:
- self.next_meta = False
- control, meta, shift, code = event.keyinfo
- event.keyinfo = (control, True, shift, code)
-
- try:
- dispatch_func = self.key_dispatch[event.keyinfo]
- except KeyError:
- c.bell()
- continue
- 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
+ log('error')
+ traceback.print_exc()
+ raise
+ log('return')
+
+ def get_line_buffer(self):
+ '''Return the current contents of the line buffer.'''
+ return "".join(self.line_buffer)
+
+ 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
+
+ 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
+
+ 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()
+
+ def get_history_length(self, ):
+ '''Return the desired length of the history file.
+
+ Negative values imply unlimited history file size.'''
+ return self.history_length
+
+ def set_history_length(self, length):
+ '''Set the number of lines to save in the history file.
+
+ write_history_file() uses this value to truncate the history file
+ when saving. Negative values imply unlimited history file size.
+ '''
+ self.history_length = length
+
+ def set_startup_hook(self, function=None):
+ '''Set or remove the startup_hook function.
+
+ If function is specified, it will be used as the new startup_hook
+ function; if omitted or None, any hook function already installed is
+ removed. The startup_hook function is called with no arguments just
+ before readline prints the first prompt.
+
+ '''
+ self.startup_hook = function
+
+ def set_pre_input_hook(self, function=None):
+ '''Set or remove the pre_input_hook function.
+
+ If function is specified, it will be used as the new pre_input_hook
+ function; if omitted or None, any hook function already installed is
+ removed. The pre_input_hook function is called with no arguments
+ after the first prompt has been printed and just before readline
+ starts reading input characters.
+
+ '''
+ self.pre_input_hook = function
+
+ def set_completer(self, function=None):
+ '''Set or remove the completer function.
+
+ If function is specified, it will be used as the new completer
+ function; if omitted or None, any completer function already
+ installed is removed. The completer function is called as
+ function(text, state), for state in 0, 1, 2, ..., until it returns a
+ non-string value. It should return the next possible completion
+ starting with text.
+ '''
+ log('set_completer')
+ self.completer = function
+
+ def get_completer(self):
+ '''Get the completer function.
+ '''
+
+ log('get_completer')
+ return self.completer
+
+ def get_begidx(self):
+ '''Get the beginning index of the readline tab-completion scope.'''
+ return self.begidx
+
+ def get_endidx(self):
+ '''Get the ending index of the readline tab-completion scope.'''
+ return self.endidx
+
+ def set_completer_delims(self, string):
+ '''Set the readline word delimiters for tab-completion.'''
+ self.completer_delims = string
+
+ def get_completer_delims(self):
+ '''Get the readline word delimiters for tab-completion.'''
+ return self.completer_delims
+
+ 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.undo_stack.append([self.line_cursor, ltext])
-
- self.previous_func = dispatch_func
- if r:
- break
-
- c.write('\r\n')
-
- rtext = self._line_text()
- self.add_history(rtext)
-
- log('returning(%s)' % rtext)
- return rtext + '\n'
-
- def parse_and_bind(self, string):
- '''Parse and execute single line of a readline init file.'''
- try:
- log('parse_and_bind("%s")' % string)
- if string.startswith('#'):
- return
- if string.startswith('set'):
- m = re.compile(r'set\s+([-a-zA-Z0-9]+)\s+(.+)\s*$').match(string)
- if m:
- var_name = m.group(1)
- val = m.group(2)
- try:
- setattr(self, var_name.replace('-','_'), val)
- except AttributeError:
- log('unknown var="%s" val="%s"' % (var_name, val))
+ 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)
+
+ ### 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
+
+ def end_of_line(self, e): # (C-e)
+ '''Move to the end of the line. '''
+ self.line_cursor = len(self.line_buffer)
+
+ def forward_char(self, e): # (C-f)
+ '''Move forward a character. '''
+ if self.line_cursor < len(self.line_buffer):
+ self.line_cursor += 1
else:
- log('bad set "%s"' % string)
- return
- log('before')
- m = re.compile(r'\s*(.+)\s*:\s*([-a-zA-Z]+)\s*$').match(string)
- log('here')
- if m:
- key = m.group(1)
- func_name = m.group(2)
- py_name = func_name.replace('-', '_')
- try:
- func = getattr(self, py_name)
- except AttributeError:
- log('unknown func key="%s" func="%s"' % (key, func_name))
- print 'unknown function to bind: "%s"' % func_name
- self._bind_key(key, func)
- except:
- log('error')
- traceback.print_exc()
- raise
- log('return')
-
- def get_line_buffer(self):
- '''Return the current contents of the line buffer.'''
- return "".join(self.line_buffer)
-
- 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
-
- 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='~/.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
-
- def write_history_file(self, filename='~/.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()
-
- def get_history_length(self, ):
- '''Return the desired length of the history file.
-
- Negative values imply unlimited history file size.'''
- return self.history_length
-
- def set_history_length(self, length):
- '''Set the number of lines to save in the history file.
-
- write_history_file() uses this value to truncate the history file
- when saving. Negative values imply unlimited history file size.
- '''
- self.history_length = length
-
- def set_startup_hook(self, function=None):
- '''Set or remove the startup_hook function.
-
- If function is specified, it will be used as the new startup_hook
- function; if omitted or None, any hook function already installed is
- removed. The startup_hook function is called with no arguments just
- before readline prints the first prompt.
-
- '''
- self.startup_hook = function
-
- def set_pre_input_hook(self, function=None):
- '''Set or remove the pre_input_hook function.
-
- If function is specified, it will be used as the new pre_input_hook
- function; if omitted or None, any hook function already installed is
- removed. The pre_input_hook function is called with no arguments
- after the first prompt has been printed and just before readline
- starts reading input characters.
-
- '''
- self.pre_input_hook = function
-
- def set_completer(self, function=None):
- '''Set or remove the completer function.
-
- If function is specified, it will be used as the new completer
- function; if omitted or None, any completer function already
- installed is removed. The completer function is called as
- function(text, state), for state in 0, 1, 2, ..., until it returns a
- non-string value. It should return the next possible completion
- starting with text.
- '''
- log('set_completer')
- self.completer = function
-
- def get_completer(self):
- '''Get the completer function.
- '''
-
- log('get_completer')
- return self.completer
-
- def get_begidx(self):
- '''Get the beginning index of the readline tab-completion scope.'''
- return self.begidx
-
- def get_endidx(self):
- '''Get the ending index of the readline tab-completion scope.'''
- return self.endidx
-
- def set_completer_delims(self, string):
- '''Set the readline word delimiters for tab-completion.'''
- self.completer_delims = string
-
- def get_completer_delims(self):
- '''Get the readline word delimiters for tab-completion.'''
- return self.completer_delims
-
- 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)
-
- ### 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
-
- def end_of_line(self, e): # (C-e)
- '''Move to the end of the line. '''
- self.line_cursor = len(self.line_buffer)
-
- 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._bell()
- def backward_char(self, e): # (C-b)
- '''Move back a character. '''
- if self.line_cursor > 0:
- self.line_cursor -= 1
- else:
- self._bell()
-
- 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
-
- 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
-
- def clear_screen(self, e): # (C-l)
- '''Clear the screen and redraw the current line, leaving the current
- line at the top of the screen.'''
- self.console.page()
-
- def redraw_current_line(self, e): # ()
- '''Refresh the current line. By default, this is unbound.'''
- pass
-
- def accept_line(self, e): # (Newline or Return)
- '''Accept the line regardless of where the cursor is. If this line
- is non-empty, it may be added to the history list for future recall
- with add_history(). If this line is a modified history line, the
- history line is restored to its original state.'''
- return True
-
- def 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()
-
- 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()
+ def backward_char(self, e): # (C-b)
+ '''Move back a character. '''
+ if self.line_cursor > 0:
+ self.line_cursor -= 1
+ else:
+ self._bell()
+
+ 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
+
+ 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
+
+ def clear_screen(self, e): # (C-l)
+ '''Clear the screen and redraw the current line, leaving the current
+ line at the top of the screen.'''
+ self.console.page()
+
+ def redraw_current_line(self, e): # ()
+ '''Refresh the current line. By default, this is unbound.'''
+ pass
+
+ def accept_line(self, e): # (Newline or Return)
+ '''Accept the line regardless of where the cursor is. If this line
+ is non-empty, it may be added to the history list for future recall
+ with add_history(). If this line is a modified history line, the
+ history line is restored to its original state.'''
+ return True
+
+ def 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()
+
+ 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()
- 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()
-
- 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()
-
- def _i_search(self, direction, init_event):
- c = self.console
- line = self._line_text()
- query = ''
- hc_start = self.history_cursor + direction
- hc = hc_start
- 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()
- if event.keysym == 'BackSpace':
- if len(query) > 0:
- query = query[:-1]
- hc = hc_start
+ 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()
+
+ 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:
- c.bell()
- elif event.char in string.letters + string.digits + string.punctuation + ' ':
- query += event.char
+ self._bell()
+
+ def _i_search(self, direction, init_event):
+ c = self.console
+ line = self._line_text()
+ query = ''
+ hc_start = self.history_cursor + direction
hc = hc_start
- elif event.keyinfo == init_event.keyinfo:
- hc += direction
- else:
- if event.keysym != 'Return':
- c.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:
- c.bell()
- continue
- line = self.history[hc]
-
- px, py = self.prompt_begin_pos
- c.pos(0, py)
- self._set_line(line)
- self._print_prompt()
-
- 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)
-
- 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]
+ 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()
+ if event.keysym == 'BackSpace':
+ if len(query) > 0:
+ query = query[:-1]
+ hc = hc_start
+ else:
+ c.bell()
+ elif event.char in string.letters + string.digits + string.punctuation + ' ':
+ query += event.char
+ hc = hc_start
+ elif event.keyinfo == init_event.keyinfo:
+ hc += direction
+ else:
+ if event.keysym != 'Return':
+ c.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:
+ c.bell()
+ continue
+ line = self.history[hc]
+
+ px, py = self.prompt_begin_pos
+ c.pos(0, py)
+ self._set_line(line)
+ self._print_prompt()
+
+ 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)
+
+ 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:
+ c.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:
+ c.bell()
+
+
+ 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)
+
+ 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:
- break