diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..c1db38b --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ +link doc/ChangeLog \ No newline at end of file diff --git a/doc/ChangeLog b/doc/ChangeLog new file mode 100644 index 0000000..74a3a55 --- /dev/null +++ b/doc/ChangeLog @@ -0,0 +1,6 @@ +2006-01-21 Jörgen Stenarson + + * Changed all python files to conform to 4 space indent. + * Added changelog + * Added os.path.expanduser to expand out ~/.history paths + \ No newline at end of file diff --git a/readline/Console.py b/readline/Console.py index b926b60..9c66792 100644 --- a/readline/Console.py +++ b/readline/Console.py @@ -5,26 +5,26 @@ 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: - 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 @@ GENERIC_WRITE = 0x40000000 # 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. + '''Console driver for Windows. - ''' - - 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) + def __init__(self, newbuffer=0): + '''Initialize the Console object. - 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 + 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() - 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) + 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) - 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() + 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 - 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 + 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) - # 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 __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 home(self): - '''Move to home.''' - self.pos(0,0) + 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) # Map ANSI color escape sequences into Windows Console Attributes - 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, - } + 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. + # 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])') - 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. + def write_scrolling(self, text, attr=None): + '''write text at current cursor position while watching for scrolling. - 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. + 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. - ''' - x, y = self.pos() - w, h = self.size() - scroll = 0 # the result + 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. - # 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. + ''' + x, y = self.pos() + w, h = self.size() + scroll = 0 # the result - 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 + # 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_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) + def write_color(self, text, attr=None): + '''write text at current cursor position and interpret color escapes. - # make this class look like a file object - def write(self, text): - log('write("%s")' % text) - return self.write_color(text) - - #write = write_scrolling + 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 isatty(self): - return True + 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) - 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)) + # make this class look like a file object + def write(self, text): + log('write("%s")' % text) + return self.write_color(text) - 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)) + #write = write_scrolling - self.attr = attr + def isatty(self): + return True - def text(self, x, y, text, attr=None): - '''Write text at the given position.''' - if attr is None: - attr = self.attr + def flush(self): + pass - 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 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)) - 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)) + 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)) - 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 + self.attr = attr - return self.ScrollConsoleScreenBufferA(self.hout, byref(source), byref(source), - dest, byref(style)) + def text(self, x, y, text, attr=None): + '''Write text at the given position.''' + if attr is None: + attr = self.attr - 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 + 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)) - 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 + 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)) - 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 scroll(self, rect, dx, dy, attr=None, fill=' '): + '''Scroll a rectangle.''' + if attr is None: + attr = self.attr - 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) + 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.''' + buffer = c_buffer(200) + n = self.GetConsoleTitleA(buffer, 200) + if n > 0: + return buffer.value[:n] - 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 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 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 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 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 bell(self): + self.write('\007') - 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 next_serial(self): + '''Get next event serial number.''' + self.serial += 1 + return self.serial - 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 + '''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 + 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 @@ readline_hook = None # the python hook goes here 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 diff --git a/readline/PyReadline.py b/readline/PyReadline.py index 212e78d..8023a90 100644 --- a/readline/PyReadline.py +++ b/readline/PyReadline.py @@ -16,1083 +16,1083 @@ from Console import log from keysyms import key_text_to_keyinfo def quote_char(c): - if ' ' <= c <= '~': - return c - else: - return repr(c)[1:-1] - + 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) - 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() + 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 - 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() + self.emacs_editing_mode(None) + self.begidx = 0 + self.endidx = 0 - 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 + # 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.undo_stack.append([self.line_cursor, ltext]) + self.line_cursor = cursor - self.previous_func = dispatch_func - if r: - break - - c.write('\r\n') + def _reset_line(self): + self.line_buffer = [] + self.line_cursor = 0 + self.undo_stack = [] - rtext = self._line_text() - self.add_history(rtext) - - log('returning(%s)' % rtext) - return rtext + '\n' + 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 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)) - 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('-', '_') + 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: - 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() - - 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 - 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: - self._set_line(self.query) - c.bell() - - 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) - - 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) - - def yank_nth_arg(self, e): # (M-C-y) - '''Insert the first argument to the previous command (usually the - second word on the previous line) at point. With an argument n, - insert the nth word from the previous command (the words in the - previous command begin with word 0). A negative argument inserts the - nth word from the end of the previous command.''' - pass - - def yank_last_arg(self, e): # (M-. or M-_) - '''Insert last argument to the previous command (the last word of - the previous history entry). With an argument, behave exactly like - yank-nth-arg. Successive calls to yank-last-arg move back through - the history list, inserting the last argument of each line in turn.''' - pass - - def delete_char(self, e): # (C-d) - '''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 len(self.line_buffer) == 0: - if self.previous_func != self.delete_char: - raise EOFError - self._bell() - if self.line_cursor < len(self.line_buffer): - del self.line_buffer[self.line_cursor] - else: - self._bell() - - 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 - - def forward_backward_delete_char(self, e): # () - '''Delete the character under the cursor, unless the cursor is at - the end of the line, in which case the character behind the cursor - is deleted. By default, this is not bound to a key.''' - pass - - def quoted_insert(self, e): # (C-q or C-v) - '''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 - - def tab_insert(self, e): # (M-TAB) - '''Insert a tab character. ''' - ws = ' ' * (self.tabstop - (self.line_cursor%self.tabstop)) - self.insert_text(ws) - - def self_insert(self, e): # (a, b, A, 1, !, ...) - '''Insert yourself. ''' - self.line_buffer.insert(self.line_cursor, e.char) - self.line_cursor += 1 - - 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 - - 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 - - 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 - - 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 - - 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 - - def overwrite_mode(self, e): # () - '''Toggle overwrite mode. With an explicit positive numeric - argument, switches to overwrite mode. With an explicit non-positive - numeric argument, switches to insert mode. This command affects only - emacs mode; vi mode does overwrite differently. Each call to - readline() starts in insert mode. In overwrite mode, characters - bound to self-insert replace the text at point rather than pushing - 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. ''' - self.line_buffer[self.line_cursor:] = [] - - 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 - - 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 - - def kill_whole_line(self, e): # () - '''Kill all characters on the current line, no matter where point - is. By default, this is unbound.''' - pass - - 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 - - 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] = [] - - 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] = [] - - def delete_horizontal_space(self, e): # () - '''Delete all spaces and tabs around point. By default, this is unbound. ''' - pass - - def kill_region(self, e): # () - '''Kill the text in the current region. By default, this command is unbound. ''' - pass - - def copy_region_as_kill(self, e): # () - '''Copy the text in the region to the kill buffer, so it can be - yanked right away. By default, this command is unbound.''' - pass - - def copy_backward_word(self, e): # () - '''Copy the word before point to the kill buffer. The word - boundaries are the same as backward-word. By default, this command - is unbound.''' - pass - - def copy_forward_word(self, e): # () - '''Copy the word following point to the kill buffer. The word - boundaries are the same as forward-word. By default, this command is - unbound.''' - pass - - def yank(self, e): # (C-y) - '''Yank the top of the kill ring into the buffer at point. ''' - pass - - def yank_pop(self, e): # (M-y) - '''Rotate the kill-ring, and yank the new top. You can only do this - if the prior command is yank or yank-pop.''' - pass - - - def digit_argument(self, e): # (M-0, M-1, ... M--) - '''Add this digit to the argument already accumulating, or start a - new argument. M-- starts a negative argument.''' - pass - - def universal_argument(self, e): # () - '''This is another way to specify an argument. If this command is - followed by one or more digits, optionally with a leading minus - sign, those digits define the argument. If the command is followed - by digits, executing universal-argument again ends the numeric - argument, but is otherwise ignored. As a special case, if this - command is immediately followed by a character that is neither a - digit or minus sign, the argument count for the next command is - multiplied by four. The argument count is initially one, so - executing this function the first time makes the argument count - four, a second time makes the argument count sixteen, and so on. By - default, this is not bound to a key.''' - pass - - - def _get_completions(self): - '''Return a list of possible completions for the string ending at the point. - - Also set begidx and endidx in the process.''' - completions = [] - self.begidx = self.line_cursor - self.endidx = self.line_cursor - 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: - self.begidx += 1 - break - text = ''.join(self.line_buffer[self.begidx:self.endidx]) - log('complete text="%s"' % text) - i = 0 - while 1: - try: - r = self.completer(text, i) + 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: - break - i += 1 - if r and r not in completions: - completions.append(r) + 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: - break - log('text completions=%s' % completions) - if not completions: - # get the filename to complete - while self.begidx > 0: - self.begidx -= 1 - if self.line_buffer[self.begidx] in ' \t\n': - self.begidx += 1 - break - text = ''.join(self.line_buffer[self.begidx:self.endidx]) - log('file complete text="%s"' % text) - completions = glob(os.path.expanduser(text) + '*') - if self.mark_directories == 'on': - mc = [] - for f in completions: - if os.path.isdir(f): - mc.append(f + os.sep) - else: - mc.append(f) - completions = mc - log('fnames=%s' % completions) - return completions + 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 _display_completions(self, completions): - if not completions: - return - self.console.write('\n') - wmax = max(map(len, completions)) - w, h = self.console.size() - cols = max(1, int((w-1) / (wmax+1))) - rows = int(math.ceil(float(len(completions)) / cols)) - for row in range(rows): - s = '' - for col in range(cols): - i = col*rows + row - if i < len(completions): - self.console.write(completions[i].ljust(wmax+1)) - self.console.write('\n') - self._print_prompt() - - def complete(self, e): # (TAB) - '''Attempt to perform completion on the text before point. The - actual completion performed is application-specific. The default is - filename completion.''' - completions = self._get_completions() - 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) - if len(completions) > 1: - if self.show_all_if_ambiguous == 'on': - self._display_completions(completions) + ### 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() - else: - self._bell() + self._bell() - def possible_completions(self, e): # (M-?) - '''List the possible completions of the text before point. ''' - completions = self._get_completions() - self._display_completions(completions) + def backward_char(self, e): # (C-b) + '''Move back a character. ''' + if self.line_cursor > 0: + self.line_cursor -= 1 + else: + self._bell() - def insert_completions(self, e): # (M-*) - '''Insert all completions of the text before point that would have - been generated by possible-completions.''' - completions = self._get_completions() - b = self.begidx - e = self.endidx - for comp in completions: - rep = [ c for c in comp ] - rep.append(' ') - self.line_buffer[b:e] = rep - b += len(rep) - e = b - self.line_cursor = b + 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 menu_complete(self, e): # () - '''Similar to complete, but replaces the word to be completed with a - single match from the list of possible completions. Repeated - execution of menu-complete steps through the list of possible - completions, inserting each match in turn. At the end of the list of - completions, the bell is rung (subject to the setting of bell-style) - and the original text is restored. An argument of n moves n - positions forward in the list of matches; a negative argument may be - used to move backward through the list. This command is intended to - be bound to TAB, but is unbound by default.''' - pass + 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 delete_char_or_list(self, e): # () - '''Deletes the character under the cursor if not at the beginning or - end of the line (like delete-char). If at the end of the line, - behaves identically to possible-completions. This command is unbound - by default.''' - pass + 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 start_kbd_macro(self, e): # (C-x () - '''Begin saving the characters typed into the current keyboard macro. ''' - pass + def redraw_current_line(self, e): # () + '''Refresh the current line. By default, this is unbound.''' + pass - def end_kbd_macro(self, e): # (C-x )) - '''Stop saving the characters typed into the current keyboard macro - and save the definition.''' - 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 call_last_kbd_macro(self, e): # (C-x e) - '''Re-execute the last keyboard macro defined, by making the - characters in the macro appear as if typed at the keyboard.''' - pass + 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 re_read_init_file(self, e): # (C-x C-r) - '''Read in the contents of the inputrc file, and incorporate any - bindings or variable assignments found there.''' - pass + 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 abort(self, e): # (C-g) - '''Abort the current editing command and ring the terminals bell - (subject to the setting of bell-style).''' - 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 do_uppercase_version(self, e): # (M-a, M-b, M-x, ...) - '''If the metafied character x is lowercase, run the command that is - bound to the corresponding uppercase character.''' - pass + 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 prefix_meta(self, e): # (ESC) - '''Metafy the next character typed. This is for keyboards without a - meta key. Typing ESC f is equivalent to typing M-f. ''' - self.next_meta = True + 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' - 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) + scroll = c.write_scrolling("%s`%s': %s" % (prompt, query, line)) + self._update_prompt_pos(scroll) + self._clear_after() - def revert_line(self, e): # (M-r) - '''Undo all changes made to this line. This is like executing the - undo command enough times to get back to the beginning.''' - pass + 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 - def tilde_expand(self, e): # (M-~) - '''Perform tilde expansion on the current word.''' - pass + 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] - 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.''' - pass + px, py = self.prompt_begin_pos + c.pos(0, py) + self._set_line(line) + self._print_prompt() - def exchange_point_and_mark(self, e): # (C-x C-x) - '''Swap the point with the mark. The current cursor position is set - to the saved position, and the old cursor position is saved as the - mark.''' - pass + 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 character_search(self, e): # (C-]) - '''A character is read and point is moved to the next occurrence of - that character. A negative count searches for previous occurrences.''' - pass + 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 character_search_backward(self, e): # (M-C-]) - '''A character is read and point is moved to the previous occurrence - of that character. A negative count searches for subsequent - occurrences.''' - pass + 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() - def insert_comment(self, e): # (M-#) - '''Without a numeric argument, the value of the comment-begin - variable is inserted at the beginning of the current line. If a - numeric argument is supplied, this command acts as a toggle: if the - characters at the beginning of the line do not match the value of - comment-begin, the value is inserted, otherwise the characters in - comment-begin are deleted from the beginning of the line. In either - case, the line is accepted as if a newline had been typed.''' - pass + 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() - def dump_functions(self, e): # () - '''Print all of the functions and their key bindings to the Readline - output stream. If a numeric argument is supplied, the output is - formatted in such a way that it can be made part of an inputrc - file. This command is unbound by default.''' - pass + 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 dump_variables(self, e): # () - '''Print all of the settable variables and their values to the - Readline output stream. If a numeric argument is supplied, the - output is formatted in such a way that it can be made part of an - inputrc file. This command is unbound by default.''' - pass - def dump_macros(self, e): # () - '''Print all of the Readline key sequences bound to macros and the - strings they output. If a numeric argument is supplied, the output - is formatted in such a way that it can be made part of an inputrc - file. This command is unbound by default.''' - pass + def 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 _bind_key(self, key, func): - '''setup the mapping from key to call the function.''' - keyinfo = key_text_to_keyinfo(key) - self.key_dispatch[keyinfo] = func - - def emacs_editing_mode(self, e): # (C-e) - '''When in vi command mode, this causes a switch to emacs editing - mode.''' - # make ' ' to ~ self insert - for c in range(ord(' '), 127): - self._bind_key('"%s"' % chr(c), self.self_insert) - # I often accidentally hold the shift or control while typing space - self._bind_key('Shift-space', self.self_insert) - self._bind_key('Control-space', self.self_insert) - self._bind_key('Return', self.accept_line) - self._bind_key('Left', self.backward_char) - self._bind_key('Control-b', self.backward_char) - self._bind_key('Right', self.forward_char) - self._bind_key('Control-f', self.forward_char) - self._bind_key('BackSpace', self.backward_delete_char) - self._bind_key('Home', self.beginning_of_line) - self._bind_key('End', self.end_of_line) - self._bind_key('Delete', self.delete_char) - self._bind_key('Control-d', self.delete_char) - self._bind_key('Clear', self.clear_screen) - self._bind_key('Alt-f', self.forward_word) - self._bind_key('Alt-b', self.backward_word) - self._bind_key('Control-l', self.clear_screen) - self._bind_key('Control-p', self.previous_history) - self._bind_key('Up', self.history_search_backward) - self._bind_key('Control-n', self.next_history) - self._bind_key('Down', self.history_search_forward) - self._bind_key('Control-a', self.beginning_of_line) - self._bind_key('Control-e', self.end_of_line) - self._bind_key('Alt-<', self.beginning_of_history) - self._bind_key('Alt->', self.end_of_history) - self._bind_key('Control-r', self.reverse_search_history) - self._bind_key('Control-s', self.forward_search_history) - self._bind_key('Alt-p', self.non_incremental_reverse_search_history) - self._bind_key('Alt-n', self.non_incremental_forward_search_history) - self._bind_key('Control-z', self.undo) - self._bind_key('Control-_', self.undo) - self._bind_key('Escape', self.prefix_meta) - self._bind_key('Meta-d', self.kill_word) - self._bind_key('Meta-Delete', self.backward_kill_word) - self._bind_key('Control-w', self.unix_word_rubout) - self._bind_key('Control-v', self.quoted_insert) - - # Add keybindings for numpad - # first the number keys - self._bind_key('NUMPAD0', self.self_insert) - self._bind_key('NUMPAD1', self.self_insert) - self._bind_key('NUMPAD2', self.self_insert) - self._bind_key('NUMPAD3', self.self_insert) - self._bind_key('NUMPAD4', self.self_insert) - self._bind_key('NUMPAD5', self.self_insert) - self._bind_key('NUMPAD6', self.self_insert) - self._bind_key('NUMPAD7', self.self_insert) - self._bind_key('NUMPAD8', self.self_insert) - self._bind_key('NUMPAD9', self.self_insert) - # then the others: / * - + - self._bind_key('Divide', self.self_insert) - self._bind_key('Multiply', self.self_insert) - self._bind_key('Add', self.self_insert) - self._bind_key('Subtract', self.self_insert) - # the decimal separator: '.' on US keyboards, ',' on DE one's - self._bind_key('VK_DECIMAL', self.self_insert) - - - def vi_editing_mode(self, e): # (M-C-j) - '''When in emacs editing mode, this causes a switch to vi editing - mode.''' - pass + 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) + c.bell() + + 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) + + 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) + + def yank_nth_arg(self, e): # (M-C-y) + '''Insert the first argument to the previous command (usually the + second word on the previous line) at point. With an argument n, + insert the nth word from the previous command (the words in the + previous command begin with word 0). A negative argument inserts the + nth word from the end of the previous command.''' + pass + + def yank_last_arg(self, e): # (M-. or M-_) + '''Insert last argument to the previous command (the last word of + the previous history entry). With an argument, behave exactly like + yank-nth-arg. Successive calls to yank-last-arg move back through + the history list, inserting the last argument of each line in turn.''' + pass + + def delete_char(self, e): # (C-d) + '''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 len(self.line_buffer) == 0: + if self.previous_func != self.delete_char: + raise EOFError + self._bell() + if self.line_cursor < len(self.line_buffer): + del self.line_buffer[self.line_cursor] + else: + self._bell() + + 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 + + def forward_backward_delete_char(self, e): # () + '''Delete the character under the cursor, unless the cursor is at + the end of the line, in which case the character behind the cursor + is deleted. By default, this is not bound to a key.''' + pass + + def quoted_insert(self, e): # (C-q or C-v) + '''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 + + def tab_insert(self, e): # (M-TAB) + '''Insert a tab character. ''' + ws = ' ' * (self.tabstop - (self.line_cursor%self.tabstop)) + self.insert_text(ws) + + def self_insert(self, e): # (a, b, A, 1, !, ...) + '''Insert yourself. ''' + self.line_buffer.insert(self.line_cursor, e.char) + self.line_cursor += 1 + + 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 + + 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 + + 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 + + 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 + + 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 + + def overwrite_mode(self, e): # () + '''Toggle overwrite mode. With an explicit positive numeric + argument, switches to overwrite mode. With an explicit non-positive + numeric argument, switches to insert mode. This command affects only + emacs mode; vi mode does overwrite differently. Each call to + readline() starts in insert mode. In overwrite mode, characters + bound to self-insert replace the text at point rather than pushing + 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. ''' + self.line_buffer[self.line_cursor:] = [] + + 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 + + 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 + + def kill_whole_line(self, e): # () + '''Kill all characters on the current line, no matter where point + is. By default, this is unbound.''' + pass + + 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 + + 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] = [] + + 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] = [] + + def delete_horizontal_space(self, e): # () + '''Delete all spaces and tabs around point. By default, this is unbound. ''' + pass + + def kill_region(self, e): # () + '''Kill the text in the current region. By default, this command is unbound. ''' + pass + + def copy_region_as_kill(self, e): # () + '''Copy the text in the region to the kill buffer, so it can be + yanked right away. By default, this command is unbound.''' + pass + + def copy_backward_word(self, e): # () + '''Copy the word before point to the kill buffer. The word + boundaries are the same as backward-word. By default, this command + is unbound.''' + pass + + def copy_forward_word(self, e): # () + '''Copy the word following point to the kill buffer. The word + boundaries are the same as forward-word. By default, this command is + unbound.''' + pass + + def yank(self, e): # (C-y) + '''Yank the top of the kill ring into the buffer at point. ''' + pass + + def yank_pop(self, e): # (M-y) + '''Rotate the kill-ring, and yank the new top. You can only do this + if the prior command is yank or yank-pop.''' + pass + + + def digit_argument(self, e): # (M-0, M-1, ... M--) + '''Add this digit to the argument already accumulating, or start a + new argument. M-- starts a negative argument.''' + pass + + def universal_argument(self, e): # () + '''This is another way to specify an argument. If this command is + followed by one or more digits, optionally with a leading minus + sign, those digits define the argument. If the command is followed + by digits, executing universal-argument again ends the numeric + argument, but is otherwise ignored. As a special case, if this + command is immediately followed by a character that is neither a + digit or minus sign, the argument count for the next command is + multiplied by four. The argument count is initially one, so + executing this function the first time makes the argument count + four, a second time makes the argument count sixteen, and so on. By + default, this is not bound to a key.''' + pass + + + def _get_completions(self): + '''Return a list of possible completions for the string ending at the point. + + Also set begidx and endidx in the process.''' + completions = [] + self.begidx = self.line_cursor + self.endidx = self.line_cursor + 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: + self.begidx += 1 + break + text = ''.join(self.line_buffer[self.begidx:self.endidx]) + log('complete text="%s"' % text) + i = 0 + while 1: + try: + r = self.completer(text, i) + except: + break + i += 1 + if r and r not in completions: + completions.append(r) + else: + break + log('text completions=%s' % completions) + if not completions: + # get the filename to complete + while self.begidx > 0: + self.begidx -= 1 + if self.line_buffer[self.begidx] in ' \t\n': + self.begidx += 1 + break + text = ''.join(self.line_buffer[self.begidx:self.endidx]) + log('file complete text="%s"' % text) + completions = glob(os.path.expanduser(text) + '*') + if self.mark_directories == 'on': + mc = [] + for f in completions: + if os.path.isdir(f): + mc.append(f + os.sep) + else: + mc.append(f) + completions = mc + log('fnames=%s' % completions) + return completions + + def _display_completions(self, completions): + if not completions: + return + self.console.write('\n') + wmax = max(map(len, completions)) + w, h = self.console.size() + cols = max(1, int((w-1) / (wmax+1))) + rows = int(math.ceil(float(len(completions)) / cols)) + for row in range(rows): + s = '' + for col in range(cols): + i = col*rows + row + if i < len(completions): + self.console.write(completions[i].ljust(wmax+1)) + self.console.write('\n') + self._print_prompt() + + def complete(self, e): # (TAB) + '''Attempt to perform completion on the text before point. The + actual completion performed is application-specific. The default is + filename completion.''' + completions = self._get_completions() + 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) + if len(completions) > 1: + if self.show_all_if_ambiguous == 'on': + self._display_completions(completions) + else: + self._bell() + else: + self._bell() + + def possible_completions(self, e): # (M-?) + '''List the possible completions of the text before point. ''' + completions = self._get_completions() + self._display_completions(completions) + + def insert_completions(self, e): # (M-*) + '''Insert all completions of the text before point that would have + been generated by possible-completions.''' + completions = self._get_completions() + b = self.begidx + e = self.endidx + for comp in completions: + rep = [ c for c in comp ] + rep.append(' ') + self.line_buffer[b:e] = rep + b += len(rep) + e = b + self.line_cursor = b + + def menu_complete(self, e): # () + '''Similar to complete, but replaces the word to be completed with a + single match from the list of possible completions. Repeated + execution of menu-complete steps through the list of possible + completions, inserting each match in turn. At the end of the list of + completions, the bell is rung (subject to the setting of bell-style) + and the original text is restored. An argument of n moves n + positions forward in the list of matches; a negative argument may be + used to move backward through the list. This command is intended to + be bound to TAB, but is unbound by default.''' + pass + + def delete_char_or_list(self, e): # () + '''Deletes the character under the cursor if not at the beginning or + end of the line (like delete-char). If at the end of the line, + behaves identically to possible-completions. This command is unbound + by default.''' + pass + + def start_kbd_macro(self, e): # (C-x () + '''Begin saving the characters typed into the current keyboard macro. ''' + pass + + def end_kbd_macro(self, e): # (C-x )) + '''Stop saving the characters typed into the current keyboard macro + and save the definition.''' + pass + + def call_last_kbd_macro(self, e): # (C-x e) + '''Re-execute the last keyboard macro defined, by making the + characters in the macro appear as if typed at the keyboard.''' + pass + + def re_read_init_file(self, e): # (C-x C-r) + '''Read in the contents of the inputrc file, and incorporate any + bindings or variable assignments found there.''' + pass + + def abort(self, e): # (C-g) + '''Abort the current editing command and ring the terminals bell + (subject to the setting of bell-style).''' + self._bell() + + def do_uppercase_version(self, e): # (M-a, M-b, M-x, ...) + '''If the metafied character x is lowercase, run the command that is + bound to the corresponding uppercase character.''' + pass + + def prefix_meta(self, e): # (ESC) + '''Metafy the next character typed. This is for keyboards without a + meta key. Typing ESC f is equivalent to typing M-f. ''' + self.next_meta = True + + 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) + + def revert_line(self, e): # (M-r) + '''Undo all changes made to this line. This is like executing the + undo command enough times to get back to the beginning.''' + pass + + def tilde_expand(self, e): # (M-~) + '''Perform tilde expansion on the current word.''' + pass + + 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.''' + pass + + def exchange_point_and_mark(self, e): # (C-x C-x) + '''Swap the point with the mark. The current cursor position is set + to the saved position, and the old cursor position is saved as the + mark.''' + pass + + def character_search(self, e): # (C-]) + '''A character is read and point is moved to the next occurrence of + that character. A negative count searches for previous occurrences.''' + pass + + def character_search_backward(self, e): # (M-C-]) + '''A character is read and point is moved to the previous occurrence + of that character. A negative count searches for subsequent + occurrences.''' + pass + + def insert_comment(self, e): # (M-#) + '''Without a numeric argument, the value of the comment-begin + variable is inserted at the beginning of the current line. If a + numeric argument is supplied, this command acts as a toggle: if the + characters at the beginning of the line do not match the value of + comment-begin, the value is inserted, otherwise the characters in + comment-begin are deleted from the beginning of the line. In either + case, the line is accepted as if a newline had been typed.''' + pass + + def dump_functions(self, e): # () + '''Print all of the functions and their key bindings to the Readline + output stream. If a numeric argument is supplied, the output is + formatted in such a way that it can be made part of an inputrc + file. This command is unbound by default.''' + pass + + def dump_variables(self, e): # () + '''Print all of the settable variables and their values to the + Readline output stream. If a numeric argument is supplied, the + output is formatted in such a way that it can be made part of an + inputrc file. This command is unbound by default.''' + pass + + def dump_macros(self, e): # () + '''Print all of the Readline key sequences bound to macros and the + strings they output. If a numeric argument is supplied, the output + is formatted in such a way that it can be made part of an inputrc + file. This command is unbound by default.''' + pass + + def _bind_key(self, key, func): + '''setup the mapping from key to call the function.''' + keyinfo = key_text_to_keyinfo(key) + self.key_dispatch[keyinfo] = func + + def emacs_editing_mode(self, e): # (C-e) + '''When in vi command mode, this causes a switch to emacs editing + mode.''' + # make ' ' to ~ self insert + for c in range(ord(' '), 127): + self._bind_key('"%s"' % chr(c), self.self_insert) + # I often accidentally hold the shift or control while typing space + self._bind_key('Shift-space', self.self_insert) + self._bind_key('Control-space', self.self_insert) + self._bind_key('Return', self.accept_line) + self._bind_key('Left', self.backward_char) + self._bind_key('Control-b', self.backward_char) + self._bind_key('Right', self.forward_char) + self._bind_key('Control-f', self.forward_char) + self._bind_key('BackSpace', self.backward_delete_char) + self._bind_key('Home', self.beginning_of_line) + self._bind_key('End', self.end_of_line) + self._bind_key('Delete', self.delete_char) + self._bind_key('Control-d', self.delete_char) + self._bind_key('Clear', self.clear_screen) + self._bind_key('Alt-f', self.forward_word) + self._bind_key('Alt-b', self.backward_word) + self._bind_key('Control-l', self.clear_screen) + self._bind_key('Control-p', self.previous_history) + self._bind_key('Up', self.history_search_backward) + self._bind_key('Control-n', self.next_history) + self._bind_key('Down', self.history_search_forward) + self._bind_key('Control-a', self.beginning_of_line) + self._bind_key('Control-e', self.end_of_line) + self._bind_key('Alt-<', self.beginning_of_history) + self._bind_key('Alt->', self.end_of_history) + self._bind_key('Control-r', self.reverse_search_history) + self._bind_key('Control-s', self.forward_search_history) + self._bind_key('Alt-p', self.non_incremental_reverse_search_history) + self._bind_key('Alt-n', self.non_incremental_forward_search_history) + self._bind_key('Control-z', self.undo) + self._bind_key('Control-_', self.undo) + self._bind_key('Escape', self.prefix_meta) + self._bind_key('Meta-d', self.kill_word) + self._bind_key('Meta-Delete', self.backward_kill_word) + self._bind_key('Control-w', self.unix_word_rubout) + self._bind_key('Control-v', self.quoted_insert) + + # Add keybindings for numpad + # first the number keys + self._bind_key('NUMPAD0', self.self_insert) + self._bind_key('NUMPAD1', self.self_insert) + self._bind_key('NUMPAD2', self.self_insert) + self._bind_key('NUMPAD3', self.self_insert) + self._bind_key('NUMPAD4', self.self_insert) + self._bind_key('NUMPAD5', self.self_insert) + self._bind_key('NUMPAD6', self.self_insert) + self._bind_key('NUMPAD7', self.self_insert) + self._bind_key('NUMPAD8', self.self_insert) + self._bind_key('NUMPAD9', self.self_insert) + # then the others: / * - + + self._bind_key('Divide', self.self_insert) + self._bind_key('Multiply', self.self_insert) + self._bind_key('Add', self.self_insert) + self._bind_key('Subtract', self.self_insert) + # the decimal separator: '.' on US keyboards, ',' on DE one's + self._bind_key('VK_DECIMAL', self.self_insert) + + + def vi_editing_mode(self, e): # (M-C-j) + '''When in emacs editing mode, this causes a switch to vi editing + mode.''' + pass def CTRL(c): - '''make a control character''' - assert '@' <= c <= '_' - return chr(ord(c) - ord('@')) - + '''make a control character''' + assert '@' <= c <= '_' + return chr(ord(c) - ord('@')) + # make it case insensitive def commonprefix(m): - "Given a list of pathnames, returns the longest common leading component" - if not m: return '' - prefix = m[0] - for item in m: - for i in range(len(prefix)): - if prefix[:i+1].lower() != item[:i+1].lower(): - prefix = prefix[:i] - if i == 0: return '' - break - return prefix + "Given a list of pathnames, returns the longest common leading component" + if not m: return '' + prefix = m[0] + for item in m: + for i in range(len(prefix)): + if prefix[:i+1].lower() != item[:i+1].lower(): + prefix = prefix[:i] + if i == 0: return '' + break + return prefix # create a Readline object to contain the state rl = Readline() def GetOutputFile(): - '''Return the console object used by readline so that it can be used for printing in color.''' - return rl.console + '''Return the console object used by readline so that it can be used for printing in color.''' + return rl.console # make these available so this looks like the python readline module parse_and_bind = rl.parse_and_bind @@ -1114,9 +1114,9 @@ get_completer_delims = rl.get_completer_delims add_history = rl.add_history if __name__ == '__main__': - res = [ rl.readline('In[%d] ' % i) for i in range(3) ] - print res + res = [ rl.readline('In[%d] ' % i) for i in range(3) ] + print res else: - #import wingdbstub - Console.install_readline(rl.readline) - + #import wingdbstub + Console.install_readline(rl.readline) + diff --git a/readline/keysyms.py b/readline/keysyms.py index 8712c84..c1de7d7 100644 --- a/readline/keysyms.py +++ b/readline/keysyms.py @@ -81,93 +81,93 @@ code2sym_map = {c32.VK_CANCEL: 'Cancel', c32.VK_ADD: 'Add', c32.VK_SUBTRACT: 'Subtract', c32.VK_DECIMAL: 'VK_DECIMAL' - } + } # function to handle the mapping def make_keysym(keycode): - try: - sym = code2sym_map[keycode] - except KeyError: - sym = '' - return sym + try: + sym = code2sym_map[keycode] + except KeyError: + sym = '' + return sym sym2code_map = {} for code,sym in code2sym_map.iteritems(): - sym2code_map[sym.lower()] = code - + sym2code_map[sym.lower()] = code + def key_text_to_keyinfo(keytext): - '''Convert a GNU readline style textual description of a key to keycode with modifiers''' - if keytext.startswith('"'): # " - return keyseq_to_keyinfo(keytext[1:-1]) - else: - return keyname_to_keyinfo(keytext) + '''Convert a GNU readline style textual description of a key to keycode with modifiers''' + if keytext.startswith('"'): # " + return keyseq_to_keyinfo(keytext[1:-1]) + else: + return keyname_to_keyinfo(keytext) VkKeyScan = windll.user32.VkKeyScanA def char_to_keyinfo(char, control=False, meta=False, shift=False): - vk = VkKeyScan(ord(char)) - if vk & 0xffff == 0xffff: - print 'VkKeyScan("%s") = %x' % (char, vk) - raise ValueError, 'bad key' - if vk & 0x100: - shift = True - if vk & 0x200: - control = True - if vk & 0x400: - meta = True - return (control, meta, shift, vk & 0xff) + vk = VkKeyScan(ord(char)) + if vk & 0xffff == 0xffff: + print 'VkKeyScan("%s") = %x' % (char, vk) + raise ValueError, 'bad key' + if vk & 0x100: + shift = True + if vk & 0x200: + control = True + if vk & 0x400: + meta = True + return (control, meta, shift, vk & 0xff) def keyname_to_keyinfo(keyname): - control = False - meta = False - shift = False + control = False + meta = False + shift = False + + while 1: + lkeyname = keyname.lower() + if lkeyname.startswith('control-'): + control = True + keyname = keyname[8:] + elif lkeyname.startswith('meta-'): + meta = True + keyname = keyname[5:] + elif lkeyname.startswith('alt-'): + meta = True + keyname = keyname[4:] + elif lkeyname.startswith('shift-'): + shift = True + keyname = keyname[6:] + else: + if len(keyname) > 1: + return (control, meta, shift, sym2code_map[keyname.lower()]) + else: + return char_to_keyinfo(keyname, control, meta, shift) - while 1: - lkeyname = keyname.lower() - if lkeyname.startswith('control-'): - control = True - keyname = keyname[8:] - elif lkeyname.startswith('meta-'): - meta = True - keyname = keyname[5:] - elif lkeyname.startswith('alt-'): - meta = True - keyname = keyname[4:] - elif lkeyname.startswith('shift-'): - shift = True - keyname = keyname[6:] - else: - if len(keyname) > 1: - return (control, meta, shift, sym2code_map[keyname.lower()]) - else: - return char_to_keyinfo(keyname, control, meta, shift) - def keyseq_to_keyinfo(keyseq): - res = [] - control = False - meta = False - shift = False + res = [] + control = False + meta = False + shift = False - while 1: - if keyseq.startswith('\\C-'): - control = True - keyseq = keyseq[3:] - elif keyseq.startswith('\\M-'): - meta = True - keyseq = keyseq[3:] - elif keyseq.startswith('\\e'): - res.append(char_to_keyinfo('\033', control, meta, shift)) - control = meta = shift = False - keyseq = keyseq[2:] - elif len(keyseq) >= 1: - res.append(char_to_keyinfo(keyseq[0], control, meta, shift)) - control = meta = shift = False - keyseq = keyseq[1:] - else: - return res[0] + while 1: + if keyseq.startswith('\\C-'): + control = True + keyseq = keyseq[3:] + elif keyseq.startswith('\\M-'): + meta = True + keyseq = keyseq[3:] + elif keyseq.startswith('\\e'): + res.append(char_to_keyinfo('\033', control, meta, shift)) + control = meta = shift = False + keyseq = keyseq[2:] + elif len(keyseq) >= 1: + res.append(char_to_keyinfo(keyseq[0], control, meta, shift)) + control = meta = shift = False + keyseq = keyseq[1:] + else: + return res[0] def make_keyinfo(keycode, state): - control = (state & (4+8)) != 0 - meta = (state & (1+2)) != 0 - shift = (state & 0x10) != 0 - return (control, meta, shift, keycode) + control = (state & (4+8)) != 0 + meta = (state & (1+2)) != 0 + shift = (state & 0x10) != 0 + return (control, meta, shift, keycode)