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.''' log("fwd_search_history") self._init_incremental_search(self._history.forward_search_history, e) self.finalize()
def get_current_history_length(self): '''Return the number of lines currently in the history. (This is different from get_history_length(), which returns the maximum number of lines that will be written to a history file.)''' value = len(self.history) log("get_current_history_length:%d" % value) return value
def _init_incremental_search(self, searchfun, init_event): """Initialize search prompt """ log("init_incremental_search") self.subsearch_query = '' self.subsearch_fun = searchfun self.subsearch_old_line = self.l_buffer.get_line_text() queue = self.process_keyevent_queue queue.append(self._process_incremental_search_keyevent) self.subsearch_oldprompt = self.prompt if (self.previous_func != self.reverse_search_history and self.previous_func != self.forward_search_history): self.subsearch_query = self.l_buffer[0:Point].get_line_text() if self.subsearch_fun == self.reverse_search_history: self.subsearch_prompt = "reverse-i-search%d`%s': " else: self.subsearch_prompt = "forward-i-search%d`%s': " self.prompt = self.subsearch_prompt % (self._history.history_cursor, "") if self.subsearch_query: self.line = self._process_incremental_search_keyevent(init_event) else: self.line = ""
def reverse_search_history(self, searchfor, startpos=None): if startpos is None: startpos = self.history_cursor origpos = startpos result = lineobj.ReadLineTextBuffer("") for idx, line in list(enumerate(self.history))[startpos:0:-1]: if searchfor in line: startpos = idx break # If we get a new search without change in search term it means # someone pushed ctrl-r and we should find the next match if self.last_search_for == searchfor and startpos > 0: startpos -= 1 for idx, line in list(enumerate(self.history))[startpos:0:-1]: if searchfor in line: startpos = idx break if self.history: result = self.history[startpos].get_line_text() else: result = "" self.history_cursor = startpos self.last_search_for = searchfor log("reverse_search_history: old:%d new:%d result:%r" % (origpos, self.history_cursor, result)) return result
def test_complete(self): import rlcompleter logger.sock_silent = False log("-" * 50) r = EmacsModeTest() completerobj = rlcompleter.Completer() def _nop(val, word): return word completerobj._callable_postfix = _nop r.completer = completerobj.complete r._bind_key("tab", r.complete) r.input('"exi(ksdjksjd)"') r.input('Control-a') r.input('Right') r.input('Right') r.input('Right') r.input('Tab') self.assert_line(r, "exit(ksdjksjd)", 4) r.input('Escape') r.input('"exi"') r.input('Control-a') r.input('Right') r.input('Right') r.input('Right') r.input('Tab') self.assert_line(r, "exit", 4)
def _process_non_incremental_search_keyevent(self, keyinfo): keytuple = keyinfo.tuple() log("SearchPromptMode %s %s" % (keyinfo, keytuple)) history = self._history if keyinfo.keyname == 'backspace': self.non_inc_query = self.non_inc_query[:-1] elif keyinfo.keyname in ['return', 'escape']: if self.non_inc_query: if self.non_inc_direction == -1: res = history.reverse_search_history(self.non_inc_query) else: res = history.forward_search_history(self.non_inc_query) self._bell() self.prompt = self.non_inc_oldprompt self.process_keyevent_queue = self.process_keyevent_queue[:-1] self._history.history_cursor = len(self._history.history) if keyinfo.keyname == 'escape': self.l_buffer = self.non_inc_oldline else: self.l_buffer.set_line(res) return False elif keyinfo.keyname: pass elif keyinfo.control == False and keyinfo.meta == False: self.non_inc_query += keyinfo.char else: pass self.prompt = self.non_inc_oldprompt + ":" + self.non_inc_query
def _readline_from_keyboard(self): c = self.console while True: self._update_line() event = c.getkeypress() if self.next_meta: self.next_meta = False control, meta, shift, code = event.keyinfo event.keyinfo = (control, True, shift, code) # Process exit keys. Only exit on empty line if event.keyinfo in self.exit_dispatch: if lineobj.EndOfLine(self.l_buffer) == 0: raise EOFError dispatch_func = self.key_dispatch.get( event.keyinfo, self.self_insert) log("readline from keyboard:%s" % (event.keyinfo,)) r = None if dispatch_func: r = dispatch_func(event) self.l_buffer.push_undo() self.previous_func = dispatch_func if r: self._update_line() break
def paste(self, e): """Paste windows clipboard. Assume single line strip other lines and end of line markers and trailing spaces""" # (Control-v) if self.enable_win32_clipboard: txt = clipboard.get_clipboard_text_and_convert(False) txt = txt.split("\n")[0].strip("\r").strip("\n") log("paste: >%s<" % list(map(ord, txt))) self.insert_text(txt) self.finalize()
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 _bind_key(self, key, func): """setup the mapping from key to call the function.""" if not callable(func): print("Trying to bind non method to keystroke:%s,%s" % (key, func)) raise ReadlineError( "Trying to bind non method to keystroke:%s,%s,%s,%s" % (key, func, type(func), type(self._bind_key))) keyinfo = make_KeyPress_from_keydescr(key.lower()).tuple() log(">>>%s -> %s<<<" % (keyinfo, func.__name__)) self.key_dispatch[keyinfo] = func
def __init__(self, newbuffer=0): '''Initialize the Console object. newbuffer=1 will allocate a new buffer so the old content will be restored on exit. ''' self.serial = 0 self.attr = System.Console.ForegroundColor self.saveattr = winattr[str(System.Console.ForegroundColor).lower()] self.savebg = System.Console.BackgroundColor log('initial attr=%s' % self.attr)
def write_color(self, text, attr=None): text = ensure_unicode(text) n, res = self.ansiwriter.write_color(text, attr) junk = DWORD(0) for attr, chunk in res: log("console.attr:%s" % (attr)) log("console.chunk:%s" % (chunk)) self.SetConsoleTextAttribute(self.hout, attr.winattr) for short_chunk in split_block(chunk): self.WriteConsoleW(self.hout, short_chunk, len(short_chunk), byref(junk), None) return n
def write_plain(self, text, attr=None): '''write text at current cursor position.''' text = ensure_unicode(text) log('write("%s", %s)' % (text, attr)) if attr is None: attr = self.attr junk = DWORD(0) self.SetConsoleTextAttribute(self.hout, attr) for short_chunk in split_block(chunk): self.WriteConsoleW(self.hout, ensure_unicode(short_chunk), len(short_chunk), byref(junk), None) return len(text)
def input(self, keytext): if keytext[0:1] == '"' and keytext[-1:] == '"': lst_key = ['"%s"' % c for c in keytext[1:-1]] else: lst_key = [keytext] for key in lst_key: keyinfo, event = keytext_to_keyinfo_and_event(key) dispatch_func = self.key_dispatch.get(keyinfo.tuple(), self.self_insert) self.tested_commands[dispatch_func.__name__] = dispatch_func log("keydisp: %s %s" % (key, dispatch_func.__name__)) dispatch_func(event) self.previous_func = dispatch_func
def getkeypress(self): '''Return next key press event from the queue, ignoring others.''' ck = System.ConsoleKey while True: e = System.Console.ReadKey(True) if e.Key == System.ConsoleKey.PageDown: # PageDown self.scroll_window(12) elif e.Key == System.ConsoleKey.PageUp: # PageUp self.scroll_window(-12) elif str(e.KeyChar) == "\000": # Drop deadkeys log("Deadkey: %s" % e) return event(self, e) else: return event(self, e)
def __init__(self, newbuffer=0): '''Initialize the Console object. newbuffer=1 will allocate a new buffer so the old content will be restored on exit. ''' # Do I need the following line? It causes a console to be created whenever # readline is imported into a pythonw application which seems wrong. Things # seem to work without it... # self.AllocConsole() if newbuffer: self.hout = self.CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, 0, None, 1, None) self.SetConsoleActiveScreenBuffer(self.hout) else: self.hout = self.GetStdHandle(STD_OUTPUT_HANDLE) self.hin = self.GetStdHandle(STD_INPUT_HANDLE) self.inmode = DWORD(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 self.saveattr = info.wAttributes # remember the initial colors self.defaultstate = AnsiState() self.defaultstate.winattr = info.wAttributes self.ansiwriter = AnsiWriter(self.defaultstate) background = self.attr & 0xf0 for escape in self.escape_to_color: if self.escape_to_color[escape] is not None: self.escape_to_color[escape] |= background log('initial attr=%x' % self.attr) self.softspace = 0 # this is for using it as a file-like object self.serial = 0 self.pythondll = ctypes.pythonapi self.inputHookPtr = c_void_p.from_address( addressof(self.pythondll.PyOS_InputHook)).value if sys.version_info[:2] > (3, 4): self.pythondll.PyMem_RawMalloc.restype = c_size_t self.pythondll.PyMem_Malloc.argtypes = [c_size_t] setattr(Console, 'PyMem_Malloc', self.pythondll.PyMem_RawMalloc) else: self.pythondll.PyMem_Malloc.restype = c_size_t self.pythondll.PyMem_Malloc.argtypes = [c_size_t] setattr(Console, 'PyMem_Malloc', self.pythondll.PyMem_Malloc)
def paste_mulitline_code(self, e): '''Paste windows clipboard''' reg = re.compile("\r?\n") if self.enable_win32_clipboard: txt = clipboard.get_clipboard_text_and_convert(False) t = reg.split(txt) t = [row for row in t if row.strip() != ""] # remove empty lines if t != [""]: self.insert_text(t[0]) self.add_history(self.l_buffer.copy()) self.paste_line_buffer = t[1:] log("multi: %s" % self.paste_line_buffer) return True else: return False
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 = str(input.KeyChar) self.keycode = input.Key self.state = input.Modifiers log("%s,%s,%s" % (input.Modifiers, input.Key, input.KeyChar)) self.type = "KeyRelease" self.keysym = make_keysym(self.keycode) self.keyinfo = make_KeyPress(self.char, self.state, self.keycode)
def getkeypress(self): '''Return next key press event from the queue, ignoring others.''' while True: e = self.get() if e.type == 'KeyPress' and e.keycode not in key_modifiers: log("console.getkeypress %s" % e) if e.keyinfo.keyname == 'next': self.scroll_window(12) elif e.keyinfo.keyname == 'prior': self.scroll_window(-12) else: return e elif ((e.type == 'KeyRelease') and (e.keyinfo == KeyPress('S', False, True, False, 'S'))): log("getKeypress:%s,%s,%s" % (e.keyinfo, e.keycode, e.type)) return e
def _process_incremental_search_keyevent(self, keyinfo): log("_process_incremental_search_keyevent") keytuple = keyinfo.tuple() #dispatch_func = self.key_dispatch.get(keytuple, default) revtuples = [] fwdtuples = [] for ktuple, func in self.key_dispatch.items(): if func == self.reverse_search_history: revtuples.append(ktuple) elif func == self.forward_search_history: fwdtuples.append(ktuple) log("IncrementalSearchPromptMode %s %s" % (keyinfo, keytuple)) if keyinfo.keyname == 'backspace': self.subsearch_query = self.subsearch_query[:-1] if len(self.subsearch_query) > 0: self.line = self.subsearch_fun(self.subsearch_query) else: self._bell() self.line = "" # empty query means no search result elif keyinfo.keyname in ['return', 'escape']: self._bell() self.prompt = self.subsearch_oldprompt self.process_keyevent_queue = self.process_keyevent_queue[:-1] self._history.history_cursor = len(self._history.history) if keyinfo.keyname == 'escape': self.l_buffer.set_line(self.subsearch_old_line) return True elif keyinfo.keyname: pass elif keytuple in revtuples: self.subsearch_fun = self._history.reverse_search_history self.subsearch_prompt = "reverse-i-search%d`%s': " self.line = self.subsearch_fun(self.subsearch_query) elif keytuple in fwdtuples: self.subsearch_fun = self._history.forward_search_history self.subsearch_prompt = "forward-i-search%d`%s': " self.line = self.subsearch_fun(self.subsearch_query) elif keyinfo.control == False and keyinfo.meta == False: self.subsearch_query += keyinfo.char self.line = self.subsearch_fun(self.subsearch_query) else: pass self.prompt = self.subsearch_prompt % (self._history.history_cursor, self.subsearch_query) self.l_buffer.set_line(self.line)
def _init_digit_argument(self, keyinfo): """Initialize search prompt """ c = self.console line = self.l_buffer.get_line_text() self._digit_argument_oldprompt = self.prompt queue = self.process_keyevent_queue queue = self.process_keyevent_queue queue.append(self._process_digit_argument_keyevent) if keyinfo.char == "-": self.argument = -1 elif keyinfo.char in "0123456789": self.argument = int(keyinfo.char) log("<%s> %s" % (self.argument, type(self.argument))) self.prompt = "(arg: %s) " % self.argument log("arg-init %s %s" % (self.argument, keyinfo.char))
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.l_buffer.point self.endidx = self.l_buffer.point buf = self.l_buffer.line_buffer if self.completer: # get the string to complete while self.begidx > 0: self.begidx -= 1 if buf[self.begidx] in self.completer_delims: self.begidx += 1 break text = ensure_str(''.join(buf[self.begidx:self.endidx])) log('complete text="%s"' % ensure_unicode(text)) i = 0 while True: try: r = self.completer(ensure_unicode(text), i) except IndexError: break i += 1 if r is None: break elif r and r not in completions: completions.append(r) else: pass log('text completions=<%s>' % list(map(ensure_unicode, completions))) if (self.complete_filesystem == "on") and not completions: # get the filename to complete while self.begidx > 0: self.begidx -= 1 if buf[self.begidx] in ' \t\n': self.begidx += 1 break text = ensure_str(''.join(buf[self.begidx:self.endidx])) log('file complete text="%s"' % ensure_unicode(text)) completions = list( map(ensure_unicode, glob.glob(os.path.expanduser(text) + '*'.encode('ascii')))) 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>' % list(map(ensure_unicode, completions))) return completions
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 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 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 BaseException: print('startup hook failed') traceback.print_exc() c = self.console self.l_buffer.reset_line() self.prompt = prompt self._print_prompt() if self.pre_input_hook: try: self.pre_input_hook() except BaseException: print('pre_input_hook failed') traceback.print_exc() self.pre_input_hook = None log("in readline: %s" % self.paste_line_buffer) if len(self.paste_line_buffer) > 0: self.l_buffer = lineobj.ReadlineTextBuffer( self.paste_line_buffer[0]) self._update_line() self.paste_line_buffer = self.paste_line_buffer[1:] c.write('\r\n') else: self._readline_from_keyboard() c.write('\r\n') self.add_history(self.l_buffer.copy()) log('returning(%s)' % self.l_buffer.get_line_text()) return self.l_buffer.get_line_text() + '\n'
def write_color(self, text, attr=None): '''write text at current cursor position and interpret color escapes. return the number of characters written. ''' log('write_color("%s", %s)' % (text, attr)) chunks = self.terminal_escape.split(text) log('chunks=%s' % repr(chunks)) bg = self.savebg n = 0 # count the characters we actually write, omitting the escapes if attr is None: # use attribute from initial console attr = self.attr try: fg = self.trtable[(0x000f & attr)] bg = self.trtable[(0x00f0 & attr) >> 4] except TypeError: fg = attr for chunk in chunks: m = self.escape_parts.match(chunk) if m: log(m.group(1)) attr = ansicolor.get(m.group(1), self.attr) n += len(chunk) System.Console.ForegroundColor = fg System.Console.BackgroundColor = bg System.Console.Write(chunk) return n
def process_keyevent(self, keyinfo): def nop(e): pass keytuple = keyinfo.tuple() # Process exit keys. Only exit on empty line if keytuple in self.exit_dispatch: if lineobj.EndOfLine(self.l_buffer) == 0: raise EOFError dispatch_func = self.key_dispatch.get(keytuple, self.vi_key) log("readline from keyboard:%s->%s" % (keytuple, dispatch_func)) r = None if dispatch_func: r = dispatch_func(keyinfo) self.l_buffer.push_undo() self.previous_func = dispatch_func if r: self._update_line() return True return False
def debug_output(on, filename="pyreadline_debug_log.txt"): # Not implemented yet if on in ["on", "on_nologfile"]: self.debug = True if on == "on": logger.start_file_log(filename) logger.start_socket_log() logger.log("STARTING LOG") elif on == "on_nologfile": logger.start_socket_log() logger.log("STARTING LOG") else: logger.log("STOPING LOG") logger.stop_file_log() logger.stop_socket_log()
def _process_digit_argument_keyevent(self, keyinfo): log("DigitArgumentMode.keyinfo %s" % keyinfo) keytuple = keyinfo.tuple() log("DigitArgumentMode.keytuple %s %s" % (keyinfo, keytuple)) if keyinfo.keyname in ['return']: self.prompt = self._digit_argument_oldprompt self.process_keyevent_queue = self.process_keyevent_queue[:-1] return True elif keyinfo.keyname: pass elif (keyinfo.char in "0123456789" and keyinfo.control == False and keyinfo.meta == False): log("arg %s %s" % (self.argument, keyinfo.char)) self.argument = self.argument * 10 + int(keyinfo.char) else: self.prompt = self._digit_argument_oldprompt raise LeaveModeTryNext self.prompt = "(arg: %s) " % self.argument
def _process_keyevent(self, keyinfo): """return True when line is final """ # Process exit keys. Only exit on empty line log("_process_keyevent <%s>" % keyinfo) def nop(e): pass if self.next_meta: self.next_meta = False keyinfo.meta = True keytuple = keyinfo.tuple() if self._insert_verbatim: self.insert_text(keyinfo) self._insert_verbatim = False self.argument = 0 return False if keytuple in self.exit_dispatch: pars = (self.l_buffer, lineobj.EndOfLine(self.l_buffer)) log("exit_dispatch:<%s, %s>" % pars) if lineobj.EndOfLine(self.l_buffer) == 0: raise EOFError if keyinfo.keyname or keyinfo.control or keyinfo.meta: default = nop else: default = self.self_insert dispatch_func = self.key_dispatch.get(keytuple, default) log("readline from keyboard:<%s,%s>" % (keytuple, dispatch_func)) r = None if dispatch_func: r = dispatch_func(keyinfo) self._keylog(dispatch_func, self.l_buffer) self.l_buffer.push_undo() self.previous_func = dispatch_func return r
def write(self, text): log('write("%s")' % text) return self.write_color(text)