##// END OF EJS Templates
Only handle Ctrl-C when in main input buffer...
Thomas Kluyver -
Show More
@@ -1,289 +1,289 b''
1 1 """IPython terminal interface using prompt_toolkit in place of readline"""
2 2 from __future__ import print_function
3 3
4 4 import os
5 5 import sys
6 6
7 7 from IPython.core.interactiveshell import InteractiveShell
8 8 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
9 9 from IPython.utils.terminal import toggle_set_term_title, set_term_title
10 10 from IPython.utils.process import abbrev_cwd
11 11 from traitlets import Bool, Unicode, Dict
12 12
13 13 from prompt_toolkit.completion import Completer, Completion
14 14 from prompt_toolkit.enums import DEFAULT_BUFFER
15 15 from prompt_toolkit.filters import HasFocus, HasSelection, Condition
16 16 from prompt_toolkit.history import InMemoryHistory
17 17 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop
18 18 from prompt_toolkit.interface import CommandLineInterface
19 19 from prompt_toolkit.key_binding.manager import KeyBindingManager
20 20 from prompt_toolkit.key_binding.vi_state import InputMode
21 21 from prompt_toolkit.key_binding.bindings.vi import ViStateFilter
22 22 from prompt_toolkit.keys import Keys
23 23 from prompt_toolkit.layout.lexers import PygmentsLexer
24 24 from prompt_toolkit.styles import PygmentsStyle
25 25
26 26 from pygments.styles import get_style_by_name
27 27 from pygments.lexers import Python3Lexer, PythonLexer
28 28 from pygments.token import Token
29 29
30 30 from .pt_inputhooks import get_inputhook_func
31 31 from .interactiveshell import get_default_editor, TerminalMagics
32 32
33 33
34 34
35 35 class IPythonPTCompleter(Completer):
36 36 """Adaptor to provide IPython completions to prompt_toolkit"""
37 37 def __init__(self, ipy_completer):
38 38 self.ipy_completer = ipy_completer
39 39
40 40 def get_completions(self, document, complete_event):
41 41 if not document.current_line.strip():
42 42 return
43 43
44 44 used, matches = self.ipy_completer.complete(
45 45 line_buffer=document.current_line,
46 46 cursor_pos=document.cursor_position_col
47 47 )
48 48 start_pos = -len(used)
49 49 for m in matches:
50 50 yield Completion(m, start_position=start_pos)
51 51
52 52 class TerminalInteractiveShell(InteractiveShell):
53 53 colors_force = True
54 54
55 55 pt_cli = None
56 56
57 57 vi_mode = Bool(False, config=True,
58 58 help="Use vi style keybindings at the prompt",
59 59 )
60 60
61 61 mouse_support = Bool(False, config=True,
62 62 help="Enable mouse support in the prompt"
63 63 )
64 64
65 65 highlighting_style = Unicode('', config=True,
66 66 help="The name of a Pygments style to use for syntax highlighting"
67 67 )
68 68
69 69 highlighting_style_overrides = Dict(config=True,
70 70 help="Override highlighting format for specific tokens"
71 71 )
72 72
73 73 editor = Unicode(get_default_editor(), config=True,
74 74 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
75 75 )
76 76
77 77 term_title = Bool(True, config=True,
78 78 help="Automatically set the terminal title"
79 79 )
80 80 def _term_title_changed(self, name, new_value):
81 81 self.init_term_title()
82 82
83 83 def init_term_title(self):
84 84 # Enable or disable the terminal title.
85 85 if self.term_title:
86 86 toggle_set_term_title(True)
87 87 set_term_title('IPython: ' + abbrev_cwd())
88 88 else:
89 89 toggle_set_term_title(False)
90 90
91 91 def get_prompt_tokens(self, cli):
92 92 return [
93 93 (Token.Prompt, 'In ['),
94 94 (Token.PromptNum, str(self.execution_count)),
95 95 (Token.Prompt, ']: '),
96 96 ]
97 97
98 98 def get_continuation_tokens(self, cli, width):
99 99 return [
100 100 (Token.Prompt, (' ' * (width - 5)) + '...: '),
101 101 ]
102 102
103 103 def init_prompt_toolkit_cli(self):
104 104 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
105 105 # Fall back to plain non-interactive output for tests.
106 106 # This is very limited, and only accepts a single line.
107 107 def prompt():
108 108 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
109 109 self.prompt_for_code = prompt
110 110 return
111 111
112 112 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
113 113 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
114 114 # Ctrl+J == Enter, seemingly
115 115 @kbmanager.registry.add_binding(Keys.ControlJ,
116 116 filter=(HasFocus(DEFAULT_BUFFER)
117 117 & ~HasSelection()
118 118 & insert_mode
119 119 ))
120 120 def _(event):
121 121 b = event.current_buffer
122 122 d = b.document
123 123 if not (d.on_last_line or d.cursor_position_row >= d.line_count
124 124 - d.empty_line_count_at_the_end()):
125 125 b.newline()
126 126 return
127 127
128 128 status, indent = self.input_splitter.check_complete(d.text)
129 129
130 130 if (status != 'incomplete') and b.accept_action.is_returnable:
131 131 b.accept_action.validate_and_handle(event.cli, b)
132 132 else:
133 133 b.insert_text('\n' + (' ' * (indent or 0)))
134 134
135 @kbmanager.registry.add_binding(Keys.ControlC)
135 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
136 136 def _(event):
137 137 event.current_buffer.reset()
138 138
139 139 @Condition
140 140 def cursor_in_leading_ws(cli):
141 141 before = cli.application.buffer.document.current_line_before_cursor
142 142 return (not before) or before.isspace()
143 143
144 144 # Ctrl+I == Tab
145 145 @kbmanager.registry.add_binding(Keys.ControlI,
146 146 filter=(HasFocus(DEFAULT_BUFFER)
147 147 & ~HasSelection()
148 148 & insert_mode
149 149 & cursor_in_leading_ws
150 150 ))
151 151 def _(event):
152 152 event.current_buffer.insert_text(' ' * 4)
153 153
154 154 # Pre-populate history from IPython's history database
155 155 history = InMemoryHistory()
156 156 last_cell = u""
157 157 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
158 158 include_latest=True):
159 159 # Ignore blank lines and consecutive duplicates
160 160 cell = cell.rstrip()
161 161 if cell and (cell != last_cell):
162 162 history.append(cell)
163 163
164 164 style_overrides = {
165 165 Token.Prompt: '#009900',
166 166 Token.PromptNum: '#00ff00 bold',
167 167 }
168 168 if self.highlighting_style:
169 169 style_cls = get_style_by_name(self.highlighting_style)
170 170 else:
171 171 style_cls = get_style_by_name('default')
172 172 # The default theme needs to be visible on both a dark background
173 173 # and a light background, because we can't tell what the terminal
174 174 # looks like. These tweaks to the default theme help with that.
175 175 style_overrides.update({
176 176 Token.Number: '#007700',
177 177 Token.Operator: 'noinherit',
178 178 Token.String: '#BB6622',
179 179 Token.Name.Function: '#2080D0',
180 180 Token.Name.Class: 'bold #2080D0',
181 181 Token.Name.Namespace: 'bold #2080D0',
182 182 })
183 183 style_overrides.update(self.highlighting_style_overrides)
184 184 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
185 185 style_dict=style_overrides)
186 186
187 187 app = create_prompt_application(multiline=True,
188 188 lexer=PygmentsLexer(Python3Lexer if PY3 else PythonLexer),
189 189 get_prompt_tokens=self.get_prompt_tokens,
190 190 get_continuation_tokens=self.get_continuation_tokens,
191 191 key_bindings_registry=kbmanager.registry,
192 192 history=history,
193 193 completer=IPythonPTCompleter(self.Completer),
194 194 enable_history_search=True,
195 195 style=style,
196 196 mouse_support=self.mouse_support,
197 197 )
198 198
199 199 self.pt_cli = CommandLineInterface(app,
200 200 eventloop=create_eventloop(self.inputhook))
201 201
202 202 def prompt_for_code(self):
203 203 document = self.pt_cli.run(pre_run=self.pre_prompt)
204 204 return document.text
205 205
206 206 def init_io(self):
207 207 if sys.platform not in {'win32', 'cli'}:
208 208 return
209 209
210 210 import colorama
211 211 colorama.init()
212 212
213 213 # For some reason we make these wrappers around stdout/stderr.
214 214 # For now, we need to reset them so all output gets coloured.
215 215 # https://github.com/ipython/ipython/issues/8669
216 216 from IPython.utils import io
217 217 io.stdout = io.IOStream(sys.stdout)
218 218 io.stderr = io.IOStream(sys.stderr)
219 219
220 220 def init_magics(self):
221 221 super(TerminalInteractiveShell, self).init_magics()
222 222 self.register_magics(TerminalMagics)
223 223
224 224 def init_alias(self):
225 225 # The parent class defines aliases that can be safely used with any
226 226 # frontend.
227 227 super(TerminalInteractiveShell, self).init_alias()
228 228
229 229 # Now define aliases that only make sense on the terminal, because they
230 230 # need direct access to the console in a way that we can't emulate in
231 231 # GUI or web frontend
232 232 if os.name == 'posix':
233 233 for cmd in ['clear', 'more', 'less', 'man']:
234 234 self.alias_manager.soft_define_alias(cmd, cmd)
235 235
236 236
237 237 def __init__(self, *args, **kwargs):
238 238 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
239 239 self.init_prompt_toolkit_cli()
240 240 self.init_term_title()
241 241 self.keep_running = True
242 242
243 243 def ask_exit(self):
244 244 self.keep_running = False
245 245
246 246 rl_next_input = None
247 247
248 248 def pre_prompt(self):
249 249 if self.rl_next_input:
250 250 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
251 251 self.rl_next_input = None
252 252
253 253 def interact(self):
254 254 while self.keep_running:
255 255 print(self.separate_in, end='')
256 256
257 257 try:
258 258 code = self.prompt_for_code()
259 259 except EOFError:
260 260 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
261 261 self.ask_exit()
262 262
263 263 else:
264 264 if code:
265 265 self.run_cell(code, store_history=True)
266 266
267 267 def mainloop(self):
268 268 # An extra layer of protection in case someone mashing Ctrl-C breaks
269 269 # out of our internal code.
270 270 while True:
271 271 try:
272 272 self.interact()
273 273 break
274 274 except KeyboardInterrupt:
275 275 print("\nKeyboardInterrupt escaped interact()\n")
276 276
277 277 _inputhook = None
278 278 def inputhook(self, context):
279 279 if self._inputhook is not None:
280 280 self._inputhook(context)
281 281
282 282 def enable_gui(self, gui=None):
283 283 if gui:
284 284 self._inputhook = get_inputhook_func(gui)
285 285 else:
286 286 self._inputhook = None
287 287
288 288 if __name__ == '__main__':
289 289 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now