PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib_pypy/pyrepl/completing_reader.py

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