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