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