##// END OF EJS Templates
Merge pull request #10223 from takluyver/i10222...
Min RK -
r23275:c5b62bb0 merge
parent child Browse files
Show More
@@ -1,95 +1,98 b''
1 1 from IPython.core.debugger import Pdb
2 2
3 3 from IPython.core.completer import IPCompleter
4 4 from .ptutils import IPythonPTCompleter
5 5
6 6 from prompt_toolkit.token import Token
7 7 from prompt_toolkit.shortcuts import create_prompt_application
8 8 from prompt_toolkit.interface import CommandLineInterface
9 9 from prompt_toolkit.enums import EditingMode
10 10 import sys
11 11
12 12
13 13 class TerminalPdb(Pdb):
14 14 def __init__(self, *args, **kwargs):
15 15 Pdb.__init__(self, *args, **kwargs)
16 16 self._ptcomp = None
17 17 self.pt_init()
18 18
19 19 def pt_init(self):
20 20 def get_prompt_tokens(cli):
21 21 return [(Token.Prompt, self.prompt)]
22 22
23 def patch_stdout(**kwargs):
24 return self.pt_cli.patch_stdout_context(**kwargs)
25
23 26 if self._ptcomp is None:
24 27 compl = IPCompleter(shell=self.shell,
25 28 namespace={},
26 29 global_namespace={},
27 30 parent=self.shell,
28 31 )
29 self._ptcomp = IPythonPTCompleter(compl)
32 self._ptcomp = IPythonPTCompleter(compl, patch_stdout=patch_stdout)
30 33
31 34 self._pt_app = create_prompt_application(
32 35 editing_mode=getattr(EditingMode, self.shell.editing_mode.upper()),
33 36 history=self.shell.debugger_history,
34 37 completer= self._ptcomp,
35 38 enable_history_search=True,
36 39 mouse_support=self.shell.mouse_support,
37 40 get_prompt_tokens=get_prompt_tokens
38 41 )
39 42 self.pt_cli = CommandLineInterface(self._pt_app, eventloop=self.shell._eventloop)
40 43
41 44 def cmdloop(self, intro=None):
42 45 """Repeatedly issue a prompt, accept input, parse an initial prefix
43 46 off the received input, and dispatch to action methods, passing them
44 47 the remainder of the line as argument.
45 48
46 49 override the same methods from cmd.Cmd to provide prompt toolkit replacement.
47 50 """
48 51 if not self.use_rawinput:
49 52 raise ValueError('Sorry ipdb does not support use_rawinput=False')
50 53
51 54 self.preloop()
52 55
53 56 try:
54 57 if intro is not None:
55 58 self.intro = intro
56 59 if self.intro:
57 60 self.stdout.write(str(self.intro)+"\n")
58 61 stop = None
59 62 while not stop:
60 63 if self.cmdqueue:
61 64 line = self.cmdqueue.pop(0)
62 65 else:
63 66 self._ptcomp.ipy_completer.namespace = self.curframe_locals
64 67 self._ptcomp.ipy_completer.global_namespace = self.curframe.f_globals
65 68 try:
66 69 line = self.pt_cli.run(reset_current_buffer=True).text
67 70 except EOFError:
68 71 line = 'EOF'
69 72 line = self.precmd(line)
70 73 stop = self.onecmd(line)
71 74 stop = self.postcmd(stop, line)
72 75 self.postloop()
73 76 except Exception:
74 77 raise
75 78
76 79
77 80 def set_trace(frame=None):
78 81 """
79 82 Start debugging from `frame`.
80 83
81 84 If frame is not specified, debugging starts from caller's frame.
82 85 """
83 86 TerminalPdb().set_trace(frame or sys._getframe().f_back)
84 87
85 88
86 89 if __name__ == '__main__':
87 90 import pdb
88 91 # IPython.core.debugger.Pdb.trace_dispatch shall not catch
89 92 # bdb.BdbQuit. When started through __main__ and an exception
90 93 # happened after hitting "c", this is needed in order to
91 94 # be able to quit the debugging session (see #9950).
92 95 old_trace_dispatch = pdb.Pdb.trace_dispatch
93 96 pdb.Pdb = TerminalPdb
94 97 pdb.Pdb.trace_dispatch = old_trace_dispatch
95 98 pdb.main()
@@ -1,485 +1,489 b''
1 1 """IPython terminal interface using prompt_toolkit"""
2 2
3 3 import os
4 4 import sys
5 5 import warnings
6 6 from warnings import warn
7 7
8 8 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
9 9 from IPython.utils import io
10 10 from IPython.utils.py3compat import cast_unicode_py2, input
11 11 from IPython.utils.terminal import toggle_set_term_title, set_term_title
12 12 from IPython.utils.process import abbrev_cwd
13 13 from traitlets import Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union
14 14
15 15 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
16 16 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
17 17 from prompt_toolkit.history import InMemoryHistory
18 18 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout, create_output
19 19 from prompt_toolkit.interface import CommandLineInterface
20 20 from prompt_toolkit.key_binding.manager import KeyBindingManager
21 21 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
22 22 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
23 23
24 24 from pygments.styles import get_style_by_name, get_all_styles
25 25 from pygments.style import Style
26 26 from pygments.token import Token
27 27
28 28 from .debugger import TerminalPdb, Pdb
29 29 from .magics import TerminalMagics
30 30 from .pt_inputhooks import get_inputhook_name_and_func
31 31 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
32 32 from .ptutils import IPythonPTCompleter, IPythonPTLexer
33 33 from .shortcuts import register_ipython_shortcuts
34 34
35 35 DISPLAY_BANNER_DEPRECATED = object()
36 36
37 37
38 38 from pygments.style import Style
39 39
40 40 class _NoStyle(Style): pass
41 41
42 42
43 43
44 44 _style_overrides_light_bg = {
45 45 Token.Prompt: '#0000ff',
46 46 Token.PromptNum: '#0000ee bold',
47 47 Token.OutPrompt: '#cc0000',
48 48 Token.OutPromptNum: '#bb0000 bold',
49 49 }
50 50
51 51 _style_overrides_linux = {
52 52 Token.Prompt: '#00cc00',
53 53 Token.PromptNum: '#00bb00 bold',
54 54 Token.OutPrompt: '#cc0000',
55 55 Token.OutPromptNum: '#bb0000 bold',
56 56 }
57 57
58 58
59 59
60 60 def get_default_editor():
61 61 try:
62 62 return os.environ['EDITOR']
63 63 except KeyError:
64 64 pass
65 65 except UnicodeError:
66 66 warn("$EDITOR environment variable is not pure ASCII. Using platform "
67 67 "default editor.")
68 68
69 69 if os.name == 'posix':
70 70 return 'vi' # the only one guaranteed to be there!
71 71 else:
72 72 return 'notepad' # same in Windows!
73 73
74 74 # conservatively check for tty
75 75 # overridden streams can result in things like:
76 76 # - sys.stdin = None
77 77 # - no isatty method
78 78 for _name in ('stdin', 'stdout', 'stderr'):
79 79 _stream = getattr(sys, _name)
80 80 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
81 81 _is_tty = False
82 82 break
83 83 else:
84 84 _is_tty = True
85 85
86 86
87 87 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
88 88
89 89 class TerminalInteractiveShell(InteractiveShell):
90 90 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
91 91 'to reserve for the completion menu'
92 92 ).tag(config=True)
93 93
94 94 def _space_for_menu_changed(self, old, new):
95 95 self._update_layout()
96 96
97 97 pt_cli = None
98 98 debugger_history = None
99 99 _pt_app = None
100 100
101 101 simple_prompt = Bool(_use_simple_prompt,
102 102 help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors.
103 103
104 104 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
105 105 IPython own testing machinery, and emacs inferior-shell integration through elpy.
106 106
107 107 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
108 108 environment variable is set, or the current terminal is not a tty.
109 109
110 110 """
111 111 ).tag(config=True)
112 112
113 113 @property
114 114 def debugger_cls(self):
115 115 return Pdb if self.simple_prompt else TerminalPdb
116 116
117 117 confirm_exit = Bool(True,
118 118 help="""
119 119 Set to confirm when you try to exit IPython with an EOF (Control-D
120 120 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
121 121 you can force a direct exit without any confirmation.""",
122 122 ).tag(config=True)
123 123
124 124 editing_mode = Unicode('emacs',
125 125 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
126 126 ).tag(config=True)
127 127
128 128 mouse_support = Bool(False,
129 129 help="Enable mouse support in the prompt"
130 130 ).tag(config=True)
131 131
132 132 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
133 133 help="""The name or class of a Pygments style to use for syntax
134 134 highlighting: \n %s""" % ', '.join(get_all_styles())
135 135 ).tag(config=True)
136 136
137 137
138 138 @observe('highlighting_style')
139 139 @observe('colors')
140 140 def _highlighting_style_changed(self, change):
141 141 self.refresh_style()
142 142
143 143 def refresh_style(self):
144 144 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
145 145
146 146
147 147 highlighting_style_overrides = Dict(
148 148 help="Override highlighting format for specific tokens"
149 149 ).tag(config=True)
150 150
151 151 true_color = Bool(False,
152 152 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
153 153 "If your terminal supports true color, the following command "
154 154 "should print 'TRUECOLOR' in orange: "
155 155 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
156 156 ).tag(config=True)
157 157
158 158 editor = Unicode(get_default_editor(),
159 159 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
160 160 ).tag(config=True)
161 161
162 162 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
163 163
164 164 prompts = Instance(Prompts)
165 165
166 166 @default('prompts')
167 167 def _prompts_default(self):
168 168 return self.prompts_class(self)
169 169
170 170 @observe('prompts')
171 171 def _(self, change):
172 172 self._update_layout()
173 173
174 174 @default('displayhook_class')
175 175 def _displayhook_class_default(self):
176 176 return RichPromptDisplayHook
177 177
178 178 term_title = Bool(True,
179 179 help="Automatically set the terminal title"
180 180 ).tag(config=True)
181 181
182 182 display_completions = Enum(('column', 'multicolumn','readlinelike'),
183 183 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
184 184 "'readlinelike'. These options are for `prompt_toolkit`, see "
185 185 "`prompt_toolkit` documentation for more information."
186 186 ),
187 187 default_value='multicolumn').tag(config=True)
188 188
189 189 highlight_matching_brackets = Bool(True,
190 190 help="Highlight matching brackets .",
191 191 ).tag(config=True)
192 192
193 193 @observe('term_title')
194 194 def init_term_title(self, change=None):
195 195 # Enable or disable the terminal title.
196 196 if self.term_title:
197 197 toggle_set_term_title(True)
198 198 set_term_title('IPython: ' + abbrev_cwd())
199 199 else:
200 200 toggle_set_term_title(False)
201 201
202 202 def init_display_formatter(self):
203 203 super(TerminalInteractiveShell, self).init_display_formatter()
204 204 # terminal only supports plain text
205 205 self.display_formatter.active_types = ['text/plain']
206 206
207 207 def init_prompt_toolkit_cli(self):
208 208 if self.simple_prompt:
209 209 # Fall back to plain non-interactive output for tests.
210 210 # This is very limited, and only accepts a single line.
211 211 def prompt():
212 212 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
213 213 self.prompt_for_code = prompt
214 214 return
215 215
216 216 # Set up keyboard shortcuts
217 217 kbmanager = KeyBindingManager.for_prompt()
218 218 register_ipython_shortcuts(kbmanager.registry, self)
219 219
220 220 # Pre-populate history from IPython's history database
221 221 history = InMemoryHistory()
222 222 last_cell = u""
223 223 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
224 224 include_latest=True):
225 225 # Ignore blank lines and consecutive duplicates
226 226 cell = cell.rstrip()
227 227 if cell and (cell != last_cell):
228 228 history.append(cell)
229 229 last_cell = cell
230 230
231 231 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
232 232 style = DynamicStyle(lambda: self._style)
233 233
234 234 editing_mode = getattr(EditingMode, self.editing_mode.upper())
235 235
236 def patch_stdout(**kwargs):
237 return self.pt_cli.patch_stdout_context(**kwargs)
238
236 239 self._pt_app = create_prompt_application(
237 240 editing_mode=editing_mode,
238 241 key_bindings_registry=kbmanager.registry,
239 242 history=history,
240 completer=IPythonPTCompleter(shell=self),
243 completer=IPythonPTCompleter(shell=self,
244 patch_stdout=patch_stdout),
241 245 enable_history_search=True,
242 246 style=style,
243 247 mouse_support=self.mouse_support,
244 248 **self._layout_options()
245 249 )
246 250 self._eventloop = create_eventloop(self.inputhook)
247 251 self.pt_cli = CommandLineInterface(
248 252 self._pt_app, eventloop=self._eventloop,
249 253 output=create_output(true_color=self.true_color))
250 254
251 255 def _make_style_from_name_or_cls(self, name_or_cls):
252 256 """
253 257 Small wrapper that make an IPython compatible style from a style name
254 258
255 259 We need that to add style for prompt ... etc.
256 260 """
257 261 style_overrides = {}
258 262 if name_or_cls == 'legacy':
259 263 legacy = self.colors.lower()
260 264 if legacy == 'linux':
261 265 style_cls = get_style_by_name('monokai')
262 266 style_overrides = _style_overrides_linux
263 267 elif legacy == 'lightbg':
264 268 style_overrides = _style_overrides_light_bg
265 269 style_cls = get_style_by_name('pastie')
266 270 elif legacy == 'neutral':
267 271 # The default theme needs to be visible on both a dark background
268 272 # and a light background, because we can't tell what the terminal
269 273 # looks like. These tweaks to the default theme help with that.
270 274 style_cls = get_style_by_name('default')
271 275 style_overrides.update({
272 276 Token.Number: '#007700',
273 277 Token.Operator: 'noinherit',
274 278 Token.String: '#BB6622',
275 279 Token.Name.Function: '#2080D0',
276 280 Token.Name.Class: 'bold #2080D0',
277 281 Token.Name.Namespace: 'bold #2080D0',
278 282 Token.Prompt: '#009900',
279 283 Token.PromptNum: '#00ff00 bold',
280 284 Token.OutPrompt: '#990000',
281 285 Token.OutPromptNum: '#ff0000 bold',
282 286 })
283 287 elif legacy =='nocolor':
284 288 style_cls=_NoStyle
285 289 style_overrides = {}
286 290 else :
287 291 raise ValueError('Got unknown colors: ', legacy)
288 292 else :
289 293 if isinstance(name_or_cls, str):
290 294 style_cls = get_style_by_name(name_or_cls)
291 295 else:
292 296 style_cls = name_or_cls
293 297 style_overrides = {
294 298 Token.Prompt: '#009900',
295 299 Token.PromptNum: '#00ff00 bold',
296 300 Token.OutPrompt: '#990000',
297 301 Token.OutPromptNum: '#ff0000 bold',
298 302 }
299 303 style_overrides.update(self.highlighting_style_overrides)
300 304 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
301 305 style_dict=style_overrides)
302 306
303 307 return style
304 308
305 309 def _layout_options(self):
306 310 """
307 311 Return the current layout option for the current Terminal InteractiveShell
308 312 """
309 313 return {
310 314 'lexer':IPythonPTLexer(),
311 315 'reserve_space_for_menu':self.space_for_menu,
312 316 'get_prompt_tokens':self.prompts.in_prompt_tokens,
313 317 'get_continuation_tokens':self.prompts.continuation_prompt_tokens,
314 318 'multiline':True,
315 319 'display_completions_in_columns': (self.display_completions == 'multicolumn'),
316 320
317 321 # Highlight matching brackets, but only when this setting is
318 322 # enabled, and only when the DEFAULT_BUFFER has the focus.
319 323 'extra_input_processors': [ConditionalProcessor(
320 324 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
321 325 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
322 326 Condition(lambda cli: self.highlight_matching_brackets))],
323 327 }
324 328
325 329 def _update_layout(self):
326 330 """
327 331 Ask for a re computation of the application layout, if for example ,
328 332 some configuration options have changed.
329 333 """
330 334 if self._pt_app:
331 335 self._pt_app.layout = create_prompt_layout(**self._layout_options())
332 336
333 337 def prompt_for_code(self):
334 338 document = self.pt_cli.run(
335 339 pre_run=self.pre_prompt, reset_current_buffer=True)
336 340 return document.text
337 341
338 342 def enable_win_unicode_console(self):
339 343 if sys.version_info >= (3, 6):
340 344 # Since PEP 528, Python uses the unicode APIs for the Windows
341 345 # console by default, so WUC shouldn't be needed.
342 346 return
343 347
344 348 import win_unicode_console
345 349 win_unicode_console.enable()
346 350
347 351 def init_io(self):
348 352 if sys.platform not in {'win32', 'cli'}:
349 353 return
350 354
351 355 self.enable_win_unicode_console()
352 356
353 357 import colorama
354 358 colorama.init()
355 359
356 360 # For some reason we make these wrappers around stdout/stderr.
357 361 # For now, we need to reset them so all output gets coloured.
358 362 # https://github.com/ipython/ipython/issues/8669
359 363 # io.std* are deprecated, but don't show our own deprecation warnings
360 364 # during initialization of the deprecated API.
361 365 with warnings.catch_warnings():
362 366 warnings.simplefilter('ignore', DeprecationWarning)
363 367 io.stdout = io.IOStream(sys.stdout)
364 368 io.stderr = io.IOStream(sys.stderr)
365 369
366 370 def init_magics(self):
367 371 super(TerminalInteractiveShell, self).init_magics()
368 372 self.register_magics(TerminalMagics)
369 373
370 374 def init_alias(self):
371 375 # The parent class defines aliases that can be safely used with any
372 376 # frontend.
373 377 super(TerminalInteractiveShell, self).init_alias()
374 378
375 379 # Now define aliases that only make sense on the terminal, because they
376 380 # need direct access to the console in a way that we can't emulate in
377 381 # GUI or web frontend
378 382 if os.name == 'posix':
379 383 for cmd in ['clear', 'more', 'less', 'man']:
380 384 self.alias_manager.soft_define_alias(cmd, cmd)
381 385
382 386
383 387 def __init__(self, *args, **kwargs):
384 388 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
385 389 self.init_prompt_toolkit_cli()
386 390 self.init_term_title()
387 391 self.keep_running = True
388 392
389 393 self.debugger_history = InMemoryHistory()
390 394
391 395 def ask_exit(self):
392 396 self.keep_running = False
393 397
394 398 rl_next_input = None
395 399
396 400 def pre_prompt(self):
397 401 if self.rl_next_input:
398 402 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
399 403 self.rl_next_input = None
400 404
401 405 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
402 406
403 407 if display_banner is not DISPLAY_BANNER_DEPRECATED:
404 408 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
405 409
406 410 self.keep_running = True
407 411 while self.keep_running:
408 412 print(self.separate_in, end='')
409 413
410 414 try:
411 415 code = self.prompt_for_code()
412 416 except EOFError:
413 417 if (not self.confirm_exit) \
414 418 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
415 419 self.ask_exit()
416 420
417 421 else:
418 422 if code:
419 423 self.run_cell(code, store_history=True)
420 424
421 425 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
422 426 # An extra layer of protection in case someone mashing Ctrl-C breaks
423 427 # out of our internal code.
424 428 if display_banner is not DISPLAY_BANNER_DEPRECATED:
425 429 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
426 430 while True:
427 431 try:
428 432 self.interact()
429 433 break
430 434 except KeyboardInterrupt as e:
431 435 print("\n%s escaped interact()\n" % type(e).__name__)
432 436 finally:
433 437 # An interrupt during the eventloop will mess up the
434 438 # internal state of the prompt_toolkit library.
435 439 # Stopping the eventloop fixes this, see
436 440 # https://github.com/ipython/ipython/pull/9867
437 441 if hasattr(self, '_eventloop'):
438 442 self._eventloop.stop()
439 443
440 444 _inputhook = None
441 445 def inputhook(self, context):
442 446 if self._inputhook is not None:
443 447 self._inputhook(context)
444 448
445 449 active_eventloop = None
446 450 def enable_gui(self, gui=None):
447 451 if gui:
448 452 self.active_eventloop, self._inputhook =\
449 453 get_inputhook_name_and_func(gui)
450 454 else:
451 455 self.active_eventloop = self._inputhook = None
452 456
453 457 # Run !system commands directly, not through pipes, so terminal programs
454 458 # work correctly.
455 459 system = InteractiveShell.system_raw
456 460
457 461 def auto_rewrite_input(self, cmd):
458 462 """Overridden from the parent class to use fancy rewriting prompt"""
459 463 if not self.show_rewritten_input:
460 464 return
461 465
462 466 tokens = self.prompts.rewrite_prompt_tokens()
463 467 if self.pt_cli:
464 468 self.pt_cli.print_tokens(tokens)
465 469 print(cmd)
466 470 else:
467 471 prompt = ''.join(s for t, s in tokens)
468 472 print(prompt, cmd, sep='')
469 473
470 474 _prompts_before = None
471 475 def switch_doctest_mode(self, mode):
472 476 """Switch prompts to classic for %doctest_mode"""
473 477 if mode:
474 478 self._prompts_before = self.prompts
475 479 self.prompts = ClassicPrompts(self)
476 480 elif self._prompts_before:
477 481 self.prompts = self._prompts_before
478 482 self._prompts_before = None
479 483 self._update_layout()
480 484
481 485
482 486 InteractiveShellABC.register(TerminalInteractiveShell)
483 487
484 488 if __name__ == '__main__':
485 489 TerminalInteractiveShell.instance().interact()
@@ -1,110 +1,113 b''
1 1 """prompt-toolkit utilities
2 2
3 3 Everything in this module is a private API,
4 4 not to be used outside IPython.
5 5 """
6 6
7 7 # Copyright (c) IPython Development Team.
8 8 # Distributed under the terms of the Modified BSD License.
9 9
10 10 import unicodedata
11 11 from wcwidth import wcwidth
12 12
13 13 from IPython.core.completer import IPCompleter
14 14 from prompt_toolkit.completion import Completer, Completion
15 15 from prompt_toolkit.layout.lexers import Lexer
16 16 from prompt_toolkit.layout.lexers import PygmentsLexer
17 17
18 18 import pygments.lexers as pygments_lexers
19 19
20 20
21 21 class IPythonPTCompleter(Completer):
22 22 """Adaptor to provide IPython completions to prompt_toolkit"""
23 def __init__(self, ipy_completer=None, shell=None):
23 def __init__(self, ipy_completer=None, shell=None, patch_stdout=None):
24 24 if shell is None and ipy_completer is None:
25 25 raise TypeError("Please pass shell=an InteractiveShell instance.")
26 26 self._ipy_completer = ipy_completer
27 27 self.shell = shell
28 if patch_stdout is None:
29 raise TypeError("Please pass patch_stdout")
30 self.patch_stdout = patch_stdout
28 31
29 32 @property
30 33 def ipy_completer(self):
31 34 if self._ipy_completer:
32 35 return self._ipy_completer
33 36 else:
34 37 return self.shell.Completer
35 38
36 39 def get_completions(self, document, complete_event):
37 40 if not document.current_line.strip():
38 41 return
39 42
40 43 # Some bits of our completion system may print stuff (e.g. if a module
41 44 # is imported). This context manager ensures that doesn't interfere with
42 45 # the prompt.
43 with self.shell.pt_cli.patch_stdout_context():
46 with self.patch_stdout():
44 47 used, matches = self.ipy_completer.complete(
45 48 line_buffer=document.current_line,
46 49 cursor_pos=document.cursor_position_col
47 50 )
48 51 start_pos = -len(used)
49 52 for m in matches:
50 53 if not m:
51 54 # Guard against completion machinery giving us an empty string.
52 55 continue
53 56
54 57 m = unicodedata.normalize('NFC', m)
55 58
56 59 # When the first character of the completion has a zero length,
57 60 # then it's probably a decomposed unicode character. E.g. caused by
58 61 # the "\dot" completion. Try to compose again with the previous
59 62 # character.
60 63 if wcwidth(m[0]) == 0:
61 64 if document.cursor_position + start_pos > 0:
62 65 char_before = document.text[document.cursor_position + start_pos - 1]
63 66 m = unicodedata.normalize('NFC', char_before + m)
64 67
65 68 # Yield the modified completion instead, if this worked.
66 69 if wcwidth(m[0:1]) == 1:
67 70 yield Completion(m, start_position=start_pos - 1)
68 71 continue
69 72
70 73 # TODO: Use Jedi to determine meta_text
71 74 # (Jedi currently has a bug that results in incorrect information.)
72 75 # meta_text = ''
73 76 # yield Completion(m, start_position=start_pos,
74 77 # display_meta=meta_text)
75 78 yield Completion(m, start_position=start_pos)
76 79
77 80 class IPythonPTLexer(Lexer):
78 81 """
79 82 Wrapper around PythonLexer and BashLexer.
80 83 """
81 84 def __init__(self):
82 85 l = pygments_lexers
83 86 self.python_lexer = PygmentsLexer(l.Python3Lexer)
84 87 self.shell_lexer = PygmentsLexer(l.BashLexer)
85 88
86 89 self.magic_lexers = {
87 90 'HTML': PygmentsLexer(l.HtmlLexer),
88 91 'html': PygmentsLexer(l.HtmlLexer),
89 92 'javascript': PygmentsLexer(l.JavascriptLexer),
90 93 'js': PygmentsLexer(l.JavascriptLexer),
91 94 'perl': PygmentsLexer(l.PerlLexer),
92 95 'ruby': PygmentsLexer(l.RubyLexer),
93 96 'latex': PygmentsLexer(l.TexLexer),
94 97 }
95 98
96 99 def lex_document(self, cli, document):
97 100 text = document.text.lstrip()
98 101
99 102 lexer = self.python_lexer
100 103
101 104 if text.startswith('!') or text.startswith('%%bash'):
102 105 lexer = self.shell_lexer
103 106
104 107 elif text.startswith('%%'):
105 108 for magic, l in self.magic_lexers.items():
106 109 if text.startswith('%%' + magic):
107 110 lexer = l
108 111 break
109 112
110 113 return lexer.lex_document(cli, document)
General Comments 0
You need to be logged in to leave comments. Login now