# -*- coding: utf-8 -*- #***************************************************************************** # Copyright (C) 2006 Jorgen Stenarson. # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. #***************************************************************************** import re,operator,string,sys,os #import wordmatcher #import pyreadline.clipboard as clipboard if "pyreadline" in sys.modules: pyreadline= sys.modules["pyreadline"] else: import pyreadline import lineobj import exceptions class EscapeHistory(exceptions.Exception): pass from pyreadline.logger import log_sock class LineHistory(object): def __init__(self): self.history=[] self._history_length=100 self._history_cursor=0 self.history_filename=os.path.expanduser('~/.history') self.lastcommand=None self.query="" def get_history_length(self): value=self._history_length log_sock("get_history_length:%d"%value,"history") return value def set_history_length(self,value): log_sock("set_history_length: old:%d new:%d"%(self._history_length,value),"history") self._history_length=value def get_history_cursor(self): value=self._history_cursor log_sock("get_history_cursor:%d"%value,"history") return value def set_history_cursor(self,value): log_sock("set_history_cursor: old:%d new:%d"%(self._history_cursor,value),"history") self._history_cursor=value history_length=property(get_history_length,set_history_length) history_cursor=property(get_history_cursor,set_history_cursor) def clear_history(self): ''''Clear readline's history''' self.history = [] self.history_cursor = 0 def read_history_file(self, filename=None): '''Load a readline history file.''' if filename is None: filename=self.history_filename try: for line in open(filename, 'r'): self.add_history(lineobj.ReadLineTextBuffer(line.rstrip())) except IOError: self.history = [] self.history_cursor = 0 def write_history_file(self, filename=None): '''Save a readline history file.''' if filename is None: filename=self.history_filename fp = open(filename, 'wb') for line in self.history[-self.history_length:]: fp.write(line.get_line_text()) fp.write('\n') fp.close() def add_history(self, line): '''Append a line to the history buffer, as if it was the last line typed.''' if not line.get_line_text(): pass elif len(self.history) > 0 and self.history[-1].get_line_text() == line.get_line_text(): pass else: self.history.append(line) self.history_cursor = len(self.history) def previous_history(self,current): # (C-p) '''Move back through the history list, fetching the previous command. ''' if self.history_cursor==len(self.history): self.history.append(current.copy()) #do not use add_history since we do not want to increment cursor if self.history_cursor > 0: self.history_cursor -= 1 current.set_line(self.history[self.history_cursor].get_line_text()) current.point=lineobj.EndOfLine def next_history(self,current): # (C-n) '''Move forward through the history list, fetching the next command. ''' if self.history_cursor < len(self.history)-1: self.history_cursor += 1 current.set_line(self.history[self.history_cursor].get_line_text()) def beginning_of_history(self): # (M-<) '''Move to the first line in the history.''' self.history_cursor = 0 if len(self.history) > 0: self.l_buffer = self.history[0] def end_of_history(self,current): # (M->) '''Move to the end of the input history, i.e., the line currently being entered.''' self.history_cursor=len(self.history) current.set_line(self.history[-1].get_line_text()) def reverse_search_history(self,searchfor,startpos=None): if startpos is None: startpos=self.history_cursor res=[(idx,line) for idx,line in enumerate(self.history[startpos:0:-1]) if line.startswith(searchfor)] if res: self.history_cursor-=res[0][0] return res[0][1].get_line_text() return "" def forward_search_history(self,searchfor,startpos=None): if startpos is None: startpos=self.history_cursor res=[(idx,line) for idx,line in enumerate(self.history[startpos:]) if line.startswith(searchfor)] if res: self.history_cursor+=res[0][0] return res[0][1].get_line_text() return "" def _non_i_search(self, direction, current): c = pyreadline.rl.console line = current.get_line_text() query = '' while 1: c.pos(*pyreadline.rl.prompt_end_pos) scroll = c.write_scrolling(":%s" % query) pyreadline.rl._update_prompt_pos(scroll) pyreadline.rl._clear_after() event = c.getkeypress() log_sock(str(event),"history") if event.keyinfo.keyname == '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.keyinfo.keyname == 'return': break else: pyreadline.rl._bell() log_sock(query,"history") res="" if query: if direction==-1: res=self.reverse_search_history(query) else: res=self.forward_search_history(query) log_sock(res,"history") return lineobj.ReadLineTextBuffer(res,point=0) def non_incremental_reverse_search_history(self,current): # (M-p) '''Search backward starting at the current line and moving up through the history as necessary using a non-incremental search for a string supplied by the user.''' return self._non_i_search(-1,current) def non_incremental_forward_search_history(self,current): # (M-n) '''Search forward starting at the current line and moving down through the the history as necessary using a non-incremental search for a string supplied by the user.''' return self._non_i_search(1,current) def _search(self, direction, partial): try: if (self.lastcommand != self.history_search_forward and self.lastcommand != self.history_search_backward): self.query = ''.join(partial[0:partial.point].get_line_text()) hcstart=max(self.history_cursor,0) log_sock("hcstart %s"%hcstart,"history") 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.history_cursor = hc result=lineobj.ReadLineTextBuffer(h,point=len(h.get_line_text())) return result elif h.get_line_text().startswith(self.query) and h != partial.get_line_text(): self.history_cursor = hc result=lineobj.ReadLineTextBuffer(h,point=partial.point) return result hc += direction else: if len(self.history)==0: pass elif hc>=len(self.history) and not self.query: self.history_cursor=len(self.history) return lineobj.ReadLineTextBuffer("",point=0) elif self.history[max(min(hcstart,len(self.history)-1),0)].get_line_text().startswith(self.query) and self.query: return lineobj.ReadLineTextBuffer(self.history[max(min(hcstart,len(self.history)-1),0)],point=partial.point) else: return lineobj.ReadLineTextBuffer(partial,point=partial.point) return lineobj.ReadLineTextBuffer(self.query,point=min(len(self.query),partial.point)) except IndexError: log_sock("hcstart:%s %s"%(hcstart,len(self.history)),"history") raise def history_search_forward(self,partial): # () '''Search forward through the history for the string of characters between the start of the current line and the point. This is a non-incremental search. By default, this command is unbound.''' q= self._search(1,partial) return q def history_search_backward(self,partial): # () '''Search backward through the history for the string of characters between the start of the current line and the point. This is a non-incremental search. By default, this command is unbound.''' q= self._search(-1,partial) return q if __name__=="__main__": q=LineHistory() RL=lineobj.ReadLineTextBuffer q.add_history(RL("aaaa")) q.add_history(RL("aaba")) q.add_history(RL("aaca")) q.add_history(RL("akca")) q.add_history(RL("bbb")) q.add_history(RL("ako"))