tako is a command language and shell based on Python. https://takoshell.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

readline_shell.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. # This file is part of tako
  2. # Copyright (c) 2015-2017 Adam Hartz <hartz@mit.edu> and contributors
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU General Public License as published by the Free Software
  6. # Foundation, either version 3 of the License, or (at your option) any later
  7. # version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU General Public License along with
  15. # this program. If not, see <http://www.gnu.org/licenses/>.
  16. #
  17. #
  18. # tako is a fork of xonsh (http://xon.sh)
  19. # xonsh is Copyright (c) 2015-2016 the xonsh developers and is licensed under
  20. # the 2-Clause BSD license.
  21. # -*- coding: utf-8 -*-
  22. """The readline based tako shell.
  23. Portions of this code related to initializing the readline library
  24. are included from the IPython project. The IPython project is:
  25. * Copyright (c) 2008-2014, IPython Development Team
  26. * Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
  27. * Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
  28. * Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
  29. """
  30. import os
  31. import sys
  32. import select
  33. import builtins
  34. import importlib
  35. from cmd import Cmd
  36. from collections import deque
  37. from takoshell.base_shell import BaseShell
  38. from takoshell.tools import print_exception, check_for_partial_string
  39. from takoshell.platform import ON_CYGWIN, ON_DARWIN
  40. readline = None
  41. RL_COMPLETION_SUPPRESS_APPEND = RL_LIB = RL_STATE = None
  42. RL_CAN_RESIZE = False
  43. RL_DONE = None
  44. RL_VARIABLE_VALUE = None
  45. _RL_STATE_DONE = 0x1000000
  46. _RL_STATE_ISEARCH = 0x0000080
  47. def setup_readline():
  48. """Sets up the readline module and completion suppression, if available."""
  49. global RL_COMPLETION_SUPPRESS_APPEND, RL_LIB, RL_CAN_RESIZE, RL_STATE, readline
  50. if RL_COMPLETION_SUPPRESS_APPEND is not None:
  51. return
  52. for _rlmod_name in ('gnureadline', 'readline'):
  53. try:
  54. readline = importlib.import_module(_rlmod_name)
  55. sys.modules['readline'] = readline
  56. except ImportError:
  57. pass
  58. else:
  59. break
  60. if readline is None:
  61. print("No readline implementation available. Skipping setup.")
  62. return
  63. import ctypes
  64. import ctypes.util
  65. uses_libedit = readline.__doc__ and 'libedit' in readline.__doc__
  66. readline.set_completer_delims(' \t\n')
  67. # Cygwin seems to hang indefinitely when querying the readline lib
  68. if (not ON_CYGWIN) and (not readline.__file__.endswith('.py')):
  69. RL_LIB = lib = ctypes.cdll.LoadLibrary(readline.__file__)
  70. try:
  71. RL_COMPLETION_SUPPRESS_APPEND = ctypes.c_int.in_dll(
  72. lib, 'rl_completion_suppress_append')
  73. except ValueError:
  74. # not all versions of readline have this symbol, ie Macs sometimes
  75. RL_COMPLETION_SUPPRESS_APPEND = None
  76. try:
  77. RL_STATE = ctypes.c_int.in_dll(lib, 'rl_readline_state')
  78. except:
  79. pass
  80. RL_CAN_RESIZE = hasattr(lib, 'rl_reset_screen_size')
  81. env = builtins.__tako_env__
  82. settings = env['TAKO_SETTINGS']
  83. # reads in history
  84. hf = settings.history_file
  85. if os.path.isfile(hf):
  86. try:
  87. readline.read_history_file(hf)
  88. except PermissionError:
  89. pass
  90. # sets up IPython-like history matching with up and down
  91. readline.parse_and_bind('"\e[B": history-search-forward')
  92. readline.parse_and_bind('"\e[A": history-search-backward')
  93. # Setup Shift-Tab to indent
  94. readline.parse_and_bind('"\e[Z": "{0}"'.format(settings.indent))
  95. # handle tab completion differences found in libedit readline compatibility
  96. # as discussed at http://stackoverflow.com/a/7116997
  97. if uses_libedit and ON_DARWIN:
  98. readline.parse_and_bind("bind ^I rl_complete")
  99. print('\n'.join(['', "*"*78,
  100. "libedit detected - readline will not be well behaved, including but not limited to:",
  101. " * crashes on tab completion",
  102. " * incorrect history navigation",
  103. " * corrupting long-lines",
  104. " * failure to wrap or indent lines properly",
  105. "",
  106. "It is highly recommended that you install gnureadline, which is installable with:",
  107. " pip install gnureadline",
  108. "*"*78]), file=sys.stderr)
  109. else:
  110. readline.parse_and_bind("tab: complete")
  111. # try to load custom user settings
  112. inputrc_name = os.environ.get('INPUTRC')
  113. if inputrc_name is None:
  114. if uses_libedit:
  115. inputrc_name = '.editrc'
  116. else:
  117. inputrc_name = '.inputrc'
  118. inputrc_name = os.path.join(os.path.expanduser('~'), inputrc_name)
  119. if not os.path.isfile(inputrc_name):
  120. inputrc_name = '/etc/inputrc'
  121. if os.path.isfile(inputrc_name):
  122. try:
  123. readline.read_init_file(inputrc_name)
  124. except Exception:
  125. # this seems to fail with libedit
  126. print_exception('tako: could not load readline default init file.')
  127. def teardown_readline():
  128. """Tears down up the readline module, if available."""
  129. try:
  130. import readline
  131. except (ImportError, TypeError):
  132. return
  133. env = builtins.__tako_env__
  134. settings = env['TAKO_SETTINGS']
  135. hs = settings.history_size
  136. readline.set_history_length(hs)
  137. hf = settings.history_file
  138. try:
  139. readline.write_history_file(hf)
  140. except PermissionError:
  141. pass
  142. def fix_readline_state_after_ctrl_c():
  143. """
  144. Fix to allow Ctrl-C to exit reverse-i-search.
  145. Based on code from:
  146. http://bugs.python.org/file39467/raw_input__workaround_demo.py
  147. """
  148. if RL_STATE is None:
  149. return
  150. if RL_STATE.value & _RL_STATE_ISEARCH:
  151. RL_STATE.value &= ~_RL_STATE_ISEARCH
  152. if not RL_STATE.value & _RL_STATE_DONE:
  153. RL_STATE.value |= _RL_STATE_DONE
  154. def rl_completion_suppress_append(val=1):
  155. """Sets the rl_completion_suppress_append varaiable, if possible.
  156. A value of 1 (default) means to suppress, a value of 0 means to enable.
  157. """
  158. if RL_COMPLETION_SUPPRESS_APPEND is None:
  159. return
  160. RL_COMPLETION_SUPPRESS_APPEND.value = val
  161. def rl_variable_dumper(readable=True):
  162. """Dumps the currently set readline variables. If readable is True, then this
  163. output may be used in an inputrc file.
  164. """
  165. RL_LIB.rl_variable_dumper(int(readable))
  166. def rl_variable_value(variable):
  167. """Returns the currently set value for a readline configuration variable."""
  168. global RL_VARIABLE_VALUE
  169. if RL_VARIABLE_VALUE is None:
  170. import ctypes
  171. RL_VARIABLE_VALUE = RL_LIB.rl_variable_value
  172. RL_VARIABLE_VALUE.restype = ctypes.c_char_p
  173. env = builtins.__tako_env__
  174. settings = env['TAKO_SETTINGS']
  175. enc, errors = settings.encoding, settings.encoding_errors
  176. if isinstance(variable, str):
  177. variable = variable.encode(encoding=enc, errors=errors)
  178. rtn = RL_VARIABLE_VALUE(variable)
  179. return rtn.decode(encoding=enc, errors=errors)
  180. def _insert_text_func(s, readline):
  181. """Creates a function to insert text via readline."""
  182. def inserter():
  183. readline.insert_text(s)
  184. readline.redisplay()
  185. return inserter
  186. DEDENT_TOKENS = frozenset(['raise', 'return', 'pass', 'break', 'continue'])
  187. class ReadlineShell(BaseShell, Cmd):
  188. """The readline based tako shell."""
  189. def __init__(self, completekey='tab', stdin=None, stdout=None, **kwargs):
  190. super().__init__(completekey=completekey,
  191. stdin=stdin,
  192. stdout=stdout,
  193. **kwargs)
  194. setup_readline()
  195. self._current_indent = ''
  196. self._current_prompt = ''
  197. self._force_hide = None
  198. self.cmdqueue = deque()
  199. def __del__(self):
  200. teardown_readline()
  201. def singleline(self, store_in_history=True, **kwargs):
  202. """Reads a single line of input. The store_in_history kwarg
  203. flags whether the input should be stored in readline's in-memory
  204. history.
  205. """
  206. if not store_in_history: # store current position to remove it later
  207. try:
  208. import readline
  209. except ImportError:
  210. store_in_history = True
  211. pos = readline.get_current_history_length() - 1
  212. rtn = input(self.prompt)
  213. if not store_in_history and pos >= 0:
  214. readline.remove_history_item(pos)
  215. return rtn
  216. def parseline(self, line):
  217. """Overridden to no-op."""
  218. return '', line, line
  219. def completedefault(self, text, line, begidx, endidx):
  220. """Implements tab-completion for text."""
  221. if self.completer is None:
  222. return []
  223. rl_completion_suppress_append() # this needs to be called each time
  224. offset = 0
  225. effective_line = line[:endidx]
  226. _s, _e, _q = check_for_partial_string(effective_line)
  227. if _s is not None:
  228. if _e is not None and ' ' in line[_e:]:
  229. mline = effective_line.rpartition(' ')[2]
  230. else:
  231. mline = effective_line[_s:]
  232. else:
  233. mline = effective_line.rpartition(' ')[2]
  234. offset = len(mline) - len(text)
  235. comps = self.completer.complete(text, line,
  236. begidx, endidx,
  237. ctx=self.ctx)
  238. x = [i[offset:] for i in comps[0]]
  239. return x
  240. # tab complete on first index too
  241. completenames = completedefault
  242. def _load_remaining_input_into_queue(self):
  243. buf = b''
  244. while True:
  245. r, w, x = select.select([self.stdin], [], [], 1e-6)
  246. if len(r) == 0:
  247. break
  248. buf += os.read(self.stdin.fileno(), 1024)
  249. if len(buf) > 0:
  250. buf = buf.decode().replace('\r\n', '\n').replace('\r', '\n')
  251. self.cmdqueue.extend(buf.splitlines(keepends=True))
  252. def postcmd(self, stop, line):
  253. """Called just before execution of line. For readline, this handles the
  254. automatic indentation of code blocks.
  255. """
  256. try:
  257. import readline
  258. except ImportError:
  259. return stop
  260. env = builtins.__tako_env__
  261. settings = env['TAKO_SETTINGS']
  262. if self.need_more_lines:
  263. if len(line.strip()) == 0:
  264. readline.set_pre_input_hook(None)
  265. self._current_indent = ''
  266. elif line.rstrip()[-1] == ':':
  267. ind = line[:len(line) - len(line.lstrip())]
  268. ind += settings.indent
  269. readline.set_pre_input_hook(_insert_text_func(ind, readline))
  270. self._current_indent = ind
  271. elif line.split(maxsplit=1)[0] in DEDENT_TOKENS:
  272. ind = self._current_indent[:-len(settings.indent)]
  273. readline.set_pre_input_hook(_insert_text_func(ind, readline))
  274. self._current_indent = ind
  275. else:
  276. ind = line[:len(line) - len(line.lstrip())]
  277. if ind != self._current_indent:
  278. insert_func = _insert_text_func(ind, readline)
  279. readline.set_pre_input_hook(insert_func)
  280. self._current_indent = ind
  281. else:
  282. readline.set_pre_input_hook(None)
  283. return stop
  284. def _cmdloop(self, intro=None):
  285. """Repeatedly issue a prompt, accept input, parse an initial prefix
  286. off the received input, and dispatch to action methods, passing them
  287. the remainder of the line as argument.
  288. This was forked from Lib/cmd.py from the Python standard library v3.4.3,
  289. (C) Python Software Foundation, 2015.
  290. """
  291. self.preloop()
  292. env = builtins.__tako_env__
  293. settings = env['TAKO_SETTINGS']
  294. if self.use_rawinput and self.completekey:
  295. try:
  296. import readline
  297. self.old_completer = readline.get_completer()
  298. readline.set_completer(self.complete)
  299. readline.parse_and_bind(self.completekey + ": complete")
  300. have_readline = True
  301. except ImportError:
  302. have_readline = False
  303. try:
  304. if intro is not None:
  305. self.intro = intro
  306. if self.intro:
  307. self.stdout.write(str(self.intro)+"\n")
  308. stop = None
  309. while not stop:
  310. line = None
  311. exec_now = False
  312. if len(self.cmdqueue) > 0:
  313. line = self.cmdqueue.popleft()
  314. exec_now = line.endswith('\n')
  315. if self.use_rawinput and not exec_now:
  316. inserter = None if line is None \
  317. else _insert_text_func(line, readline)
  318. if inserter is not None:
  319. readline.set_pre_input_hook(inserter)
  320. try:
  321. line = self.singleline()
  322. except EOFError:
  323. if settings.ignoreEOF:
  324. self.stdout.write('Use "exit" to leave the shell.'
  325. '\n')
  326. line = ''
  327. else:
  328. line = 'EOF'
  329. if inserter is not None:
  330. readline.set_pre_input_hook(None)
  331. else:
  332. print(self.prompt, file=self.stdout)
  333. if line is not None:
  334. os.write(self.stdin.fileno(), line.encode())
  335. if not exec_now:
  336. line = self.stdin.readline()
  337. if len(line) == 0:
  338. line = 'EOF'
  339. else:
  340. line = line.rstrip('\r\n')
  341. if have_readline and line != 'EOF':
  342. readline.add_history(line)
  343. self._load_remaining_input_into_queue()
  344. line = self.precmd(line)
  345. stop = self.onecmd(line)
  346. stop = self.postcmd(stop, line)
  347. self.postloop()
  348. finally:
  349. if self.use_rawinput and self.completekey:
  350. try:
  351. import readline
  352. readline.set_completer(self.old_completer)
  353. except ImportError:
  354. pass
  355. def cmdloop(self, intro=None):
  356. while not builtins.__tako_exit__:
  357. try:
  358. self._cmdloop(intro=intro)
  359. except KeyboardInterrupt:
  360. print() # Gives a newline
  361. readline.set_pre_input_hook()
  362. fix_readline_state_after_ctrl_c()
  363. self.reset_buffer()
  364. intro = None
  365. @property
  366. def prompt(self):
  367. """Obtains the current prompt string."""
  368. global RL_LIB, RL_CAN_RESIZE
  369. if RL_CAN_RESIZE:
  370. # This is needed to support some system where line-wrapping doesn't
  371. # work. This is a bug in upstream Python, or possibly readline.
  372. RL_LIB.rl_reset_screen_size()
  373. return super().prompt