history.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. # -*- coding: utf-8 -*-
  2. # *****************************************************************************
  3. # Copyright (C) 2006-2020 Jorgen Stenarson. <jorgen.stenarson@bostream.nu>
  4. # Copyright (C) 2020 Bassem Girgis. <brgirgis@gmail.com>
  5. #
  6. # Distributed under the terms of the BSD License. The full license is in
  7. # the file COPYING, distributed as part of this software.
  8. # *****************************************************************************
  9. import re, operator, string, sys, os, io
  10. from pyreadline3.logger import log
  11. from pyreadline3.unicode_helper import ensure_str, ensure_unicode
  12. from . import lineobj
  13. class EscapeHistory(Exception):
  14. pass
  15. class LineHistory(object):
  16. def __init__(self):
  17. self.history = []
  18. self._history_length = 100
  19. self._history_cursor = 0
  20. # Cannot expand unicode strings correctly on python2.4
  21. self.history_filename = os.path.expanduser(ensure_str("~/.history"))
  22. self.lastcommand = None
  23. self.query = ""
  24. self.last_search_for = ""
  25. def get_current_history_length(self):
  26. """Return the number of lines currently in the history.
  27. (This is different from get_history_length(), which returns
  28. the maximum number of lines that will be written to a history file.)"""
  29. value = len(self.history)
  30. log("get_current_history_length:%d" % value)
  31. return value
  32. def get_history_length(self):
  33. """Return the desired length of the history file. Negative values imply
  34. unlimited history file size."""
  35. value = self._history_length
  36. log("get_history_length:%d" % value)
  37. return value
  38. def get_history_item(self, index):
  39. """Return the current contents of history item at index (starts with index 1)."""
  40. item = self.history[index - 1]
  41. log("get_history_item: index:%d item:%r" % (index, item))
  42. return item.get_line_text()
  43. def set_history_length(self, value):
  44. log("set_history_length: old:%d new:%d" % (self._history_length, value))
  45. self._history_length = value
  46. def get_history_cursor(self):
  47. value = self._history_cursor
  48. log("get_history_cursor:%d" % value)
  49. return value
  50. def set_history_cursor(self, value):
  51. log("set_history_cursor: old:%d new:%d" % (self._history_cursor, value))
  52. self._history_cursor = value
  53. history_length = property(get_history_length, set_history_length)
  54. history_cursor = property(get_history_cursor, set_history_cursor)
  55. def clear_history(self):
  56. """Clear readline history."""
  57. self.history[:] = []
  58. self.history_cursor = 0
  59. def read_history_file(self, filename=None):
  60. """Load a readline history file."""
  61. if filename is None:
  62. filename = self.history_filename
  63. try:
  64. with io.open(filename, "rt", errors="replace") as fd:
  65. for line in fd:
  66. self.add_history(lineobj.ReadLineTextBuffer(line.rstrip()))
  67. except IOError:
  68. self.history = []
  69. self.history_cursor = 0
  70. def write_history_file(self, filename=None):
  71. """Save a readline history file."""
  72. if filename is None:
  73. filename = self.history_filename
  74. with io.open(filename, "wt", errors="replace") as fp:
  75. fp.writelines(
  76. tuple(
  77. line.get_line_text()+"\n"
  78. for line in self.history[-self.history_length :]
  79. )
  80. )
  81. def add_history(self, line):
  82. """Append a line to the history buffer, as if it was the last line typed."""
  83. line = ensure_unicode(line)
  84. if not hasattr(line, "get_line_text"):
  85. line = lineobj.ReadLineTextBuffer(line)
  86. if not line.get_line_text():
  87. pass
  88. elif (
  89. len(self.history) > 0
  90. and self.history[-1].get_line_text() == line.get_line_text()
  91. ):
  92. pass
  93. else:
  94. self.history.append(line)
  95. self.history_cursor = len(self.history)
  96. def previous_history(self, current): # (C-p)
  97. """Move back through the history list, fetching the previous command."""
  98. if self.history_cursor == len(self.history):
  99. # do not use add_history since we do not want to increment cursor
  100. self.history.append(current.copy())
  101. if self.history_cursor > 0:
  102. self.history_cursor -= 1
  103. current.set_line(self.history[self.history_cursor].get_line_text())
  104. current.point = lineobj.EndOfLine
  105. def next_history(self, current): # (C-n)
  106. """Move forward through the history list, fetching the next command."""
  107. if self.history_cursor < len(self.history) - 1:
  108. self.history_cursor += 1
  109. current.set_line(self.history[self.history_cursor].get_line_text())
  110. def beginning_of_history(self): # (M-<)
  111. """Move to the first line in the history."""
  112. self.history_cursor = 0
  113. if len(self.history) > 0:
  114. self.l_buffer = self.history[0]
  115. def end_of_history(self, current): # (M->)
  116. """Move to the end of the input history, i.e., the line currently
  117. being entered."""
  118. self.history_cursor = len(self.history)
  119. current.set_line(self.history[-1].get_line_text())
  120. def reverse_search_history(self, searchfor, startpos=None):
  121. if startpos is None:
  122. startpos = self.history_cursor
  123. origpos = startpos
  124. result = lineobj.ReadLineTextBuffer("")
  125. for idx, line in list(enumerate(self.history))[startpos:0:-1]:
  126. if searchfor in line:
  127. startpos = idx
  128. break
  129. # If we get a new search without change in search term it means
  130. # someone pushed ctrl-r and we should find the next match
  131. if self.last_search_for == searchfor and startpos > 0:
  132. startpos -= 1
  133. for idx, line in list(enumerate(self.history))[startpos:0:-1]:
  134. if searchfor in line:
  135. startpos = idx
  136. break
  137. if self.history:
  138. result = self.history[startpos].get_line_text()
  139. else:
  140. result = ""
  141. self.history_cursor = startpos
  142. self.last_search_for = searchfor
  143. log(
  144. "reverse_search_history: old:%d new:%d result:%r"
  145. % (origpos, self.history_cursor, result)
  146. )
  147. return result
  148. def forward_search_history(self, searchfor, startpos=None):
  149. if startpos is None:
  150. startpos = min(
  151. self.history_cursor, max(0, self.get_current_history_length() - 1)
  152. )
  153. # origpos = startpos
  154. result = lineobj.ReadLineTextBuffer("")
  155. for idx, line in list(enumerate(self.history))[startpos:]:
  156. if searchfor in line:
  157. startpos = idx
  158. break
  159. # If we get a new search without change in search term it means
  160. # someone pushed ctrl-r and we should find the next match
  161. if (
  162. self.last_search_for == searchfor
  163. and startpos < self.get_current_history_length() - 1
  164. ):
  165. startpos += 1
  166. for idx, line in list(enumerate(self.history))[startpos:]:
  167. if searchfor in line:
  168. startpos = idx
  169. break
  170. if self.history:
  171. result = self.history[startpos].get_line_text()
  172. else:
  173. result = ""
  174. self.history_cursor = startpos
  175. self.last_search_for = searchfor
  176. return result
  177. def _search(self, direction, partial):
  178. try:
  179. if (
  180. self.lastcommand != self.history_search_forward
  181. and self.lastcommand != self.history_search_backward
  182. ):
  183. self.query = "".join(partial[0 : partial.point].get_line_text())
  184. hcstart = max(self.history_cursor, 0)
  185. hc = self.history_cursor + direction
  186. while (direction < 0 and hc >= 0) or (
  187. direction > 0 and hc < len(self.history)
  188. ):
  189. h = self.history[hc]
  190. if not self.query:
  191. self.history_cursor = hc
  192. result = lineobj.ReadLineTextBuffer(h, point=len(h.get_line_text()))
  193. return result
  194. elif h.get_line_text().startswith(self.query) and (
  195. h != partial.get_line_text()
  196. ):
  197. self.history_cursor = hc
  198. result = lineobj.ReadLineTextBuffer(h, point=partial.point)
  199. return result
  200. hc += direction
  201. else:
  202. if len(self.history) == 0:
  203. pass
  204. elif hc >= len(self.history) and not self.query:
  205. self.history_cursor = len(self.history)
  206. return lineobj.ReadLineTextBuffer("", point=0)
  207. elif (
  208. self.history[max(min(hcstart, len(self.history) - 1), 0)]
  209. .get_line_text()
  210. .startswith(self.query)
  211. and self.query
  212. ):
  213. return lineobj.ReadLineTextBuffer(
  214. self.history[max(min(hcstart, len(self.history) - 1), 0)],
  215. point=partial.point,
  216. )
  217. else:
  218. return lineobj.ReadLineTextBuffer(partial, point=partial.point)
  219. return lineobj.ReadLineTextBuffer(
  220. self.query, point=min(len(self.query), partial.point)
  221. )
  222. except IndexError:
  223. raise
  224. def history_search_forward(self, partial): # ()
  225. """Search forward through the history for the string of characters
  226. between the start of the current line and the point. This is a
  227. non-incremental search. By default, this command is unbound."""
  228. return self._search(1, partial)
  229. def history_search_backward(self, partial): # ()
  230. """Search backward through the history for the string of characters
  231. between the start of the current line and the point. This is a
  232. non-incremental search. By default, this command is unbound."""
  233. return self._search(-1, partial)
  234. if __name__ == "__main__":
  235. q = LineHistory()
  236. r = LineHistory()
  237. s = LineHistory()
  238. RL = lineobj.ReadLineTextBuffer
  239. q.add_history(RL("aaaa"))
  240. q.add_history(RL("aaba"))
  241. q.add_history(RL("aaca"))
  242. q.add_history(RL("akca"))
  243. q.add_history(RL("bbb"))
  244. q.add_history(RL("ako"))
  245. r.add_history(RL("ako"))