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