PageRenderTime 89ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/lib_pypy/pyrepl/completing_reader.py

https://bitbucket.org/varialus/jyjy
Python | 281 lines | 226 code | 10 blank | 45 comment | 16 complexity | a22fde461959ccf0bf74e99c47f823fe MD5 | raw file
  1. # Copyright 2000-2010 Michael Hudson-Doyle <micahel@gmail.com>
  2. # Antonio Cuni
  3. #
  4. # All Rights Reserved
  5. #
  6. #
  7. # Permission to use, copy, modify, and distribute this software and
  8. # its documentation for any purpose is hereby granted without fee,
  9. # provided that the above copyright notice appear in all copies and
  10. # that both that copyright notice and this permission notice appear in
  11. # supporting documentation.
  12. #
  13. # THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
  14. # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  15. # AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
  16. # INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  17. # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  18. # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  19. # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  20. from pyrepl import commands, reader
  21. from pyrepl.reader import Reader
  22. def uniqify(l):
  23. d = {}
  24. for i in l:
  25. d[i] = 1
  26. r = d.keys()
  27. r.sort()
  28. return r
  29. def prefix(wordlist, j = 0):
  30. d = {}
  31. i = j
  32. try:
  33. while 1:
  34. for word in wordlist:
  35. d[word[i]] = 1
  36. if len(d) > 1:
  37. return wordlist[0][j:i]
  38. i += 1
  39. d = {}
  40. except IndexError:
  41. return wordlist[0][j:i]
  42. import re
  43. def stripcolor(s):
  44. return stripcolor.regexp.sub('', s)
  45. stripcolor.regexp = re.compile(r"\x1B\[([0-9]{1,3}(;[0-9]{1,2})?)?[m|K]")
  46. def real_len(s):
  47. return len(stripcolor(s))
  48. def left_align(s, maxlen):
  49. stripped = stripcolor(s)
  50. if len(stripped) > maxlen:
  51. # too bad, we remove the color
  52. return stripped[:maxlen]
  53. padding = maxlen - len(stripped)
  54. return s + ' '*padding
  55. def build_menu(cons, wordlist, start, use_brackets, sort_in_column):
  56. if use_brackets:
  57. item = "[ %s ]"
  58. padding = 4
  59. else:
  60. item = "%s "
  61. padding = 2
  62. maxlen = min(max(map(real_len, wordlist)), cons.width - padding)
  63. cols = cons.width / (maxlen + padding)
  64. rows = (len(wordlist) - 1)/cols + 1
  65. if sort_in_column:
  66. # sort_in_column=False (default) sort_in_column=True
  67. # A B C A D G
  68. # D E F B E
  69. # G C F
  70. #
  71. # "fill" the table with empty words, so we always have the same amout
  72. # of rows for each column
  73. missing = cols*rows - len(wordlist)
  74. wordlist = wordlist + ['']*missing
  75. indexes = [(i%cols)*rows + i//cols for i in range(len(wordlist))]
  76. wordlist = [wordlist[i] for i in indexes]
  77. menu = []
  78. i = start
  79. for r in range(rows):
  80. row = []
  81. for col in range(cols):
  82. row.append(item % left_align(wordlist[i], maxlen))
  83. i += 1
  84. if i >= len(wordlist):
  85. break
  86. menu.append( ''.join(row) )
  87. if i >= len(wordlist):
  88. i = 0
  89. break
  90. if r + 5 > cons.height:
  91. menu.append(" %d more... "%(len(wordlist) - i))
  92. break
  93. return menu, i
  94. # this gets somewhat user interface-y, and as a result the logic gets
  95. # very convoluted.
  96. #
  97. # To summarise the summary of the summary:- people are a problem.
  98. # -- The Hitch-Hikers Guide to the Galaxy, Episode 12
  99. #### Desired behaviour of the completions commands.
  100. # the considerations are:
  101. # (1) how many completions are possible
  102. # (2) whether the last command was a completion
  103. # (3) if we can assume that the completer is going to return the same set of
  104. # completions: this is controlled by the ``assume_immutable_completions``
  105. # variable on the reader, which is True by default to match the historical
  106. # behaviour of pyrepl, but e.g. False in the ReadlineAlikeReader to match
  107. # more closely readline's semantics (this is needed e.g. by
  108. # fancycompleter)
  109. #
  110. # if there's no possible completion, beep at the user and point this out.
  111. # this is easy.
  112. #
  113. # if there's only one possible completion, stick it in. if the last thing
  114. # user did was a completion, point out that he isn't getting anywhere, but
  115. # only if the ``assume_immutable_completions`` is True.
  116. #
  117. # now it gets complicated.
  118. #
  119. # for the first press of a completion key:
  120. # if there's a common prefix, stick it in.
  121. # irrespective of whether anything got stuck in, if the word is now
  122. # complete, show the "complete but not unique" message
  123. # if there's no common prefix and if the word is not now complete,
  124. # beep.
  125. # common prefix -> yes no
  126. # word complete \/
  127. # yes "cbnu" "cbnu"
  128. # no - beep
  129. # for the second bang on the completion key
  130. # there will necessarily be no common prefix
  131. # show a menu of the choices.
  132. # for subsequent bangs, rotate the menu around (if there are sufficient
  133. # choices).
  134. class complete(commands.Command):
  135. def do(self):
  136. r = self.reader
  137. stem = r.get_stem()
  138. if r.assume_immutable_completions and \
  139. r.last_command_is(self.__class__):
  140. completions = r.cmpltn_menu_choices
  141. else:
  142. r.cmpltn_menu_choices = completions = \
  143. r.get_completions(stem)
  144. if len(completions) == 0:
  145. r.error("no matches")
  146. elif len(completions) == 1:
  147. if r.assume_immutable_completions and \
  148. len(completions[0]) == len(stem) and \
  149. r.last_command_is(self.__class__):
  150. r.msg = "[ sole completion ]"
  151. r.dirty = 1
  152. r.insert(completions[0][len(stem):])
  153. else:
  154. p = prefix(completions, len(stem))
  155. if p <> '':
  156. r.insert(p)
  157. if r.last_command_is(self.__class__):
  158. if not r.cmpltn_menu_vis:
  159. r.cmpltn_menu_vis = 1
  160. r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
  161. r.console, completions, r.cmpltn_menu_end,
  162. r.use_brackets, r.sort_in_column)
  163. r.dirty = 1
  164. elif stem + p in completions:
  165. r.msg = "[ complete but not unique ]"
  166. r.dirty = 1
  167. else:
  168. r.msg = "[ not unique ]"
  169. r.dirty = 1
  170. class self_insert(commands.self_insert):
  171. def do(self):
  172. commands.self_insert.do(self)
  173. r = self.reader
  174. if r.cmpltn_menu_vis:
  175. stem = r.get_stem()
  176. if len(stem) < 1:
  177. r.cmpltn_reset()
  178. else:
  179. completions = [w for w in r.cmpltn_menu_choices
  180. if w.startswith(stem)]
  181. if completions:
  182. r.cmpltn_menu, r.cmpltn_menu_end = build_menu(
  183. r.console, completions, 0,
  184. r.use_brackets, r.sort_in_column)
  185. else:
  186. r.cmpltn_reset()
  187. class CompletingReader(Reader):
  188. """Adds completion support
  189. Adds instance variables:
  190. * cmpltn_menu, cmpltn_menu_vis, cmpltn_menu_end, cmpltn_choices:
  191. *
  192. """
  193. # see the comment for the complete command
  194. assume_immutable_completions = True
  195. use_brackets = True # display completions inside []
  196. sort_in_column = False
  197. def collect_keymap(self):
  198. return super(CompletingReader, self).collect_keymap() + (
  199. (r'\t', 'complete'),)
  200. def __init__(self, console):
  201. super(CompletingReader, self).__init__(console)
  202. self.cmpltn_menu = ["[ menu 1 ]", "[ menu 2 ]"]
  203. self.cmpltn_menu_vis = 0
  204. self.cmpltn_menu_end = 0
  205. for c in [complete, self_insert]:
  206. self.commands[c.__name__] = c
  207. self.commands[c.__name__.replace('_', '-')] = c
  208. def after_command(self, cmd):
  209. super(CompletingReader, self).after_command(cmd)
  210. if not isinstance(cmd, self.commands['complete']) \
  211. and not isinstance(cmd, self.commands['self_insert']):
  212. self.cmpltn_reset()
  213. def calc_screen(self):
  214. screen = super(CompletingReader, self).calc_screen()
  215. if self.cmpltn_menu_vis:
  216. ly = self.lxy[1]
  217. screen[ly:ly] = self.cmpltn_menu
  218. self.screeninfo[ly:ly] = [(0, [])]*len(self.cmpltn_menu)
  219. self.cxy = self.cxy[0], self.cxy[1] + len(self.cmpltn_menu)
  220. return screen
  221. def finish(self):
  222. super(CompletingReader, self).finish()
  223. self.cmpltn_reset()
  224. def cmpltn_reset(self):
  225. self.cmpltn_menu = []
  226. self.cmpltn_menu_vis = 0
  227. self.cmpltn_menu_end = 0
  228. self.cmpltn_menu_choices = []
  229. def get_stem(self):
  230. st = self.syntax_table
  231. SW = reader.SYNTAX_WORD
  232. b = self.buffer
  233. p = self.pos - 1
  234. while p >= 0 and st.get(b[p], SW) == SW:
  235. p -= 1
  236. return u''.join(b[p+1:self.pos])
  237. def get_completions(self, stem):
  238. return []
  239. def test():
  240. class TestReader(CompletingReader):
  241. def get_completions(self, stem):
  242. return [s for l in map(lambda x:x.split(),self.history)
  243. for s in l if s and s.startswith(stem)]
  244. reader = TestReader()
  245. reader.ps1 = "c**> "
  246. reader.ps2 = "c/*> "
  247. reader.ps3 = "c|*> "
  248. reader.ps4 = "c\*> "
  249. while reader.readline():
  250. pass
  251. if __name__=='__main__':
  252. test()