##// END OF EJS Templates
Merge pull request #11910 from pasenor/restore-terminal-title-xterm...
Matthias Bussonnier -
r25198:314c1a36 merge
parent child Browse files
Show More
@@ -1,556 +1,563 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 input
10 from IPython.utils.py3compat import 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, restore_term_title
12 from IPython.utils.process import abbrev_cwd
12 from IPython.utils.process import abbrev_cwd
13 from traitlets import (
13 from traitlets import (
14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
14 Bool, Unicode, Dict, Integer, observe, Instance, Type, default, Enum, Union,
15 Any, validate
15 Any, validate
16 )
16 )
17
17
18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
18 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
19 from prompt_toolkit.filters import (HasFocus, Condition, IsDone)
20 from prompt_toolkit.formatted_text import PygmentsTokens
20 from prompt_toolkit.formatted_text import PygmentsTokens
21 from prompt_toolkit.history import InMemoryHistory
21 from prompt_toolkit.history import InMemoryHistory
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
22 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
23 from prompt_toolkit.output import ColorDepth
23 from prompt_toolkit.output import ColorDepth
24 from prompt_toolkit.patch_stdout import patch_stdout
24 from prompt_toolkit.patch_stdout import patch_stdout
25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
25 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
26 from prompt_toolkit.styles import DynamicStyle, merge_styles
26 from prompt_toolkit.styles import DynamicStyle, merge_styles
27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
27 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
28
28
29 from pygments.styles import get_style_by_name
29 from pygments.styles import get_style_by_name
30 from pygments.style import Style
30 from pygments.style import Style
31 from pygments.token import Token
31 from pygments.token import Token
32
32
33 from .debugger import TerminalPdb, Pdb
33 from .debugger import TerminalPdb, Pdb
34 from .magics import TerminalMagics
34 from .magics import TerminalMagics
35 from .pt_inputhooks import get_inputhook_name_and_func
35 from .pt_inputhooks import get_inputhook_name_and_func
36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
36 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
37 from .ptutils import IPythonPTCompleter, IPythonPTLexer
38 from .shortcuts import create_ipython_shortcuts
38 from .shortcuts import create_ipython_shortcuts
39
39
40 DISPLAY_BANNER_DEPRECATED = object()
40 DISPLAY_BANNER_DEPRECATED = object()
41
41
42
42
43 class _NoStyle(Style): pass
43 class _NoStyle(Style): pass
44
44
45
45
46
46
47 _style_overrides_light_bg = {
47 _style_overrides_light_bg = {
48 Token.Prompt: '#0000ff',
48 Token.Prompt: '#0000ff',
49 Token.PromptNum: '#0000ee bold',
49 Token.PromptNum: '#0000ee bold',
50 Token.OutPrompt: '#cc0000',
50 Token.OutPrompt: '#cc0000',
51 Token.OutPromptNum: '#bb0000 bold',
51 Token.OutPromptNum: '#bb0000 bold',
52 }
52 }
53
53
54 _style_overrides_linux = {
54 _style_overrides_linux = {
55 Token.Prompt: '#00cc00',
55 Token.Prompt: '#00cc00',
56 Token.PromptNum: '#00bb00 bold',
56 Token.PromptNum: '#00bb00 bold',
57 Token.OutPrompt: '#cc0000',
57 Token.OutPrompt: '#cc0000',
58 Token.OutPromptNum: '#bb0000 bold',
58 Token.OutPromptNum: '#bb0000 bold',
59 }
59 }
60
60
61 def get_default_editor():
61 def get_default_editor():
62 try:
62 try:
63 return os.environ['EDITOR']
63 return os.environ['EDITOR']
64 except KeyError:
64 except KeyError:
65 pass
65 pass
66 except UnicodeError:
66 except UnicodeError:
67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
67 warn("$EDITOR environment variable is not pure ASCII. Using platform "
68 "default editor.")
68 "default editor.")
69
69
70 if os.name == 'posix':
70 if os.name == 'posix':
71 return 'vi' # the only one guaranteed to be there!
71 return 'vi' # the only one guaranteed to be there!
72 else:
72 else:
73 return 'notepad' # same in Windows!
73 return 'notepad' # same in Windows!
74
74
75 # conservatively check for tty
75 # conservatively check for tty
76 # overridden streams can result in things like:
76 # overridden streams can result in things like:
77 # - sys.stdin = None
77 # - sys.stdin = None
78 # - no isatty method
78 # - no isatty method
79 for _name in ('stdin', 'stdout', 'stderr'):
79 for _name in ('stdin', 'stdout', 'stderr'):
80 _stream = getattr(sys, _name)
80 _stream = getattr(sys, _name)
81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
81 if not _stream or not hasattr(_stream, 'isatty') or not _stream.isatty():
82 _is_tty = False
82 _is_tty = False
83 break
83 break
84 else:
84 else:
85 _is_tty = True
85 _is_tty = True
86
86
87
87
88 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
88 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
89
89
90 class TerminalInteractiveShell(InteractiveShell):
90 class TerminalInteractiveShell(InteractiveShell):
91 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
91 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
92 'to reserve for the completion menu'
92 'to reserve for the completion menu'
93 ).tag(config=True)
93 ).tag(config=True)
94
94
95 pt_app = None
95 pt_app = None
96 debugger_history = None
96 debugger_history = None
97
97
98 simple_prompt = Bool(_use_simple_prompt,
98 simple_prompt = Bool(_use_simple_prompt,
99 help="""Use `raw_input` for the REPL, without completion and prompt colors.
99 help="""Use `raw_input` for the REPL, without completion and prompt colors.
100
100
101 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
101 Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are:
102 IPython own testing machinery, and emacs inferior-shell integration through elpy.
102 IPython own testing machinery, and emacs inferior-shell integration through elpy.
103
103
104 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
104 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
105 environment variable is set, or the current terminal is not a tty."""
105 environment variable is set, or the current terminal is not a tty."""
106 ).tag(config=True)
106 ).tag(config=True)
107
107
108 @property
108 @property
109 def debugger_cls(self):
109 def debugger_cls(self):
110 return Pdb if self.simple_prompt else TerminalPdb
110 return Pdb if self.simple_prompt else TerminalPdb
111
111
112 confirm_exit = Bool(True,
112 confirm_exit = Bool(True,
113 help="""
113 help="""
114 Set to confirm when you try to exit IPython with an EOF (Control-D
114 Set to confirm when you try to exit IPython with an EOF (Control-D
115 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
115 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
116 you can force a direct exit without any confirmation.""",
116 you can force a direct exit without any confirmation.""",
117 ).tag(config=True)
117 ).tag(config=True)
118
118
119 editing_mode = Unicode('emacs',
119 editing_mode = Unicode('emacs',
120 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
120 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
121 ).tag(config=True)
121 ).tag(config=True)
122
122
123 mouse_support = Bool(False,
123 mouse_support = Bool(False,
124 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
124 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
125 ).tag(config=True)
125 ).tag(config=True)
126
126
127 # We don't load the list of styles for the help string, because loading
127 # We don't load the list of styles for the help string, because loading
128 # Pygments plugins takes time and can cause unexpected errors.
128 # Pygments plugins takes time and can cause unexpected errors.
129 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
129 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
130 help="""The name or class of a Pygments style to use for syntax
130 help="""The name or class of a Pygments style to use for syntax
131 highlighting. To see available styles, run `pygmentize -L styles`."""
131 highlighting. To see available styles, run `pygmentize -L styles`."""
132 ).tag(config=True)
132 ).tag(config=True)
133
133
134 @validate('editing_mode')
134 @validate('editing_mode')
135 def _validate_editing_mode(self, proposal):
135 def _validate_editing_mode(self, proposal):
136 if proposal['value'].lower() == 'vim':
136 if proposal['value'].lower() == 'vim':
137 proposal['value']= 'vi'
137 proposal['value']= 'vi'
138 elif proposal['value'].lower() == 'default':
138 elif proposal['value'].lower() == 'default':
139 proposal['value']= 'emacs'
139 proposal['value']= 'emacs'
140
140
141 if hasattr(EditingMode, proposal['value'].upper()):
141 if hasattr(EditingMode, proposal['value'].upper()):
142 return proposal['value'].lower()
142 return proposal['value'].lower()
143
143
144 return self.editing_mode
144 return self.editing_mode
145
145
146
146
147 @observe('editing_mode')
147 @observe('editing_mode')
148 def _editing_mode(self, change):
148 def _editing_mode(self, change):
149 u_mode = change.new.upper()
149 u_mode = change.new.upper()
150 if self.pt_app:
150 if self.pt_app:
151 self.pt_app.editing_mode = u_mode
151 self.pt_app.editing_mode = u_mode
152
152
153 @observe('highlighting_style')
153 @observe('highlighting_style')
154 @observe('colors')
154 @observe('colors')
155 def _highlighting_style_changed(self, change):
155 def _highlighting_style_changed(self, change):
156 self.refresh_style()
156 self.refresh_style()
157
157
158 def refresh_style(self):
158 def refresh_style(self):
159 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
159 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
160
160
161
161
162 highlighting_style_overrides = Dict(
162 highlighting_style_overrides = Dict(
163 help="Override highlighting format for specific tokens"
163 help="Override highlighting format for specific tokens"
164 ).tag(config=True)
164 ).tag(config=True)
165
165
166 true_color = Bool(False,
166 true_color = Bool(False,
167 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
167 help=("Use 24bit colors instead of 256 colors in prompt highlighting. "
168 "If your terminal supports true color, the following command "
168 "If your terminal supports true color, the following command "
169 "should print 'TRUECOLOR' in orange: "
169 "should print 'TRUECOLOR' in orange: "
170 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
170 "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")
171 ).tag(config=True)
171 ).tag(config=True)
172
172
173 editor = Unicode(get_default_editor(),
173 editor = Unicode(get_default_editor(),
174 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
174 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
175 ).tag(config=True)
175 ).tag(config=True)
176
176
177 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
177 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
178
178
179 prompts = Instance(Prompts)
179 prompts = Instance(Prompts)
180
180
181 @default('prompts')
181 @default('prompts')
182 def _prompts_default(self):
182 def _prompts_default(self):
183 return self.prompts_class(self)
183 return self.prompts_class(self)
184
184
185 # @observe('prompts')
185 # @observe('prompts')
186 # def _(self, change):
186 # def _(self, change):
187 # self._update_layout()
187 # self._update_layout()
188
188
189 @default('displayhook_class')
189 @default('displayhook_class')
190 def _displayhook_class_default(self):
190 def _displayhook_class_default(self):
191 return RichPromptDisplayHook
191 return RichPromptDisplayHook
192
192
193 term_title = Bool(True,
193 term_title = Bool(True,
194 help="Automatically set the terminal title"
194 help="Automatically set the terminal title"
195 ).tag(config=True)
195 ).tag(config=True)
196
196
197 term_title_format = Unicode("IPython: {cwd}",
197 term_title_format = Unicode("IPython: {cwd}",
198 help="Customize the terminal title format. This is a python format string. " +
198 help="Customize the terminal title format. This is a python format string. " +
199 "Available substitutions are: {cwd}."
199 "Available substitutions are: {cwd}."
200 ).tag(config=True)
200 ).tag(config=True)
201
201
202 display_completions = Enum(('column', 'multicolumn','readlinelike'),
202 display_completions = Enum(('column', 'multicolumn','readlinelike'),
203 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
203 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
204 "'readlinelike'. These options are for `prompt_toolkit`, see "
204 "'readlinelike'. These options are for `prompt_toolkit`, see "
205 "`prompt_toolkit` documentation for more information."
205 "`prompt_toolkit` documentation for more information."
206 ),
206 ),
207 default_value='multicolumn').tag(config=True)
207 default_value='multicolumn').tag(config=True)
208
208
209 highlight_matching_brackets = Bool(True,
209 highlight_matching_brackets = Bool(True,
210 help="Highlight matching brackets.",
210 help="Highlight matching brackets.",
211 ).tag(config=True)
211 ).tag(config=True)
212
212
213 extra_open_editor_shortcuts = Bool(False,
213 extra_open_editor_shortcuts = Bool(False,
214 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
214 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
215 "This is in addition to the F2 binding, which is always enabled."
215 "This is in addition to the F2 binding, which is always enabled."
216 ).tag(config=True)
216 ).tag(config=True)
217
217
218 handle_return = Any(None,
218 handle_return = Any(None,
219 help="Provide an alternative handler to be called when the user presses "
219 help="Provide an alternative handler to be called when the user presses "
220 "Return. This is an advanced option intended for debugging, which "
220 "Return. This is an advanced option intended for debugging, which "
221 "may be changed or removed in later releases."
221 "may be changed or removed in later releases."
222 ).tag(config=True)
222 ).tag(config=True)
223
223
224 enable_history_search = Bool(True,
224 enable_history_search = Bool(True,
225 help="Allows to enable/disable the prompt toolkit history search"
225 help="Allows to enable/disable the prompt toolkit history search"
226 ).tag(config=True)
226 ).tag(config=True)
227
227
228 prompt_includes_vi_mode = Bool(True,
228 prompt_includes_vi_mode = Bool(True,
229 help="Display the current vi mode (when using vi editing mode)."
229 help="Display the current vi mode (when using vi editing mode)."
230 ).tag(config=True)
230 ).tag(config=True)
231
231
232 @observe('term_title')
232 @observe('term_title')
233 def init_term_title(self, change=None):
233 def init_term_title(self, change=None):
234 # Enable or disable the terminal title.
234 # Enable or disable the terminal title.
235 if self.term_title:
235 if self.term_title:
236 toggle_set_term_title(True)
236 toggle_set_term_title(True)
237 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
237 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
238 else:
238 else:
239 toggle_set_term_title(False)
239 toggle_set_term_title(False)
240
240
241 def restore_term_title(self):
242 if self.term_title:
243 restore_term_title()
244
241 def init_display_formatter(self):
245 def init_display_formatter(self):
242 super(TerminalInteractiveShell, self).init_display_formatter()
246 super(TerminalInteractiveShell, self).init_display_formatter()
243 # terminal only supports plain text
247 # terminal only supports plain text
244 self.display_formatter.active_types = ['text/plain']
248 self.display_formatter.active_types = ['text/plain']
245 # disable `_ipython_display_`
249 # disable `_ipython_display_`
246 self.display_formatter.ipython_display_formatter.enabled = False
250 self.display_formatter.ipython_display_formatter.enabled = False
247
251
248 def init_prompt_toolkit_cli(self):
252 def init_prompt_toolkit_cli(self):
249 if self.simple_prompt:
253 if self.simple_prompt:
250 # Fall back to plain non-interactive output for tests.
254 # Fall back to plain non-interactive output for tests.
251 # This is very limited.
255 # This is very limited.
252 def prompt():
256 def prompt():
253 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
257 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
254 lines = [input(prompt_text)]
258 lines = [input(prompt_text)]
255 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
259 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
256 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
260 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
257 lines.append( input(prompt_continuation) )
261 lines.append( input(prompt_continuation) )
258 return '\n'.join(lines)
262 return '\n'.join(lines)
259 self.prompt_for_code = prompt
263 self.prompt_for_code = prompt
260 return
264 return
261
265
262 # Set up keyboard shortcuts
266 # Set up keyboard shortcuts
263 key_bindings = create_ipython_shortcuts(self)
267 key_bindings = create_ipython_shortcuts(self)
264
268
265 # Pre-populate history from IPython's history database
269 # Pre-populate history from IPython's history database
266 history = InMemoryHistory()
270 history = InMemoryHistory()
267 last_cell = u""
271 last_cell = u""
268 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
272 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
269 include_latest=True):
273 include_latest=True):
270 # Ignore blank lines and consecutive duplicates
274 # Ignore blank lines and consecutive duplicates
271 cell = cell.rstrip()
275 cell = cell.rstrip()
272 if cell and (cell != last_cell):
276 if cell and (cell != last_cell):
273 history.append_string(cell)
277 history.append_string(cell)
274 last_cell = cell
278 last_cell = cell
275
279
276 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
280 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
277 self.style = DynamicStyle(lambda: self._style)
281 self.style = DynamicStyle(lambda: self._style)
278
282
279 editing_mode = getattr(EditingMode, self.editing_mode.upper())
283 editing_mode = getattr(EditingMode, self.editing_mode.upper())
280
284
281 self.pt_app = PromptSession(
285 self.pt_app = PromptSession(
282 editing_mode=editing_mode,
286 editing_mode=editing_mode,
283 key_bindings=key_bindings,
287 key_bindings=key_bindings,
284 history=history,
288 history=history,
285 completer=IPythonPTCompleter(shell=self),
289 completer=IPythonPTCompleter(shell=self),
286 enable_history_search = self.enable_history_search,
290 enable_history_search = self.enable_history_search,
287 style=self.style,
291 style=self.style,
288 include_default_pygments_style=False,
292 include_default_pygments_style=False,
289 mouse_support=self.mouse_support,
293 mouse_support=self.mouse_support,
290 enable_open_in_editor=self.extra_open_editor_shortcuts,
294 enable_open_in_editor=self.extra_open_editor_shortcuts,
291 color_depth=self.color_depth,
295 color_depth=self.color_depth,
292 **self._extra_prompt_options())
296 **self._extra_prompt_options())
293
297
294 def _make_style_from_name_or_cls(self, name_or_cls):
298 def _make_style_from_name_or_cls(self, name_or_cls):
295 """
299 """
296 Small wrapper that make an IPython compatible style from a style name
300 Small wrapper that make an IPython compatible style from a style name
297
301
298 We need that to add style for prompt ... etc.
302 We need that to add style for prompt ... etc.
299 """
303 """
300 style_overrides = {}
304 style_overrides = {}
301 if name_or_cls == 'legacy':
305 if name_or_cls == 'legacy':
302 legacy = self.colors.lower()
306 legacy = self.colors.lower()
303 if legacy == 'linux':
307 if legacy == 'linux':
304 style_cls = get_style_by_name('monokai')
308 style_cls = get_style_by_name('monokai')
305 style_overrides = _style_overrides_linux
309 style_overrides = _style_overrides_linux
306 elif legacy == 'lightbg':
310 elif legacy == 'lightbg':
307 style_overrides = _style_overrides_light_bg
311 style_overrides = _style_overrides_light_bg
308 style_cls = get_style_by_name('pastie')
312 style_cls = get_style_by_name('pastie')
309 elif legacy == 'neutral':
313 elif legacy == 'neutral':
310 # The default theme needs to be visible on both a dark background
314 # The default theme needs to be visible on both a dark background
311 # and a light background, because we can't tell what the terminal
315 # and a light background, because we can't tell what the terminal
312 # looks like. These tweaks to the default theme help with that.
316 # looks like. These tweaks to the default theme help with that.
313 style_cls = get_style_by_name('default')
317 style_cls = get_style_by_name('default')
314 style_overrides.update({
318 style_overrides.update({
315 Token.Number: '#007700',
319 Token.Number: '#007700',
316 Token.Operator: 'noinherit',
320 Token.Operator: 'noinherit',
317 Token.String: '#BB6622',
321 Token.String: '#BB6622',
318 Token.Name.Function: '#2080D0',
322 Token.Name.Function: '#2080D0',
319 Token.Name.Class: 'bold #2080D0',
323 Token.Name.Class: 'bold #2080D0',
320 Token.Name.Namespace: 'bold #2080D0',
324 Token.Name.Namespace: 'bold #2080D0',
321 Token.Prompt: '#009900',
325 Token.Prompt: '#009900',
322 Token.PromptNum: '#ansibrightgreen bold',
326 Token.PromptNum: '#ansibrightgreen bold',
323 Token.OutPrompt: '#990000',
327 Token.OutPrompt: '#990000',
324 Token.OutPromptNum: '#ansibrightred bold',
328 Token.OutPromptNum: '#ansibrightred bold',
325 })
329 })
326
330
327 # Hack: Due to limited color support on the Windows console
331 # Hack: Due to limited color support on the Windows console
328 # the prompt colors will be wrong without this
332 # the prompt colors will be wrong without this
329 if os.name == 'nt':
333 if os.name == 'nt':
330 style_overrides.update({
334 style_overrides.update({
331 Token.Prompt: '#ansidarkgreen',
335 Token.Prompt: '#ansidarkgreen',
332 Token.PromptNum: '#ansigreen bold',
336 Token.PromptNum: '#ansigreen bold',
333 Token.OutPrompt: '#ansidarkred',
337 Token.OutPrompt: '#ansidarkred',
334 Token.OutPromptNum: '#ansired bold',
338 Token.OutPromptNum: '#ansired bold',
335 })
339 })
336 elif legacy =='nocolor':
340 elif legacy =='nocolor':
337 style_cls=_NoStyle
341 style_cls=_NoStyle
338 style_overrides = {}
342 style_overrides = {}
339 else :
343 else :
340 raise ValueError('Got unknown colors: ', legacy)
344 raise ValueError('Got unknown colors: ', legacy)
341 else :
345 else :
342 if isinstance(name_or_cls, str):
346 if isinstance(name_or_cls, str):
343 style_cls = get_style_by_name(name_or_cls)
347 style_cls = get_style_by_name(name_or_cls)
344 else:
348 else:
345 style_cls = name_or_cls
349 style_cls = name_or_cls
346 style_overrides = {
350 style_overrides = {
347 Token.Prompt: '#009900',
351 Token.Prompt: '#009900',
348 Token.PromptNum: '#ansibrightgreen bold',
352 Token.PromptNum: '#ansibrightgreen bold',
349 Token.OutPrompt: '#990000',
353 Token.OutPrompt: '#990000',
350 Token.OutPromptNum: '#ansibrightred bold',
354 Token.OutPromptNum: '#ansibrightred bold',
351 }
355 }
352 style_overrides.update(self.highlighting_style_overrides)
356 style_overrides.update(self.highlighting_style_overrides)
353 style = merge_styles([
357 style = merge_styles([
354 style_from_pygments_cls(style_cls),
358 style_from_pygments_cls(style_cls),
355 style_from_pygments_dict(style_overrides),
359 style_from_pygments_dict(style_overrides),
356 ])
360 ])
357
361
358 return style
362 return style
359
363
360 @property
364 @property
361 def pt_complete_style(self):
365 def pt_complete_style(self):
362 return {
366 return {
363 'multicolumn': CompleteStyle.MULTI_COLUMN,
367 'multicolumn': CompleteStyle.MULTI_COLUMN,
364 'column': CompleteStyle.COLUMN,
368 'column': CompleteStyle.COLUMN,
365 'readlinelike': CompleteStyle.READLINE_LIKE,
369 'readlinelike': CompleteStyle.READLINE_LIKE,
366 }[self.display_completions]
370 }[self.display_completions]
367
371
368 @property
372 @property
369 def color_depth(self):
373 def color_depth(self):
370 return (ColorDepth.TRUE_COLOR if self.true_color else None)
374 return (ColorDepth.TRUE_COLOR if self.true_color else None)
371
375
372 def _extra_prompt_options(self):
376 def _extra_prompt_options(self):
373 """
377 """
374 Return the current layout option for the current Terminal InteractiveShell
378 Return the current layout option for the current Terminal InteractiveShell
375 """
379 """
376 def get_message():
380 def get_message():
377 return PygmentsTokens(self.prompts.in_prompt_tokens())
381 return PygmentsTokens(self.prompts.in_prompt_tokens())
378
382
379 return {
383 return {
380 'complete_in_thread': False,
384 'complete_in_thread': False,
381 'lexer':IPythonPTLexer(),
385 'lexer':IPythonPTLexer(),
382 'reserve_space_for_menu':self.space_for_menu,
386 'reserve_space_for_menu':self.space_for_menu,
383 'message': get_message,
387 'message': get_message,
384 'prompt_continuation': (
388 'prompt_continuation': (
385 lambda width, lineno, is_soft_wrap:
389 lambda width, lineno, is_soft_wrap:
386 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
390 PygmentsTokens(self.prompts.continuation_prompt_tokens(width))),
387 'multiline': True,
391 'multiline': True,
388 'complete_style': self.pt_complete_style,
392 'complete_style': self.pt_complete_style,
389
393
390 # Highlight matching brackets, but only when this setting is
394 # Highlight matching brackets, but only when this setting is
391 # enabled, and only when the DEFAULT_BUFFER has the focus.
395 # enabled, and only when the DEFAULT_BUFFER has the focus.
392 'input_processors': [ConditionalProcessor(
396 'input_processors': [ConditionalProcessor(
393 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
397 processor=HighlightMatchingBracketProcessor(chars='[](){}'),
394 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
398 filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() &
395 Condition(lambda: self.highlight_matching_brackets))],
399 Condition(lambda: self.highlight_matching_brackets))],
396 'inputhook': self.inputhook,
400 'inputhook': self.inputhook,
397 }
401 }
398
402
399 def prompt_for_code(self):
403 def prompt_for_code(self):
400 if self.rl_next_input:
404 if self.rl_next_input:
401 default = self.rl_next_input
405 default = self.rl_next_input
402 self.rl_next_input = None
406 self.rl_next_input = None
403 else:
407 else:
404 default = ''
408 default = ''
405
409
406 with patch_stdout(raw=True):
410 with patch_stdout(raw=True):
407 text = self.pt_app.prompt(
411 text = self.pt_app.prompt(
408 default=default,
412 default=default,
409 # pre_run=self.pre_prompt,# reset_current_buffer=True,
413 # pre_run=self.pre_prompt,# reset_current_buffer=True,
410 **self._extra_prompt_options())
414 **self._extra_prompt_options())
411 return text
415 return text
412
416
413 def enable_win_unicode_console(self):
417 def enable_win_unicode_console(self):
414 if sys.version_info >= (3, 6):
418 if sys.version_info >= (3, 6):
415 # Since PEP 528, Python uses the unicode APIs for the Windows
419 # Since PEP 528, Python uses the unicode APIs for the Windows
416 # console by default, so WUC shouldn't be needed.
420 # console by default, so WUC shouldn't be needed.
417 return
421 return
418
422
419 import win_unicode_console
423 import win_unicode_console
420 win_unicode_console.enable()
424 win_unicode_console.enable()
421
425
422 def init_io(self):
426 def init_io(self):
423 if sys.platform not in {'win32', 'cli'}:
427 if sys.platform not in {'win32', 'cli'}:
424 return
428 return
425
429
426 self.enable_win_unicode_console()
430 self.enable_win_unicode_console()
427
431
428 import colorama
432 import colorama
429 colorama.init()
433 colorama.init()
430
434
431 # For some reason we make these wrappers around stdout/stderr.
435 # For some reason we make these wrappers around stdout/stderr.
432 # For now, we need to reset them so all output gets coloured.
436 # For now, we need to reset them so all output gets coloured.
433 # https://github.com/ipython/ipython/issues/8669
437 # https://github.com/ipython/ipython/issues/8669
434 # io.std* are deprecated, but don't show our own deprecation warnings
438 # io.std* are deprecated, but don't show our own deprecation warnings
435 # during initialization of the deprecated API.
439 # during initialization of the deprecated API.
436 with warnings.catch_warnings():
440 with warnings.catch_warnings():
437 warnings.simplefilter('ignore', DeprecationWarning)
441 warnings.simplefilter('ignore', DeprecationWarning)
438 io.stdout = io.IOStream(sys.stdout)
442 io.stdout = io.IOStream(sys.stdout)
439 io.stderr = io.IOStream(sys.stderr)
443 io.stderr = io.IOStream(sys.stderr)
440
444
441 def init_magics(self):
445 def init_magics(self):
442 super(TerminalInteractiveShell, self).init_magics()
446 super(TerminalInteractiveShell, self).init_magics()
443 self.register_magics(TerminalMagics)
447 self.register_magics(TerminalMagics)
444
448
445 def init_alias(self):
449 def init_alias(self):
446 # The parent class defines aliases that can be safely used with any
450 # The parent class defines aliases that can be safely used with any
447 # frontend.
451 # frontend.
448 super(TerminalInteractiveShell, self).init_alias()
452 super(TerminalInteractiveShell, self).init_alias()
449
453
450 # Now define aliases that only make sense on the terminal, because they
454 # Now define aliases that only make sense on the terminal, because they
451 # need direct access to the console in a way that we can't emulate in
455 # need direct access to the console in a way that we can't emulate in
452 # GUI or web frontend
456 # GUI or web frontend
453 if os.name == 'posix':
457 if os.name == 'posix':
454 for cmd in ('clear', 'more', 'less', 'man'):
458 for cmd in ('clear', 'more', 'less', 'man'):
455 self.alias_manager.soft_define_alias(cmd, cmd)
459 self.alias_manager.soft_define_alias(cmd, cmd)
456
460
457
461
458 def __init__(self, *args, **kwargs):
462 def __init__(self, *args, **kwargs):
459 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
463 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
460 self.init_prompt_toolkit_cli()
464 self.init_prompt_toolkit_cli()
461 self.init_term_title()
465 self.init_term_title()
462 self.keep_running = True
466 self.keep_running = True
463
467
464 self.debugger_history = InMemoryHistory()
468 self.debugger_history = InMemoryHistory()
465
469
466 def ask_exit(self):
470 def ask_exit(self):
467 self.keep_running = False
471 self.keep_running = False
468
472
469 rl_next_input = None
473 rl_next_input = None
470
474
471 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
475 def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED):
472
476
473 if display_banner is not DISPLAY_BANNER_DEPRECATED:
477 if display_banner is not DISPLAY_BANNER_DEPRECATED:
474 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
478 warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
475
479
476 self.keep_running = True
480 self.keep_running = True
477 while self.keep_running:
481 while self.keep_running:
478 print(self.separate_in, end='')
482 print(self.separate_in, end='')
479
483
480 try:
484 try:
481 code = self.prompt_for_code()
485 code = self.prompt_for_code()
482 except EOFError:
486 except EOFError:
483 if (not self.confirm_exit) \
487 if (not self.confirm_exit) \
484 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
488 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
485 self.ask_exit()
489 self.ask_exit()
486
490
487 else:
491 else:
488 if code:
492 if code:
489 self.run_cell(code, store_history=True)
493 self.run_cell(code, store_history=True)
490
494
491 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
495 def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED):
492 # An extra layer of protection in case someone mashing Ctrl-C breaks
496 # An extra layer of protection in case someone mashing Ctrl-C breaks
493 # out of our internal code.
497 # out of our internal code.
494 if display_banner is not DISPLAY_BANNER_DEPRECATED:
498 if display_banner is not DISPLAY_BANNER_DEPRECATED:
495 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
499 warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2)
496 while True:
500 while True:
497 try:
501 try:
498 self.interact()
502 self.interact()
499 break
503 break
500 except KeyboardInterrupt as e:
504 except KeyboardInterrupt as e:
501 print("\n%s escaped interact()\n" % type(e).__name__)
505 print("\n%s escaped interact()\n" % type(e).__name__)
502 finally:
506 finally:
503 # An interrupt during the eventloop will mess up the
507 # An interrupt during the eventloop will mess up the
504 # internal state of the prompt_toolkit library.
508 # internal state of the prompt_toolkit library.
505 # Stopping the eventloop fixes this, see
509 # Stopping the eventloop fixes this, see
506 # https://github.com/ipython/ipython/pull/9867
510 # https://github.com/ipython/ipython/pull/9867
507 if hasattr(self, '_eventloop'):
511 if hasattr(self, '_eventloop'):
508 self._eventloop.stop()
512 self._eventloop.stop()
509
513
514 self.restore_term_title()
515
516
510 _inputhook = None
517 _inputhook = None
511 def inputhook(self, context):
518 def inputhook(self, context):
512 if self._inputhook is not None:
519 if self._inputhook is not None:
513 self._inputhook(context)
520 self._inputhook(context)
514
521
515 active_eventloop = None
522 active_eventloop = None
516 def enable_gui(self, gui=None):
523 def enable_gui(self, gui=None):
517 if gui:
524 if gui:
518 self.active_eventloop, self._inputhook =\
525 self.active_eventloop, self._inputhook =\
519 get_inputhook_name_and_func(gui)
526 get_inputhook_name_and_func(gui)
520 else:
527 else:
521 self.active_eventloop = self._inputhook = None
528 self.active_eventloop = self._inputhook = None
522
529
523 # Run !system commands directly, not through pipes, so terminal programs
530 # Run !system commands directly, not through pipes, so terminal programs
524 # work correctly.
531 # work correctly.
525 system = InteractiveShell.system_raw
532 system = InteractiveShell.system_raw
526
533
527 def auto_rewrite_input(self, cmd):
534 def auto_rewrite_input(self, cmd):
528 """Overridden from the parent class to use fancy rewriting prompt"""
535 """Overridden from the parent class to use fancy rewriting prompt"""
529 if not self.show_rewritten_input:
536 if not self.show_rewritten_input:
530 return
537 return
531
538
532 tokens = self.prompts.rewrite_prompt_tokens()
539 tokens = self.prompts.rewrite_prompt_tokens()
533 if self.pt_app:
540 if self.pt_app:
534 print_formatted_text(PygmentsTokens(tokens), end='',
541 print_formatted_text(PygmentsTokens(tokens), end='',
535 style=self.pt_app.app.style)
542 style=self.pt_app.app.style)
536 print(cmd)
543 print(cmd)
537 else:
544 else:
538 prompt = ''.join(s for t, s in tokens)
545 prompt = ''.join(s for t, s in tokens)
539 print(prompt, cmd, sep='')
546 print(prompt, cmd, sep='')
540
547
541 _prompts_before = None
548 _prompts_before = None
542 def switch_doctest_mode(self, mode):
549 def switch_doctest_mode(self, mode):
543 """Switch prompts to classic for %doctest_mode"""
550 """Switch prompts to classic for %doctest_mode"""
544 if mode:
551 if mode:
545 self._prompts_before = self.prompts
552 self._prompts_before = self.prompts
546 self.prompts = ClassicPrompts(self)
553 self.prompts = ClassicPrompts(self)
547 elif self._prompts_before:
554 elif self._prompts_before:
548 self.prompts = self._prompts_before
555 self.prompts = self._prompts_before
549 self._prompts_before = None
556 self._prompts_before = None
550 # self._update_layout()
557 # self._update_layout()
551
558
552
559
553 InteractiveShellABC.register(TerminalInteractiveShell)
560 InteractiveShellABC.register(TerminalInteractiveShell)
554
561
555 if __name__ == '__main__':
562 if __name__ == '__main__':
556 TerminalInteractiveShell.instance().interact()
563 TerminalInteractiveShell.instance().interact()
@@ -1,110 +1,129 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Utilities for working with terminals.
3 Utilities for working with terminals.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian E. Granger
7 * Brian E. Granger
8 * Fernando Perez
8 * Fernando Perez
9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
9 * Alexander Belchenko (e-mail: bialix AT ukr.net)
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 import os
15 import os
16 import sys
16 import sys
17 import warnings
17 import warnings
18 from shutil import get_terminal_size as _get_terminal_size
18 from shutil import get_terminal_size as _get_terminal_size
19
19
20 # This variable is part of the expected API of the module:
20 # This variable is part of the expected API of the module:
21 ignore_termtitle = True
21 ignore_termtitle = True
22
22
23
23
24
24
25 if os.name == 'posix':
25 if os.name == 'posix':
26 def _term_clear():
26 def _term_clear():
27 os.system('clear')
27 os.system('clear')
28 elif sys.platform == 'win32':
28 elif sys.platform == 'win32':
29 def _term_clear():
29 def _term_clear():
30 os.system('cls')
30 os.system('cls')
31 else:
31 else:
32 def _term_clear():
32 def _term_clear():
33 pass
33 pass
34
34
35
35
36
36
37 def toggle_set_term_title(val):
37 def toggle_set_term_title(val):
38 """Control whether set_term_title is active or not.
38 """Control whether set_term_title is active or not.
39
39
40 set_term_title() allows writing to the console titlebar. In embedded
40 set_term_title() allows writing to the console titlebar. In embedded
41 widgets this can cause problems, so this call can be used to toggle it on
41 widgets this can cause problems, so this call can be used to toggle it on
42 or off as needed.
42 or off as needed.
43
43
44 The default state of the module is for the function to be disabled.
44 The default state of the module is for the function to be disabled.
45
45
46 Parameters
46 Parameters
47 ----------
47 ----------
48 val : bool
48 val : bool
49 If True, set_term_title() actually writes to the terminal (using the
49 If True, set_term_title() actually writes to the terminal (using the
50 appropriate platform-specific module). If False, it is a no-op.
50 appropriate platform-specific module). If False, it is a no-op.
51 """
51 """
52 global ignore_termtitle
52 global ignore_termtitle
53 ignore_termtitle = not(val)
53 ignore_termtitle = not(val)
54
54
55
55
56 def _set_term_title(*args,**kw):
56 def _set_term_title(*args,**kw):
57 """Dummy no-op."""
57 """Dummy no-op."""
58 pass
58 pass
59
59
60
60
61 def _restore_term_title():
62 pass
63
64
61 def _set_term_title_xterm(title):
65 def _set_term_title_xterm(title):
62 """ Change virtual terminal title in xterm-workalikes """
66 """ Change virtual terminal title in xterm-workalikes """
67 # save the current title to the xterm "stack"
68 sys.stdout.write('\033[22;0t')
63 sys.stdout.write('\033]0;%s\007' % title)
69 sys.stdout.write('\033]0;%s\007' % title)
64
70
71
72 def _restore_term_title_xterm():
73 sys.stdout.write('\033[23;0t')
74
75
65 if os.name == 'posix':
76 if os.name == 'posix':
66 TERM = os.environ.get('TERM','')
77 TERM = os.environ.get('TERM','')
67 if TERM.startswith('xterm'):
78 if TERM.startswith('xterm'):
68 _set_term_title = _set_term_title_xterm
79 _set_term_title = _set_term_title_xterm
80 _restore_term_title = _restore_term_title_xterm
69 elif sys.platform == 'win32':
81 elif sys.platform == 'win32':
70 try:
82 try:
71 import ctypes
83 import ctypes
72
84
73 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
85 SetConsoleTitleW = ctypes.windll.kernel32.SetConsoleTitleW
74 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
86 SetConsoleTitleW.argtypes = [ctypes.c_wchar_p]
75
87
76 def _set_term_title(title):
88 def _set_term_title(title):
77 """Set terminal title using ctypes to access the Win32 APIs."""
89 """Set terminal title using ctypes to access the Win32 APIs."""
78 SetConsoleTitleW(title)
90 SetConsoleTitleW(title)
79 except ImportError:
91 except ImportError:
80 def _set_term_title(title):
92 def _set_term_title(title):
81 """Set terminal title using the 'title' command."""
93 """Set terminal title using the 'title' command."""
82 global ignore_termtitle
94 global ignore_termtitle
83
95
84 try:
96 try:
85 # Cannot be on network share when issuing system commands
97 # Cannot be on network share when issuing system commands
86 curr = os.getcwd()
98 curr = os.getcwd()
87 os.chdir("C:")
99 os.chdir("C:")
88 ret = os.system("title " + title)
100 ret = os.system("title " + title)
89 finally:
101 finally:
90 os.chdir(curr)
102 os.chdir(curr)
91 if ret:
103 if ret:
92 # non-zero return code signals error, don't try again
104 # non-zero return code signals error, don't try again
93 ignore_termtitle = True
105 ignore_termtitle = True
94
106
95
107
96 def set_term_title(title):
108 def set_term_title(title):
97 """Set terminal title using the necessary platform-dependent calls."""
109 """Set terminal title using the necessary platform-dependent calls."""
98 if ignore_termtitle:
110 if ignore_termtitle:
99 return
111 return
100 _set_term_title(title)
112 _set_term_title(title)
101
113
102
114
115 def restore_term_title():
116 """Restore, if possible, terminal title to the original state"""
117 if ignore_termtitle:
118 return
119 _restore_term_title()
120
121
103 def freeze_term_title():
122 def freeze_term_title():
104 warnings.warn("This function is deprecated, use toggle_set_term_title()")
123 warnings.warn("This function is deprecated, use toggle_set_term_title()")
105 global ignore_termtitle
124 global ignore_termtitle
106 ignore_termtitle = True
125 ignore_termtitle = True
107
126
108
127
109 def get_terminal_size(defaultx=80, defaulty=25):
128 def get_terminal_size(defaultx=80, defaulty=25):
110 return _get_terminal_size((defaultx, defaulty))
129 return _get_terminal_size((defaultx, defaulty))
General Comments 0
You need to be logged in to leave comments. Login now