##// END OF EJS Templates
Write & borrow some inputhooks for prompt_toolkit
Thomas Kluyver -
Show More
@@ -0,0 +1,16 b''
1 import importlib
2 import os
3
4 aliases = {
5 'qt4': 'qt'
6 }
7
8 def get_inputhook_func(gui):
9 if gui in aliases:
10 return get_inputhook_func(aliases[gui])
11
12 if gui == 'qt5':
13 os.environ['QT_API'] = 'pyqt5'
14
15 mod = importlib.import_module('IPython.terminal.pt_inputhooks.'+gui)
16 return mod.inputhook
@@ -0,0 +1,55 b''
1 # Code borrowed from python-prompt-toolkit examples
2 # https://github.com/jonathanslenders/python-prompt-toolkit/blob/77cdcfbc7f4b4c34a9d2f9a34d422d7152f16209/examples/inputhook.py
3
4 # Copyright (c) 2014, Jonathan Slenders
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without modification,
8 # are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice, this
14 # list of conditions and the following disclaimer in the documentation and/or
15 # other materials provided with the distribution.
16 #
17 # * Neither the name of the {organization} nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 """
33 PyGTK input hook for prompt_toolkit.
34
35 Listens on the pipe prompt_toolkit sets up for a notification that it should
36 return control to the terminal event loop.
37 """
38
39 import gtk, gobject
40
41 def inputhook(context):
42 """
43 When the eventloop of prompt-toolkit is idle, call this inputhook.
44
45 This will run the GTK main loop until the file descriptor
46 `context.fileno()` becomes ready.
47
48 :param context: An `InputHookContext` instance.
49 """
50 def _main_quit(*a, **kw):
51 gtk.main_quit()
52 return False
53
54 gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit)
55 gtk.main()
@@ -0,0 +1,12 b''
1 """prompt_toolkit input hook for GTK 3
2 """
3
4 from gi.repository import Gtk, GLib
5
6 def _main_quit(*args, **kwargs):
7 Gtk.main_quit()
8 return False
9
10 def inputhook(context):
11 GLib.io_add_watch(context.fileno(), GLib.IO_IN, _main_quit)
12 Gtk.main()
@@ -0,0 +1,11 b''
1 from IPython.external.qt_for_kernel import QtCore, QtGui
2
3 def inputhook(context):
4 app = QtCore.QCoreApplication.instance()
5 if not app:
6 return
7 event_loop = QtCore.QEventLoop(app)
8 notifier = QtCore.QSocketNotifier(context.fileno(), QtCore.QSocketNotifier.Read)
9 notifier.setEnabled(True)
10 notifier.activated.connect(event_loop.exit)
11 event_loop.exec_()
@@ -0,0 +1,93 b''
1 # Code borrowed from ptpython
2 # https://github.com/jonathanslenders/ptpython/blob/86b71a89626114b18898a0af463978bdb32eeb70/ptpython/eventloop.py
3
4 # Copyright (c) 2015, Jonathan Slenders
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without modification,
8 # are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice, this
14 # list of conditions and the following disclaimer in the documentation and/or
15 # other materials provided with the distribution.
16 #
17 # * Neither the name of the {organization} nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
25 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 """
33 Wrapper around the eventloop that gives some time to the Tkinter GUI to process
34 events when it's loaded and while we are waiting for input at the REPL. This
35 way we don't block the UI of for instance ``turtle`` and other Tk libraries.
36
37 (Normally Tkinter registeres it's callbacks in ``PyOS_InputHook`` to integrate
38 in readline. ``prompt-toolkit`` doesn't understand that input hook, but this
39 will fix it for Tk.)
40 """
41 import time
42
43 import _tkinter
44 try:
45 import tkinter
46 except ImportError:
47 import Tkinter as tkinter # Python 2
48
49 def inputhook(inputhook_context):
50 """
51 Inputhook for Tk.
52 Run the Tk eventloop until prompt-toolkit needs to process the next input.
53 """
54 # Get the current TK application.
55 root = tkinter._default_root
56
57 def wait_using_filehandler():
58 """
59 Run the TK eventloop until the file handler that we got from the
60 inputhook becomes readable.
61 """
62 # Add a handler that sets the stop flag when `prompt-toolkit` has input
63 # to process.
64 stop = [False]
65 def done(*a):
66 stop[0] = True
67
68 root.createfilehandler(inputhook_context.fileno(), _tkinter.READABLE, done)
69
70 # Run the TK event loop as long as we don't receive input.
71 while root.dooneevent(_tkinter.ALL_EVENTS):
72 if stop[0]:
73 break
74
75 root.deletefilehandler(inputhook_context.fileno())
76
77 def wait_using_polling():
78 """
79 Windows TK doesn't support 'createfilehandler'.
80 So, run the TK eventloop and poll until input is ready.
81 """
82 while not inputhook_context.input_is_ready():
83 while root.dooneevent(_tkinter.ALL_EVENTS | _tkinter.DONT_WAIT):
84 pass
85 # Sleep to make the CPU idle, but not too long, so that the UI
86 # stays responsive.
87 time.sleep(.01)
88
89 if root is not None:
90 if hasattr(root, 'createfilehandler'):
91 wait_using_filehandler()
92 else:
93 wait_using_polling()
@@ -1,163 +1,174 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 from IPython.core.interactiveshell import InteractiveShell
4 from IPython.core.interactiveshell import InteractiveShell
5 from IPython.utils.py3compat import PY3
5 from IPython.utils.py3compat import PY3
6 from traitlets import Bool, Unicode, Dict
6 from traitlets import Bool, Unicode, Dict
7
7
8 from prompt_toolkit.completion import Completer, Completion
8 from prompt_toolkit.completion import Completer, Completion
9 from prompt_toolkit.enums import DEFAULT_BUFFER
9 from prompt_toolkit.enums import DEFAULT_BUFFER
10 from prompt_toolkit.filters import HasFocus, HasSelection
10 from prompt_toolkit.filters import HasFocus, HasSelection
11 from prompt_toolkit.history import InMemoryHistory
11 from prompt_toolkit.history import InMemoryHistory
12 from prompt_toolkit.shortcuts import create_prompt_application
12 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop
13 from prompt_toolkit.interface import CommandLineInterface
13 from prompt_toolkit.interface import CommandLineInterface
14 from prompt_toolkit.key_binding.manager import KeyBindingManager
14 from prompt_toolkit.key_binding.manager import KeyBindingManager
15 from prompt_toolkit.key_binding.vi_state import InputMode
15 from prompt_toolkit.key_binding.vi_state import InputMode
16 from prompt_toolkit.key_binding.bindings.vi import ViStateFilter
16 from prompt_toolkit.key_binding.bindings.vi import ViStateFilter
17 from prompt_toolkit.keys import Keys
17 from prompt_toolkit.keys import Keys
18 from prompt_toolkit.layout.lexers import PygmentsLexer
18 from prompt_toolkit.layout.lexers import PygmentsLexer
19 from prompt_toolkit.styles import PygmentsStyle
19 from prompt_toolkit.styles import PygmentsStyle
20
20
21 from pygments.styles import get_style_by_name
21 from pygments.styles import get_style_by_name
22 from pygments.lexers import Python3Lexer, PythonLexer
22 from pygments.lexers import Python3Lexer, PythonLexer
23 from pygments.token import Token
23 from pygments.token import Token
24
24
25 from .pt_inputhooks import get_inputhook_func
26
25
27
26 class IPythonPTCompleter(Completer):
28 class IPythonPTCompleter(Completer):
27 """Adaptor to provide IPython completions to prompt_toolkit"""
29 """Adaptor to provide IPython completions to prompt_toolkit"""
28 def __init__(self, ipy_completer):
30 def __init__(self, ipy_completer):
29 self.ipy_completer = ipy_completer
31 self.ipy_completer = ipy_completer
30
32
31 def get_completions(self, document, complete_event):
33 def get_completions(self, document, complete_event):
32 if not document.current_line.strip():
34 if not document.current_line.strip():
33 return
35 return
34
36
35 used, matches = self.ipy_completer.complete(
37 used, matches = self.ipy_completer.complete(
36 line_buffer=document.current_line,
38 line_buffer=document.current_line,
37 cursor_pos=document.cursor_position_col
39 cursor_pos=document.cursor_position_col
38 )
40 )
39 start_pos = -len(used)
41 start_pos = -len(used)
40 for m in matches:
42 for m in matches:
41 yield Completion(m, start_position=start_pos)
43 yield Completion(m, start_position=start_pos)
42
44
43
44 class PTInteractiveShell(InteractiveShell):
45 class PTInteractiveShell(InteractiveShell):
45 colors_force = True
46 colors_force = True
46
47
47 pt_cli = None
48 pt_cli = None
48
49
49 vi_mode = Bool(False, config=True,
50 vi_mode = Bool(False, config=True,
50 help="Use vi style keybindings at the prompt",
51 help="Use vi style keybindings at the prompt",
51 )
52 )
52
53
53 highlighting_style = Unicode('', config=True,
54 highlighting_style = Unicode('', config=True,
54 help="The name of a Pygments style to use for syntax highlighting"
55 help="The name of a Pygments style to use for syntax highlighting"
55 )
56 )
56
57
57 highlighting_style_overrides = Dict(config=True,
58 highlighting_style_overrides = Dict(config=True,
58 help="Override highlighting format for specific tokens"
59 help="Override highlighting format for specific tokens"
59 )
60 )
60
61
61 def get_prompt_tokens(self, cli):
62 def get_prompt_tokens(self, cli):
62 return [
63 return [
63 (Token.Prompt, 'In ['),
64 (Token.Prompt, 'In ['),
64 (Token.PromptNum, str(self.execution_count)),
65 (Token.PromptNum, str(self.execution_count)),
65 (Token.Prompt, ']: '),
66 (Token.Prompt, ']: '),
66 ]
67 ]
67
68
68 def get_continuation_tokens(self, cli, width):
69 def get_continuation_tokens(self, cli, width):
69 return [
70 return [
70 (Token.Prompt, (' ' * (width - 2)) + ': '),
71 (Token.Prompt, (' ' * (width - 2)) + ': '),
71 ]
72 ]
72
73
73
74 def init_prompt_toolkit_cli(self):
74 def init_prompt_toolkit_cli(self):
75 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
75 kbmanager = KeyBindingManager.for_prompt(enable_vi_mode=self.vi_mode)
76 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
76 insert_mode = ViStateFilter(kbmanager.get_vi_state, InputMode.INSERT)
77 # Ctrl+J == Enter, seemingly
77 # Ctrl+J == Enter, seemingly
78 @kbmanager.registry.add_binding(Keys.ControlJ,
78 @kbmanager.registry.add_binding(Keys.ControlJ,
79 filter=(HasFocus(DEFAULT_BUFFER)
79 filter=(HasFocus(DEFAULT_BUFFER)
80 & ~HasSelection()
80 & ~HasSelection()
81 & insert_mode
81 & insert_mode
82 ))
82 ))
83 def _(event):
83 def _(event):
84 b = event.current_buffer
84 b = event.current_buffer
85 if not b.document.on_last_line:
85 if not b.document.on_last_line:
86 b.newline()
86 b.newline()
87 return
87 return
88
88
89 status, indent = self.input_splitter.check_complete(b.document.text)
89 status, indent = self.input_splitter.check_complete(b.document.text)
90
90
91 if (status != 'incomplete') and b.accept_action.is_returnable:
91 if (status != 'incomplete') and b.accept_action.is_returnable:
92 b.accept_action.validate_and_handle(event.cli, b)
92 b.accept_action.validate_and_handle(event.cli, b)
93 else:
93 else:
94 b.insert_text('\n' + (' ' * (indent or 0)))
94 b.insert_text('\n' + (' ' * (indent or 0)))
95
95
96 @kbmanager.registry.add_binding(Keys.ControlC)
96 @kbmanager.registry.add_binding(Keys.ControlC)
97 def _(event):
97 def _(event):
98 event.current_buffer.reset()
98 event.current_buffer.reset()
99
99
100 # Pre-populate history from IPython's history database
100 # Pre-populate history from IPython's history database
101 history = InMemoryHistory()
101 history = InMemoryHistory()
102 last_cell = u""
102 last_cell = u""
103 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
103 for _, _, cell in self.history_manager.get_tail(self.history_load_length,
104 include_latest=True):
104 include_latest=True):
105 # Ignore blank lines and consecutive duplicates
105 # Ignore blank lines and consecutive duplicates
106 cell = cell.rstrip()
106 cell = cell.rstrip()
107 if cell and (cell != last_cell):
107 if cell and (cell != last_cell):
108 history.append(cell)
108 history.append(cell)
109
109
110 style_overrides = {
110 style_overrides = {
111 Token.Prompt: '#009900',
111 Token.Prompt: '#009900',
112 Token.PromptNum: '#00ff00 bold',
112 Token.PromptNum: '#00ff00 bold',
113 }
113 }
114 if self.highlighting_style:
114 if self.highlighting_style:
115 style_cls = get_style_by_name(self.highlighting_style)
115 style_cls = get_style_by_name(self.highlighting_style)
116 else:
116 else:
117 style_cls = get_style_by_name('default')
117 style_cls = get_style_by_name('default')
118 style_overrides.update({
118 style_overrides.update({
119 Token.Number: '#007700',
119 Token.Number: '#007700',
120 Token.Operator: 'noinherit',
120 Token.Operator: 'noinherit',
121 Token.String: '#BB6622',
121 Token.String: '#BB6622',
122 })
122 })
123 style_overrides.update(self.highlighting_style_overrides)
123 style_overrides.update(self.highlighting_style_overrides)
124 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
124 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
125 style_dict=style_overrides)
125 style_dict=style_overrides)
126
126
127 app = create_prompt_application(multiline=True,
127 app = create_prompt_application(multiline=True,
128 lexer=PygmentsLexer(Python3Lexer if PY3 else PythonLexer),
128 lexer=PygmentsLexer(Python3Lexer if PY3 else PythonLexer),
129 get_prompt_tokens=self.get_prompt_tokens,
129 get_prompt_tokens=self.get_prompt_tokens,
130 get_continuation_tokens=self.get_continuation_tokens,
130 get_continuation_tokens=self.get_continuation_tokens,
131 key_bindings_registry=kbmanager.registry,
131 key_bindings_registry=kbmanager.registry,
132 history=history,
132 history=history,
133 completer=IPythonPTCompleter(self.Completer),
133 completer=IPythonPTCompleter(self.Completer),
134 enable_history_search=True,
134 enable_history_search=True,
135 style=style,
135 style=style,
136 )
136 )
137
137
138 self.pt_cli = CommandLineInterface(app)
138 self.pt_cli = CommandLineInterface(app,
139 eventloop=create_eventloop(self.inputhook))
139
140
140 def __init__(self, *args, **kwargs):
141 def __init__(self, *args, **kwargs):
141 super(PTInteractiveShell, self).__init__(*args, **kwargs)
142 super(PTInteractiveShell, self).__init__(*args, **kwargs)
142 self.init_prompt_toolkit_cli()
143 self.init_prompt_toolkit_cli()
143 self.keep_running = True
144 self.keep_running = True
144
145
145 def ask_exit(self):
146 def ask_exit(self):
146 self.keep_running = False
147 self.keep_running = False
147
148
148 def interact(self):
149 def interact(self):
149 while self.keep_running:
150 while self.keep_running:
150 print(self.separate_in, end='')
151 print(self.separate_in, end='')
151 try:
152 try:
152 document = self.pt_cli.run()
153 document = self.pt_cli.run()
153 except EOFError:
154 except EOFError:
154 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
155 if self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
155 self.ask_exit()
156 self.ask_exit()
156
157
157 else:
158 else:
158 if document:
159 if document:
159 self.run_cell(document.text, store_history=True)
160 self.run_cell(document.text, store_history=True)
160
161
162 _inputhook = None
163 def inputhook(self, context):
164 if self._inputhook is not None:
165 self._inputhook(context)
166
167 def enable_gui(self, gui=None):
168 if gui:
169 self._inputhook = get_inputhook_func(gui)
170 else:
171 self._inputhook = None
161
172
162 if __name__ == '__main__':
173 if __name__ == '__main__':
163 PTInteractiveShell.instance().interact()
174 PTInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now