##// END OF EJS Templates
update Qt to use KernelClient
MinRK -
Show More
@@ -1,121 +1,119 b''
1 """ Defines a convenient mix-in class for implementing Qt frontends.
1 """ Defines a convenient mix-in class for implementing Qt frontends.
2 """
2 """
3
3
4 class BaseFrontendMixin(object):
4 class BaseFrontendMixin(object):
5 """ A mix-in class for implementing Qt frontends.
5 """ A mix-in class for implementing Qt frontends.
6
6
7 To handle messages of a particular type, frontends need only define an
7 To handle messages of a particular type, frontends need only define an
8 appropriate handler method. For example, to handle 'stream' messaged, define
8 appropriate handler method. For example, to handle 'stream' messaged, define
9 a '_handle_stream(msg)' method.
9 a '_handle_stream(msg)' method.
10 """
10 """
11
11
12 #---------------------------------------------------------------------------
12 #---------------------------------------------------------------------------
13 # 'BaseFrontendMixin' concrete interface
13 # 'BaseFrontendMixin' concrete interface
14 #---------------------------------------------------------------------------
14 #---------------------------------------------------------------------------
15
15
16 def _get_kernel_manager(self):
16 def _get_kernel_client(self):
17 """ Returns the current kernel manager.
17 """Returns the current kernel client.
18 """
18 """
19 return self._kernel_manager
19 return self._kernel_client
20
20
21 def _set_kernel_manager(self, kernel_manager):
21 def _set_kernel_client(self, kernel_client):
22 """ Disconnect from the current kernel manager (if any) and set a new
22 """Disconnect from the current kernel client (if any) and set a new
23 kernel manager.
23 kernel client.
24 """
24 """
25 # Disconnect the old kernel manager, if necessary.
25 # Disconnect the old kernel client, if necessary.
26 old_manager = self._kernel_manager
26 old_client = self._kernel_client
27 if old_manager is not None:
27 if old_client is not None:
28 old_manager.started_kernel.disconnect(self._started_kernel)
28 old_client.started_channels.disconnect(self._started_channels)
29 old_manager.started_channels.disconnect(self._started_channels)
29 old_client.stopped_channels.disconnect(self._stopped_channels)
30 old_manager.stopped_channels.disconnect(self._stopped_channels)
30
31
31 # Disconnect the old kernel client's channels.
32 # Disconnect the old kernel manager's channels.
32 old_client.iopub_channel.message_received.disconnect(self._dispatch)
33 old_manager.iopub_channel.message_received.disconnect(self._dispatch)
33 old_client.shell_channel.message_received.disconnect(self._dispatch)
34 old_manager.shell_channel.message_received.disconnect(self._dispatch)
34 old_client.stdin_channel.message_received.disconnect(self._dispatch)
35 old_manager.stdin_channel.message_received.disconnect(self._dispatch)
35 old_client.hb_channel.kernel_died.disconnect(
36 old_manager.hb_channel.kernel_died.disconnect(
37 self._handle_kernel_died)
36 self._handle_kernel_died)
38
37
39 # Handle the case where the old kernel manager is still listening.
38 # Handle the case where the old kernel client is still listening.
40 if old_manager.channels_running:
39 if old_client.channels_running:
41 self._stopped_channels()
40 self._stopped_channels()
42
41
43 # Set the new kernel manager.
42 # Set the new kernel client.
44 self._kernel_manager = kernel_manager
43 self._kernel_client = kernel_client
45 if kernel_manager is None:
44 if kernel_client is None:
46 return
45 return
47
46
48 # Connect the new kernel manager.
47 # Connect the new kernel client.
49 kernel_manager.started_kernel.connect(self._started_kernel)
48 kernel_client.started_channels.connect(self._started_channels)
50 kernel_manager.started_channels.connect(self._started_channels)
49 kernel_client.stopped_channels.connect(self._stopped_channels)
51 kernel_manager.stopped_channels.connect(self._stopped_channels)
52
50
53 # Connect the new kernel manager's channels.
51 # Connect the new kernel client's channels.
54 kernel_manager.iopub_channel.message_received.connect(self._dispatch)
52 kernel_client.iopub_channel.message_received.connect(self._dispatch)
55 kernel_manager.shell_channel.message_received.connect(self._dispatch)
53 kernel_client.shell_channel.message_received.connect(self._dispatch)
56 kernel_manager.stdin_channel.message_received.connect(self._dispatch)
54 kernel_client.stdin_channel.message_received.connect(self._dispatch)
57 kernel_manager.hb_channel.kernel_died.connect(self._handle_kernel_died)
55 kernel_client.hb_channel.kernel_died.connect(self._handle_kernel_died)
58
56
59 # Handle the case where the kernel manager started channels before
57 # Handle the case where the kernel client started channels before
60 # we connected.
58 # we connected.
61 if kernel_manager.channels_running:
59 if kernel_client.channels_running:
62 self._started_channels()
60 self._started_channels()
63
61
64 kernel_manager = property(_get_kernel_manager, _set_kernel_manager)
62 kernel_client = property(_get_kernel_client, _set_kernel_client)
65
63
66 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
67 # 'BaseFrontendMixin' abstract interface
65 # 'BaseFrontendMixin' abstract interface
68 #---------------------------------------------------------------------------
66 #---------------------------------------------------------------------------
69
67
70 def _handle_kernel_died(self, since_last_heartbeat):
68 def _handle_kernel_died(self, since_last_heartbeat):
71 """ This is called when the ``kernel_died`` signal is emitted.
69 """ This is called when the ``kernel_died`` signal is emitted.
72
70
73 This method is called when the kernel heartbeat has not been
71 This method is called when the kernel heartbeat has not been
74 active for a certain amount of time. The typical action will be to
72 active for a certain amount of time. The typical action will be to
75 give the user the option of restarting the kernel.
73 give the user the option of restarting the kernel.
76
74
77 Parameters
75 Parameters
78 ----------
76 ----------
79 since_last_heartbeat : float
77 since_last_heartbeat : float
80 The time since the heartbeat was last received.
78 The time since the heartbeat was last received.
81 """
79 """
82
80
83 def _started_kernel(self):
81 def _started_kernel(self):
84 """Called when the KernelManager starts (or restarts) the kernel subprocess.
82 """Called when the KernelManager starts (or restarts) the kernel subprocess.
85 Channels may or may not be running at this point.
83 Channels may or may not be running at this point.
86 """
84 """
87
85
88 def _started_channels(self):
86 def _started_channels(self):
89 """ Called when the KernelManager channels have started listening or
87 """ Called when the KernelManager channels have started listening or
90 when the frontend is assigned an already listening KernelManager.
88 when the frontend is assigned an already listening KernelManager.
91 """
89 """
92
90
93 def _stopped_channels(self):
91 def _stopped_channels(self):
94 """ Called when the KernelManager channels have stopped listening or
92 """ Called when the KernelManager channels have stopped listening or
95 when a listening KernelManager is removed from the frontend.
93 when a listening KernelManager is removed from the frontend.
96 """
94 """
97
95
98 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
99 # 'BaseFrontendMixin' protected interface
97 # 'BaseFrontendMixin' protected interface
100 #---------------------------------------------------------------------------
98 #---------------------------------------------------------------------------
101
99
102 def _dispatch(self, msg):
100 def _dispatch(self, msg):
103 """ Calls the frontend handler associated with the message type of the
101 """ Calls the frontend handler associated with the message type of the
104 given message.
102 given message.
105 """
103 """
106 msg_type = msg['header']['msg_type']
104 msg_type = msg['header']['msg_type']
107 handler = getattr(self, '_handle_' + msg_type, None)
105 handler = getattr(self, '_handle_' + msg_type, None)
108 if handler:
106 if handler:
109 handler(msg)
107 handler(msg)
110
108
111 def _is_from_this_session(self, msg):
109 def _is_from_this_session(self, msg):
112 """ Returns whether a reply from the kernel originated from a request
110 """ Returns whether a reply from the kernel originated from a request
113 from this frontend.
111 from this frontend.
114 """
112 """
115 session = self._kernel_manager.session.session
113 session = self._kernel_client.session.session
116 parent = msg['parent_header']
114 parent = msg['parent_header']
117 if not parent:
115 if not parent:
118 # if the message has no parent, assume it is meant for all frontends
116 # if the message has no parent, assume it is meant for all frontends
119 return True
117 return True
120 else:
118 else:
121 return parent.get('session') == session
119 return parent.get('session') == session
@@ -1,32 +1,37 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelClient that provides signals and slots.
2 """
2 """
3
3
4 # Local imports.
4 # Local imports
5 from IPython.utils.traitlets import Type
5 from IPython.utils.traitlets import Type
6 from IPython.kernel.kernelmanager import ShellChannel, IOPubChannel, \
6 from IPython.kernel.channels import (
7 StdInChannel, HBChannel, KernelManager
7 ShellChannel, IOPubChannel, StdInChannel, HBChannel
8 from base_kernelmanager import QtShellChannelMixin, QtIOPubChannelMixin, \
8 )
9 QtStdInChannelMixin, QtHBChannelMixin, QtKernelManagerMixin
9 from IPython.kernel import KernelClient
10
10
11 from .kernel_mixins import (
12 QtShellChannelMixin, QtIOPubChannelMixin,
13 QtStdInChannelMixin, QtHBChannelMixin,
14 QtKernelClientMixin
15 )
11
16
12 class QtShellChannel(QtShellChannelMixin, ShellChannel):
17 class QtShellChannel(QtShellChannelMixin, ShellChannel):
13 pass
18 pass
14
19
15 class QtIOPubChannel(QtIOPubChannelMixin, IOPubChannel):
20 class QtIOPubChannel(QtIOPubChannelMixin, IOPubChannel):
16 pass
21 pass
17
22
18 class QtStdInChannel(QtStdInChannelMixin, StdInChannel):
23 class QtStdInChannel(QtStdInChannelMixin, StdInChannel):
19 pass
24 pass
20
25
21 class QtHBChannel(QtHBChannelMixin, HBChannel):
26 class QtHBChannel(QtHBChannelMixin, HBChannel):
22 pass
27 pass
23
28
24
29
25 class QtKernelManager(QtKernelManagerMixin, KernelManager):
30 class QtKernelClient(QtKernelClientMixin, KernelClient):
26 """ A KernelManager that provides signals and slots.
31 """ A KernelClient that provides signals and slots.
27 """
32 """
28
33
29 iopub_channel_class = Type(QtIOPubChannel)
34 iopub_channel_class = Type(QtIOPubChannel)
30 shell_channel_class = Type(QtShellChannel)
35 shell_channel_class = Type(QtShellChannel)
31 stdin_channel_class = Type(QtStdInChannel)
36 stdin_channel_class = Type(QtStdInChannel)
32 hb_channel_class = Type(QtHBChannel)
37 hb_channel_class = Type(QtHBChannel)
@@ -1,771 +1,772 b''
1 from __future__ import print_function
1 from __future__ import print_function
2
2
3 # Standard library imports
3 # Standard library imports
4 from collections import namedtuple
4 from collections import namedtuple
5 import sys
5 import sys
6 import time
6 import time
7 import uuid
7 import uuid
8
8
9 # System library imports
9 # System library imports
10 from pygments.lexers import PythonLexer
10 from pygments.lexers import PythonLexer
11 from IPython.external import qt
11 from IPython.external import qt
12 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtCore, QtGui
13
13
14 # Local imports
14 # Local imports
15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
15 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
16 from IPython.core.inputtransformer import classic_prompt
16 from IPython.core.inputtransformer import classic_prompt
17 from IPython.core.oinspect import call_tip
17 from IPython.core.oinspect import call_tip
18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
18 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
19 from IPython.utils.traitlets import Bool, Instance, Unicode
19 from IPython.utils.traitlets import Bool, Instance, Unicode
20 from bracket_matcher import BracketMatcher
20 from bracket_matcher import BracketMatcher
21 from call_tip_widget import CallTipWidget
21 from call_tip_widget import CallTipWidget
22 from completion_lexer import CompletionLexer
22 from completion_lexer import CompletionLexer
23 from history_console_widget import HistoryConsoleWidget
23 from history_console_widget import HistoryConsoleWidget
24 from pygments_highlighter import PygmentsHighlighter
24 from pygments_highlighter import PygmentsHighlighter
25
25
26
26
27 class FrontendHighlighter(PygmentsHighlighter):
27 class FrontendHighlighter(PygmentsHighlighter):
28 """ A PygmentsHighlighter that understands and ignores prompts.
28 """ A PygmentsHighlighter that understands and ignores prompts.
29 """
29 """
30
30
31 def __init__(self, frontend):
31 def __init__(self, frontend):
32 super(FrontendHighlighter, self).__init__(frontend._control.document())
32 super(FrontendHighlighter, self).__init__(frontend._control.document())
33 self._current_offset = 0
33 self._current_offset = 0
34 self._frontend = frontend
34 self._frontend = frontend
35 self.highlighting_on = False
35 self.highlighting_on = False
36
36
37 def highlightBlock(self, string):
37 def highlightBlock(self, string):
38 """ Highlight a block of text. Reimplemented to highlight selectively.
38 """ Highlight a block of text. Reimplemented to highlight selectively.
39 """
39 """
40 if not self.highlighting_on:
40 if not self.highlighting_on:
41 return
41 return
42
42
43 # The input to this function is a unicode string that may contain
43 # The input to this function is a unicode string that may contain
44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
44 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 # the string as plain text so we can compare it.
45 # the string as plain text so we can compare it.
46 current_block = self.currentBlock()
46 current_block = self.currentBlock()
47 string = self._frontend._get_block_plain_text(current_block)
47 string = self._frontend._get_block_plain_text(current_block)
48
48
49 # Decide whether to check for the regular or continuation prompt.
49 # Decide whether to check for the regular or continuation prompt.
50 if current_block.contains(self._frontend._prompt_pos):
50 if current_block.contains(self._frontend._prompt_pos):
51 prompt = self._frontend._prompt
51 prompt = self._frontend._prompt
52 else:
52 else:
53 prompt = self._frontend._continuation_prompt
53 prompt = self._frontend._continuation_prompt
54
54
55 # Only highlight if we can identify a prompt, but make sure not to
55 # Only highlight if we can identify a prompt, but make sure not to
56 # highlight the prompt.
56 # highlight the prompt.
57 if string.startswith(prompt):
57 if string.startswith(prompt):
58 self._current_offset = len(prompt)
58 self._current_offset = len(prompt)
59 string = string[len(prompt):]
59 string = string[len(prompt):]
60 super(FrontendHighlighter, self).highlightBlock(string)
60 super(FrontendHighlighter, self).highlightBlock(string)
61
61
62 def rehighlightBlock(self, block):
62 def rehighlightBlock(self, block):
63 """ Reimplemented to temporarily enable highlighting if disabled.
63 """ Reimplemented to temporarily enable highlighting if disabled.
64 """
64 """
65 old = self.highlighting_on
65 old = self.highlighting_on
66 self.highlighting_on = True
66 self.highlighting_on = True
67 super(FrontendHighlighter, self).rehighlightBlock(block)
67 super(FrontendHighlighter, self).rehighlightBlock(block)
68 self.highlighting_on = old
68 self.highlighting_on = old
69
69
70 def setFormat(self, start, count, format):
70 def setFormat(self, start, count, format):
71 """ Reimplemented to highlight selectively.
71 """ Reimplemented to highlight selectively.
72 """
72 """
73 start += self._current_offset
73 start += self._current_offset
74 super(FrontendHighlighter, self).setFormat(start, count, format)
74 super(FrontendHighlighter, self).setFormat(start, count, format)
75
75
76
76
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
77 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 """ A Qt frontend for a generic Python kernel.
78 """ A Qt frontend for a generic Python kernel.
79 """
79 """
80
80
81 # The text to show when the kernel is (re)started.
81 # The text to show when the kernel is (re)started.
82 banner = Unicode()
82 banner = Unicode()
83
83
84 # An option and corresponding signal for overriding the default kernel
84 # An option and corresponding signal for overriding the default kernel
85 # interrupt behavior.
85 # interrupt behavior.
86 custom_interrupt = Bool(False)
86 custom_interrupt = Bool(False)
87 custom_interrupt_requested = QtCore.Signal()
87 custom_interrupt_requested = QtCore.Signal()
88
88
89 # An option and corresponding signals for overriding the default kernel
89 # An option and corresponding signals for overriding the default kernel
90 # restart behavior.
90 # restart behavior.
91 custom_restart = Bool(False)
91 custom_restart = Bool(False)
92 custom_restart_kernel_died = QtCore.Signal(float)
92 custom_restart_kernel_died = QtCore.Signal(float)
93 custom_restart_requested = QtCore.Signal()
93 custom_restart_requested = QtCore.Signal()
94
94
95 # Whether to automatically show calltips on open-parentheses.
95 # Whether to automatically show calltips on open-parentheses.
96 enable_calltips = Bool(True, config=True,
96 enable_calltips = Bool(True, config=True,
97 help="Whether to draw information calltips on open-parentheses.")
97 help="Whether to draw information calltips on open-parentheses.")
98
98
99 clear_on_kernel_restart = Bool(True, config=True,
99 clear_on_kernel_restart = Bool(True, config=True,
100 help="Whether to clear the console when the kernel is restarted")
100 help="Whether to clear the console when the kernel is restarted")
101
101
102 confirm_restart = Bool(True, config=True,
102 confirm_restart = Bool(True, config=True,
103 help="Whether to ask for user confirmation when restarting kernel")
103 help="Whether to ask for user confirmation when restarting kernel")
104
104
105 # Emitted when a user visible 'execute_request' has been submitted to the
105 # Emitted when a user visible 'execute_request' has been submitted to the
106 # kernel from the FrontendWidget. Contains the code to be executed.
106 # kernel from the FrontendWidget. Contains the code to be executed.
107 executing = QtCore.Signal(object)
107 executing = QtCore.Signal(object)
108
108
109 # Emitted when a user-visible 'execute_reply' has been received from the
109 # Emitted when a user-visible 'execute_reply' has been received from the
110 # kernel and processed by the FrontendWidget. Contains the response message.
110 # kernel and processed by the FrontendWidget. Contains the response message.
111 executed = QtCore.Signal(object)
111 executed = QtCore.Signal(object)
112
112
113 # Emitted when an exit request has been received from the kernel.
113 # Emitted when an exit request has been received from the kernel.
114 exit_requested = QtCore.Signal(object)
114 exit_requested = QtCore.Signal(object)
115
115
116 # Protected class variables.
116 # Protected class variables.
117 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
117 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
118 logical_line_transforms=[],
118 logical_line_transforms=[],
119 python_line_transforms=[],
119 python_line_transforms=[],
120 )
120 )
121 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
121 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
122 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
122 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
123 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
123 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
124 _input_splitter_class = InputSplitter
124 _input_splitter_class = InputSplitter
125 _local_kernel = False
125 _local_kernel = False
126 _highlighter = Instance(FrontendHighlighter)
126 _highlighter = Instance(FrontendHighlighter)
127
127
128 #---------------------------------------------------------------------------
128 #---------------------------------------------------------------------------
129 # 'object' interface
129 # 'object' interface
130 #---------------------------------------------------------------------------
130 #---------------------------------------------------------------------------
131
131
132 def __init__(self, *args, **kw):
132 def __init__(self, *args, **kw):
133 super(FrontendWidget, self).__init__(*args, **kw)
133 super(FrontendWidget, self).__init__(*args, **kw)
134 # FIXME: remove this when PySide min version is updated past 1.0.7
134 # FIXME: remove this when PySide min version is updated past 1.0.7
135 # forcefully disable calltips if PySide is < 1.0.7, because they crash
135 # forcefully disable calltips if PySide is < 1.0.7, because they crash
136 if qt.QT_API == qt.QT_API_PYSIDE:
136 if qt.QT_API == qt.QT_API_PYSIDE:
137 import PySide
137 import PySide
138 if PySide.__version_info__ < (1,0,7):
138 if PySide.__version_info__ < (1,0,7):
139 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
139 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
140 self.enable_calltips = False
140 self.enable_calltips = False
141
141
142 # FrontendWidget protected variables.
142 # FrontendWidget protected variables.
143 self._bracket_matcher = BracketMatcher(self._control)
143 self._bracket_matcher = BracketMatcher(self._control)
144 self._call_tip_widget = CallTipWidget(self._control)
144 self._call_tip_widget = CallTipWidget(self._control)
145 self._completion_lexer = CompletionLexer(PythonLexer())
145 self._completion_lexer = CompletionLexer(PythonLexer())
146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
146 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
147 self._hidden = False
147 self._hidden = False
148 self._highlighter = FrontendHighlighter(self)
148 self._highlighter = FrontendHighlighter(self)
149 self._input_splitter = self._input_splitter_class()
149 self._input_splitter = self._input_splitter_class()
150 self._kernel_manager = None
150 self._kernel_manager = None
151 self._kernel_client = None
151 self._request_info = {}
152 self._request_info = {}
152 self._request_info['execute'] = {};
153 self._request_info['execute'] = {};
153 self._callback_dict = {}
154 self._callback_dict = {}
154
155
155 # Configure the ConsoleWidget.
156 # Configure the ConsoleWidget.
156 self.tab_width = 4
157 self.tab_width = 4
157 self._set_continuation_prompt('... ')
158 self._set_continuation_prompt('... ')
158
159
159 # Configure the CallTipWidget.
160 # Configure the CallTipWidget.
160 self._call_tip_widget.setFont(self.font)
161 self._call_tip_widget.setFont(self.font)
161 self.font_changed.connect(self._call_tip_widget.setFont)
162 self.font_changed.connect(self._call_tip_widget.setFont)
162
163
163 # Configure actions.
164 # Configure actions.
164 action = self._copy_raw_action
165 action = self._copy_raw_action
165 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
166 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
166 action.setEnabled(False)
167 action.setEnabled(False)
167 action.setShortcut(QtGui.QKeySequence(key))
168 action.setShortcut(QtGui.QKeySequence(key))
168 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
169 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
169 action.triggered.connect(self.copy_raw)
170 action.triggered.connect(self.copy_raw)
170 self.copy_available.connect(action.setEnabled)
171 self.copy_available.connect(action.setEnabled)
171 self.addAction(action)
172 self.addAction(action)
172
173
173 # Connect signal handlers.
174 # Connect signal handlers.
174 document = self._control.document()
175 document = self._control.document()
175 document.contentsChange.connect(self._document_contents_change)
176 document.contentsChange.connect(self._document_contents_change)
176
177
177 # Set flag for whether we are connected via localhost.
178 # Set flag for whether we are connected via localhost.
178 self._local_kernel = kw.get('local_kernel',
179 self._local_kernel = kw.get('local_kernel',
179 FrontendWidget._local_kernel)
180 FrontendWidget._local_kernel)
180
181
181 #---------------------------------------------------------------------------
182 #---------------------------------------------------------------------------
182 # 'ConsoleWidget' public interface
183 # 'ConsoleWidget' public interface
183 #---------------------------------------------------------------------------
184 #---------------------------------------------------------------------------
184
185
185 def copy(self):
186 def copy(self):
186 """ Copy the currently selected text to the clipboard, removing prompts.
187 """ Copy the currently selected text to the clipboard, removing prompts.
187 """
188 """
188 if self._page_control is not None and self._page_control.hasFocus():
189 if self._page_control is not None and self._page_control.hasFocus():
189 self._page_control.copy()
190 self._page_control.copy()
190 elif self._control.hasFocus():
191 elif self._control.hasFocus():
191 text = self._control.textCursor().selection().toPlainText()
192 text = self._control.textCursor().selection().toPlainText()
192 if text:
193 if text:
193 text = self._prompt_transformer.transform_cell(text)
194 text = self._prompt_transformer.transform_cell(text)
194 QtGui.QApplication.clipboard().setText(text)
195 QtGui.QApplication.clipboard().setText(text)
195 else:
196 else:
196 self.log.debug("frontend widget : unknown copy target")
197 self.log.debug("frontend widget : unknown copy target")
197
198
198 #---------------------------------------------------------------------------
199 #---------------------------------------------------------------------------
199 # 'ConsoleWidget' abstract interface
200 # 'ConsoleWidget' abstract interface
200 #---------------------------------------------------------------------------
201 #---------------------------------------------------------------------------
201
202
202 def _is_complete(self, source, interactive):
203 def _is_complete(self, source, interactive):
203 """ Returns whether 'source' can be completely processed and a new
204 """ Returns whether 'source' can be completely processed and a new
204 prompt created. When triggered by an Enter/Return key press,
205 prompt created. When triggered by an Enter/Return key press,
205 'interactive' is True; otherwise, it is False.
206 'interactive' is True; otherwise, it is False.
206 """
207 """
207 self._input_splitter.reset()
208 self._input_splitter.reset()
208 complete = self._input_splitter.push(source)
209 complete = self._input_splitter.push(source)
209 if interactive:
210 if interactive:
210 complete = not self._input_splitter.push_accepts_more()
211 complete = not self._input_splitter.push_accepts_more()
211 return complete
212 return complete
212
213
213 def _execute(self, source, hidden):
214 def _execute(self, source, hidden):
214 """ Execute 'source'. If 'hidden', do not show any output.
215 """ Execute 'source'. If 'hidden', do not show any output.
215
216
216 See parent class :meth:`execute` docstring for full details.
217 See parent class :meth:`execute` docstring for full details.
217 """
218 """
218 msg_id = self.kernel_manager.shell_channel.execute(source, hidden)
219 msg_id = self.kernel_client.shell_channel.execute(source, hidden)
219 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
220 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
220 self._hidden = hidden
221 self._hidden = hidden
221 if not hidden:
222 if not hidden:
222 self.executing.emit(source)
223 self.executing.emit(source)
223
224
224 def _prompt_started_hook(self):
225 def _prompt_started_hook(self):
225 """ Called immediately after a new prompt is displayed.
226 """ Called immediately after a new prompt is displayed.
226 """
227 """
227 if not self._reading:
228 if not self._reading:
228 self._highlighter.highlighting_on = True
229 self._highlighter.highlighting_on = True
229
230
230 def _prompt_finished_hook(self):
231 def _prompt_finished_hook(self):
231 """ Called immediately after a prompt is finished, i.e. when some input
232 """ Called immediately after a prompt is finished, i.e. when some input
232 will be processed and a new prompt displayed.
233 will be processed and a new prompt displayed.
233 """
234 """
234 # Flush all state from the input splitter so the next round of
235 # Flush all state from the input splitter so the next round of
235 # reading input starts with a clean buffer.
236 # reading input starts with a clean buffer.
236 self._input_splitter.reset()
237 self._input_splitter.reset()
237
238
238 if not self._reading:
239 if not self._reading:
239 self._highlighter.highlighting_on = False
240 self._highlighter.highlighting_on = False
240
241
241 def _tab_pressed(self):
242 def _tab_pressed(self):
242 """ Called when the tab key is pressed. Returns whether to continue
243 """ Called when the tab key is pressed. Returns whether to continue
243 processing the event.
244 processing the event.
244 """
245 """
245 # Perform tab completion if:
246 # Perform tab completion if:
246 # 1) The cursor is in the input buffer.
247 # 1) The cursor is in the input buffer.
247 # 2) There is a non-whitespace character before the cursor.
248 # 2) There is a non-whitespace character before the cursor.
248 text = self._get_input_buffer_cursor_line()
249 text = self._get_input_buffer_cursor_line()
249 if text is None:
250 if text is None:
250 return False
251 return False
251 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
252 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
252 if complete:
253 if complete:
253 self._complete()
254 self._complete()
254 return not complete
255 return not complete
255
256
256 #---------------------------------------------------------------------------
257 #---------------------------------------------------------------------------
257 # 'ConsoleWidget' protected interface
258 # 'ConsoleWidget' protected interface
258 #---------------------------------------------------------------------------
259 #---------------------------------------------------------------------------
259
260
260 def _context_menu_make(self, pos):
261 def _context_menu_make(self, pos):
261 """ Reimplemented to add an action for raw copy.
262 """ Reimplemented to add an action for raw copy.
262 """
263 """
263 menu = super(FrontendWidget, self)._context_menu_make(pos)
264 menu = super(FrontendWidget, self)._context_menu_make(pos)
264 for before_action in menu.actions():
265 for before_action in menu.actions():
265 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
266 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
266 QtGui.QKeySequence.ExactMatch:
267 QtGui.QKeySequence.ExactMatch:
267 menu.insertAction(before_action, self._copy_raw_action)
268 menu.insertAction(before_action, self._copy_raw_action)
268 break
269 break
269 return menu
270 return menu
270
271
271 def request_interrupt_kernel(self):
272 def request_interrupt_kernel(self):
272 if self._executing:
273 if self._executing:
273 self.interrupt_kernel()
274 self.interrupt_kernel()
274
275
275 def request_restart_kernel(self):
276 def request_restart_kernel(self):
276 message = 'Are you sure you want to restart the kernel?'
277 message = 'Are you sure you want to restart the kernel?'
277 self.restart_kernel(message, now=False)
278 self.restart_kernel(message, now=False)
278
279
279 def _event_filter_console_keypress(self, event):
280 def _event_filter_console_keypress(self, event):
280 """ Reimplemented for execution interruption and smart backspace.
281 """ Reimplemented for execution interruption and smart backspace.
281 """
282 """
282 key = event.key()
283 key = event.key()
283 if self._control_key_down(event.modifiers(), include_command=False):
284 if self._control_key_down(event.modifiers(), include_command=False):
284
285
285 if key == QtCore.Qt.Key_C and self._executing:
286 if key == QtCore.Qt.Key_C and self._executing:
286 self.request_interrupt_kernel()
287 self.request_interrupt_kernel()
287 return True
288 return True
288
289
289 elif key == QtCore.Qt.Key_Period:
290 elif key == QtCore.Qt.Key_Period:
290 self.request_restart_kernel()
291 self.request_restart_kernel()
291 return True
292 return True
292
293
293 elif not event.modifiers() & QtCore.Qt.AltModifier:
294 elif not event.modifiers() & QtCore.Qt.AltModifier:
294
295
295 # Smart backspace: remove four characters in one backspace if:
296 # Smart backspace: remove four characters in one backspace if:
296 # 1) everything left of the cursor is whitespace
297 # 1) everything left of the cursor is whitespace
297 # 2) the four characters immediately left of the cursor are spaces
298 # 2) the four characters immediately left of the cursor are spaces
298 if key == QtCore.Qt.Key_Backspace:
299 if key == QtCore.Qt.Key_Backspace:
299 col = self._get_input_buffer_cursor_column()
300 col = self._get_input_buffer_cursor_column()
300 cursor = self._control.textCursor()
301 cursor = self._control.textCursor()
301 if col > 3 and not cursor.hasSelection():
302 if col > 3 and not cursor.hasSelection():
302 text = self._get_input_buffer_cursor_line()[:col]
303 text = self._get_input_buffer_cursor_line()[:col]
303 if text.endswith(' ') and not text.strip():
304 if text.endswith(' ') and not text.strip():
304 cursor.movePosition(QtGui.QTextCursor.Left,
305 cursor.movePosition(QtGui.QTextCursor.Left,
305 QtGui.QTextCursor.KeepAnchor, 4)
306 QtGui.QTextCursor.KeepAnchor, 4)
306 cursor.removeSelectedText()
307 cursor.removeSelectedText()
307 return True
308 return True
308
309
309 return super(FrontendWidget, self)._event_filter_console_keypress(event)
310 return super(FrontendWidget, self)._event_filter_console_keypress(event)
310
311
311 def _insert_continuation_prompt(self, cursor):
312 def _insert_continuation_prompt(self, cursor):
312 """ Reimplemented for auto-indentation.
313 """ Reimplemented for auto-indentation.
313 """
314 """
314 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
315 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
315 cursor.insertText(' ' * self._input_splitter.indent_spaces)
316 cursor.insertText(' ' * self._input_splitter.indent_spaces)
316
317
317 #---------------------------------------------------------------------------
318 #---------------------------------------------------------------------------
318 # 'BaseFrontendMixin' abstract interface
319 # 'BaseFrontendMixin' abstract interface
319 #---------------------------------------------------------------------------
320 #---------------------------------------------------------------------------
320
321
321 def _handle_complete_reply(self, rep):
322 def _handle_complete_reply(self, rep):
322 """ Handle replies for tab completion.
323 """ Handle replies for tab completion.
323 """
324 """
324 self.log.debug("complete: %s", rep.get('content', ''))
325 self.log.debug("complete: %s", rep.get('content', ''))
325 cursor = self._get_cursor()
326 cursor = self._get_cursor()
326 info = self._request_info.get('complete')
327 info = self._request_info.get('complete')
327 if info and info.id == rep['parent_header']['msg_id'] and \
328 if info and info.id == rep['parent_header']['msg_id'] and \
328 info.pos == cursor.position():
329 info.pos == cursor.position():
329 text = '.'.join(self._get_context())
330 text = '.'.join(self._get_context())
330 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
331 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
331 self._complete_with_items(cursor, rep['content']['matches'])
332 self._complete_with_items(cursor, rep['content']['matches'])
332
333
333 def _silent_exec_callback(self, expr, callback):
334 def _silent_exec_callback(self, expr, callback):
334 """Silently execute `expr` in the kernel and call `callback` with reply
335 """Silently execute `expr` in the kernel and call `callback` with reply
335
336
336 the `expr` is evaluated silently in the kernel (without) output in
337 the `expr` is evaluated silently in the kernel (without) output in
337 the frontend. Call `callback` with the
338 the frontend. Call `callback` with the
338 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
339 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
339
340
340 Parameters
341 Parameters
341 ----------
342 ----------
342 expr : string
343 expr : string
343 valid string to be executed by the kernel.
344 valid string to be executed by the kernel.
344 callback : function
345 callback : function
345 function accepting one argument, as a string. The string will be
346 function accepting one argument, as a string. The string will be
346 the `repr` of the result of evaluating `expr`
347 the `repr` of the result of evaluating `expr`
347
348
348 The `callback` is called with the `repr()` of the result of `expr` as
349 The `callback` is called with the `repr()` of the result of `expr` as
349 first argument. To get the object, do `eval()` on the passed value.
350 first argument. To get the object, do `eval()` on the passed value.
350
351
351 See Also
352 See Also
352 --------
353 --------
353 _handle_exec_callback : private method, deal with calling callback with reply
354 _handle_exec_callback : private method, deal with calling callback with reply
354
355
355 """
356 """
356
357
357 # generate uuid, which would be used as an indication of whether or
358 # generate uuid, which would be used as an indication of whether or
358 # not the unique request originated from here (can use msg id ?)
359 # not the unique request originated from here (can use msg id ?)
359 local_uuid = str(uuid.uuid1())
360 local_uuid = str(uuid.uuid1())
360 msg_id = self.kernel_manager.shell_channel.execute('',
361 msg_id = self.kernel_client.shell_channel.execute('',
361 silent=True, user_expressions={ local_uuid:expr })
362 silent=True, user_expressions={ local_uuid:expr })
362 self._callback_dict[local_uuid] = callback
363 self._callback_dict[local_uuid] = callback
363 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
364 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
364
365
365 def _handle_exec_callback(self, msg):
366 def _handle_exec_callback(self, msg):
366 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
367 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
367
368
368 Parameters
369 Parameters
369 ----------
370 ----------
370 msg : raw message send by the kernel containing an `user_expressions`
371 msg : raw message send by the kernel containing an `user_expressions`
371 and having a 'silent_exec_callback' kind.
372 and having a 'silent_exec_callback' kind.
372
373
373 Notes
374 Notes
374 -----
375 -----
375 This function will look for a `callback` associated with the
376 This function will look for a `callback` associated with the
376 corresponding message id. Association has been made by
377 corresponding message id. Association has been made by
377 `_silent_exec_callback`. `callback` is then called with the `repr()`
378 `_silent_exec_callback`. `callback` is then called with the `repr()`
378 of the value of corresponding `user_expressions` as argument.
379 of the value of corresponding `user_expressions` as argument.
379 `callback` is then removed from the known list so that any message
380 `callback` is then removed from the known list so that any message
380 coming again with the same id won't trigger it.
381 coming again with the same id won't trigger it.
381
382
382 """
383 """
383
384
384 user_exp = msg['content'].get('user_expressions')
385 user_exp = msg['content'].get('user_expressions')
385 if not user_exp:
386 if not user_exp:
386 return
387 return
387 for expression in user_exp:
388 for expression in user_exp:
388 if expression in self._callback_dict:
389 if expression in self._callback_dict:
389 self._callback_dict.pop(expression)(user_exp[expression])
390 self._callback_dict.pop(expression)(user_exp[expression])
390
391
391 def _handle_execute_reply(self, msg):
392 def _handle_execute_reply(self, msg):
392 """ Handles replies for code execution.
393 """ Handles replies for code execution.
393 """
394 """
394 self.log.debug("execute: %s", msg.get('content', ''))
395 self.log.debug("execute: %s", msg.get('content', ''))
395 msg_id = msg['parent_header']['msg_id']
396 msg_id = msg['parent_header']['msg_id']
396 info = self._request_info['execute'].get(msg_id)
397 info = self._request_info['execute'].get(msg_id)
397 # unset reading flag, because if execute finished, raw_input can't
398 # unset reading flag, because if execute finished, raw_input can't
398 # still be pending.
399 # still be pending.
399 self._reading = False
400 self._reading = False
400 if info and info.kind == 'user' and not self._hidden:
401 if info and info.kind == 'user' and not self._hidden:
401 # Make sure that all output from the SUB channel has been processed
402 # Make sure that all output from the SUB channel has been processed
402 # before writing a new prompt.
403 # before writing a new prompt.
403 self.kernel_manager.iopub_channel.flush()
404 self.kernel_client.iopub_channel.flush()
404
405
405 # Reset the ANSI style information to prevent bad text in stdout
406 # Reset the ANSI style information to prevent bad text in stdout
406 # from messing up our colors. We're not a true terminal so we're
407 # from messing up our colors. We're not a true terminal so we're
407 # allowed to do this.
408 # allowed to do this.
408 if self.ansi_codes:
409 if self.ansi_codes:
409 self._ansi_processor.reset_sgr()
410 self._ansi_processor.reset_sgr()
410
411
411 content = msg['content']
412 content = msg['content']
412 status = content['status']
413 status = content['status']
413 if status == 'ok':
414 if status == 'ok':
414 self._process_execute_ok(msg)
415 self._process_execute_ok(msg)
415 elif status == 'error':
416 elif status == 'error':
416 self._process_execute_error(msg)
417 self._process_execute_error(msg)
417 elif status == 'aborted':
418 elif status == 'aborted':
418 self._process_execute_abort(msg)
419 self._process_execute_abort(msg)
419
420
420 self._show_interpreter_prompt_for_reply(msg)
421 self._show_interpreter_prompt_for_reply(msg)
421 self.executed.emit(msg)
422 self.executed.emit(msg)
422 self._request_info['execute'].pop(msg_id)
423 self._request_info['execute'].pop(msg_id)
423 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
424 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
424 self._handle_exec_callback(msg)
425 self._handle_exec_callback(msg)
425 self._request_info['execute'].pop(msg_id)
426 self._request_info['execute'].pop(msg_id)
426 else:
427 else:
427 super(FrontendWidget, self)._handle_execute_reply(msg)
428 super(FrontendWidget, self)._handle_execute_reply(msg)
428
429
429 def _handle_input_request(self, msg):
430 def _handle_input_request(self, msg):
430 """ Handle requests for raw_input.
431 """ Handle requests for raw_input.
431 """
432 """
432 self.log.debug("input: %s", msg.get('content', ''))
433 self.log.debug("input: %s", msg.get('content', ''))
433 if self._hidden:
434 if self._hidden:
434 raise RuntimeError('Request for raw input during hidden execution.')
435 raise RuntimeError('Request for raw input during hidden execution.')
435
436
436 # Make sure that all output from the SUB channel has been processed
437 # Make sure that all output from the SUB channel has been processed
437 # before entering readline mode.
438 # before entering readline mode.
438 self.kernel_manager.iopub_channel.flush()
439 self.kernel_client.iopub_channel.flush()
439
440
440 def callback(line):
441 def callback(line):
441 self.kernel_manager.stdin_channel.input(line)
442 self.kernel_client.stdin_channel.input(line)
442 if self._reading:
443 if self._reading:
443 self.log.debug("Got second input request, assuming first was interrupted.")
444 self.log.debug("Got second input request, assuming first was interrupted.")
444 self._reading = False
445 self._reading = False
445 self._readline(msg['content']['prompt'], callback=callback)
446 self._readline(msg['content']['prompt'], callback=callback)
446
447
447 def _handle_kernel_died(self, since_last_heartbeat):
448 def _handle_kernel_died(self, since_last_heartbeat):
448 """ Handle the kernel's death by asking if the user wants to restart.
449 """ Handle the kernel's death by asking if the user wants to restart.
449 """
450 """
450 self.log.debug("kernel died: %s", since_last_heartbeat)
451 self.log.debug("kernel died: %s", since_last_heartbeat)
451 if self.custom_restart:
452 if self.custom_restart:
452 self.custom_restart_kernel_died.emit(since_last_heartbeat)
453 self.custom_restart_kernel_died.emit(since_last_heartbeat)
453 else:
454 else:
454 message = 'The kernel heartbeat has been inactive for %.2f ' \
455 message = 'The kernel heartbeat has been inactive for %.2f ' \
455 'seconds. Do you want to restart the kernel? You may ' \
456 'seconds. Do you want to restart the kernel? You may ' \
456 'first want to check the network connection.' % \
457 'first want to check the network connection.' % \
457 since_last_heartbeat
458 since_last_heartbeat
458 self.restart_kernel(message, now=True)
459 self.restart_kernel(message, now=True)
459
460
460 def _handle_object_info_reply(self, rep):
461 def _handle_object_info_reply(self, rep):
461 """ Handle replies for call tips.
462 """ Handle replies for call tips.
462 """
463 """
463 self.log.debug("oinfo: %s", rep.get('content', ''))
464 self.log.debug("oinfo: %s", rep.get('content', ''))
464 cursor = self._get_cursor()
465 cursor = self._get_cursor()
465 info = self._request_info.get('call_tip')
466 info = self._request_info.get('call_tip')
466 if info and info.id == rep['parent_header']['msg_id'] and \
467 if info and info.id == rep['parent_header']['msg_id'] and \
467 info.pos == cursor.position():
468 info.pos == cursor.position():
468 # Get the information for a call tip. For now we format the call
469 # Get the information for a call tip. For now we format the call
469 # line as string, later we can pass False to format_call and
470 # line as string, later we can pass False to format_call and
470 # syntax-highlight it ourselves for nicer formatting in the
471 # syntax-highlight it ourselves for nicer formatting in the
471 # calltip.
472 # calltip.
472 content = rep['content']
473 content = rep['content']
473 # if this is from pykernel, 'docstring' will be the only key
474 # if this is from pykernel, 'docstring' will be the only key
474 if content.get('ismagic', False):
475 if content.get('ismagic', False):
475 # Don't generate a call-tip for magics. Ideally, we should
476 # Don't generate a call-tip for magics. Ideally, we should
476 # generate a tooltip, but not on ( like we do for actual
477 # generate a tooltip, but not on ( like we do for actual
477 # callables.
478 # callables.
478 call_info, doc = None, None
479 call_info, doc = None, None
479 else:
480 else:
480 call_info, doc = call_tip(content, format_call=True)
481 call_info, doc = call_tip(content, format_call=True)
481 if call_info or doc:
482 if call_info or doc:
482 self._call_tip_widget.show_call_info(call_info, doc)
483 self._call_tip_widget.show_call_info(call_info, doc)
483
484
484 def _handle_pyout(self, msg):
485 def _handle_pyout(self, msg):
485 """ Handle display hook output.
486 """ Handle display hook output.
486 """
487 """
487 self.log.debug("pyout: %s", msg.get('content', ''))
488 self.log.debug("pyout: %s", msg.get('content', ''))
488 if not self._hidden and self._is_from_this_session(msg):
489 if not self._hidden and self._is_from_this_session(msg):
489 text = msg['content']['data']
490 text = msg['content']['data']
490 self._append_plain_text(text + '\n', before_prompt=True)
491 self._append_plain_text(text + '\n', before_prompt=True)
491
492
492 def _handle_stream(self, msg):
493 def _handle_stream(self, msg):
493 """ Handle stdout, stderr, and stdin.
494 """ Handle stdout, stderr, and stdin.
494 """
495 """
495 self.log.debug("stream: %s", msg.get('content', ''))
496 self.log.debug("stream: %s", msg.get('content', ''))
496 if not self._hidden and self._is_from_this_session(msg):
497 if not self._hidden and self._is_from_this_session(msg):
497 # Most consoles treat tabs as being 8 space characters. Convert tabs
498 # Most consoles treat tabs as being 8 space characters. Convert tabs
498 # to spaces so that output looks as expected regardless of this
499 # to spaces so that output looks as expected regardless of this
499 # widget's tab width.
500 # widget's tab width.
500 text = msg['content']['data'].expandtabs(8)
501 text = msg['content']['data'].expandtabs(8)
501
502
502 self._append_plain_text(text, before_prompt=True)
503 self._append_plain_text(text, before_prompt=True)
503 self._control.moveCursor(QtGui.QTextCursor.End)
504 self._control.moveCursor(QtGui.QTextCursor.End)
504
505
505 def _handle_shutdown_reply(self, msg):
506 def _handle_shutdown_reply(self, msg):
506 """ Handle shutdown signal, only if from other console.
507 """ Handle shutdown signal, only if from other console.
507 """
508 """
508 self.log.debug("shutdown: %s", msg.get('content', ''))
509 self.log.debug("shutdown: %s", msg.get('content', ''))
509 if not self._hidden and not self._is_from_this_session(msg):
510 if not self._hidden and not self._is_from_this_session(msg):
510 if self._local_kernel:
511 if self._local_kernel:
511 if not msg['content']['restart']:
512 if not msg['content']['restart']:
512 self.exit_requested.emit(self)
513 self.exit_requested.emit(self)
513 else:
514 else:
514 # we just got notified of a restart!
515 # we just got notified of a restart!
515 time.sleep(0.25) # wait 1/4 sec to reset
516 time.sleep(0.25) # wait 1/4 sec to reset
516 # lest the request for a new prompt
517 # lest the request for a new prompt
517 # goes to the old kernel
518 # goes to the old kernel
518 self.reset()
519 self.reset()
519 else: # remote kernel, prompt on Kernel shutdown/reset
520 else: # remote kernel, prompt on Kernel shutdown/reset
520 title = self.window().windowTitle()
521 title = self.window().windowTitle()
521 if not msg['content']['restart']:
522 if not msg['content']['restart']:
522 reply = QtGui.QMessageBox.question(self, title,
523 reply = QtGui.QMessageBox.question(self, title,
523 "Kernel has been shutdown permanently. "
524 "Kernel has been shutdown permanently. "
524 "Close the Console?",
525 "Close the Console?",
525 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
526 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
526 if reply == QtGui.QMessageBox.Yes:
527 if reply == QtGui.QMessageBox.Yes:
527 self.exit_requested.emit(self)
528 self.exit_requested.emit(self)
528 else:
529 else:
529 # XXX: remove message box in favor of using the
530 # XXX: remove message box in favor of using the
530 # clear_on_kernel_restart setting?
531 # clear_on_kernel_restart setting?
531 reply = QtGui.QMessageBox.question(self, title,
532 reply = QtGui.QMessageBox.question(self, title,
532 "Kernel has been reset. Clear the Console?",
533 "Kernel has been reset. Clear the Console?",
533 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
534 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
534 if reply == QtGui.QMessageBox.Yes:
535 if reply == QtGui.QMessageBox.Yes:
535 time.sleep(0.25) # wait 1/4 sec to reset
536 time.sleep(0.25) # wait 1/4 sec to reset
536 # lest the request for a new prompt
537 # lest the request for a new prompt
537 # goes to the old kernel
538 # goes to the old kernel
538 self.reset()
539 self.reset()
539
540
540 def _started_channels(self):
541 def _started_channels(self):
541 """ Called when the KernelManager channels have started listening or
542 """ Called when the KernelManager channels have started listening or
542 when the frontend is assigned an already listening KernelManager.
543 when the frontend is assigned an already listening KernelManager.
543 """
544 """
544 self.reset(clear=True)
545 self.reset(clear=True)
545
546
546 #---------------------------------------------------------------------------
547 #---------------------------------------------------------------------------
547 # 'FrontendWidget' public interface
548 # 'FrontendWidget' public interface
548 #---------------------------------------------------------------------------
549 #---------------------------------------------------------------------------
549
550
550 def copy_raw(self):
551 def copy_raw(self):
551 """ Copy the currently selected text to the clipboard without attempting
552 """ Copy the currently selected text to the clipboard without attempting
552 to remove prompts or otherwise alter the text.
553 to remove prompts or otherwise alter the text.
553 """
554 """
554 self._control.copy()
555 self._control.copy()
555
556
556 def execute_file(self, path, hidden=False):
557 def execute_file(self, path, hidden=False):
557 """ Attempts to execute file with 'path'. If 'hidden', no output is
558 """ Attempts to execute file with 'path'. If 'hidden', no output is
558 shown.
559 shown.
559 """
560 """
560 self.execute('execfile(%r)' % path, hidden=hidden)
561 self.execute('execfile(%r)' % path, hidden=hidden)
561
562
562 def interrupt_kernel(self):
563 def interrupt_kernel(self):
563 """ Attempts to interrupt the running kernel.
564 """ Attempts to interrupt the running kernel.
564
565
565 Also unsets _reading flag, to avoid runtime errors
566 Also unsets _reading flag, to avoid runtime errors
566 if raw_input is called again.
567 if raw_input is called again.
567 """
568 """
568 if self.custom_interrupt:
569 if self.custom_interrupt:
569 self._reading = False
570 self._reading = False
570 self.custom_interrupt_requested.emit()
571 self.custom_interrupt_requested.emit()
571 elif self.kernel_manager.has_kernel:
572 elif self.kernel_manager:
572 self._reading = False
573 self._reading = False
573 self.kernel_manager.interrupt_kernel()
574 self.kernel_manager.interrupt_kernel()
574 else:
575 else:
575 self._append_plain_text('Kernel process is either remote or '
576 self._append_plain_text('Kernel process is either remote or '
576 'unspecified. Cannot interrupt.\n')
577 'unspecified. Cannot interrupt.\n')
577
578
578 def reset(self, clear=False):
579 def reset(self, clear=False):
579 """ Resets the widget to its initial state if ``clear`` parameter or
580 """ Resets the widget to its initial state if ``clear`` parameter or
580 ``clear_on_kernel_restart`` configuration setting is True, otherwise
581 ``clear_on_kernel_restart`` configuration setting is True, otherwise
581 prints a visual indication of the fact that the kernel restarted, but
582 prints a visual indication of the fact that the kernel restarted, but
582 does not clear the traces from previous usage of the kernel before it
583 does not clear the traces from previous usage of the kernel before it
583 was restarted. With ``clear=True``, it is similar to ``%clear``, but
584 was restarted. With ``clear=True``, it is similar to ``%clear``, but
584 also re-writes the banner and aborts execution if necessary.
585 also re-writes the banner and aborts execution if necessary.
585 """
586 """
586 if self._executing:
587 if self._executing:
587 self._executing = False
588 self._executing = False
588 self._request_info['execute'] = {}
589 self._request_info['execute'] = {}
589 self._reading = False
590 self._reading = False
590 self._highlighter.highlighting_on = False
591 self._highlighter.highlighting_on = False
591
592
592 if self.clear_on_kernel_restart or clear:
593 if self.clear_on_kernel_restart or clear:
593 self._control.clear()
594 self._control.clear()
594 self._append_plain_text(self.banner)
595 self._append_plain_text(self.banner)
595 else:
596 else:
596 self._append_plain_text("# restarting kernel...")
597 self._append_plain_text("# restarting kernel...")
597 self._append_html("<hr><br>")
598 self._append_html("<hr><br>")
598 # XXX: Reprinting the full banner may be too much, but once #1680 is
599 # XXX: Reprinting the full banner may be too much, but once #1680 is
599 # addressed, that will mitigate it.
600 # addressed, that will mitigate it.
600 #self._append_plain_text(self.banner)
601 #self._append_plain_text(self.banner)
601 # update output marker for stdout/stderr, so that startup
602 # update output marker for stdout/stderr, so that startup
602 # messages appear after banner:
603 # messages appear after banner:
603 self._append_before_prompt_pos = self._get_cursor().position()
604 self._append_before_prompt_pos = self._get_cursor().position()
604 self._show_interpreter_prompt()
605 self._show_interpreter_prompt()
605
606
606 def restart_kernel(self, message, now=False):
607 def restart_kernel(self, message, now=False):
607 """ Attempts to restart the running kernel.
608 """ Attempts to restart the running kernel.
608 """
609 """
609 # FIXME: now should be configurable via a checkbox in the dialog. Right
610 # FIXME: now should be configurable via a checkbox in the dialog. Right
610 # now at least the heartbeat path sets it to True and the manual restart
611 # now at least the heartbeat path sets it to True and the manual restart
611 # to False. But those should just be the pre-selected states of a
612 # to False. But those should just be the pre-selected states of a
612 # checkbox that the user could override if so desired. But I don't know
613 # checkbox that the user could override if so desired. But I don't know
613 # enough Qt to go implementing the checkbox now.
614 # enough Qt to go implementing the checkbox now.
614
615
615 if self.custom_restart:
616 if self.custom_restart:
616 self.custom_restart_requested.emit()
617 self.custom_restart_requested.emit()
617
618
618 elif self.kernel_manager.has_kernel:
619 elif self.kernel_manager:
619 # Pause the heart beat channel to prevent further warnings.
620 # Pause the heart beat channel to prevent further warnings.
620 self.kernel_manager.hb_channel.pause()
621 self.kernel_client.hb_channel.pause()
621
622
622 # Prompt the user to restart the kernel. Un-pause the heartbeat if
623 # Prompt the user to restart the kernel. Un-pause the heartbeat if
623 # they decline. (If they accept, the heartbeat will be un-paused
624 # they decline. (If they accept, the heartbeat will be un-paused
624 # automatically when the kernel is restarted.)
625 # automatically when the kernel is restarted.)
625 if self.confirm_restart:
626 if self.confirm_restart:
626 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
627 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
627 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
628 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
628 message, buttons)
629 message, buttons)
629 do_restart = result == QtGui.QMessageBox.Yes
630 do_restart = result == QtGui.QMessageBox.Yes
630 else:
631 else:
631 # confirm_restart is False, so we don't need to ask user
632 # confirm_restart is False, so we don't need to ask user
632 # anything, just do the restart
633 # anything, just do the restart
633 do_restart = True
634 do_restart = True
634 if do_restart:
635 if do_restart:
635 try:
636 try:
636 self.kernel_manager.restart_kernel(now=now)
637 self.kernel_manager.restart_kernel(now=now)
637 except RuntimeError:
638 except RuntimeError:
638 self._append_plain_text('Kernel started externally. '
639 self._append_plain_text('Kernel started externally. '
639 'Cannot restart.\n',
640 'Cannot restart.\n',
640 before_prompt=True
641 before_prompt=True
641 )
642 )
642 else:
643 else:
643 self.reset()
644 self.reset()
644 else:
645 else:
645 self.kernel_manager.hb_channel.unpause()
646 self.kernel_client.hb_channel.unpause()
646
647
647 else:
648 else:
648 self._append_plain_text('Kernel process is either remote or '
649 self._append_plain_text('Kernel process is either remote or '
649 'unspecified. Cannot restart.\n',
650 'unspecified. Cannot restart.\n',
650 before_prompt=True
651 before_prompt=True
651 )
652 )
652
653
653 #---------------------------------------------------------------------------
654 #---------------------------------------------------------------------------
654 # 'FrontendWidget' protected interface
655 # 'FrontendWidget' protected interface
655 #---------------------------------------------------------------------------
656 #---------------------------------------------------------------------------
656
657
657 def _call_tip(self):
658 def _call_tip(self):
658 """ Shows a call tip, if appropriate, at the current cursor location.
659 """ Shows a call tip, if appropriate, at the current cursor location.
659 """
660 """
660 # Decide if it makes sense to show a call tip
661 # Decide if it makes sense to show a call tip
661 if not self.enable_calltips:
662 if not self.enable_calltips:
662 return False
663 return False
663 cursor = self._get_cursor()
664 cursor = self._get_cursor()
664 cursor.movePosition(QtGui.QTextCursor.Left)
665 cursor.movePosition(QtGui.QTextCursor.Left)
665 if cursor.document().characterAt(cursor.position()) != '(':
666 if cursor.document().characterAt(cursor.position()) != '(':
666 return False
667 return False
667 context = self._get_context(cursor)
668 context = self._get_context(cursor)
668 if not context:
669 if not context:
669 return False
670 return False
670
671
671 # Send the metadata request to the kernel
672 # Send the metadata request to the kernel
672 name = '.'.join(context)
673 name = '.'.join(context)
673 msg_id = self.kernel_manager.shell_channel.object_info(name)
674 msg_id = self.kernel_client.shell_channel.object_info(name)
674 pos = self._get_cursor().position()
675 pos = self._get_cursor().position()
675 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
676 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
676 return True
677 return True
677
678
678 def _complete(self):
679 def _complete(self):
679 """ Performs completion at the current cursor location.
680 """ Performs completion at the current cursor location.
680 """
681 """
681 context = self._get_context()
682 context = self._get_context()
682 if context:
683 if context:
683 # Send the completion request to the kernel
684 # Send the completion request to the kernel
684 msg_id = self.kernel_manager.shell_channel.complete(
685 msg_id = self.kernel_client.shell_channel.complete(
685 '.'.join(context), # text
686 '.'.join(context), # text
686 self._get_input_buffer_cursor_line(), # line
687 self._get_input_buffer_cursor_line(), # line
687 self._get_input_buffer_cursor_column(), # cursor_pos
688 self._get_input_buffer_cursor_column(), # cursor_pos
688 self.input_buffer) # block
689 self.input_buffer) # block
689 pos = self._get_cursor().position()
690 pos = self._get_cursor().position()
690 info = self._CompletionRequest(msg_id, pos)
691 info = self._CompletionRequest(msg_id, pos)
691 self._request_info['complete'] = info
692 self._request_info['complete'] = info
692
693
693 def _get_context(self, cursor=None):
694 def _get_context(self, cursor=None):
694 """ Gets the context for the specified cursor (or the current cursor
695 """ Gets the context for the specified cursor (or the current cursor
695 if none is specified).
696 if none is specified).
696 """
697 """
697 if cursor is None:
698 if cursor is None:
698 cursor = self._get_cursor()
699 cursor = self._get_cursor()
699 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
700 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
700 QtGui.QTextCursor.KeepAnchor)
701 QtGui.QTextCursor.KeepAnchor)
701 text = cursor.selection().toPlainText()
702 text = cursor.selection().toPlainText()
702 return self._completion_lexer.get_context(text)
703 return self._completion_lexer.get_context(text)
703
704
704 def _process_execute_abort(self, msg):
705 def _process_execute_abort(self, msg):
705 """ Process a reply for an aborted execution request.
706 """ Process a reply for an aborted execution request.
706 """
707 """
707 self._append_plain_text("ERROR: execution aborted\n")
708 self._append_plain_text("ERROR: execution aborted\n")
708
709
709 def _process_execute_error(self, msg):
710 def _process_execute_error(self, msg):
710 """ Process a reply for an execution request that resulted in an error.
711 """ Process a reply for an execution request that resulted in an error.
711 """
712 """
712 content = msg['content']
713 content = msg['content']
713 # If a SystemExit is passed along, this means exit() was called - also
714 # If a SystemExit is passed along, this means exit() was called - also
714 # all the ipython %exit magic syntax of '-k' to be used to keep
715 # all the ipython %exit magic syntax of '-k' to be used to keep
715 # the kernel running
716 # the kernel running
716 if content['ename']=='SystemExit':
717 if content['ename']=='SystemExit':
717 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
718 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
718 self._keep_kernel_on_exit = keepkernel
719 self._keep_kernel_on_exit = keepkernel
719 self.exit_requested.emit(self)
720 self.exit_requested.emit(self)
720 else:
721 else:
721 traceback = ''.join(content['traceback'])
722 traceback = ''.join(content['traceback'])
722 self._append_plain_text(traceback)
723 self._append_plain_text(traceback)
723
724
724 def _process_execute_ok(self, msg):
725 def _process_execute_ok(self, msg):
725 """ Process a reply for a successful execution request.
726 """ Process a reply for a successful execution request.
726 """
727 """
727 payload = msg['content']['payload']
728 payload = msg['content']['payload']
728 for item in payload:
729 for item in payload:
729 if not self._process_execute_payload(item):
730 if not self._process_execute_payload(item):
730 warning = 'Warning: received unknown payload of type %s'
731 warning = 'Warning: received unknown payload of type %s'
731 print(warning % repr(item['source']))
732 print(warning % repr(item['source']))
732
733
733 def _process_execute_payload(self, item):
734 def _process_execute_payload(self, item):
734 """ Process a single payload item from the list of payload items in an
735 """ Process a single payload item from the list of payload items in an
735 execution reply. Returns whether the payload was handled.
736 execution reply. Returns whether the payload was handled.
736 """
737 """
737 # The basic FrontendWidget doesn't handle payloads, as they are a
738 # The basic FrontendWidget doesn't handle payloads, as they are a
738 # mechanism for going beyond the standard Python interpreter model.
739 # mechanism for going beyond the standard Python interpreter model.
739 return False
740 return False
740
741
741 def _show_interpreter_prompt(self):
742 def _show_interpreter_prompt(self):
742 """ Shows a prompt for the interpreter.
743 """ Shows a prompt for the interpreter.
743 """
744 """
744 self._show_prompt('>>> ')
745 self._show_prompt('>>> ')
745
746
746 def _show_interpreter_prompt_for_reply(self, msg):
747 def _show_interpreter_prompt_for_reply(self, msg):
747 """ Shows a prompt for the interpreter given an 'execute_reply' message.
748 """ Shows a prompt for the interpreter given an 'execute_reply' message.
748 """
749 """
749 self._show_interpreter_prompt()
750 self._show_interpreter_prompt()
750
751
751 #------ Signal handlers ----------------------------------------------------
752 #------ Signal handlers ----------------------------------------------------
752
753
753 def _document_contents_change(self, position, removed, added):
754 def _document_contents_change(self, position, removed, added):
754 """ Called whenever the document's content changes. Display a call tip
755 """ Called whenever the document's content changes. Display a call tip
755 if appropriate.
756 if appropriate.
756 """
757 """
757 # Calculate where the cursor should be *after* the change:
758 # Calculate where the cursor should be *after* the change:
758 position += added
759 position += added
759
760
760 document = self._control.document()
761 document = self._control.document()
761 if position == self._get_cursor().position():
762 if position == self._get_cursor().position():
762 self._call_tip()
763 self._call_tip()
763
764
764 #------ Trait default initializers -----------------------------------------
765 #------ Trait default initializers -----------------------------------------
765
766
766 def _banner_default(self):
767 def _banner_default(self):
767 """ Returns the standard Python banner.
768 """ Returns the standard Python banner.
768 """
769 """
769 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
770 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
770 '"license" for more information.'
771 '"license" for more information.'
771 return banner % (sys.version, sys.platform)
772 return banner % (sys.version, sys.platform)
@@ -1,302 +1,302 b''
1 # System library imports
1 # System library imports
2 from IPython.external.qt import QtGui
2 from IPython.external.qt import QtGui
3
3
4 # Local imports
4 # Local imports
5 from IPython.utils.traitlets import Bool
5 from IPython.utils.traitlets import Bool
6 from console_widget import ConsoleWidget
6 from console_widget import ConsoleWidget
7
7
8
8
9 class HistoryConsoleWidget(ConsoleWidget):
9 class HistoryConsoleWidget(ConsoleWidget):
10 """ A ConsoleWidget that keeps a history of the commands that have been
10 """ A ConsoleWidget that keeps a history of the commands that have been
11 executed and provides a readline-esque interface to this history.
11 executed and provides a readline-esque interface to this history.
12 """
12 """
13
13
14 #------ Configuration ------------------------------------------------------
14 #------ Configuration ------------------------------------------------------
15
15
16 # If enabled, the input buffer will become "locked" to history movement when
16 # If enabled, the input buffer will become "locked" to history movement when
17 # an edit is made to a multi-line input buffer. To override the lock, use
17 # an edit is made to a multi-line input buffer. To override the lock, use
18 # Shift in conjunction with the standard history cycling keys.
18 # Shift in conjunction with the standard history cycling keys.
19 history_lock = Bool(False, config=True)
19 history_lock = Bool(False, config=True)
20
20
21 #---------------------------------------------------------------------------
21 #---------------------------------------------------------------------------
22 # 'object' interface
22 # 'object' interface
23 #---------------------------------------------------------------------------
23 #---------------------------------------------------------------------------
24
24
25 def __init__(self, *args, **kw):
25 def __init__(self, *args, **kw):
26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
26 super(HistoryConsoleWidget, self).__init__(*args, **kw)
27
27
28 # HistoryConsoleWidget protected variables.
28 # HistoryConsoleWidget protected variables.
29 self._history = []
29 self._history = []
30 self._history_edits = {}
30 self._history_edits = {}
31 self._history_index = 0
31 self._history_index = 0
32 self._history_prefix = ''
32 self._history_prefix = ''
33
33
34 #---------------------------------------------------------------------------
34 #---------------------------------------------------------------------------
35 # 'ConsoleWidget' public interface
35 # 'ConsoleWidget' public interface
36 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
37
37
38 def execute(self, source=None, hidden=False, interactive=False):
38 def execute(self, source=None, hidden=False, interactive=False):
39 """ Reimplemented to the store history.
39 """ Reimplemented to the store history.
40 """
40 """
41 if not hidden:
41 if not hidden:
42 history = self.input_buffer if source is None else source
42 history = self.input_buffer if source is None else source
43
43
44 executed = super(HistoryConsoleWidget, self).execute(
44 executed = super(HistoryConsoleWidget, self).execute(
45 source, hidden, interactive)
45 source, hidden, interactive)
46
46
47 if executed and not hidden:
47 if executed and not hidden:
48 # Save the command unless it was an empty string or was identical
48 # Save the command unless it was an empty string or was identical
49 # to the previous command.
49 # to the previous command.
50 history = history.rstrip()
50 history = history.rstrip()
51 if history and (not self._history or self._history[-1] != history):
51 if history and (not self._history or self._history[-1] != history):
52 self._history.append(history)
52 self._history.append(history)
53
53
54 # Emulate readline: reset all history edits.
54 # Emulate readline: reset all history edits.
55 self._history_edits = {}
55 self._history_edits = {}
56
56
57 # Move the history index to the most recent item.
57 # Move the history index to the most recent item.
58 self._history_index = len(self._history)
58 self._history_index = len(self._history)
59
59
60 return executed
60 return executed
61
61
62 #---------------------------------------------------------------------------
62 #---------------------------------------------------------------------------
63 # 'ConsoleWidget' abstract interface
63 # 'ConsoleWidget' abstract interface
64 #---------------------------------------------------------------------------
64 #---------------------------------------------------------------------------
65
65
66 def _up_pressed(self, shift_modifier):
66 def _up_pressed(self, shift_modifier):
67 """ Called when the up key is pressed. Returns whether to continue
67 """ Called when the up key is pressed. Returns whether to continue
68 processing the event.
68 processing the event.
69 """
69 """
70 prompt_cursor = self._get_prompt_cursor()
70 prompt_cursor = self._get_prompt_cursor()
71 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
71 if self._get_cursor().blockNumber() == prompt_cursor.blockNumber():
72 # Bail out if we're locked.
72 # Bail out if we're locked.
73 if self._history_locked() and not shift_modifier:
73 if self._history_locked() and not shift_modifier:
74 return False
74 return False
75
75
76 # Set a search prefix based on the cursor position.
76 # Set a search prefix based on the cursor position.
77 col = self._get_input_buffer_cursor_column()
77 col = self._get_input_buffer_cursor_column()
78 input_buffer = self.input_buffer
78 input_buffer = self.input_buffer
79 # use the *shortest* of the cursor column and the history prefix
79 # use the *shortest* of the cursor column and the history prefix
80 # to determine if the prefix has changed
80 # to determine if the prefix has changed
81 n = min(col, len(self._history_prefix))
81 n = min(col, len(self._history_prefix))
82
82
83 # prefix changed, restart search from the beginning
83 # prefix changed, restart search from the beginning
84 if (self._history_prefix[:n] != input_buffer[:n]):
84 if (self._history_prefix[:n] != input_buffer[:n]):
85 self._history_index = len(self._history)
85 self._history_index = len(self._history)
86
86
87 # the only time we shouldn't set the history prefix
87 # the only time we shouldn't set the history prefix
88 # to the line up to the cursor is if we are already
88 # to the line up to the cursor is if we are already
89 # in a simple scroll (no prefix),
89 # in a simple scroll (no prefix),
90 # and the cursor is at the end of the first line
90 # and the cursor is at the end of the first line
91
91
92 # check if we are at the end of the first line
92 # check if we are at the end of the first line
93 c = self._get_cursor()
93 c = self._get_cursor()
94 current_pos = c.position()
94 current_pos = c.position()
95 c.movePosition(QtGui.QTextCursor.EndOfLine)
95 c.movePosition(QtGui.QTextCursor.EndOfLine)
96 at_eol = (c.position() == current_pos)
96 at_eol = (c.position() == current_pos)
97
97
98 if self._history_index == len(self._history) or \
98 if self._history_index == len(self._history) or \
99 not (self._history_prefix == '' and at_eol) or \
99 not (self._history_prefix == '' and at_eol) or \
100 not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
100 not (self._get_edited_history(self._history_index)[:col] == input_buffer[:col]):
101 self._history_prefix = input_buffer[:col]
101 self._history_prefix = input_buffer[:col]
102
102
103 # Perform the search.
103 # Perform the search.
104 self.history_previous(self._history_prefix,
104 self.history_previous(self._history_prefix,
105 as_prefix=not shift_modifier)
105 as_prefix=not shift_modifier)
106
106
107 # Go to the first line of the prompt for seemless history scrolling.
107 # Go to the first line of the prompt for seemless history scrolling.
108 # Emulate readline: keep the cursor position fixed for a prefix
108 # Emulate readline: keep the cursor position fixed for a prefix
109 # search.
109 # search.
110 cursor = self._get_prompt_cursor()
110 cursor = self._get_prompt_cursor()
111 if self._history_prefix:
111 if self._history_prefix:
112 cursor.movePosition(QtGui.QTextCursor.Right,
112 cursor.movePosition(QtGui.QTextCursor.Right,
113 n=len(self._history_prefix))
113 n=len(self._history_prefix))
114 else:
114 else:
115 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
115 cursor.movePosition(QtGui.QTextCursor.EndOfLine)
116 self._set_cursor(cursor)
116 self._set_cursor(cursor)
117
117
118 return False
118 return False
119
119
120 return True
120 return True
121
121
122 def _down_pressed(self, shift_modifier):
122 def _down_pressed(self, shift_modifier):
123 """ Called when the down key is pressed. Returns whether to continue
123 """ Called when the down key is pressed. Returns whether to continue
124 processing the event.
124 processing the event.
125 """
125 """
126 end_cursor = self._get_end_cursor()
126 end_cursor = self._get_end_cursor()
127 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
127 if self._get_cursor().blockNumber() == end_cursor.blockNumber():
128 # Bail out if we're locked.
128 # Bail out if we're locked.
129 if self._history_locked() and not shift_modifier:
129 if self._history_locked() and not shift_modifier:
130 return False
130 return False
131
131
132 # Perform the search.
132 # Perform the search.
133 replaced = self.history_next(self._history_prefix,
133 replaced = self.history_next(self._history_prefix,
134 as_prefix=not shift_modifier)
134 as_prefix=not shift_modifier)
135
135
136 # Emulate readline: keep the cursor position fixed for a prefix
136 # Emulate readline: keep the cursor position fixed for a prefix
137 # search. (We don't need to move the cursor to the end of the buffer
137 # search. (We don't need to move the cursor to the end of the buffer
138 # in the other case because this happens automatically when the
138 # in the other case because this happens automatically when the
139 # input buffer is set.)
139 # input buffer is set.)
140 if self._history_prefix and replaced:
140 if self._history_prefix and replaced:
141 cursor = self._get_prompt_cursor()
141 cursor = self._get_prompt_cursor()
142 cursor.movePosition(QtGui.QTextCursor.Right,
142 cursor.movePosition(QtGui.QTextCursor.Right,
143 n=len(self._history_prefix))
143 n=len(self._history_prefix))
144 self._set_cursor(cursor)
144 self._set_cursor(cursor)
145
145
146 return False
146 return False
147
147
148 return True
148 return True
149
149
150 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
151 # 'HistoryConsoleWidget' public interface
151 # 'HistoryConsoleWidget' public interface
152 #---------------------------------------------------------------------------
152 #---------------------------------------------------------------------------
153
153
154 def history_previous(self, substring='', as_prefix=True):
154 def history_previous(self, substring='', as_prefix=True):
155 """ If possible, set the input buffer to a previous history item.
155 """ If possible, set the input buffer to a previous history item.
156
156
157 Parameters:
157 Parameters:
158 -----------
158 -----------
159 substring : str, optional
159 substring : str, optional
160 If specified, search for an item with this substring.
160 If specified, search for an item with this substring.
161 as_prefix : bool, optional
161 as_prefix : bool, optional
162 If True, the substring must match at the beginning (default).
162 If True, the substring must match at the beginning (default).
163
163
164 Returns:
164 Returns:
165 --------
165 --------
166 Whether the input buffer was changed.
166 Whether the input buffer was changed.
167 """
167 """
168 index = self._history_index
168 index = self._history_index
169 replace = False
169 replace = False
170 while index > 0:
170 while index > 0:
171 index -= 1
171 index -= 1
172 history = self._get_edited_history(index)
172 history = self._get_edited_history(index)
173 if (as_prefix and history.startswith(substring)) \
173 if (as_prefix and history.startswith(substring)) \
174 or (not as_prefix and substring in history):
174 or (not as_prefix and substring in history):
175 replace = True
175 replace = True
176 break
176 break
177
177
178 if replace:
178 if replace:
179 self._store_edits()
179 self._store_edits()
180 self._history_index = index
180 self._history_index = index
181 self.input_buffer = history
181 self.input_buffer = history
182
182
183 return replace
183 return replace
184
184
185 def history_next(self, substring='', as_prefix=True):
185 def history_next(self, substring='', as_prefix=True):
186 """ If possible, set the input buffer to a subsequent history item.
186 """ If possible, set the input buffer to a subsequent history item.
187
187
188 Parameters:
188 Parameters:
189 -----------
189 -----------
190 substring : str, optional
190 substring : str, optional
191 If specified, search for an item with this substring.
191 If specified, search for an item with this substring.
192 as_prefix : bool, optional
192 as_prefix : bool, optional
193 If True, the substring must match at the beginning (default).
193 If True, the substring must match at the beginning (default).
194
194
195 Returns:
195 Returns:
196 --------
196 --------
197 Whether the input buffer was changed.
197 Whether the input buffer was changed.
198 """
198 """
199 index = self._history_index
199 index = self._history_index
200 replace = False
200 replace = False
201 while index < len(self._history):
201 while index < len(self._history):
202 index += 1
202 index += 1
203 history = self._get_edited_history(index)
203 history = self._get_edited_history(index)
204 if (as_prefix and history.startswith(substring)) \
204 if (as_prefix and history.startswith(substring)) \
205 or (not as_prefix and substring in history):
205 or (not as_prefix and substring in history):
206 replace = True
206 replace = True
207 break
207 break
208
208
209 if replace:
209 if replace:
210 self._store_edits()
210 self._store_edits()
211 self._history_index = index
211 self._history_index = index
212 self.input_buffer = history
212 self.input_buffer = history
213
213
214 return replace
214 return replace
215
215
216 def history_tail(self, n=10):
216 def history_tail(self, n=10):
217 """ Get the local history list.
217 """ Get the local history list.
218
218
219 Parameters:
219 Parameters:
220 -----------
220 -----------
221 n : int
221 n : int
222 The (maximum) number of history items to get.
222 The (maximum) number of history items to get.
223 """
223 """
224 return self._history[-n:]
224 return self._history[-n:]
225
225
226 def _request_update_session_history_length(self):
226 def _request_update_session_history_length(self):
227 msg_id = self.kernel_manager.shell_channel.execute('',
227 msg_id = self.kernel_client.shell_channel.execute('',
228 silent=True,
228 silent=True,
229 user_expressions={
229 user_expressions={
230 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
230 'hlen':'len(get_ipython().history_manager.input_hist_raw)',
231 }
231 }
232 )
232 )
233 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
233 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'save_magic')
234
234
235 def _handle_execute_reply(self, msg):
235 def _handle_execute_reply(self, msg):
236 """ Handles replies for code execution, here only session history length
236 """ Handles replies for code execution, here only session history length
237 """
237 """
238 msg_id = msg['parent_header']['msg_id']
238 msg_id = msg['parent_header']['msg_id']
239 info = self._request_info['execute'].pop(msg_id,None)
239 info = self._request_info['execute'].pop(msg_id,None)
240 if info and info.kind == 'save_magic' and not self._hidden:
240 if info and info.kind == 'save_magic' and not self._hidden:
241 content = msg['content']
241 content = msg['content']
242 status = content['status']
242 status = content['status']
243 if status == 'ok':
243 if status == 'ok':
244 self._max_session_history=(int(content['user_expressions']['hlen']))
244 self._max_session_history=(int(content['user_expressions']['hlen']))
245
245
246 def save_magic(self):
246 def save_magic(self):
247 # update the session history length
247 # update the session history length
248 self._request_update_session_history_length()
248 self._request_update_session_history_length()
249
249
250 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
250 file_name,extFilter = QtGui.QFileDialog.getSaveFileName(self,
251 "Enter A filename",
251 "Enter A filename",
252 filter='Python File (*.py);; All files (*.*)'
252 filter='Python File (*.py);; All files (*.*)'
253 )
253 )
254
254
255 # let's the user search/type for a file name, while the history length
255 # let's the user search/type for a file name, while the history length
256 # is fetched
256 # is fetched
257
257
258 if file_name:
258 if file_name:
259 hist_range, ok = QtGui.QInputDialog.getText(self,
259 hist_range, ok = QtGui.QInputDialog.getText(self,
260 'Please enter an interval of command to save',
260 'Please enter an interval of command to save',
261 'Saving commands:',
261 'Saving commands:',
262 text=str('1-'+str(self._max_session_history))
262 text=str('1-'+str(self._max_session_history))
263 )
263 )
264 if ok:
264 if ok:
265 self.execute("%save"+" "+file_name+" "+str(hist_range))
265 self.execute("%save"+" "+file_name+" "+str(hist_range))
266
266
267 #---------------------------------------------------------------------------
267 #---------------------------------------------------------------------------
268 # 'HistoryConsoleWidget' protected interface
268 # 'HistoryConsoleWidget' protected interface
269 #---------------------------------------------------------------------------
269 #---------------------------------------------------------------------------
270
270
271 def _history_locked(self):
271 def _history_locked(self):
272 """ Returns whether history movement is locked.
272 """ Returns whether history movement is locked.
273 """
273 """
274 return (self.history_lock and
274 return (self.history_lock and
275 (self._get_edited_history(self._history_index) !=
275 (self._get_edited_history(self._history_index) !=
276 self.input_buffer) and
276 self.input_buffer) and
277 (self._get_prompt_cursor().blockNumber() !=
277 (self._get_prompt_cursor().blockNumber() !=
278 self._get_end_cursor().blockNumber()))
278 self._get_end_cursor().blockNumber()))
279
279
280 def _get_edited_history(self, index):
280 def _get_edited_history(self, index):
281 """ Retrieves a history item, possibly with temporary edits.
281 """ Retrieves a history item, possibly with temporary edits.
282 """
282 """
283 if index in self._history_edits:
283 if index in self._history_edits:
284 return self._history_edits[index]
284 return self._history_edits[index]
285 elif index == len(self._history):
285 elif index == len(self._history):
286 return unicode()
286 return unicode()
287 return self._history[index]
287 return self._history[index]
288
288
289 def _set_history(self, history):
289 def _set_history(self, history):
290 """ Replace the current history with a sequence of history items.
290 """ Replace the current history with a sequence of history items.
291 """
291 """
292 self._history = list(history)
292 self._history = list(history)
293 self._history_edits = {}
293 self._history_edits = {}
294 self._history_index = len(self._history)
294 self._history_index = len(self._history)
295
295
296 def _store_edits(self):
296 def _store_edits(self):
297 """ If there are edits to the current input buffer, store them.
297 """ If there are edits to the current input buffer, store them.
298 """
298 """
299 current = self.input_buffer
299 current = self.input_buffer
300 if self._history_index == len(self._history) or \
300 if self._history_index == len(self._history) or \
301 self._history[self._history_index] != current:
301 self._history[self._history_index] != current:
302 self._history_edits[self._history_index] = current
302 self._history_edits[self._history_index] = current
@@ -1,584 +1,584 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3 """
3 """
4
4
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Imports
6 # Imports
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 # Standard library imports
9 # Standard library imports
10 from collections import namedtuple
10 from collections import namedtuple
11 import os.path
11 import os.path
12 import re
12 import re
13 from subprocess import Popen
13 from subprocess import Popen
14 import sys
14 import sys
15 import time
15 import time
16 from textwrap import dedent
16 from textwrap import dedent
17
17
18 # System library imports
18 # System library imports
19 from IPython.external.qt import QtCore, QtGui
19 from IPython.external.qt import QtCore, QtGui
20
20
21 # Local imports
21 # Local imports
22 from IPython.core.inputsplitter import IPythonInputSplitter
22 from IPython.core.inputsplitter import IPythonInputSplitter
23 from IPython.core.inputtransformer import ipy_prompt
23 from IPython.core.inputtransformer import ipy_prompt
24 from IPython.utils.traitlets import Bool, Unicode
24 from IPython.utils.traitlets import Bool, Unicode
25 from frontend_widget import FrontendWidget
25 from frontend_widget import FrontendWidget
26 import styles
26 import styles
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Constants
29 # Constants
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 # Default strings to build and display input and output prompts (and separators
32 # Default strings to build and display input and output prompts (and separators
33 # in between)
33 # in between)
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
34 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
35 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
36 default_input_sep = '\n'
36 default_input_sep = '\n'
37 default_output_sep = ''
37 default_output_sep = ''
38 default_output_sep2 = ''
38 default_output_sep2 = ''
39
39
40 # Base path for most payload sources.
40 # Base path for most payload sources.
41 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
41 zmq_shell_source = 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell'
42
42
43 if sys.platform.startswith('win'):
43 if sys.platform.startswith('win'):
44 default_editor = 'notepad'
44 default_editor = 'notepad'
45 else:
45 else:
46 default_editor = ''
46 default_editor = ''
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # IPythonWidget class
49 # IPythonWidget class
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class IPythonWidget(FrontendWidget):
52 class IPythonWidget(FrontendWidget):
53 """ A FrontendWidget for an IPython kernel.
53 """ A FrontendWidget for an IPython kernel.
54 """
54 """
55
55
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
58 # settings.
58 # settings.
59 custom_edit = Bool(False)
59 custom_edit = Bool(False)
60 custom_edit_requested = QtCore.Signal(object, object)
60 custom_edit_requested = QtCore.Signal(object, object)
61
61
62 editor = Unicode(default_editor, config=True,
62 editor = Unicode(default_editor, config=True,
63 help="""
63 help="""
64 A command for invoking a system text editor. If the string contains a
64 A command for invoking a system text editor. If the string contains a
65 {filename} format specifier, it will be used. Otherwise, the filename
65 {filename} format specifier, it will be used. Otherwise, the filename
66 will be appended to the end the command.
66 will be appended to the end the command.
67 """)
67 """)
68
68
69 editor_line = Unicode(config=True,
69 editor_line = Unicode(config=True,
70 help="""
70 help="""
71 The editor command to use when a specific line number is requested. The
71 The editor command to use when a specific line number is requested. The
72 string should contain two format specifiers: {line} and {filename}. If
72 string should contain two format specifiers: {line} and {filename}. If
73 this parameter is not specified, the line number option to the %edit
73 this parameter is not specified, the line number option to the %edit
74 magic will be ignored.
74 magic will be ignored.
75 """)
75 """)
76
76
77 style_sheet = Unicode(config=True,
77 style_sheet = Unicode(config=True,
78 help="""
78 help="""
79 A CSS stylesheet. The stylesheet can contain classes for:
79 A CSS stylesheet. The stylesheet can contain classes for:
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
80 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
81 2. Pygments: .c, .k, .o, etc. (see PygmentsHighlighter)
82 3. IPython: .error, .in-prompt, .out-prompt, etc
82 3. IPython: .error, .in-prompt, .out-prompt, etc
83 """)
83 """)
84
84
85 syntax_style = Unicode(config=True,
85 syntax_style = Unicode(config=True,
86 help="""
86 help="""
87 If not empty, use this Pygments style for syntax highlighting.
87 If not empty, use this Pygments style for syntax highlighting.
88 Otherwise, the style sheet is queried for Pygments style
88 Otherwise, the style sheet is queried for Pygments style
89 information.
89 information.
90 """)
90 """)
91
91
92 # Prompts.
92 # Prompts.
93 in_prompt = Unicode(default_in_prompt, config=True)
93 in_prompt = Unicode(default_in_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
94 out_prompt = Unicode(default_out_prompt, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
95 input_sep = Unicode(default_input_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
96 output_sep = Unicode(default_output_sep, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
97 output_sep2 = Unicode(default_output_sep2, config=True)
98
98
99 # FrontendWidget protected class variables.
99 # FrontendWidget protected class variables.
100 _input_splitter_class = IPythonInputSplitter
100 _input_splitter_class = IPythonInputSplitter
101 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
101 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[ipy_prompt()],
102 logical_line_transforms=[],
102 logical_line_transforms=[],
103 python_line_transforms=[],
103 python_line_transforms=[],
104 )
104 )
105
105
106 # IPythonWidget protected class variables.
106 # IPythonWidget protected class variables.
107 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
107 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
108 _payload_source_edit = zmq_shell_source + '.edit_magic'
108 _payload_source_edit = zmq_shell_source + '.edit_magic'
109 _payload_source_exit = zmq_shell_source + '.ask_exit'
109 _payload_source_exit = zmq_shell_source + '.ask_exit'
110 _payload_source_next_input = zmq_shell_source + '.set_next_input'
110 _payload_source_next_input = zmq_shell_source + '.set_next_input'
111 _payload_source_page = 'IPython.kernel.zmq.page.page'
111 _payload_source_page = 'IPython.kernel.zmq.page.page'
112 _retrying_history_request = False
112 _retrying_history_request = False
113
113
114 #---------------------------------------------------------------------------
114 #---------------------------------------------------------------------------
115 # 'object' interface
115 # 'object' interface
116 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
117
117
118 def __init__(self, *args, **kw):
118 def __init__(self, *args, **kw):
119 super(IPythonWidget, self).__init__(*args, **kw)
119 super(IPythonWidget, self).__init__(*args, **kw)
120
120
121 # IPythonWidget protected variables.
121 # IPythonWidget protected variables.
122 self._payload_handlers = {
122 self._payload_handlers = {
123 self._payload_source_edit : self._handle_payload_edit,
123 self._payload_source_edit : self._handle_payload_edit,
124 self._payload_source_exit : self._handle_payload_exit,
124 self._payload_source_exit : self._handle_payload_exit,
125 self._payload_source_page : self._handle_payload_page,
125 self._payload_source_page : self._handle_payload_page,
126 self._payload_source_next_input : self._handle_payload_next_input }
126 self._payload_source_next_input : self._handle_payload_next_input }
127 self._previous_prompt_obj = None
127 self._previous_prompt_obj = None
128 self._keep_kernel_on_exit = None
128 self._keep_kernel_on_exit = None
129
129
130 # Initialize widget styling.
130 # Initialize widget styling.
131 if self.style_sheet:
131 if self.style_sheet:
132 self._style_sheet_changed()
132 self._style_sheet_changed()
133 self._syntax_style_changed()
133 self._syntax_style_changed()
134 else:
134 else:
135 self.set_default_style()
135 self.set_default_style()
136
136
137 #---------------------------------------------------------------------------
137 #---------------------------------------------------------------------------
138 # 'BaseFrontendMixin' abstract interface
138 # 'BaseFrontendMixin' abstract interface
139 #---------------------------------------------------------------------------
139 #---------------------------------------------------------------------------
140
140
141 def _handle_complete_reply(self, rep):
141 def _handle_complete_reply(self, rep):
142 """ Reimplemented to support IPython's improved completion machinery.
142 """ Reimplemented to support IPython's improved completion machinery.
143 """
143 """
144 self.log.debug("complete: %s", rep.get('content', ''))
144 self.log.debug("complete: %s", rep.get('content', ''))
145 cursor = self._get_cursor()
145 cursor = self._get_cursor()
146 info = self._request_info.get('complete')
146 info = self._request_info.get('complete')
147 if info and info.id == rep['parent_header']['msg_id'] and \
147 if info and info.id == rep['parent_header']['msg_id'] and \
148 info.pos == cursor.position():
148 info.pos == cursor.position():
149 matches = rep['content']['matches']
149 matches = rep['content']['matches']
150 text = rep['content']['matched_text']
150 text = rep['content']['matched_text']
151 offset = len(text)
151 offset = len(text)
152
152
153 # Clean up matches with period and path separators if the matched
153 # Clean up matches with period and path separators if the matched
154 # text has not been transformed. This is done by truncating all
154 # text has not been transformed. This is done by truncating all
155 # but the last component and then suitably decreasing the offset
155 # but the last component and then suitably decreasing the offset
156 # between the current cursor position and the start of completion.
156 # between the current cursor position and the start of completion.
157 if len(matches) > 1 and matches[0][:offset] == text:
157 if len(matches) > 1 and matches[0][:offset] == text:
158 parts = re.split(r'[./\\]', text)
158 parts = re.split(r'[./\\]', text)
159 sep_count = len(parts) - 1
159 sep_count = len(parts) - 1
160 if sep_count:
160 if sep_count:
161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
161 chop_length = sum(map(len, parts[:sep_count])) + sep_count
162 matches = [ match[chop_length:] for match in matches ]
162 matches = [ match[chop_length:] for match in matches ]
163 offset -= chop_length
163 offset -= chop_length
164
164
165 # Move the cursor to the start of the match and complete.
165 # Move the cursor to the start of the match and complete.
166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
166 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
167 self._complete_with_items(cursor, matches)
167 self._complete_with_items(cursor, matches)
168
168
169 def _handle_execute_reply(self, msg):
169 def _handle_execute_reply(self, msg):
170 """ Reimplemented to support prompt requests.
170 """ Reimplemented to support prompt requests.
171 """
171 """
172 msg_id = msg['parent_header'].get('msg_id')
172 msg_id = msg['parent_header'].get('msg_id')
173 info = self._request_info['execute'].get(msg_id)
173 info = self._request_info['execute'].get(msg_id)
174 if info and info.kind == 'prompt':
174 if info and info.kind == 'prompt':
175 number = msg['content']['execution_count'] + 1
175 number = msg['content']['execution_count'] + 1
176 self._show_interpreter_prompt(number)
176 self._show_interpreter_prompt(number)
177 self._request_info['execute'].pop(msg_id)
177 self._request_info['execute'].pop(msg_id)
178 else:
178 else:
179 super(IPythonWidget, self)._handle_execute_reply(msg)
179 super(IPythonWidget, self)._handle_execute_reply(msg)
180
180
181 def _handle_history_reply(self, msg):
181 def _handle_history_reply(self, msg):
182 """ Implemented to handle history tail replies, which are only supported
182 """ Implemented to handle history tail replies, which are only supported
183 by the IPython kernel.
183 by the IPython kernel.
184 """
184 """
185 content = msg['content']
185 content = msg['content']
186 if 'history' not in content:
186 if 'history' not in content:
187 self.log.error("History request failed: %r"%content)
187 self.log.error("History request failed: %r"%content)
188 if content.get('status', '') == 'aborted' and \
188 if content.get('status', '') == 'aborted' and \
189 not self._retrying_history_request:
189 not self._retrying_history_request:
190 # a *different* action caused this request to be aborted, so
190 # a *different* action caused this request to be aborted, so
191 # we should try again.
191 # we should try again.
192 self.log.error("Retrying aborted history request")
192 self.log.error("Retrying aborted history request")
193 # prevent multiple retries of aborted requests:
193 # prevent multiple retries of aborted requests:
194 self._retrying_history_request = True
194 self._retrying_history_request = True
195 # wait out the kernel's queue flush, which is currently timed at 0.1s
195 # wait out the kernel's queue flush, which is currently timed at 0.1s
196 time.sleep(0.25)
196 time.sleep(0.25)
197 self.kernel_manager.shell_channel.history(hist_access_type='tail',n=1000)
197 self.kernel_client.shell_channel.history(hist_access_type='tail',n=1000)
198 else:
198 else:
199 self._retrying_history_request = False
199 self._retrying_history_request = False
200 return
200 return
201 # reset retry flag
201 # reset retry flag
202 self._retrying_history_request = False
202 self._retrying_history_request = False
203 history_items = content['history']
203 history_items = content['history']
204 self.log.debug("Received history reply with %i entries", len(history_items))
204 self.log.debug("Received history reply with %i entries", len(history_items))
205 items = []
205 items = []
206 last_cell = u""
206 last_cell = u""
207 for _, _, cell in history_items:
207 for _, _, cell in history_items:
208 cell = cell.rstrip()
208 cell = cell.rstrip()
209 if cell != last_cell:
209 if cell != last_cell:
210 items.append(cell)
210 items.append(cell)
211 last_cell = cell
211 last_cell = cell
212 self._set_history(items)
212 self._set_history(items)
213
213
214 def _handle_pyout(self, msg):
214 def _handle_pyout(self, msg):
215 """ Reimplemented for IPython-style "display hook".
215 """ Reimplemented for IPython-style "display hook".
216 """
216 """
217 self.log.debug("pyout: %s", msg.get('content', ''))
217 self.log.debug("pyout: %s", msg.get('content', ''))
218 if not self._hidden and self._is_from_this_session(msg):
218 if not self._hidden and self._is_from_this_session(msg):
219 content = msg['content']
219 content = msg['content']
220 prompt_number = content.get('execution_count', 0)
220 prompt_number = content.get('execution_count', 0)
221 data = content['data']
221 data = content['data']
222 if 'text/html' in data:
222 if 'text/html' in data:
223 self._append_plain_text(self.output_sep, True)
223 self._append_plain_text(self.output_sep, True)
224 self._append_html(self._make_out_prompt(prompt_number), True)
224 self._append_html(self._make_out_prompt(prompt_number), True)
225 html = data['text/html']
225 html = data['text/html']
226 self._append_plain_text('\n', True)
226 self._append_plain_text('\n', True)
227 self._append_html(html + self.output_sep2, True)
227 self._append_html(html + self.output_sep2, True)
228 elif 'text/plain' in data:
228 elif 'text/plain' in data:
229 self._append_plain_text(self.output_sep, True)
229 self._append_plain_text(self.output_sep, True)
230 self._append_html(self._make_out_prompt(prompt_number), True)
230 self._append_html(self._make_out_prompt(prompt_number), True)
231 text = data['text/plain']
231 text = data['text/plain']
232 # If the repr is multiline, make sure we start on a new line,
232 # If the repr is multiline, make sure we start on a new line,
233 # so that its lines are aligned.
233 # so that its lines are aligned.
234 if "\n" in text and not self.output_sep.endswith("\n"):
234 if "\n" in text and not self.output_sep.endswith("\n"):
235 self._append_plain_text('\n', True)
235 self._append_plain_text('\n', True)
236 self._append_plain_text(text + self.output_sep2, True)
236 self._append_plain_text(text + self.output_sep2, True)
237
237
238 def _handle_display_data(self, msg):
238 def _handle_display_data(self, msg):
239 """ The base handler for the ``display_data`` message.
239 """ The base handler for the ``display_data`` message.
240 """
240 """
241 self.log.debug("display: %s", msg.get('content', ''))
241 self.log.debug("display: %s", msg.get('content', ''))
242 # For now, we don't display data from other frontends, but we
242 # For now, we don't display data from other frontends, but we
243 # eventually will as this allows all frontends to monitor the display
243 # eventually will as this allows all frontends to monitor the display
244 # data. But we need to figure out how to handle this in the GUI.
244 # data. But we need to figure out how to handle this in the GUI.
245 if not self._hidden and self._is_from_this_session(msg):
245 if not self._hidden and self._is_from_this_session(msg):
246 source = msg['content']['source']
246 source = msg['content']['source']
247 data = msg['content']['data']
247 data = msg['content']['data']
248 metadata = msg['content']['metadata']
248 metadata = msg['content']['metadata']
249 # In the regular IPythonWidget, we simply print the plain text
249 # In the regular IPythonWidget, we simply print the plain text
250 # representation.
250 # representation.
251 if 'text/html' in data:
251 if 'text/html' in data:
252 html = data['text/html']
252 html = data['text/html']
253 self._append_html(html, True)
253 self._append_html(html, True)
254 elif 'text/plain' in data:
254 elif 'text/plain' in data:
255 text = data['text/plain']
255 text = data['text/plain']
256 self._append_plain_text(text, True)
256 self._append_plain_text(text, True)
257 # This newline seems to be needed for text and html output.
257 # This newline seems to be needed for text and html output.
258 self._append_plain_text(u'\n', True)
258 self._append_plain_text(u'\n', True)
259
259
260 def _started_channels(self):
260 def _started_channels(self):
261 """Reimplemented to make a history request and load %guiref."""
261 """Reimplemented to make a history request and load %guiref."""
262 super(IPythonWidget, self)._started_channels()
262 super(IPythonWidget, self)._started_channels()
263 self._load_guiref_magic()
263 self._load_guiref_magic()
264 self.kernel_manager.shell_channel.history(hist_access_type='tail',
264 self.kernel_client.shell_channel.history(hist_access_type='tail',
265 n=1000)
265 n=1000)
266
266
267 def _started_kernel(self):
267 def _started_kernel(self):
268 """Load %guiref when the kernel starts (if channels are also started).
268 """Load %guiref when the kernel starts (if channels are also started).
269
269
270 Principally triggered by kernel restart.
270 Principally triggered by kernel restart.
271 """
271 """
272 if self.kernel_manager.shell_channel is not None:
272 if self.kernel_client.shell_channel is not None:
273 self._load_guiref_magic()
273 self._load_guiref_magic()
274
274
275 def _load_guiref_magic(self):
275 def _load_guiref_magic(self):
276 """Load %guiref magic."""
276 """Load %guiref magic."""
277 self.kernel_manager.shell_channel.execute('\n'.join([
277 self.kernel_client.shell_channel.execute('\n'.join([
278 "try:",
278 "try:",
279 " _usage",
279 " _usage",
280 "except:",
280 "except:",
281 " from IPython.core import usage as _usage",
281 " from IPython.core import usage as _usage",
282 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
282 " get_ipython().register_magic_function(_usage.page_guiref, 'line', 'guiref')",
283 " del _usage",
283 " del _usage",
284 ]), silent=True)
284 ]), silent=True)
285
285
286 #---------------------------------------------------------------------------
286 #---------------------------------------------------------------------------
287 # 'ConsoleWidget' public interface
287 # 'ConsoleWidget' public interface
288 #---------------------------------------------------------------------------
288 #---------------------------------------------------------------------------
289
289
290 #---------------------------------------------------------------------------
290 #---------------------------------------------------------------------------
291 # 'FrontendWidget' public interface
291 # 'FrontendWidget' public interface
292 #---------------------------------------------------------------------------
292 #---------------------------------------------------------------------------
293
293
294 def execute_file(self, path, hidden=False):
294 def execute_file(self, path, hidden=False):
295 """ Reimplemented to use the 'run' magic.
295 """ Reimplemented to use the 'run' magic.
296 """
296 """
297 # Use forward slashes on Windows to avoid escaping each separator.
297 # Use forward slashes on Windows to avoid escaping each separator.
298 if sys.platform == 'win32':
298 if sys.platform == 'win32':
299 path = os.path.normpath(path).replace('\\', '/')
299 path = os.path.normpath(path).replace('\\', '/')
300
300
301 # Perhaps we should not be using %run directly, but while we
301 # Perhaps we should not be using %run directly, but while we
302 # are, it is necessary to quote or escape filenames containing spaces
302 # are, it is necessary to quote or escape filenames containing spaces
303 # or quotes.
303 # or quotes.
304
304
305 # In earlier code here, to minimize escaping, we sometimes quoted the
305 # In earlier code here, to minimize escaping, we sometimes quoted the
306 # filename with single quotes. But to do this, this code must be
306 # filename with single quotes. But to do this, this code must be
307 # platform-aware, because run uses shlex rather than python string
307 # platform-aware, because run uses shlex rather than python string
308 # parsing, so that:
308 # parsing, so that:
309 # * In Win: single quotes can be used in the filename without quoting,
309 # * In Win: single quotes can be used in the filename without quoting,
310 # and we cannot use single quotes to quote the filename.
310 # and we cannot use single quotes to quote the filename.
311 # * In *nix: we can escape double quotes in a double quoted filename,
311 # * In *nix: we can escape double quotes in a double quoted filename,
312 # but can't escape single quotes in a single quoted filename.
312 # but can't escape single quotes in a single quoted filename.
313
313
314 # So to keep this code non-platform-specific and simple, we now only
314 # So to keep this code non-platform-specific and simple, we now only
315 # use double quotes to quote filenames, and escape when needed:
315 # use double quotes to quote filenames, and escape when needed:
316 if ' ' in path or "'" in path or '"' in path:
316 if ' ' in path or "'" in path or '"' in path:
317 path = '"%s"' % path.replace('"', '\\"')
317 path = '"%s"' % path.replace('"', '\\"')
318 self.execute('%%run %s' % path, hidden=hidden)
318 self.execute('%%run %s' % path, hidden=hidden)
319
319
320 #---------------------------------------------------------------------------
320 #---------------------------------------------------------------------------
321 # 'FrontendWidget' protected interface
321 # 'FrontendWidget' protected interface
322 #---------------------------------------------------------------------------
322 #---------------------------------------------------------------------------
323
323
324 def _complete(self):
324 def _complete(self):
325 """ Reimplemented to support IPython's improved completion machinery.
325 """ Reimplemented to support IPython's improved completion machinery.
326 """
326 """
327 # We let the kernel split the input line, so we *always* send an empty
327 # We let the kernel split the input line, so we *always* send an empty
328 # text field. Readline-based frontends do get a real text field which
328 # text field. Readline-based frontends do get a real text field which
329 # they can use.
329 # they can use.
330 text = ''
330 text = ''
331
331
332 # Send the completion request to the kernel
332 # Send the completion request to the kernel
333 msg_id = self.kernel_manager.shell_channel.complete(
333 msg_id = self.kernel_client.shell_channel.complete(
334 text, # text
334 text, # text
335 self._get_input_buffer_cursor_line(), # line
335 self._get_input_buffer_cursor_line(), # line
336 self._get_input_buffer_cursor_column(), # cursor_pos
336 self._get_input_buffer_cursor_column(), # cursor_pos
337 self.input_buffer) # block
337 self.input_buffer) # block
338 pos = self._get_cursor().position()
338 pos = self._get_cursor().position()
339 info = self._CompletionRequest(msg_id, pos)
339 info = self._CompletionRequest(msg_id, pos)
340 self._request_info['complete'] = info
340 self._request_info['complete'] = info
341
341
342 def _process_execute_error(self, msg):
342 def _process_execute_error(self, msg):
343 """ Reimplemented for IPython-style traceback formatting.
343 """ Reimplemented for IPython-style traceback formatting.
344 """
344 """
345 content = msg['content']
345 content = msg['content']
346 traceback = '\n'.join(content['traceback']) + '\n'
346 traceback = '\n'.join(content['traceback']) + '\n'
347 if False:
347 if False:
348 # FIXME: For now, tracebacks come as plain text, so we can't use
348 # FIXME: For now, tracebacks come as plain text, so we can't use
349 # the html renderer yet. Once we refactor ultratb to produce
349 # the html renderer yet. Once we refactor ultratb to produce
350 # properly styled tracebacks, this branch should be the default
350 # properly styled tracebacks, this branch should be the default
351 traceback = traceback.replace(' ', '&nbsp;')
351 traceback = traceback.replace(' ', '&nbsp;')
352 traceback = traceback.replace('\n', '<br/>')
352 traceback = traceback.replace('\n', '<br/>')
353
353
354 ename = content['ename']
354 ename = content['ename']
355 ename_styled = '<span class="error">%s</span>' % ename
355 ename_styled = '<span class="error">%s</span>' % ename
356 traceback = traceback.replace(ename, ename_styled)
356 traceback = traceback.replace(ename, ename_styled)
357
357
358 self._append_html(traceback)
358 self._append_html(traceback)
359 else:
359 else:
360 # This is the fallback for now, using plain text with ansi escapes
360 # This is the fallback for now, using plain text with ansi escapes
361 self._append_plain_text(traceback)
361 self._append_plain_text(traceback)
362
362
363 def _process_execute_payload(self, item):
363 def _process_execute_payload(self, item):
364 """ Reimplemented to dispatch payloads to handler methods.
364 """ Reimplemented to dispatch payloads to handler methods.
365 """
365 """
366 handler = self._payload_handlers.get(item['source'])
366 handler = self._payload_handlers.get(item['source'])
367 if handler is None:
367 if handler is None:
368 # We have no handler for this type of payload, simply ignore it
368 # We have no handler for this type of payload, simply ignore it
369 return False
369 return False
370 else:
370 else:
371 handler(item)
371 handler(item)
372 return True
372 return True
373
373
374 def _show_interpreter_prompt(self, number=None):
374 def _show_interpreter_prompt(self, number=None):
375 """ Reimplemented for IPython-style prompts.
375 """ Reimplemented for IPython-style prompts.
376 """
376 """
377 # If a number was not specified, make a prompt number request.
377 # If a number was not specified, make a prompt number request.
378 if number is None:
378 if number is None:
379 msg_id = self.kernel_manager.shell_channel.execute('', silent=True)
379 msg_id = self.kernel_client.shell_channel.execute('', silent=True)
380 info = self._ExecutionRequest(msg_id, 'prompt')
380 info = self._ExecutionRequest(msg_id, 'prompt')
381 self._request_info['execute'][msg_id] = info
381 self._request_info['execute'][msg_id] = info
382 return
382 return
383
383
384 # Show a new prompt and save information about it so that it can be
384 # Show a new prompt and save information about it so that it can be
385 # updated later if the prompt number turns out to be wrong.
385 # updated later if the prompt number turns out to be wrong.
386 self._prompt_sep = self.input_sep
386 self._prompt_sep = self.input_sep
387 self._show_prompt(self._make_in_prompt(number), html=True)
387 self._show_prompt(self._make_in_prompt(number), html=True)
388 block = self._control.document().lastBlock()
388 block = self._control.document().lastBlock()
389 length = len(self._prompt)
389 length = len(self._prompt)
390 self._previous_prompt_obj = self._PromptBlock(block, length, number)
390 self._previous_prompt_obj = self._PromptBlock(block, length, number)
391
391
392 # Update continuation prompt to reflect (possibly) new prompt length.
392 # Update continuation prompt to reflect (possibly) new prompt length.
393 self._set_continuation_prompt(
393 self._set_continuation_prompt(
394 self._make_continuation_prompt(self._prompt), html=True)
394 self._make_continuation_prompt(self._prompt), html=True)
395
395
396 def _show_interpreter_prompt_for_reply(self, msg):
396 def _show_interpreter_prompt_for_reply(self, msg):
397 """ Reimplemented for IPython-style prompts.
397 """ Reimplemented for IPython-style prompts.
398 """
398 """
399 # Update the old prompt number if necessary.
399 # Update the old prompt number if necessary.
400 content = msg['content']
400 content = msg['content']
401 # abort replies do not have any keys:
401 # abort replies do not have any keys:
402 if content['status'] == 'aborted':
402 if content['status'] == 'aborted':
403 if self._previous_prompt_obj:
403 if self._previous_prompt_obj:
404 previous_prompt_number = self._previous_prompt_obj.number
404 previous_prompt_number = self._previous_prompt_obj.number
405 else:
405 else:
406 previous_prompt_number = 0
406 previous_prompt_number = 0
407 else:
407 else:
408 previous_prompt_number = content['execution_count']
408 previous_prompt_number = content['execution_count']
409 if self._previous_prompt_obj and \
409 if self._previous_prompt_obj and \
410 self._previous_prompt_obj.number != previous_prompt_number:
410 self._previous_prompt_obj.number != previous_prompt_number:
411 block = self._previous_prompt_obj.block
411 block = self._previous_prompt_obj.block
412
412
413 # Make sure the prompt block has not been erased.
413 # Make sure the prompt block has not been erased.
414 if block.isValid() and block.text():
414 if block.isValid() and block.text():
415
415
416 # Remove the old prompt and insert a new prompt.
416 # Remove the old prompt and insert a new prompt.
417 cursor = QtGui.QTextCursor(block)
417 cursor = QtGui.QTextCursor(block)
418 cursor.movePosition(QtGui.QTextCursor.Right,
418 cursor.movePosition(QtGui.QTextCursor.Right,
419 QtGui.QTextCursor.KeepAnchor,
419 QtGui.QTextCursor.KeepAnchor,
420 self._previous_prompt_obj.length)
420 self._previous_prompt_obj.length)
421 prompt = self._make_in_prompt(previous_prompt_number)
421 prompt = self._make_in_prompt(previous_prompt_number)
422 self._prompt = self._insert_html_fetching_plain_text(
422 self._prompt = self._insert_html_fetching_plain_text(
423 cursor, prompt)
423 cursor, prompt)
424
424
425 # When the HTML is inserted, Qt blows away the syntax
425 # When the HTML is inserted, Qt blows away the syntax
426 # highlighting for the line, so we need to rehighlight it.
426 # highlighting for the line, so we need to rehighlight it.
427 self._highlighter.rehighlightBlock(cursor.block())
427 self._highlighter.rehighlightBlock(cursor.block())
428
428
429 self._previous_prompt_obj = None
429 self._previous_prompt_obj = None
430
430
431 # Show a new prompt with the kernel's estimated prompt number.
431 # Show a new prompt with the kernel's estimated prompt number.
432 self._show_interpreter_prompt(previous_prompt_number + 1)
432 self._show_interpreter_prompt(previous_prompt_number + 1)
433
433
434 #---------------------------------------------------------------------------
434 #---------------------------------------------------------------------------
435 # 'IPythonWidget' interface
435 # 'IPythonWidget' interface
436 #---------------------------------------------------------------------------
436 #---------------------------------------------------------------------------
437
437
438 def set_default_style(self, colors='lightbg'):
438 def set_default_style(self, colors='lightbg'):
439 """ Sets the widget style to the class defaults.
439 """ Sets the widget style to the class defaults.
440
440
441 Parameters:
441 Parameters:
442 -----------
442 -----------
443 colors : str, optional (default lightbg)
443 colors : str, optional (default lightbg)
444 Whether to use the default IPython light background or dark
444 Whether to use the default IPython light background or dark
445 background or B&W style.
445 background or B&W style.
446 """
446 """
447 colors = colors.lower()
447 colors = colors.lower()
448 if colors=='lightbg':
448 if colors=='lightbg':
449 self.style_sheet = styles.default_light_style_sheet
449 self.style_sheet = styles.default_light_style_sheet
450 self.syntax_style = styles.default_light_syntax_style
450 self.syntax_style = styles.default_light_syntax_style
451 elif colors=='linux':
451 elif colors=='linux':
452 self.style_sheet = styles.default_dark_style_sheet
452 self.style_sheet = styles.default_dark_style_sheet
453 self.syntax_style = styles.default_dark_syntax_style
453 self.syntax_style = styles.default_dark_syntax_style
454 elif colors=='nocolor':
454 elif colors=='nocolor':
455 self.style_sheet = styles.default_bw_style_sheet
455 self.style_sheet = styles.default_bw_style_sheet
456 self.syntax_style = styles.default_bw_syntax_style
456 self.syntax_style = styles.default_bw_syntax_style
457 else:
457 else:
458 raise KeyError("No such color scheme: %s"%colors)
458 raise KeyError("No such color scheme: %s"%colors)
459
459
460 #---------------------------------------------------------------------------
460 #---------------------------------------------------------------------------
461 # 'IPythonWidget' protected interface
461 # 'IPythonWidget' protected interface
462 #---------------------------------------------------------------------------
462 #---------------------------------------------------------------------------
463
463
464 def _edit(self, filename, line=None):
464 def _edit(self, filename, line=None):
465 """ Opens a Python script for editing.
465 """ Opens a Python script for editing.
466
466
467 Parameters:
467 Parameters:
468 -----------
468 -----------
469 filename : str
469 filename : str
470 A path to a local system file.
470 A path to a local system file.
471
471
472 line : int, optional
472 line : int, optional
473 A line of interest in the file.
473 A line of interest in the file.
474 """
474 """
475 if self.custom_edit:
475 if self.custom_edit:
476 self.custom_edit_requested.emit(filename, line)
476 self.custom_edit_requested.emit(filename, line)
477 elif not self.editor:
477 elif not self.editor:
478 self._append_plain_text('No default editor available.\n'
478 self._append_plain_text('No default editor available.\n'
479 'Specify a GUI text editor in the `IPythonWidget.editor` '
479 'Specify a GUI text editor in the `IPythonWidget.editor` '
480 'configurable to enable the %edit magic')
480 'configurable to enable the %edit magic')
481 else:
481 else:
482 try:
482 try:
483 filename = '"%s"' % filename
483 filename = '"%s"' % filename
484 if line and self.editor_line:
484 if line and self.editor_line:
485 command = self.editor_line.format(filename=filename,
485 command = self.editor_line.format(filename=filename,
486 line=line)
486 line=line)
487 else:
487 else:
488 try:
488 try:
489 command = self.editor.format()
489 command = self.editor.format()
490 except KeyError:
490 except KeyError:
491 command = self.editor.format(filename=filename)
491 command = self.editor.format(filename=filename)
492 else:
492 else:
493 command += ' ' + filename
493 command += ' ' + filename
494 except KeyError:
494 except KeyError:
495 self._append_plain_text('Invalid editor command.\n')
495 self._append_plain_text('Invalid editor command.\n')
496 else:
496 else:
497 try:
497 try:
498 Popen(command, shell=True)
498 Popen(command, shell=True)
499 except OSError:
499 except OSError:
500 msg = 'Opening editor with command "%s" failed.\n'
500 msg = 'Opening editor with command "%s" failed.\n'
501 self._append_plain_text(msg % command)
501 self._append_plain_text(msg % command)
502
502
503 def _make_in_prompt(self, number):
503 def _make_in_prompt(self, number):
504 """ Given a prompt number, returns an HTML In prompt.
504 """ Given a prompt number, returns an HTML In prompt.
505 """
505 """
506 try:
506 try:
507 body = self.in_prompt % number
507 body = self.in_prompt % number
508 except TypeError:
508 except TypeError:
509 # allow in_prompt to leave out number, e.g. '>>> '
509 # allow in_prompt to leave out number, e.g. '>>> '
510 body = self.in_prompt
510 body = self.in_prompt
511 return '<span class="in-prompt">%s</span>' % body
511 return '<span class="in-prompt">%s</span>' % body
512
512
513 def _make_continuation_prompt(self, prompt):
513 def _make_continuation_prompt(self, prompt):
514 """ Given a plain text version of an In prompt, returns an HTML
514 """ Given a plain text version of an In prompt, returns an HTML
515 continuation prompt.
515 continuation prompt.
516 """
516 """
517 end_chars = '...: '
517 end_chars = '...: '
518 space_count = len(prompt.lstrip('\n')) - len(end_chars)
518 space_count = len(prompt.lstrip('\n')) - len(end_chars)
519 body = '&nbsp;' * space_count + end_chars
519 body = '&nbsp;' * space_count + end_chars
520 return '<span class="in-prompt">%s</span>' % body
520 return '<span class="in-prompt">%s</span>' % body
521
521
522 def _make_out_prompt(self, number):
522 def _make_out_prompt(self, number):
523 """ Given a prompt number, returns an HTML Out prompt.
523 """ Given a prompt number, returns an HTML Out prompt.
524 """
524 """
525 body = self.out_prompt % number
525 body = self.out_prompt % number
526 return '<span class="out-prompt">%s</span>' % body
526 return '<span class="out-prompt">%s</span>' % body
527
527
528 #------ Payload handlers --------------------------------------------------
528 #------ Payload handlers --------------------------------------------------
529
529
530 # Payload handlers with a generic interface: each takes the opaque payload
530 # Payload handlers with a generic interface: each takes the opaque payload
531 # dict, unpacks it and calls the underlying functions with the necessary
531 # dict, unpacks it and calls the underlying functions with the necessary
532 # arguments.
532 # arguments.
533
533
534 def _handle_payload_edit(self, item):
534 def _handle_payload_edit(self, item):
535 self._edit(item['filename'], item['line_number'])
535 self._edit(item['filename'], item['line_number'])
536
536
537 def _handle_payload_exit(self, item):
537 def _handle_payload_exit(self, item):
538 self._keep_kernel_on_exit = item['keepkernel']
538 self._keep_kernel_on_exit = item['keepkernel']
539 self.exit_requested.emit(self)
539 self.exit_requested.emit(self)
540
540
541 def _handle_payload_next_input(self, item):
541 def _handle_payload_next_input(self, item):
542 self.input_buffer = dedent(item['text'].rstrip())
542 self.input_buffer = dedent(item['text'].rstrip())
543
543
544 def _handle_payload_page(self, item):
544 def _handle_payload_page(self, item):
545 # Since the plain text widget supports only a very small subset of HTML
545 # Since the plain text widget supports only a very small subset of HTML
546 # and we have no control over the HTML source, we only page HTML
546 # and we have no control over the HTML source, we only page HTML
547 # payloads in the rich text widget.
547 # payloads in the rich text widget.
548 if item['html'] and self.kind == 'rich':
548 if item['html'] and self.kind == 'rich':
549 self._page(item['html'], html=True)
549 self._page(item['html'], html=True)
550 else:
550 else:
551 self._page(item['text'], html=False)
551 self._page(item['text'], html=False)
552
552
553 #------ Trait change handlers --------------------------------------------
553 #------ Trait change handlers --------------------------------------------
554
554
555 def _style_sheet_changed(self):
555 def _style_sheet_changed(self):
556 """ Set the style sheets of the underlying widgets.
556 """ Set the style sheets of the underlying widgets.
557 """
557 """
558 self.setStyleSheet(self.style_sheet)
558 self.setStyleSheet(self.style_sheet)
559 if self._control is not None:
559 if self._control is not None:
560 self._control.document().setDefaultStyleSheet(self.style_sheet)
560 self._control.document().setDefaultStyleSheet(self.style_sheet)
561 bg_color = self._control.palette().window().color()
561 bg_color = self._control.palette().window().color()
562 self._ansi_processor.set_background_color(bg_color)
562 self._ansi_processor.set_background_color(bg_color)
563
563
564 if self._page_control is not None:
564 if self._page_control is not None:
565 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
565 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
566
566
567
567
568
568
569 def _syntax_style_changed(self):
569 def _syntax_style_changed(self):
570 """ Set the style for the syntax highlighter.
570 """ Set the style for the syntax highlighter.
571 """
571 """
572 if self._highlighter is None:
572 if self._highlighter is None:
573 # ignore premature calls
573 # ignore premature calls
574 return
574 return
575 if self.syntax_style:
575 if self.syntax_style:
576 self._highlighter.set_style(self.syntax_style)
576 self._highlighter.set_style(self.syntax_style)
577 else:
577 else:
578 self._highlighter.set_style_sheet(self.style_sheet)
578 self._highlighter.set_style_sheet(self.style_sheet)
579
579
580 #------ Trait default initializers -----------------------------------------
580 #------ Trait default initializers -----------------------------------------
581
581
582 def _banner_default(self):
582 def _banner_default(self):
583 from IPython.core.usage import default_gui_banner
583 from IPython.core.usage import default_gui_banner
584 return default_gui_banner
584 return default_gui_banner
@@ -1,990 +1,990 b''
1 """The Qt MainWindow for the QtConsole
1 """The Qt MainWindow for the QtConsole
2
2
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 common actions.
4 common actions.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14 * Paul Ivanov
14 * Paul Ivanov
15
15
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import sys
23 import sys
24 import re
24 import re
25 import webbrowser
25 import webbrowser
26 import ast
26 import ast
27 from threading import Thread
27 from threading import Thread
28
28
29 # System library imports
29 # System library imports
30 from IPython.external.qt import QtGui,QtCore
30 from IPython.external.qt import QtGui,QtCore
31
31
32 def background(f):
32 def background(f):
33 """call a function in a simple thread, to prevent blocking"""
33 """call a function in a simple thread, to prevent blocking"""
34 t = Thread(target=f)
34 t = Thread(target=f)
35 t.start()
35 t.start()
36 return t
36 return t
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Classes
39 # Classes
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class MainWindow(QtGui.QMainWindow):
42 class MainWindow(QtGui.QMainWindow):
43
43
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45 # 'object' interface
45 # 'object' interface
46 #---------------------------------------------------------------------------
46 #---------------------------------------------------------------------------
47
47
48 _magic_menu_dict = {}
48 _magic_menu_dict = {}
49
49
50 def __init__(self, app,
50 def __init__(self, app,
51 confirm_exit=True,
51 confirm_exit=True,
52 new_frontend_factory=None, slave_frontend_factory=None,
52 new_frontend_factory=None, slave_frontend_factory=None,
53 ):
53 ):
54 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
54 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
55
55
56 Parameters
56 Parameters
57 ----------
57 ----------
58
58
59 app : reference to QApplication parent
59 app : reference to QApplication parent
60 confirm_exit : bool, optional
60 confirm_exit : bool, optional
61 Whether we should prompt on close of tabs
61 Whether we should prompt on close of tabs
62 new_frontend_factory : callable
62 new_frontend_factory : callable
63 A callable that returns a new IPythonWidget instance, attached to
63 A callable that returns a new IPythonWidget instance, attached to
64 its own running kernel.
64 its own running kernel.
65 slave_frontend_factory : callable
65 slave_frontend_factory : callable
66 A callable that takes an existing IPythonWidget, and returns a new
66 A callable that takes an existing IPythonWidget, and returns a new
67 IPythonWidget instance, attached to the same kernel.
67 IPythonWidget instance, attached to the same kernel.
68 """
68 """
69
69
70 super(MainWindow, self).__init__()
70 super(MainWindow, self).__init__()
71 self._kernel_counter = 0
71 self._kernel_counter = 0
72 self._app = app
72 self._app = app
73 self.confirm_exit = confirm_exit
73 self.confirm_exit = confirm_exit
74 self.new_frontend_factory = new_frontend_factory
74 self.new_frontend_factory = new_frontend_factory
75 self.slave_frontend_factory = slave_frontend_factory
75 self.slave_frontend_factory = slave_frontend_factory
76
76
77 self.tab_widget = QtGui.QTabWidget(self)
77 self.tab_widget = QtGui.QTabWidget(self)
78 self.tab_widget.setDocumentMode(True)
78 self.tab_widget.setDocumentMode(True)
79 self.tab_widget.setTabsClosable(True)
79 self.tab_widget.setTabsClosable(True)
80 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
80 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
81
81
82 self.setCentralWidget(self.tab_widget)
82 self.setCentralWidget(self.tab_widget)
83 # hide tab bar at first, since we have no tabs:
83 # hide tab bar at first, since we have no tabs:
84 self.tab_widget.tabBar().setVisible(False)
84 self.tab_widget.tabBar().setVisible(False)
85 # prevent focus in tab bar
85 # prevent focus in tab bar
86 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
86 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
87
87
88 def update_tab_bar_visibility(self):
88 def update_tab_bar_visibility(self):
89 """ update visibility of the tabBar depending of the number of tab
89 """ update visibility of the tabBar depending of the number of tab
90
90
91 0 or 1 tab, tabBar hidden
91 0 or 1 tab, tabBar hidden
92 2+ tabs, tabBar visible
92 2+ tabs, tabBar visible
93
93
94 send a self.close if number of tab ==0
94 send a self.close if number of tab ==0
95
95
96 need to be called explicitly, or be connected to tabInserted/tabRemoved
96 need to be called explicitly, or be connected to tabInserted/tabRemoved
97 """
97 """
98 if self.tab_widget.count() <= 1:
98 if self.tab_widget.count() <= 1:
99 self.tab_widget.tabBar().setVisible(False)
99 self.tab_widget.tabBar().setVisible(False)
100 else:
100 else:
101 self.tab_widget.tabBar().setVisible(True)
101 self.tab_widget.tabBar().setVisible(True)
102 if self.tab_widget.count()==0 :
102 if self.tab_widget.count()==0 :
103 self.close()
103 self.close()
104
104
105 @property
105 @property
106 def next_kernel_id(self):
106 def next_kernel_id(self):
107 """constantly increasing counter for kernel IDs"""
107 """constantly increasing counter for kernel IDs"""
108 c = self._kernel_counter
108 c = self._kernel_counter
109 self._kernel_counter += 1
109 self._kernel_counter += 1
110 return c
110 return c
111
111
112 @property
112 @property
113 def active_frontend(self):
113 def active_frontend(self):
114 return self.tab_widget.currentWidget()
114 return self.tab_widget.currentWidget()
115
115
116 def create_tab_with_new_frontend(self):
116 def create_tab_with_new_frontend(self):
117 """create a new frontend and attach it to a new tab"""
117 """create a new frontend and attach it to a new tab"""
118 widget = self.new_frontend_factory()
118 widget = self.new_frontend_factory()
119 self.add_tab_with_frontend(widget)
119 self.add_tab_with_frontend(widget)
120
120
121 def create_tab_with_current_kernel(self):
121 def create_tab_with_current_kernel(self):
122 """create a new frontend attached to the same kernel as the current tab"""
122 """create a new frontend attached to the same kernel as the current tab"""
123 current_widget = self.tab_widget.currentWidget()
123 current_widget = self.tab_widget.currentWidget()
124 current_widget_index = self.tab_widget.indexOf(current_widget)
124 current_widget_index = self.tab_widget.indexOf(current_widget)
125 current_widget_name = self.tab_widget.tabText(current_widget_index)
125 current_widget_name = self.tab_widget.tabText(current_widget_index)
126 widget = self.slave_frontend_factory(current_widget)
126 widget = self.slave_frontend_factory(current_widget)
127 if 'slave' in current_widget_name:
127 if 'slave' in current_widget_name:
128 # don't keep stacking slaves
128 # don't keep stacking slaves
129 name = current_widget_name
129 name = current_widget_name
130 else:
130 else:
131 name = '(%s) slave' % current_widget_name
131 name = '(%s) slave' % current_widget_name
132 self.add_tab_with_frontend(widget,name=name)
132 self.add_tab_with_frontend(widget,name=name)
133
133
134 def close_tab(self,current_tab):
134 def close_tab(self,current_tab):
135 """ Called when you need to try to close a tab.
135 """ Called when you need to try to close a tab.
136
136
137 It takes the number of the tab to be closed as argument, or a reference
137 It takes the number of the tab to be closed as argument, or a reference
138 to the widget inside this tab
138 to the widget inside this tab
139 """
139 """
140
140
141 # let's be sure "tab" and "closing widget" are respectively the index
141 # let's be sure "tab" and "closing widget" are respectively the index
142 # of the tab to close and a reference to the frontend to close
142 # of the tab to close and a reference to the frontend to close
143 if type(current_tab) is not int :
143 if type(current_tab) is not int :
144 current_tab = self.tab_widget.indexOf(current_tab)
144 current_tab = self.tab_widget.indexOf(current_tab)
145 closing_widget=self.tab_widget.widget(current_tab)
145 closing_widget=self.tab_widget.widget(current_tab)
146
146
147
147
148 # when trying to be closed, widget might re-send a request to be
148 # when trying to be closed, widget might re-send a request to be
149 # closed again, but will be deleted when event will be processed. So
149 # closed again, but will be deleted when event will be processed. So
150 # need to check that widget still exists and skip if not. One example
150 # need to check that widget still exists and skip if not. One example
151 # of this is when 'exit' is sent in a slave tab. 'exit' will be
151 # of this is when 'exit' is sent in a slave tab. 'exit' will be
152 # re-sent by this function on the master widget, which ask all slave
152 # re-sent by this function on the master widget, which ask all slave
153 # widgets to exit
153 # widgets to exit
154 if closing_widget==None:
154 if closing_widget==None:
155 return
155 return
156
156
157 #get a list of all slave widgets on the same kernel.
157 #get a list of all slave widgets on the same kernel.
158 slave_tabs = self.find_slave_widgets(closing_widget)
158 slave_tabs = self.find_slave_widgets(closing_widget)
159
159
160 keepkernel = None #Use the prompt by default
160 keepkernel = None #Use the prompt by default
161 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
161 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
162 keepkernel = closing_widget._keep_kernel_on_exit
162 keepkernel = closing_widget._keep_kernel_on_exit
163 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
163 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
164 # we set local slave tabs._hidden to True to avoid prompting for kernel
164 # we set local slave tabs._hidden to True to avoid prompting for kernel
165 # restart when they get the signal. and then "forward" the 'exit'
165 # restart when they get the signal. and then "forward" the 'exit'
166 # to the main window
166 # to the main window
167 if keepkernel is not None:
167 if keepkernel is not None:
168 for tab in slave_tabs:
168 for tab in slave_tabs:
169 tab._hidden = True
169 tab._hidden = True
170 if closing_widget in slave_tabs:
170 if closing_widget in slave_tabs:
171 try :
171 try :
172 self.find_master_tab(closing_widget).execute('exit')
172 self.find_master_tab(closing_widget).execute('exit')
173 except AttributeError:
173 except AttributeError:
174 self.log.info("Master already closed or not local, closing only current tab")
174 self.log.info("Master already closed or not local, closing only current tab")
175 self.tab_widget.removeTab(current_tab)
175 self.tab_widget.removeTab(current_tab)
176 self.update_tab_bar_visibility()
176 self.update_tab_bar_visibility()
177 return
177 return
178
178
179 kernel_manager = closing_widget.kernel_manager
179 kernel_client = closing_widget.kernel_client
180
180
181 if keepkernel is None and not closing_widget._confirm_exit:
181 if keepkernel is None and not closing_widget._confirm_exit:
182 # don't prompt, just terminate the kernel if we own it
182 # don't prompt, just terminate the kernel if we own it
183 # or leave it alone if we don't
183 # or leave it alone if we don't
184 keepkernel = closing_widget._existing
184 keepkernel = closing_widget._existing
185 if keepkernel is None: #show prompt
185 if keepkernel is None: #show prompt
186 if kernel_manager and kernel_manager.channels_running:
186 if kernel_client and kernel_client.channels_running:
187 title = self.window().windowTitle()
187 title = self.window().windowTitle()
188 cancel = QtGui.QMessageBox.Cancel
188 cancel = QtGui.QMessageBox.Cancel
189 okay = QtGui.QMessageBox.Ok
189 okay = QtGui.QMessageBox.Ok
190 if closing_widget._may_close:
190 if closing_widget._may_close:
191 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
191 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
192 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
192 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
193 justthis = QtGui.QPushButton("&No, just this Tab", self)
193 justthis = QtGui.QPushButton("&No, just this Tab", self)
194 justthis.setShortcut('N')
194 justthis.setShortcut('N')
195 closeall = QtGui.QPushButton("&Yes, close all", self)
195 closeall = QtGui.QPushButton("&Yes, close all", self)
196 closeall.setShortcut('Y')
196 closeall.setShortcut('Y')
197 # allow ctrl-d ctrl-d exit, like in terminal
197 # allow ctrl-d ctrl-d exit, like in terminal
198 closeall.setShortcut('Ctrl+D')
198 closeall.setShortcut('Ctrl+D')
199 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
199 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
200 title, msg)
200 title, msg)
201 box.setInformativeText(info)
201 box.setInformativeText(info)
202 box.addButton(cancel)
202 box.addButton(cancel)
203 box.addButton(justthis, QtGui.QMessageBox.NoRole)
203 box.addButton(justthis, QtGui.QMessageBox.NoRole)
204 box.addButton(closeall, QtGui.QMessageBox.YesRole)
204 box.addButton(closeall, QtGui.QMessageBox.YesRole)
205 box.setDefaultButton(closeall)
205 box.setDefaultButton(closeall)
206 box.setEscapeButton(cancel)
206 box.setEscapeButton(cancel)
207 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
207 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
208 box.setIconPixmap(pixmap)
208 box.setIconPixmap(pixmap)
209 reply = box.exec_()
209 reply = box.exec_()
210 if reply == 1: # close All
210 if reply == 1: # close All
211 for slave in slave_tabs:
211 for slave in slave_tabs:
212 background(slave.kernel_manager.stop_channels)
212 background(slave.kernel_client.stop_channels)
213 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
213 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
214 closing_widget.execute("exit")
214 closing_widget.execute("exit")
215 self.tab_widget.removeTab(current_tab)
215 self.tab_widget.removeTab(current_tab)
216 background(kernel_manager.stop_channels)
216 background(kernel_client.stop_channels)
217 elif reply == 0: # close Console
217 elif reply == 0: # close Console
218 if not closing_widget._existing:
218 if not closing_widget._existing:
219 # Have kernel: don't quit, just close the tab
219 # Have kernel: don't quit, just close the tab
220 closing_widget.execute("exit True")
220 closing_widget.execute("exit True")
221 self.tab_widget.removeTab(current_tab)
221 self.tab_widget.removeTab(current_tab)
222 background(kernel_manager.stop_channels)
222 background(kernel_client.stop_channels)
223 else:
223 else:
224 reply = QtGui.QMessageBox.question(self, title,
224 reply = QtGui.QMessageBox.question(self, title,
225 "Are you sure you want to close this Console?"+
225 "Are you sure you want to close this Console?"+
226 "\nThe Kernel and other Consoles will remain active.",
226 "\nThe Kernel and other Consoles will remain active.",
227 okay|cancel,
227 okay|cancel,
228 defaultButton=okay
228 defaultButton=okay
229 )
229 )
230 if reply == okay:
230 if reply == okay:
231 self.tab_widget.removeTab(current_tab)
231 self.tab_widget.removeTab(current_tab)
232 elif keepkernel: #close console but leave kernel running (no prompt)
232 elif keepkernel: #close console but leave kernel running (no prompt)
233 self.tab_widget.removeTab(current_tab)
233 self.tab_widget.removeTab(current_tab)
234 background(kernel_manager.stop_channels)
234 background(kernel_client.stop_channels)
235 else: #close console and kernel (no prompt)
235 else: #close console and kernel (no prompt)
236 self.tab_widget.removeTab(current_tab)
236 self.tab_widget.removeTab(current_tab)
237 if kernel_manager and kernel_manager.channels_running:
237 if kernel_client and kernel_client.channels_running:
238 for slave in slave_tabs:
238 for slave in slave_tabs:
239 background(slave.kernel_manager.stop_channels)
239 background(slave.kernel_client.stop_channels)
240 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
240 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
241 kernel_manager.shutdown_kernel()
241 kernel_manager.shutdown_kernel()
242 background(kernel_manager.stop_channels)
242 background(kernel_client.stop_channels)
243
243
244 self.update_tab_bar_visibility()
244 self.update_tab_bar_visibility()
245
245
246 def add_tab_with_frontend(self,frontend,name=None):
246 def add_tab_with_frontend(self,frontend,name=None):
247 """ insert a tab with a given frontend in the tab bar, and give it a name
247 """ insert a tab with a given frontend in the tab bar, and give it a name
248
248
249 """
249 """
250 if not name:
250 if not name:
251 name = 'kernel %i' % self.next_kernel_id
251 name = 'kernel %i' % self.next_kernel_id
252 self.tab_widget.addTab(frontend,name)
252 self.tab_widget.addTab(frontend,name)
253 self.update_tab_bar_visibility()
253 self.update_tab_bar_visibility()
254 self.make_frontend_visible(frontend)
254 self.make_frontend_visible(frontend)
255 frontend.exit_requested.connect(self.close_tab)
255 frontend.exit_requested.connect(self.close_tab)
256
256
257 def next_tab(self):
257 def next_tab(self):
258 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
258 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
259
259
260 def prev_tab(self):
260 def prev_tab(self):
261 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
261 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
262
262
263 def make_frontend_visible(self,frontend):
263 def make_frontend_visible(self,frontend):
264 widget_index=self.tab_widget.indexOf(frontend)
264 widget_index=self.tab_widget.indexOf(frontend)
265 if widget_index > 0 :
265 if widget_index > 0 :
266 self.tab_widget.setCurrentIndex(widget_index)
266 self.tab_widget.setCurrentIndex(widget_index)
267
267
268 def find_master_tab(self,tab,as_list=False):
268 def find_master_tab(self,tab,as_list=False):
269 """
269 """
270 Try to return the frontend that owns the kernel attached to the given widget/tab.
270 Try to return the frontend that owns the kernel attached to the given widget/tab.
271
271
272 Only finds frontend owned by the current application. Selection
272 Only finds frontend owned by the current application. Selection
273 based on port of the kernel might be inaccurate if several kernel
273 based on port of the kernel might be inaccurate if several kernel
274 on different ip use same port number.
274 on different ip use same port number.
275
275
276 This function does the conversion tabNumber/widget if needed.
276 This function does the conversion tabNumber/widget if needed.
277 Might return None if no master widget (non local kernel)
277 Might return None if no master widget (non local kernel)
278 Will crash IPython if more than 1 masterWidget
278 Will crash IPython if more than 1 masterWidget
279
279
280 When asList set to True, always return a list of widget(s) owning
280 When asList set to True, always return a list of widget(s) owning
281 the kernel. The list might be empty or containing several Widget.
281 the kernel. The list might be empty or containing several Widget.
282 """
282 """
283
283
284 #convert from/to int/richIpythonWidget if needed
284 #convert from/to int/richIpythonWidget if needed
285 if isinstance(tab, int):
285 if isinstance(tab, int):
286 tab = self.tab_widget.widget(tab)
286 tab = self.tab_widget.widget(tab)
287 km=tab.kernel_manager
287 km=tab.kernel_client
288
288
289 #build list of all widgets
289 #build list of all widgets
290 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
290 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
291
291
292 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
292 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
293 # And should have a _may_close attribute
293 # And should have a _may_close attribute
294 filtered_widget_list = [ widget for widget in widget_list if
294 filtered_widget_list = [ widget for widget in widget_list if
295 widget.kernel_manager.connection_file == km.connection_file and
295 widget.kernel_client.connection_file == km.connection_file and
296 hasattr(widget,'_may_close') ]
296 hasattr(widget,'_may_close') ]
297 # the master widget is the one that may close the kernel
297 # the master widget is the one that may close the kernel
298 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
298 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
299 if as_list:
299 if as_list:
300 return master_widget
300 return master_widget
301 assert(len(master_widget)<=1 )
301 assert(len(master_widget)<=1 )
302 if len(master_widget)==0:
302 if len(master_widget)==0:
303 return None
303 return None
304
304
305 return master_widget[0]
305 return master_widget[0]
306
306
307 def find_slave_widgets(self,tab):
307 def find_slave_widgets(self,tab):
308 """return all the frontends that do not own the kernel attached to the given widget/tab.
308 """return all the frontends that do not own the kernel attached to the given widget/tab.
309
309
310 Only find frontends owned by the current application. Selection
310 Only find frontends owned by the current application. Selection
311 based on connection file of the kernel.
311 based on connection file of the kernel.
312
312
313 This function does the conversion tabNumber/widget if needed.
313 This function does the conversion tabNumber/widget if needed.
314 """
314 """
315 #convert from/to int/richIpythonWidget if needed
315 #convert from/to int/richIpythonWidget if needed
316 if isinstance(tab, int):
316 if isinstance(tab, int):
317 tab = self.tab_widget.widget(tab)
317 tab = self.tab_widget.widget(tab)
318 km=tab.kernel_manager
318 km=tab.kernel_client
319
319
320 #build list of all widgets
320 #build list of all widgets
321 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
321 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
322
322
323 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
323 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
324 filtered_widget_list = ( widget for widget in widget_list if
324 filtered_widget_list = ( widget for widget in widget_list if
325 widget.kernel_manager.connection_file == km.connection_file)
325 widget.kernel_client.connection_file == km.connection_file)
326 # Get a list of all widget owning the same kernel and removed it from
326 # Get a list of all widget owning the same kernel and removed it from
327 # the previous cadidate. (better using sets ?)
327 # the previous cadidate. (better using sets ?)
328 master_widget_list = self.find_master_tab(tab, as_list=True)
328 master_widget_list = self.find_master_tab(tab, as_list=True)
329 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
329 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
330
330
331 return slave_list
331 return slave_list
332
332
333 # Populate the menu bar with common actions and shortcuts
333 # Populate the menu bar with common actions and shortcuts
334 def add_menu_action(self, menu, action, defer_shortcut=False):
334 def add_menu_action(self, menu, action, defer_shortcut=False):
335 """Add action to menu as well as self
335 """Add action to menu as well as self
336
336
337 So that when the menu bar is invisible, its actions are still available.
337 So that when the menu bar is invisible, its actions are still available.
338
338
339 If defer_shortcut is True, set the shortcut context to widget-only,
339 If defer_shortcut is True, set the shortcut context to widget-only,
340 where it will avoid conflict with shortcuts already bound to the
340 where it will avoid conflict with shortcuts already bound to the
341 widgets themselves.
341 widgets themselves.
342 """
342 """
343 menu.addAction(action)
343 menu.addAction(action)
344 self.addAction(action)
344 self.addAction(action)
345
345
346 if defer_shortcut:
346 if defer_shortcut:
347 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
347 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
348
348
349 def init_menu_bar(self):
349 def init_menu_bar(self):
350 #create menu in the order they should appear in the menu bar
350 #create menu in the order they should appear in the menu bar
351 self.init_file_menu()
351 self.init_file_menu()
352 self.init_edit_menu()
352 self.init_edit_menu()
353 self.init_view_menu()
353 self.init_view_menu()
354 self.init_kernel_menu()
354 self.init_kernel_menu()
355 self.init_magic_menu()
355 self.init_magic_menu()
356 self.init_window_menu()
356 self.init_window_menu()
357 self.init_help_menu()
357 self.init_help_menu()
358
358
359 def init_file_menu(self):
359 def init_file_menu(self):
360 self.file_menu = self.menuBar().addMenu("&File")
360 self.file_menu = self.menuBar().addMenu("&File")
361
361
362 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
362 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
363 self,
363 self,
364 shortcut="Ctrl+T",
364 shortcut="Ctrl+T",
365 triggered=self.create_tab_with_new_frontend)
365 triggered=self.create_tab_with_new_frontend)
366 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
366 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
367
367
368 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
368 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
369 self,
369 self,
370 shortcut="Ctrl+Shift+T",
370 shortcut="Ctrl+Shift+T",
371 triggered=self.create_tab_with_current_kernel)
371 triggered=self.create_tab_with_current_kernel)
372 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
372 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
373
373
374 self.file_menu.addSeparator()
374 self.file_menu.addSeparator()
375
375
376 self.close_action=QtGui.QAction("&Close Tab",
376 self.close_action=QtGui.QAction("&Close Tab",
377 self,
377 self,
378 shortcut=QtGui.QKeySequence.Close,
378 shortcut=QtGui.QKeySequence.Close,
379 triggered=self.close_active_frontend
379 triggered=self.close_active_frontend
380 )
380 )
381 self.add_menu_action(self.file_menu, self.close_action)
381 self.add_menu_action(self.file_menu, self.close_action)
382
382
383 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
383 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
384 self,
384 self,
385 shortcut=QtGui.QKeySequence.Save,
385 shortcut=QtGui.QKeySequence.Save,
386 triggered=self.export_action_active_frontend
386 triggered=self.export_action_active_frontend
387 )
387 )
388 self.add_menu_action(self.file_menu, self.export_action, True)
388 self.add_menu_action(self.file_menu, self.export_action, True)
389
389
390 self.file_menu.addSeparator()
390 self.file_menu.addSeparator()
391
391
392 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
392 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
393 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
393 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
394 # Only override the default if there is a collision.
394 # Only override the default if there is a collision.
395 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
395 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
396 printkey = "Ctrl+Shift+P"
396 printkey = "Ctrl+Shift+P"
397 self.print_action = QtGui.QAction("&Print",
397 self.print_action = QtGui.QAction("&Print",
398 self,
398 self,
399 shortcut=printkey,
399 shortcut=printkey,
400 triggered=self.print_action_active_frontend)
400 triggered=self.print_action_active_frontend)
401 self.add_menu_action(self.file_menu, self.print_action, True)
401 self.add_menu_action(self.file_menu, self.print_action, True)
402
402
403 if sys.platform != 'darwin':
403 if sys.platform != 'darwin':
404 # OSX always has Quit in the Application menu, only add it
404 # OSX always has Quit in the Application menu, only add it
405 # to the File menu elsewhere.
405 # to the File menu elsewhere.
406
406
407 self.file_menu.addSeparator()
407 self.file_menu.addSeparator()
408
408
409 self.quit_action = QtGui.QAction("&Quit",
409 self.quit_action = QtGui.QAction("&Quit",
410 self,
410 self,
411 shortcut=QtGui.QKeySequence.Quit,
411 shortcut=QtGui.QKeySequence.Quit,
412 triggered=self.close,
412 triggered=self.close,
413 )
413 )
414 self.add_menu_action(self.file_menu, self.quit_action)
414 self.add_menu_action(self.file_menu, self.quit_action)
415
415
416
416
417 def init_edit_menu(self):
417 def init_edit_menu(self):
418 self.edit_menu = self.menuBar().addMenu("&Edit")
418 self.edit_menu = self.menuBar().addMenu("&Edit")
419
419
420 self.undo_action = QtGui.QAction("&Undo",
420 self.undo_action = QtGui.QAction("&Undo",
421 self,
421 self,
422 shortcut=QtGui.QKeySequence.Undo,
422 shortcut=QtGui.QKeySequence.Undo,
423 statusTip="Undo last action if possible",
423 statusTip="Undo last action if possible",
424 triggered=self.undo_active_frontend
424 triggered=self.undo_active_frontend
425 )
425 )
426 self.add_menu_action(self.edit_menu, self.undo_action)
426 self.add_menu_action(self.edit_menu, self.undo_action)
427
427
428 self.redo_action = QtGui.QAction("&Redo",
428 self.redo_action = QtGui.QAction("&Redo",
429 self,
429 self,
430 shortcut=QtGui.QKeySequence.Redo,
430 shortcut=QtGui.QKeySequence.Redo,
431 statusTip="Redo last action if possible",
431 statusTip="Redo last action if possible",
432 triggered=self.redo_active_frontend)
432 triggered=self.redo_active_frontend)
433 self.add_menu_action(self.edit_menu, self.redo_action)
433 self.add_menu_action(self.edit_menu, self.redo_action)
434
434
435 self.edit_menu.addSeparator()
435 self.edit_menu.addSeparator()
436
436
437 self.cut_action = QtGui.QAction("&Cut",
437 self.cut_action = QtGui.QAction("&Cut",
438 self,
438 self,
439 shortcut=QtGui.QKeySequence.Cut,
439 shortcut=QtGui.QKeySequence.Cut,
440 triggered=self.cut_active_frontend
440 triggered=self.cut_active_frontend
441 )
441 )
442 self.add_menu_action(self.edit_menu, self.cut_action, True)
442 self.add_menu_action(self.edit_menu, self.cut_action, True)
443
443
444 self.copy_action = QtGui.QAction("&Copy",
444 self.copy_action = QtGui.QAction("&Copy",
445 self,
445 self,
446 shortcut=QtGui.QKeySequence.Copy,
446 shortcut=QtGui.QKeySequence.Copy,
447 triggered=self.copy_active_frontend
447 triggered=self.copy_active_frontend
448 )
448 )
449 self.add_menu_action(self.edit_menu, self.copy_action, True)
449 self.add_menu_action(self.edit_menu, self.copy_action, True)
450
450
451 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
451 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
452 self,
452 self,
453 shortcut="Ctrl+Shift+C",
453 shortcut="Ctrl+Shift+C",
454 triggered=self.copy_raw_active_frontend
454 triggered=self.copy_raw_active_frontend
455 )
455 )
456 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
456 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
457
457
458 self.paste_action = QtGui.QAction("&Paste",
458 self.paste_action = QtGui.QAction("&Paste",
459 self,
459 self,
460 shortcut=QtGui.QKeySequence.Paste,
460 shortcut=QtGui.QKeySequence.Paste,
461 triggered=self.paste_active_frontend
461 triggered=self.paste_active_frontend
462 )
462 )
463 self.add_menu_action(self.edit_menu, self.paste_action, True)
463 self.add_menu_action(self.edit_menu, self.paste_action, True)
464
464
465 self.edit_menu.addSeparator()
465 self.edit_menu.addSeparator()
466
466
467 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
467 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
468 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
468 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
469 # Only override the default if there is a collision.
469 # Only override the default if there is a collision.
470 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
470 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
471 selectall = "Ctrl+Shift+A"
471 selectall = "Ctrl+Shift+A"
472 self.select_all_action = QtGui.QAction("Select &All",
472 self.select_all_action = QtGui.QAction("Select &All",
473 self,
473 self,
474 shortcut=selectall,
474 shortcut=selectall,
475 triggered=self.select_all_active_frontend
475 triggered=self.select_all_active_frontend
476 )
476 )
477 self.add_menu_action(self.edit_menu, self.select_all_action, True)
477 self.add_menu_action(self.edit_menu, self.select_all_action, True)
478
478
479
479
480 def init_view_menu(self):
480 def init_view_menu(self):
481 self.view_menu = self.menuBar().addMenu("&View")
481 self.view_menu = self.menuBar().addMenu("&View")
482
482
483 if sys.platform != 'darwin':
483 if sys.platform != 'darwin':
484 # disable on OSX, where there is always a menu bar
484 # disable on OSX, where there is always a menu bar
485 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
485 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
486 self,
486 self,
487 shortcut="Ctrl+Shift+M",
487 shortcut="Ctrl+Shift+M",
488 statusTip="Toggle visibility of menubar",
488 statusTip="Toggle visibility of menubar",
489 triggered=self.toggle_menu_bar)
489 triggered=self.toggle_menu_bar)
490 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
490 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
491
491
492 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
492 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
493 self.full_screen_act = QtGui.QAction("&Full Screen",
493 self.full_screen_act = QtGui.QAction("&Full Screen",
494 self,
494 self,
495 shortcut=fs_key,
495 shortcut=fs_key,
496 statusTip="Toggle between Fullscreen and Normal Size",
496 statusTip="Toggle between Fullscreen and Normal Size",
497 triggered=self.toggleFullScreen)
497 triggered=self.toggleFullScreen)
498 self.add_menu_action(self.view_menu, self.full_screen_act)
498 self.add_menu_action(self.view_menu, self.full_screen_act)
499
499
500 self.view_menu.addSeparator()
500 self.view_menu.addSeparator()
501
501
502 self.increase_font_size = QtGui.QAction("Zoom &In",
502 self.increase_font_size = QtGui.QAction("Zoom &In",
503 self,
503 self,
504 shortcut=QtGui.QKeySequence.ZoomIn,
504 shortcut=QtGui.QKeySequence.ZoomIn,
505 triggered=self.increase_font_size_active_frontend
505 triggered=self.increase_font_size_active_frontend
506 )
506 )
507 self.add_menu_action(self.view_menu, self.increase_font_size, True)
507 self.add_menu_action(self.view_menu, self.increase_font_size, True)
508
508
509 self.decrease_font_size = QtGui.QAction("Zoom &Out",
509 self.decrease_font_size = QtGui.QAction("Zoom &Out",
510 self,
510 self,
511 shortcut=QtGui.QKeySequence.ZoomOut,
511 shortcut=QtGui.QKeySequence.ZoomOut,
512 triggered=self.decrease_font_size_active_frontend
512 triggered=self.decrease_font_size_active_frontend
513 )
513 )
514 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
514 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
515
515
516 self.reset_font_size = QtGui.QAction("Zoom &Reset",
516 self.reset_font_size = QtGui.QAction("Zoom &Reset",
517 self,
517 self,
518 shortcut="Ctrl+0",
518 shortcut="Ctrl+0",
519 triggered=self.reset_font_size_active_frontend
519 triggered=self.reset_font_size_active_frontend
520 )
520 )
521 self.add_menu_action(self.view_menu, self.reset_font_size, True)
521 self.add_menu_action(self.view_menu, self.reset_font_size, True)
522
522
523 self.view_menu.addSeparator()
523 self.view_menu.addSeparator()
524
524
525 self.clear_action = QtGui.QAction("&Clear Screen",
525 self.clear_action = QtGui.QAction("&Clear Screen",
526 self,
526 self,
527 shortcut='Ctrl+L',
527 shortcut='Ctrl+L',
528 statusTip="Clear the console",
528 statusTip="Clear the console",
529 triggered=self.clear_magic_active_frontend)
529 triggered=self.clear_magic_active_frontend)
530 self.add_menu_action(self.view_menu, self.clear_action)
530 self.add_menu_action(self.view_menu, self.clear_action)
531
531
532 self.pager_menu = self.view_menu.addMenu("&Pager")
532 self.pager_menu = self.view_menu.addMenu("&Pager")
533
533
534 hsplit_action = QtGui.QAction(".. &Horizontal Split",
534 hsplit_action = QtGui.QAction(".. &Horizontal Split",
535 self,
535 self,
536 triggered=lambda: self.set_paging_active_frontend('hsplit'))
536 triggered=lambda: self.set_paging_active_frontend('hsplit'))
537
537
538 vsplit_action = QtGui.QAction(" : &Vertical Split",
538 vsplit_action = QtGui.QAction(" : &Vertical Split",
539 self,
539 self,
540 triggered=lambda: self.set_paging_active_frontend('vsplit'))
540 triggered=lambda: self.set_paging_active_frontend('vsplit'))
541
541
542 inside_action = QtGui.QAction(" &Inside Pager",
542 inside_action = QtGui.QAction(" &Inside Pager",
543 self,
543 self,
544 triggered=lambda: self.set_paging_active_frontend('inside'))
544 triggered=lambda: self.set_paging_active_frontend('inside'))
545
545
546 self.pager_menu.addAction(hsplit_action)
546 self.pager_menu.addAction(hsplit_action)
547 self.pager_menu.addAction(vsplit_action)
547 self.pager_menu.addAction(vsplit_action)
548 self.pager_menu.addAction(inside_action)
548 self.pager_menu.addAction(inside_action)
549
549
550 def init_kernel_menu(self):
550 def init_kernel_menu(self):
551 self.kernel_menu = self.menuBar().addMenu("&Kernel")
551 self.kernel_menu = self.menuBar().addMenu("&Kernel")
552 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
552 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
553 # keep the signal shortcuts to ctrl, rather than
553 # keep the signal shortcuts to ctrl, rather than
554 # platform-default like we do elsewhere.
554 # platform-default like we do elsewhere.
555
555
556 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
556 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
557
557
558 self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
558 self.interrupt_kernel_action = QtGui.QAction("&Interrupt current Kernel",
559 self,
559 self,
560 triggered=self.interrupt_kernel_active_frontend,
560 triggered=self.interrupt_kernel_active_frontend,
561 shortcut=ctrl+"+C",
561 shortcut=ctrl+"+C",
562 )
562 )
563 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
563 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
564
564
565 self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
565 self.restart_kernel_action = QtGui.QAction("&Restart current Kernel",
566 self,
566 self,
567 triggered=self.restart_kernel_active_frontend,
567 triggered=self.restart_kernel_active_frontend,
568 shortcut=ctrl+"+.",
568 shortcut=ctrl+"+.",
569 )
569 )
570 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
570 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
571
571
572 self.kernel_menu.addSeparator()
572 self.kernel_menu.addSeparator()
573
573
574 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
574 self.confirm_restart_kernel_action = QtGui.QAction("&Confirm kernel restart",
575 self,
575 self,
576 checkable=True,
576 checkable=True,
577 checked=self.active_frontend.confirm_restart,
577 checked=self.active_frontend.confirm_restart,
578 triggered=self.toggle_confirm_restart_active_frontend
578 triggered=self.toggle_confirm_restart_active_frontend
579 )
579 )
580
580
581 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
581 self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
582 self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
582 self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
583
583
584 def _make_dynamic_magic(self,magic):
584 def _make_dynamic_magic(self,magic):
585 """Return a function `fun` that will execute `magic` on active frontend.
585 """Return a function `fun` that will execute `magic` on active frontend.
586
586
587 Parameters
587 Parameters
588 ----------
588 ----------
589 magic : string
589 magic : string
590 string that will be executed as is when the returned function is called
590 string that will be executed as is when the returned function is called
591
591
592 Returns
592 Returns
593 -------
593 -------
594 fun : function
594 fun : function
595 function with no parameters, when called will execute `magic` on the
595 function with no parameters, when called will execute `magic` on the
596 current active frontend at call time
596 current active frontend at call time
597
597
598 See Also
598 See Also
599 --------
599 --------
600 populate_all_magic_menu : generate the "All Magics..." menu
600 populate_all_magic_menu : generate the "All Magics..." menu
601
601
602 Notes
602 Notes
603 -----
603 -----
604 `fun` executes `magic` in active frontend at the moment it is triggered,
604 `fun` executes `magic` in active frontend at the moment it is triggered,
605 not the active frontend at the moment it was created.
605 not the active frontend at the moment it was created.
606
606
607 This function is mostly used to create the "All Magics..." Menu at run time.
607 This function is mostly used to create the "All Magics..." Menu at run time.
608 """
608 """
609 # need two level nested function to be sure to pass magic
609 # need two level nested function to be sure to pass magic
610 # to active frontend **at run time**.
610 # to active frontend **at run time**.
611 def inner_dynamic_magic():
611 def inner_dynamic_magic():
612 self.active_frontend.execute(magic)
612 self.active_frontend.execute(magic)
613 inner_dynamic_magic.__name__ = "dynamics_magic_s"
613 inner_dynamic_magic.__name__ = "dynamics_magic_s"
614 return inner_dynamic_magic
614 return inner_dynamic_magic
615
615
616 def populate_all_magic_menu(self, listofmagic=None):
616 def populate_all_magic_menu(self, listofmagic=None):
617 """Clean "All Magics..." menu and repopulate it with `listofmagic`
617 """Clean "All Magics..." menu and repopulate it with `listofmagic`
618
618
619 Parameters
619 Parameters
620 ----------
620 ----------
621 listofmagic : string,
621 listofmagic : string,
622 repr() of a list of strings, send back by the kernel
622 repr() of a list of strings, send back by the kernel
623
623
624 Notes
624 Notes
625 -----
625 -----
626 `listofmagic`is a repr() of list because it is fed with the result of
626 `listofmagic`is a repr() of list because it is fed with the result of
627 a 'user_expression'
627 a 'user_expression'
628 """
628 """
629 for k,v in self._magic_menu_dict.items():
629 for k,v in self._magic_menu_dict.items():
630 v.clear()
630 v.clear()
631 self.all_magic_menu.clear()
631 self.all_magic_menu.clear()
632
632
633
633
634 mlist=ast.literal_eval(listofmagic)
634 mlist=ast.literal_eval(listofmagic)
635 for magic in mlist:
635 for magic in mlist:
636 cell = (magic['type'] == 'cell')
636 cell = (magic['type'] == 'cell')
637 name = magic['name']
637 name = magic['name']
638 mclass = magic['class']
638 mclass = magic['class']
639 if cell :
639 if cell :
640 prefix='%%'
640 prefix='%%'
641 else :
641 else :
642 prefix='%'
642 prefix='%'
643 magic_menu = self._get_magic_menu(mclass)
643 magic_menu = self._get_magic_menu(mclass)
644
644
645 pmagic = '%s%s'%(prefix,name)
645 pmagic = '%s%s'%(prefix,name)
646
646
647 xaction = QtGui.QAction(pmagic,
647 xaction = QtGui.QAction(pmagic,
648 self,
648 self,
649 triggered=self._make_dynamic_magic(pmagic)
649 triggered=self._make_dynamic_magic(pmagic)
650 )
650 )
651 magic_menu.addAction(xaction)
651 magic_menu.addAction(xaction)
652 self.all_magic_menu.addAction(xaction)
652 self.all_magic_menu.addAction(xaction)
653
653
654 def update_all_magic_menu(self):
654 def update_all_magic_menu(self):
655 """ Update the list of magics in the "All Magics..." Menu
655 """ Update the list of magics in the "All Magics..." Menu
656
656
657 Request the kernel with the list of available magics and populate the
657 Request the kernel with the list of available magics and populate the
658 menu with the list received back
658 menu with the list received back
659
659
660 """
660 """
661 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
661 self.active_frontend._silent_exec_callback('get_ipython().magics_manager.lsmagic_info()',
662 self.populate_all_magic_menu)
662 self.populate_all_magic_menu)
663
663
664 def _get_magic_menu(self,menuidentifier, menulabel=None):
664 def _get_magic_menu(self,menuidentifier, menulabel=None):
665 """return a submagic menu by name, and create it if needed
665 """return a submagic menu by name, and create it if needed
666
666
667 parameters:
667 parameters:
668 -----------
668 -----------
669
669
670 menulabel : str
670 menulabel : str
671 Label for the menu
671 Label for the menu
672
672
673 Will infere the menu name from the identifier at creation if menulabel not given.
673 Will infere the menu name from the identifier at creation if menulabel not given.
674 To do so you have too give menuidentifier as a CamelCassedString
674 To do so you have too give menuidentifier as a CamelCassedString
675 """
675 """
676 menu = self._magic_menu_dict.get(menuidentifier,None)
676 menu = self._magic_menu_dict.get(menuidentifier,None)
677 if not menu :
677 if not menu :
678 if not menulabel:
678 if not menulabel:
679 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
679 menulabel = re.sub("([a-zA-Z]+)([A-Z][a-z])","\g<1> \g<2>",menuidentifier)
680 menu = QtGui.QMenu(menulabel,self.magic_menu)
680 menu = QtGui.QMenu(menulabel,self.magic_menu)
681 self._magic_menu_dict[menuidentifier]=menu
681 self._magic_menu_dict[menuidentifier]=menu
682 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
682 self.magic_menu.insertMenu(self.magic_menu_separator,menu)
683 return menu
683 return menu
684
684
685
685
686
686
687 def init_magic_menu(self):
687 def init_magic_menu(self):
688 self.magic_menu = self.menuBar().addMenu("&Magic")
688 self.magic_menu = self.menuBar().addMenu("&Magic")
689 self.magic_menu_separator = self.magic_menu.addSeparator()
689 self.magic_menu_separator = self.magic_menu.addSeparator()
690
690
691 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
691 self.all_magic_menu = self._get_magic_menu("AllMagics", menulabel="&All Magics...")
692
692
693 # This action should usually not appear as it will be cleared when menu
693 # This action should usually not appear as it will be cleared when menu
694 # is updated at first kernel response. Though, it is necessary when
694 # is updated at first kernel response. Though, it is necessary when
695 # connecting through X-forwarding, as in this case, the menu is not
695 # connecting through X-forwarding, as in this case, the menu is not
696 # auto updated, SO DO NOT DELETE.
696 # auto updated, SO DO NOT DELETE.
697 self.pop = QtGui.QAction("&Update All Magic Menu ",
697 self.pop = QtGui.QAction("&Update All Magic Menu ",
698 self, triggered=self.update_all_magic_menu)
698 self, triggered=self.update_all_magic_menu)
699 self.add_menu_action(self.all_magic_menu, self.pop)
699 self.add_menu_action(self.all_magic_menu, self.pop)
700 # we need to populate the 'Magic Menu' once the kernel has answer at
700 # we need to populate the 'Magic Menu' once the kernel has answer at
701 # least once let's do it immediately, but it's assured to works
701 # least once let's do it immediately, but it's assured to works
702 self.pop.trigger()
702 self.pop.trigger()
703
703
704 self.reset_action = QtGui.QAction("&Reset",
704 self.reset_action = QtGui.QAction("&Reset",
705 self,
705 self,
706 statusTip="Clear all variables from workspace",
706 statusTip="Clear all variables from workspace",
707 triggered=self.reset_magic_active_frontend)
707 triggered=self.reset_magic_active_frontend)
708 self.add_menu_action(self.magic_menu, self.reset_action)
708 self.add_menu_action(self.magic_menu, self.reset_action)
709
709
710 self.history_action = QtGui.QAction("&History",
710 self.history_action = QtGui.QAction("&History",
711 self,
711 self,
712 statusTip="show command history",
712 statusTip="show command history",
713 triggered=self.history_magic_active_frontend)
713 triggered=self.history_magic_active_frontend)
714 self.add_menu_action(self.magic_menu, self.history_action)
714 self.add_menu_action(self.magic_menu, self.history_action)
715
715
716 self.save_action = QtGui.QAction("E&xport History ",
716 self.save_action = QtGui.QAction("E&xport History ",
717 self,
717 self,
718 statusTip="Export History as Python File",
718 statusTip="Export History as Python File",
719 triggered=self.save_magic_active_frontend)
719 triggered=self.save_magic_active_frontend)
720 self.add_menu_action(self.magic_menu, self.save_action)
720 self.add_menu_action(self.magic_menu, self.save_action)
721
721
722 self.who_action = QtGui.QAction("&Who",
722 self.who_action = QtGui.QAction("&Who",
723 self,
723 self,
724 statusTip="List interactive variables",
724 statusTip="List interactive variables",
725 triggered=self.who_magic_active_frontend)
725 triggered=self.who_magic_active_frontend)
726 self.add_menu_action(self.magic_menu, self.who_action)
726 self.add_menu_action(self.magic_menu, self.who_action)
727
727
728 self.who_ls_action = QtGui.QAction("Wh&o ls",
728 self.who_ls_action = QtGui.QAction("Wh&o ls",
729 self,
729 self,
730 statusTip="Return a list of interactive variables",
730 statusTip="Return a list of interactive variables",
731 triggered=self.who_ls_magic_active_frontend)
731 triggered=self.who_ls_magic_active_frontend)
732 self.add_menu_action(self.magic_menu, self.who_ls_action)
732 self.add_menu_action(self.magic_menu, self.who_ls_action)
733
733
734 self.whos_action = QtGui.QAction("Who&s",
734 self.whos_action = QtGui.QAction("Who&s",
735 self,
735 self,
736 statusTip="List interactive variables with details",
736 statusTip="List interactive variables with details",
737 triggered=self.whos_magic_active_frontend)
737 triggered=self.whos_magic_active_frontend)
738 self.add_menu_action(self.magic_menu, self.whos_action)
738 self.add_menu_action(self.magic_menu, self.whos_action)
739
739
740 def init_window_menu(self):
740 def init_window_menu(self):
741 self.window_menu = self.menuBar().addMenu("&Window")
741 self.window_menu = self.menuBar().addMenu("&Window")
742 if sys.platform == 'darwin':
742 if sys.platform == 'darwin':
743 # add min/maximize actions to OSX, which lacks default bindings.
743 # add min/maximize actions to OSX, which lacks default bindings.
744 self.minimizeAct = QtGui.QAction("Mini&mize",
744 self.minimizeAct = QtGui.QAction("Mini&mize",
745 self,
745 self,
746 shortcut="Ctrl+m",
746 shortcut="Ctrl+m",
747 statusTip="Minimize the window/Restore Normal Size",
747 statusTip="Minimize the window/Restore Normal Size",
748 triggered=self.toggleMinimized)
748 triggered=self.toggleMinimized)
749 # maximize is called 'Zoom' on OSX for some reason
749 # maximize is called 'Zoom' on OSX for some reason
750 self.maximizeAct = QtGui.QAction("&Zoom",
750 self.maximizeAct = QtGui.QAction("&Zoom",
751 self,
751 self,
752 shortcut="Ctrl+Shift+M",
752 shortcut="Ctrl+Shift+M",
753 statusTip="Maximize the window/Restore Normal Size",
753 statusTip="Maximize the window/Restore Normal Size",
754 triggered=self.toggleMaximized)
754 triggered=self.toggleMaximized)
755
755
756 self.add_menu_action(self.window_menu, self.minimizeAct)
756 self.add_menu_action(self.window_menu, self.minimizeAct)
757 self.add_menu_action(self.window_menu, self.maximizeAct)
757 self.add_menu_action(self.window_menu, self.maximizeAct)
758 self.window_menu.addSeparator()
758 self.window_menu.addSeparator()
759
759
760 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
760 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
761 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
761 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
762 self,
762 self,
763 shortcut=prev_key,
763 shortcut=prev_key,
764 statusTip="Select previous tab",
764 statusTip="Select previous tab",
765 triggered=self.prev_tab)
765 triggered=self.prev_tab)
766 self.add_menu_action(self.window_menu, self.prev_tab_act)
766 self.add_menu_action(self.window_menu, self.prev_tab_act)
767
767
768 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
768 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
769 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
769 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
770 self,
770 self,
771 shortcut=next_key,
771 shortcut=next_key,
772 statusTip="Select next tab",
772 statusTip="Select next tab",
773 triggered=self.next_tab)
773 triggered=self.next_tab)
774 self.add_menu_action(self.window_menu, self.next_tab_act)
774 self.add_menu_action(self.window_menu, self.next_tab_act)
775
775
776 def init_help_menu(self):
776 def init_help_menu(self):
777 # please keep the Help menu in Mac Os even if empty. It will
777 # please keep the Help menu in Mac Os even if empty. It will
778 # automatically contain a search field to search inside menus and
778 # automatically contain a search field to search inside menus and
779 # please keep it spelled in English, as long as Qt Doesn't support
779 # please keep it spelled in English, as long as Qt Doesn't support
780 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
780 # a QAction.MenuRole like HelpMenuRole otherwise it will lose
781 # this search field functionality
781 # this search field functionality
782
782
783 self.help_menu = self.menuBar().addMenu("&Help")
783 self.help_menu = self.menuBar().addMenu("&Help")
784
784
785
785
786 # Help Menu
786 # Help Menu
787
787
788 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
788 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
789 self,
789 self,
790 triggered=self.intro_active_frontend
790 triggered=self.intro_active_frontend
791 )
791 )
792 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
792 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
793
793
794 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
794 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
795 self,
795 self,
796 triggered=self.quickref_active_frontend
796 triggered=self.quickref_active_frontend
797 )
797 )
798 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
798 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
799
799
800 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
800 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
801 self,
801 self,
802 triggered=self.guiref_active_frontend
802 triggered=self.guiref_active_frontend
803 )
803 )
804 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
804 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
805
805
806 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
806 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
807 self,
807 self,
808 triggered=self._open_online_help)
808 triggered=self._open_online_help)
809 self.add_menu_action(self.help_menu, self.onlineHelpAct)
809 self.add_menu_action(self.help_menu, self.onlineHelpAct)
810
810
811 # minimize/maximize/fullscreen actions:
811 # minimize/maximize/fullscreen actions:
812
812
813 def toggle_menu_bar(self):
813 def toggle_menu_bar(self):
814 menu_bar = self.menuBar()
814 menu_bar = self.menuBar()
815 if menu_bar.isVisible():
815 if menu_bar.isVisible():
816 menu_bar.setVisible(False)
816 menu_bar.setVisible(False)
817 else:
817 else:
818 menu_bar.setVisible(True)
818 menu_bar.setVisible(True)
819
819
820 def toggleMinimized(self):
820 def toggleMinimized(self):
821 if not self.isMinimized():
821 if not self.isMinimized():
822 self.showMinimized()
822 self.showMinimized()
823 else:
823 else:
824 self.showNormal()
824 self.showNormal()
825
825
826 def _open_online_help(self):
826 def _open_online_help(self):
827 filename="http://ipython.org/ipython-doc/stable/index.html"
827 filename="http://ipython.org/ipython-doc/stable/index.html"
828 webbrowser.open(filename, new=1, autoraise=True)
828 webbrowser.open(filename, new=1, autoraise=True)
829
829
830 def toggleMaximized(self):
830 def toggleMaximized(self):
831 if not self.isMaximized():
831 if not self.isMaximized():
832 self.showMaximized()
832 self.showMaximized()
833 else:
833 else:
834 self.showNormal()
834 self.showNormal()
835
835
836 # Min/Max imizing while in full screen give a bug
836 # Min/Max imizing while in full screen give a bug
837 # when going out of full screen, at least on OSX
837 # when going out of full screen, at least on OSX
838 def toggleFullScreen(self):
838 def toggleFullScreen(self):
839 if not self.isFullScreen():
839 if not self.isFullScreen():
840 self.showFullScreen()
840 self.showFullScreen()
841 if sys.platform == 'darwin':
841 if sys.platform == 'darwin':
842 self.maximizeAct.setEnabled(False)
842 self.maximizeAct.setEnabled(False)
843 self.minimizeAct.setEnabled(False)
843 self.minimizeAct.setEnabled(False)
844 else:
844 else:
845 self.showNormal()
845 self.showNormal()
846 if sys.platform == 'darwin':
846 if sys.platform == 'darwin':
847 self.maximizeAct.setEnabled(True)
847 self.maximizeAct.setEnabled(True)
848 self.minimizeAct.setEnabled(True)
848 self.minimizeAct.setEnabled(True)
849
849
850 def set_paging_active_frontend(self, paging):
850 def set_paging_active_frontend(self, paging):
851 self.active_frontend._set_paging(paging)
851 self.active_frontend._set_paging(paging)
852
852
853 def close_active_frontend(self):
853 def close_active_frontend(self):
854 self.close_tab(self.active_frontend)
854 self.close_tab(self.active_frontend)
855
855
856 def restart_kernel_active_frontend(self):
856 def restart_kernel_active_frontend(self):
857 self.active_frontend.request_restart_kernel()
857 self.active_frontend.request_restart_kernel()
858
858
859 def interrupt_kernel_active_frontend(self):
859 def interrupt_kernel_active_frontend(self):
860 self.active_frontend.request_interrupt_kernel()
860 self.active_frontend.request_interrupt_kernel()
861
861
862 def toggle_confirm_restart_active_frontend(self):
862 def toggle_confirm_restart_active_frontend(self):
863 widget = self.active_frontend
863 widget = self.active_frontend
864 widget.confirm_restart = not widget.confirm_restart
864 widget.confirm_restart = not widget.confirm_restart
865 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
865 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
866
866
867 def update_restart_checkbox(self):
867 def update_restart_checkbox(self):
868 if self.active_frontend is None:
868 if self.active_frontend is None:
869 return
869 return
870 widget = self.active_frontend
870 widget = self.active_frontend
871 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
871 self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
872
872
873 def cut_active_frontend(self):
873 def cut_active_frontend(self):
874 widget = self.active_frontend
874 widget = self.active_frontend
875 if widget.can_cut():
875 if widget.can_cut():
876 widget.cut()
876 widget.cut()
877
877
878 def copy_active_frontend(self):
878 def copy_active_frontend(self):
879 widget = self.active_frontend
879 widget = self.active_frontend
880 widget.copy()
880 widget.copy()
881
881
882 def copy_raw_active_frontend(self):
882 def copy_raw_active_frontend(self):
883 self.active_frontend._copy_raw_action.trigger()
883 self.active_frontend._copy_raw_action.trigger()
884
884
885 def paste_active_frontend(self):
885 def paste_active_frontend(self):
886 widget = self.active_frontend
886 widget = self.active_frontend
887 if widget.can_paste():
887 if widget.can_paste():
888 widget.paste()
888 widget.paste()
889
889
890 def undo_active_frontend(self):
890 def undo_active_frontend(self):
891 self.active_frontend.undo()
891 self.active_frontend.undo()
892
892
893 def redo_active_frontend(self):
893 def redo_active_frontend(self):
894 self.active_frontend.redo()
894 self.active_frontend.redo()
895
895
896 def reset_magic_active_frontend(self):
896 def reset_magic_active_frontend(self):
897 self.active_frontend.execute("%reset")
897 self.active_frontend.execute("%reset")
898
898
899 def history_magic_active_frontend(self):
899 def history_magic_active_frontend(self):
900 self.active_frontend.execute("%history")
900 self.active_frontend.execute("%history")
901
901
902 def save_magic_active_frontend(self):
902 def save_magic_active_frontend(self):
903 self.active_frontend.save_magic()
903 self.active_frontend.save_magic()
904
904
905 def clear_magic_active_frontend(self):
905 def clear_magic_active_frontend(self):
906 self.active_frontend.execute("%clear")
906 self.active_frontend.execute("%clear")
907
907
908 def who_magic_active_frontend(self):
908 def who_magic_active_frontend(self):
909 self.active_frontend.execute("%who")
909 self.active_frontend.execute("%who")
910
910
911 def who_ls_magic_active_frontend(self):
911 def who_ls_magic_active_frontend(self):
912 self.active_frontend.execute("%who_ls")
912 self.active_frontend.execute("%who_ls")
913
913
914 def whos_magic_active_frontend(self):
914 def whos_magic_active_frontend(self):
915 self.active_frontend.execute("%whos")
915 self.active_frontend.execute("%whos")
916
916
917 def print_action_active_frontend(self):
917 def print_action_active_frontend(self):
918 self.active_frontend.print_action.trigger()
918 self.active_frontend.print_action.trigger()
919
919
920 def export_action_active_frontend(self):
920 def export_action_active_frontend(self):
921 self.active_frontend.export_action.trigger()
921 self.active_frontend.export_action.trigger()
922
922
923 def select_all_active_frontend(self):
923 def select_all_active_frontend(self):
924 self.active_frontend.select_all_action.trigger()
924 self.active_frontend.select_all_action.trigger()
925
925
926 def increase_font_size_active_frontend(self):
926 def increase_font_size_active_frontend(self):
927 self.active_frontend.increase_font_size.trigger()
927 self.active_frontend.increase_font_size.trigger()
928
928
929 def decrease_font_size_active_frontend(self):
929 def decrease_font_size_active_frontend(self):
930 self.active_frontend.decrease_font_size.trigger()
930 self.active_frontend.decrease_font_size.trigger()
931
931
932 def reset_font_size_active_frontend(self):
932 def reset_font_size_active_frontend(self):
933 self.active_frontend.reset_font_size.trigger()
933 self.active_frontend.reset_font_size.trigger()
934
934
935 def guiref_active_frontend(self):
935 def guiref_active_frontend(self):
936 self.active_frontend.execute("%guiref")
936 self.active_frontend.execute("%guiref")
937
937
938 def intro_active_frontend(self):
938 def intro_active_frontend(self):
939 self.active_frontend.execute("?")
939 self.active_frontend.execute("?")
940
940
941 def quickref_active_frontend(self):
941 def quickref_active_frontend(self):
942 self.active_frontend.execute("%quickref")
942 self.active_frontend.execute("%quickref")
943 #---------------------------------------------------------------------------
943 #---------------------------------------------------------------------------
944 # QWidget interface
944 # QWidget interface
945 #---------------------------------------------------------------------------
945 #---------------------------------------------------------------------------
946
946
947 def closeEvent(self, event):
947 def closeEvent(self, event):
948 """ Forward the close event to every tabs contained by the windows
948 """ Forward the close event to every tabs contained by the windows
949 """
949 """
950 if self.tab_widget.count() == 0:
950 if self.tab_widget.count() == 0:
951 # no tabs, just close
951 # no tabs, just close
952 event.accept()
952 event.accept()
953 return
953 return
954 # Do Not loop on the widget count as it change while closing
954 # Do Not loop on the widget count as it change while closing
955 title = self.window().windowTitle()
955 title = self.window().windowTitle()
956 cancel = QtGui.QMessageBox.Cancel
956 cancel = QtGui.QMessageBox.Cancel
957 okay = QtGui.QMessageBox.Ok
957 okay = QtGui.QMessageBox.Ok
958
958
959 if self.confirm_exit:
959 if self.confirm_exit:
960 if self.tab_widget.count() > 1:
960 if self.tab_widget.count() > 1:
961 msg = "Close all tabs, stop all kernels, and Quit?"
961 msg = "Close all tabs, stop all kernels, and Quit?"
962 else:
962 else:
963 msg = "Close console, stop kernel, and Quit?"
963 msg = "Close console, stop kernel, and Quit?"
964 info = "Kernels not started here (e.g. notebooks) will be left alone."
964 info = "Kernels not started here (e.g. notebooks) will be left alone."
965 closeall = QtGui.QPushButton("&Quit", self)
965 closeall = QtGui.QPushButton("&Quit", self)
966 closeall.setShortcut('Q')
966 closeall.setShortcut('Q')
967 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
967 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
968 title, msg)
968 title, msg)
969 box.setInformativeText(info)
969 box.setInformativeText(info)
970 box.addButton(cancel)
970 box.addButton(cancel)
971 box.addButton(closeall, QtGui.QMessageBox.YesRole)
971 box.addButton(closeall, QtGui.QMessageBox.YesRole)
972 box.setDefaultButton(closeall)
972 box.setDefaultButton(closeall)
973 box.setEscapeButton(cancel)
973 box.setEscapeButton(cancel)
974 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
974 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
975 box.setIconPixmap(pixmap)
975 box.setIconPixmap(pixmap)
976 reply = box.exec_()
976 reply = box.exec_()
977 else:
977 else:
978 reply = okay
978 reply = okay
979
979
980 if reply == cancel:
980 if reply == cancel:
981 event.ignore()
981 event.ignore()
982 return
982 return
983 if reply == okay:
983 if reply == okay:
984 while self.tab_widget.count() >= 1:
984 while self.tab_widget.count() >= 1:
985 # prevent further confirmations:
985 # prevent further confirmations:
986 widget = self.active_frontend
986 widget = self.active_frontend
987 widget._confirm_exit = False
987 widget._confirm_exit = False
988 self.close_tab(widget)
988 self.close_tab(widget)
989 event.accept()
989 event.accept()
990
990
@@ -1,370 +1,371 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2
2
3 This is not a complete console app, as subprocess will not be able to receive
3 This is not a complete console app, as subprocess will not be able to receive
4 input, there is no real readline support, among other limitations.
4 input, there is no real readline support, among other limitations.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14 * Paul Ivanov
14 * Paul Ivanov
15
15
16 """
16 """
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 # stdlib imports
22 # stdlib imports
23 import json
23 import json
24 import os
24 import os
25 import signal
25 import signal
26 import sys
26 import sys
27 import uuid
27 import uuid
28
28
29 # If run on Windows, install an exception hook which pops up a
29 # If run on Windows, install an exception hook which pops up a
30 # message box. Pythonw.exe hides the console, so without this
30 # message box. Pythonw.exe hides the console, so without this
31 # the application silently fails to load.
31 # the application silently fails to load.
32 #
32 #
33 # We always install this handler, because the expectation is for
33 # We always install this handler, because the expectation is for
34 # qtconsole to bring up a GUI even if called from the console.
34 # qtconsole to bring up a GUI even if called from the console.
35 # The old handler is called, so the exception is printed as well.
35 # The old handler is called, so the exception is printed as well.
36 # If desired, check for pythonw with an additional condition
36 # If desired, check for pythonw with an additional condition
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
37 # (sys.executable.lower().find('pythonw.exe') >= 0).
38 if os.name == 'nt':
38 if os.name == 'nt':
39 old_excepthook = sys.excepthook
39 old_excepthook = sys.excepthook
40
40
41 def gui_excepthook(exctype, value, tb):
41 def gui_excepthook(exctype, value, tb):
42 try:
42 try:
43 import ctypes, traceback
43 import ctypes, traceback
44 MB_ICONERROR = 0x00000010L
44 MB_ICONERROR = 0x00000010L
45 title = u'Error starting IPython QtConsole'
45 title = u'Error starting IPython QtConsole'
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
46 msg = u''.join(traceback.format_exception(exctype, value, tb))
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
47 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
48 finally:
48 finally:
49 # Also call the old exception hook to let it do
49 # Also call the old exception hook to let it do
50 # its thing too.
50 # its thing too.
51 old_excepthook(exctype, value, tb)
51 old_excepthook(exctype, value, tb)
52
52
53 sys.excepthook = gui_excepthook
53 sys.excepthook = gui_excepthook
54
54
55 # System library imports
55 # System library imports
56 from IPython.external.qt import QtCore, QtGui
56 from IPython.external.qt import QtCore, QtGui
57
57
58 # Local imports
58 # Local imports
59 from IPython.config.application import boolean_flag, catch_config_error
59 from IPython.config.application import boolean_flag, catch_config_error
60 from IPython.core.application import BaseIPythonApplication
60 from IPython.core.application import BaseIPythonApplication
61 from IPython.core.profiledir import ProfileDir
61 from IPython.core.profiledir import ProfileDir
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
62 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
63 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
64 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
65 from IPython.frontend.qt.console import styles
65 from IPython.frontend.qt.console import styles
66 from IPython.frontend.qt.console.mainwindow import MainWindow
66 from IPython.frontend.qt.console.mainwindow import MainWindow
67 from IPython.frontend.qt.kernelmanager import QtKernelManager
67 from IPython.frontend.qt.client import QtKernelClient
68 from IPython.kernel import tunnel_to_kernel, find_connection_file
68 from IPython.kernel import tunnel_to_kernel, find_connection_file
69 from IPython.utils.path import filefind
69 from IPython.utils.path import filefind
70 from IPython.utils.py3compat import str_to_bytes
70 from IPython.utils.py3compat import str_to_bytes
71 from IPython.utils.traitlets import (
71 from IPython.utils.traitlets import (
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
72 Dict, List, Unicode, Integer, CaselessStrEnum, CBool, Any
73 )
73 )
74 from IPython.kernel.zmq.kernelapp import IPKernelApp
74 from IPython.kernel.zmq.kernelapp import IPKernelApp
75 from IPython.kernel.zmq.session import Session, default_secure
75 from IPython.kernel.zmq.session import Session, default_secure
76 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
76 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
77
77
78 from IPython.frontend.consoleapp import (
78 from IPython.frontend.consoleapp import (
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
79 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
80 )
80 )
81
81
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83 # Network Constants
83 # Network Constants
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85
85
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
86 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
87
87
88 #-----------------------------------------------------------------------------
88 #-----------------------------------------------------------------------------
89 # Globals
89 # Globals
90 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
91
91
92 _examples = """
92 _examples = """
93 ipython qtconsole # start the qtconsole
93 ipython qtconsole # start the qtconsole
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
94 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
95 """
95 """
96
96
97 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
98 # Aliases and Flags
98 # Aliases and Flags
99 #-----------------------------------------------------------------------------
99 #-----------------------------------------------------------------------------
100
100
101 # start with copy of flags
101 # start with copy of flags
102 flags = dict(flags)
102 flags = dict(flags)
103 qt_flags = {
103 qt_flags = {
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
104 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
105 "Disable rich text support."),
105 "Disable rich text support."),
106 }
106 }
107
107
108 # and app_flags from the Console Mixin
108 # and app_flags from the Console Mixin
109 qt_flags.update(app_flags)
109 qt_flags.update(app_flags)
110 # add frontend flags to the full set
110 # add frontend flags to the full set
111 flags.update(qt_flags)
111 flags.update(qt_flags)
112
112
113 # start with copy of front&backend aliases list
113 # start with copy of front&backend aliases list
114 aliases = dict(aliases)
114 aliases = dict(aliases)
115 qt_aliases = dict(
115 qt_aliases = dict(
116 style = 'IPythonWidget.syntax_style',
116 style = 'IPythonWidget.syntax_style',
117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
117 stylesheet = 'IPythonQtConsoleApp.stylesheet',
118 colors = 'ZMQInteractiveShell.colors',
118 colors = 'ZMQInteractiveShell.colors',
119
119
120 editor = 'IPythonWidget.editor',
120 editor = 'IPythonWidget.editor',
121 paging = 'ConsoleWidget.paging',
121 paging = 'ConsoleWidget.paging',
122 )
122 )
123 # and app_aliases from the Console Mixin
123 # and app_aliases from the Console Mixin
124 qt_aliases.update(app_aliases)
124 qt_aliases.update(app_aliases)
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
125 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
126 # add frontend aliases to the full set
126 # add frontend aliases to the full set
127 aliases.update(qt_aliases)
127 aliases.update(qt_aliases)
128
128
129 # get flags&aliases into sets, and remove a couple that
129 # get flags&aliases into sets, and remove a couple that
130 # shouldn't be scrubbed from backend flags:
130 # shouldn't be scrubbed from backend flags:
131 qt_aliases = set(qt_aliases.keys())
131 qt_aliases = set(qt_aliases.keys())
132 qt_aliases.remove('colors')
132 qt_aliases.remove('colors')
133 qt_flags = set(qt_flags.keys())
133 qt_flags = set(qt_flags.keys())
134
134
135 #-----------------------------------------------------------------------------
135 #-----------------------------------------------------------------------------
136 # Classes
136 # Classes
137 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
138
138
139 #-----------------------------------------------------------------------------
139 #-----------------------------------------------------------------------------
140 # IPythonQtConsole
140 # IPythonQtConsole
141 #-----------------------------------------------------------------------------
141 #-----------------------------------------------------------------------------
142
142
143
143
144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
144 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
145 name = 'ipython-qtconsole'
145 name = 'ipython-qtconsole'
146
146
147 description = """
147 description = """
148 The IPython QtConsole.
148 The IPython QtConsole.
149
149
150 This launches a Console-style application using Qt. It is not a full
150 This launches a Console-style application using Qt. It is not a full
151 console, in that launched terminal subprocesses will not be able to accept
151 console, in that launched terminal subprocesses will not be able to accept
152 input.
152 input.
153
153
154 The QtConsole supports various extra features beyond the Terminal IPython
154 The QtConsole supports various extra features beyond the Terminal IPython
155 shell, such as inline plotting with matplotlib, via:
155 shell, such as inline plotting with matplotlib, via:
156
156
157 ipython qtconsole --pylab=inline
157 ipython qtconsole --pylab=inline
158
158
159 as well as saving your session as HTML, and printing the output.
159 as well as saving your session as HTML, and printing the output.
160
160
161 """
161 """
162 examples = _examples
162 examples = _examples
163
163
164 classes = [IPythonWidget] + IPythonConsoleApp.classes
164 classes = [IPythonWidget] + IPythonConsoleApp.classes
165 flags = Dict(flags)
165 flags = Dict(flags)
166 aliases = Dict(aliases)
166 aliases = Dict(aliases)
167 frontend_flags = Any(qt_flags)
167 frontend_flags = Any(qt_flags)
168 frontend_aliases = Any(qt_aliases)
168 frontend_aliases = Any(qt_aliases)
169 kernel_manager_class = QtKernelManager
169 kernel_client_class = QtKernelClient
170
170
171 stylesheet = Unicode('', config=True,
171 stylesheet = Unicode('', config=True,
172 help="path to a custom CSS stylesheet")
172 help="path to a custom CSS stylesheet")
173
173
174 plain = CBool(False, config=True,
174 plain = CBool(False, config=True,
175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
175 help="Use a plaintext widget instead of rich text (plain can't print/save).")
176
176
177 def _plain_changed(self, name, old, new):
177 def _plain_changed(self, name, old, new):
178 kind = 'plain' if new else 'rich'
178 kind = 'plain' if new else 'rich'
179 self.config.ConsoleWidget.kind = kind
179 self.config.ConsoleWidget.kind = kind
180 if new:
180 if new:
181 self.widget_factory = IPythonWidget
181 self.widget_factory = IPythonWidget
182 else:
182 else:
183 self.widget_factory = RichIPythonWidget
183 self.widget_factory = RichIPythonWidget
184
184
185 # the factory for creating a widget
185 # the factory for creating a widget
186 widget_factory = Any(RichIPythonWidget)
186 widget_factory = Any(RichIPythonWidget)
187
187
188 def parse_command_line(self, argv=None):
188 def parse_command_line(self, argv=None):
189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
189 super(IPythonQtConsoleApp, self).parse_command_line(argv)
190 self.build_kernel_argv(argv)
190 self.build_kernel_argv(argv)
191
191
192
192
193 def new_frontend_master(self):
193 def new_frontend_master(self):
194 """ Create and return new frontend attached to new kernel, launched on localhost.
194 """ Create and return new frontend attached to new kernel, launched on localhost.
195 """
195 """
196 kernel_manager = self.kernel_manager_class(
196 kernel_manager = self.kernel_manager_class(
197 connection_file=self._new_connection_file(),
197 connection_file=self._new_connection_file(),
198 config=self.config,
198 config=self.config,
199 )
199 )
200 # start the kernel
200 # start the kernel
201 kwargs = dict()
201 kwargs = dict()
202 kwargs['extra_arguments'] = self.kernel_argv
202 kwargs['extra_arguments'] = self.kernel_argv
203 kernel_manager.start_kernel(**kwargs)
203 kernel_manager.start_kernel(**kwargs)
204 kernel_manager.start_channels()
204 kernel_client.start_channels()
205 widget = self.widget_factory(config=self.config,
205 widget = self.widget_factory(config=self.config,
206 local_kernel=True)
206 local_kernel=True)
207 self.init_colors(widget)
207 self.init_colors(widget)
208 widget.kernel_manager = kernel_manager
208 widget.kernel_manager = kernel_manager
209 widget._existing = False
209 widget._existing = False
210 widget._may_close = True
210 widget._may_close = True
211 widget._confirm_exit = self.confirm_exit
211 widget._confirm_exit = self.confirm_exit
212 return widget
212 return widget
213
213
214 def new_frontend_slave(self, current_widget):
214 def new_frontend_slave(self, current_widget):
215 """Create and return a new frontend attached to an existing kernel.
215 """Create and return a new frontend attached to an existing kernel.
216
216
217 Parameters
217 Parameters
218 ----------
218 ----------
219 current_widget : IPythonWidget
219 current_widget : IPythonWidget
220 The IPythonWidget whose kernel this frontend is to share
220 The IPythonWidget whose kernel this frontend is to share
221 """
221 """
222 kernel_manager = self.kernel_manager_class(
222 kernel_client = self.kernel_client_class(
223 connection_file=current_widget.kernel_manager.connection_file,
223 connection_file=current_widget.kernel_client.connection_file,
224 config = self.config,
224 config = self.config,
225 )
225 )
226 kernel_manager.load_connection_file()
226 kernel_client.load_connection_file()
227 kernel_manager.start_channels()
227 kernel_client.start_channels()
228 widget = self.widget_factory(config=self.config,
228 widget = self.widget_factory(config=self.config,
229 local_kernel=False)
229 local_kernel=False)
230 self.init_colors(widget)
230 self.init_colors(widget)
231 widget._existing = True
231 widget._existing = True
232 widget._may_close = False
232 widget._may_close = False
233 widget._confirm_exit = False
233 widget._confirm_exit = False
234 widget.kernel_manager = kernel_manager
234 widget.kernel_client = kernel_client
235 return widget
235 return widget
236
236
237 def init_qt_elements(self):
237 def init_qt_elements(self):
238 # Create the widget.
238 # Create the widget.
239 self.app = QtGui.QApplication([])
239 self.app = QtGui.QApplication([])
240
240
241 base_path = os.path.abspath(os.path.dirname(__file__))
241 base_path = os.path.abspath(os.path.dirname(__file__))
242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
242 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
243 self.app.icon = QtGui.QIcon(icon_path)
243 self.app.icon = QtGui.QIcon(icon_path)
244 QtGui.QApplication.setWindowIcon(self.app.icon)
244 QtGui.QApplication.setWindowIcon(self.app.icon)
245
245
246 try:
246 try:
247 ip = self.config.KernelManager.ip
247 ip = self.config.KernelManager.ip
248 except AttributeError:
248 except AttributeError:
249 ip = LOCALHOST
249 ip = LOCALHOST
250 local_kernel = (not self.existing) or ip in LOCAL_IPS
250 local_kernel = (not self.existing) or ip in LOCAL_IPS
251 self.widget = self.widget_factory(config=self.config,
251 self.widget = self.widget_factory(config=self.config,
252 local_kernel=local_kernel)
252 local_kernel=local_kernel)
253 self.init_colors(self.widget)
253 self.init_colors(self.widget)
254 self.widget._existing = self.existing
254 self.widget._existing = self.existing
255 self.widget._may_close = not self.existing
255 self.widget._may_close = not self.existing
256 self.widget._confirm_exit = self.confirm_exit
256 self.widget._confirm_exit = self.confirm_exit
257
257
258 self.widget.kernel_manager = self.kernel_manager
258 self.widget.kernel_manager = self.kernel_manager
259 self.widget.kernel_client = self.kernel_client
259 self.window = MainWindow(self.app,
260 self.window = MainWindow(self.app,
260 confirm_exit=self.confirm_exit,
261 confirm_exit=self.confirm_exit,
261 new_frontend_factory=self.new_frontend_master,
262 new_frontend_factory=self.new_frontend_master,
262 slave_frontend_factory=self.new_frontend_slave,
263 slave_frontend_factory=self.new_frontend_slave,
263 )
264 )
264 self.window.log = self.log
265 self.window.log = self.log
265 self.window.add_tab_with_frontend(self.widget)
266 self.window.add_tab_with_frontend(self.widget)
266 self.window.init_menu_bar()
267 self.window.init_menu_bar()
267
268
268 self.window.setWindowTitle('IPython')
269 self.window.setWindowTitle('IPython')
269
270
270 def init_colors(self, widget):
271 def init_colors(self, widget):
271 """Configure the coloring of the widget"""
272 """Configure the coloring of the widget"""
272 # Note: This will be dramatically simplified when colors
273 # Note: This will be dramatically simplified when colors
273 # are removed from the backend.
274 # are removed from the backend.
274
275
275 # parse the colors arg down to current known labels
276 # parse the colors arg down to current known labels
276 try:
277 try:
277 colors = self.config.ZMQInteractiveShell.colors
278 colors = self.config.ZMQInteractiveShell.colors
278 except AttributeError:
279 except AttributeError:
279 colors = None
280 colors = None
280 try:
281 try:
281 style = self.config.IPythonWidget.syntax_style
282 style = self.config.IPythonWidget.syntax_style
282 except AttributeError:
283 except AttributeError:
283 style = None
284 style = None
284 try:
285 try:
285 sheet = self.config.IPythonWidget.style_sheet
286 sheet = self.config.IPythonWidget.style_sheet
286 except AttributeError:
287 except AttributeError:
287 sheet = None
288 sheet = None
288
289
289 # find the value for colors:
290 # find the value for colors:
290 if colors:
291 if colors:
291 colors=colors.lower()
292 colors=colors.lower()
292 if colors in ('lightbg', 'light'):
293 if colors in ('lightbg', 'light'):
293 colors='lightbg'
294 colors='lightbg'
294 elif colors in ('dark', 'linux'):
295 elif colors in ('dark', 'linux'):
295 colors='linux'
296 colors='linux'
296 else:
297 else:
297 colors='nocolor'
298 colors='nocolor'
298 elif style:
299 elif style:
299 if style=='bw':
300 if style=='bw':
300 colors='nocolor'
301 colors='nocolor'
301 elif styles.dark_style(style):
302 elif styles.dark_style(style):
302 colors='linux'
303 colors='linux'
303 else:
304 else:
304 colors='lightbg'
305 colors='lightbg'
305 else:
306 else:
306 colors=None
307 colors=None
307
308
308 # Configure the style
309 # Configure the style
309 if style:
310 if style:
310 widget.style_sheet = styles.sheet_from_template(style, colors)
311 widget.style_sheet = styles.sheet_from_template(style, colors)
311 widget.syntax_style = style
312 widget.syntax_style = style
312 widget._syntax_style_changed()
313 widget._syntax_style_changed()
313 widget._style_sheet_changed()
314 widget._style_sheet_changed()
314 elif colors:
315 elif colors:
315 # use a default dark/light/bw style
316 # use a default dark/light/bw style
316 widget.set_default_style(colors=colors)
317 widget.set_default_style(colors=colors)
317
318
318 if self.stylesheet:
319 if self.stylesheet:
319 # we got an explicit stylesheet
320 # we got an explicit stylesheet
320 if os.path.isfile(self.stylesheet):
321 if os.path.isfile(self.stylesheet):
321 with open(self.stylesheet) as f:
322 with open(self.stylesheet) as f:
322 sheet = f.read()
323 sheet = f.read()
323 else:
324 else:
324 raise IOError("Stylesheet %r not found." % self.stylesheet)
325 raise IOError("Stylesheet %r not found." % self.stylesheet)
325 if sheet:
326 if sheet:
326 widget.style_sheet = sheet
327 widget.style_sheet = sheet
327 widget._style_sheet_changed()
328 widget._style_sheet_changed()
328
329
329
330
330 def init_signal(self):
331 def init_signal(self):
331 """allow clean shutdown on sigint"""
332 """allow clean shutdown on sigint"""
332 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
333 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
333 # need a timer, so that QApplication doesn't block until a real
334 # need a timer, so that QApplication doesn't block until a real
334 # Qt event fires (can require mouse movement)
335 # Qt event fires (can require mouse movement)
335 # timer trick from http://stackoverflow.com/q/4938723/938949
336 # timer trick from http://stackoverflow.com/q/4938723/938949
336 timer = QtCore.QTimer()
337 timer = QtCore.QTimer()
337 # Let the interpreter run each 200 ms:
338 # Let the interpreter run each 200 ms:
338 timer.timeout.connect(lambda: None)
339 timer.timeout.connect(lambda: None)
339 timer.start(200)
340 timer.start(200)
340 # hold onto ref, so the timer doesn't get cleaned up
341 # hold onto ref, so the timer doesn't get cleaned up
341 self._sigint_timer = timer
342 self._sigint_timer = timer
342
343
343 @catch_config_error
344 @catch_config_error
344 def initialize(self, argv=None):
345 def initialize(self, argv=None):
345 super(IPythonQtConsoleApp, self).initialize(argv)
346 super(IPythonQtConsoleApp, self).initialize(argv)
346 IPythonConsoleApp.initialize(self,argv)
347 IPythonConsoleApp.initialize(self,argv)
347 self.init_qt_elements()
348 self.init_qt_elements()
348 self.init_signal()
349 self.init_signal()
349
350
350 def start(self):
351 def start(self):
351
352
352 # draw the window
353 # draw the window
353 self.window.show()
354 self.window.show()
354 self.window.raise_()
355 self.window.raise_()
355
356
356 # Start the application main loop.
357 # Start the application main loop.
357 self.app.exec_()
358 self.app.exec_()
358
359
359 #-----------------------------------------------------------------------------
360 #-----------------------------------------------------------------------------
360 # Main entry point
361 # Main entry point
361 #-----------------------------------------------------------------------------
362 #-----------------------------------------------------------------------------
362
363
363 def main():
364 def main():
364 app = IPythonQtConsoleApp()
365 app = IPythonQtConsoleApp()
365 app.initialize()
366 app.initialize()
366 app.start()
367 app.start()
367
368
368
369
369 if __name__ == '__main__':
370 if __name__ == '__main__':
370 main()
371 main()
@@ -1,260 +1,205 b''
1 """ Defines a KernelManager that provides signals and slots.
1 """ Defines a KernelManager that provides signals and slots.
2 """
2 """
3
3
4 # System library imports.
4 # System library imports.
5 from IPython.external.qt import QtCore
5 from IPython.external.qt import QtCore
6
6
7 # IPython imports.
7 # IPython imports.
8 from IPython.utils.traitlets import HasTraits, Type
8 from IPython.utils.traitlets import HasTraits, Type
9 from util import MetaQObjectHasTraits, SuperQObject
9 from util import MetaQObjectHasTraits, SuperQObject
10
10
11
11
12 class ChannelQObject(SuperQObject):
12 class ChannelQObject(SuperQObject):
13
13
14 # Emitted when the channel is started.
14 # Emitted when the channel is started.
15 started = QtCore.Signal()
15 started = QtCore.Signal()
16
16
17 # Emitted when the channel is stopped.
17 # Emitted when the channel is stopped.
18 stopped = QtCore.Signal()
18 stopped = QtCore.Signal()
19
19
20 #---------------------------------------------------------------------------
20 #---------------------------------------------------------------------------
21 # Channel interface
21 # Channel interface
22 #---------------------------------------------------------------------------
22 #---------------------------------------------------------------------------
23
23
24 def start(self):
24 def start(self):
25 """ Reimplemented to emit signal.
25 """ Reimplemented to emit signal.
26 """
26 """
27 super(ChannelQObject, self).start()
27 super(ChannelQObject, self).start()
28 self.started.emit()
28 self.started.emit()
29
29
30 def stop(self):
30 def stop(self):
31 """ Reimplemented to emit signal.
31 """ Reimplemented to emit signal.
32 """
32 """
33 super(ChannelQObject, self).stop()
33 super(ChannelQObject, self).stop()
34 self.stopped.emit()
34 self.stopped.emit()
35
35
36 #---------------------------------------------------------------------------
36 #---------------------------------------------------------------------------
37 # InProcessChannel interface
37 # InProcessChannel interface
38 #---------------------------------------------------------------------------
38 #---------------------------------------------------------------------------
39
39
40 def call_handlers_later(self, *args, **kwds):
40 def call_handlers_later(self, *args, **kwds):
41 """ Call the message handlers later.
41 """ Call the message handlers later.
42 """
42 """
43 do_later = lambda: self.call_handlers(*args, **kwds)
43 do_later = lambda: self.call_handlers(*args, **kwds)
44 QtCore.QTimer.singleShot(0, do_later)
44 QtCore.QTimer.singleShot(0, do_later)
45
45
46 def process_events(self):
46 def process_events(self):
47 """ Process any pending GUI events.
47 """ Process any pending GUI events.
48 """
48 """
49 QtCore.QCoreApplication.instance().processEvents()
49 QtCore.QCoreApplication.instance().processEvents()
50
50
51
51
52 class QtShellChannelMixin(ChannelQObject):
52 class QtShellChannelMixin(ChannelQObject):
53
53
54 # Emitted when any message is received.
54 # Emitted when any message is received.
55 message_received = QtCore.Signal(object)
55 message_received = QtCore.Signal(object)
56
56
57 # Emitted when a reply has been received for the corresponding request
57 # Emitted when a reply has been received for the corresponding request type.
58 # type.
59 execute_reply = QtCore.Signal(object)
58 execute_reply = QtCore.Signal(object)
60 complete_reply = QtCore.Signal(object)
59 complete_reply = QtCore.Signal(object)
61 object_info_reply = QtCore.Signal(object)
60 object_info_reply = QtCore.Signal(object)
62 history_reply = QtCore.Signal(object)
61 history_reply = QtCore.Signal(object)
63
62
64 # Emitted when the first reply comes back.
65 first_reply = QtCore.Signal()
66
67 # Used by the first_reply signal logic to determine if a reply is the
68 # first.
69 _handlers_called = False
70
71 #---------------------------------------------------------------------------
63 #---------------------------------------------------------------------------
72 # 'ShellChannel' interface
64 # 'ShellChannel' interface
73 #---------------------------------------------------------------------------
65 #---------------------------------------------------------------------------
74
66
75 def call_handlers(self, msg):
67 def call_handlers(self, msg):
76 """ Reimplemented to emit signals instead of making callbacks.
68 """ Reimplemented to emit signals instead of making callbacks.
77 """
69 """
78 # Emit the generic signal.
70 # Emit the generic signal.
79 self.message_received.emit(msg)
71 self.message_received.emit(msg)
80
72
81 # Emit signals for specialized message types.
73 # Emit signals for specialized message types.
82 msg_type = msg['header']['msg_type']
74 msg_type = msg['header']['msg_type']
83 signal = getattr(self, msg_type, None)
75 signal = getattr(self, msg_type, None)
84 if signal:
76 if signal:
85 signal.emit(msg)
77 signal.emit(msg)
86
78
87 if not self._handlers_called:
88 self.first_reply.emit()
89 self._handlers_called = True
90
91 #---------------------------------------------------------------------------
92 # 'QtShellChannelMixin' interface
93 #---------------------------------------------------------------------------
94
95 def reset_first_reply(self):
96 """ Reset the first_reply signal to fire again on the next reply.
97 """
98 self._handlers_called = False
99
100
79
101 class QtIOPubChannelMixin(ChannelQObject):
80 class QtIOPubChannelMixin(ChannelQObject):
102
81
103 # Emitted when any message is received.
82 # Emitted when any message is received.
104 message_received = QtCore.Signal(object)
83 message_received = QtCore.Signal(object)
105
84
106 # Emitted when a message of type 'stream' is received.
85 # Emitted when a message of type 'stream' is received.
107 stream_received = QtCore.Signal(object)
86 stream_received = QtCore.Signal(object)
108
87
109 # Emitted when a message of type 'pyin' is received.
88 # Emitted when a message of type 'pyin' is received.
110 pyin_received = QtCore.Signal(object)
89 pyin_received = QtCore.Signal(object)
111
90
112 # Emitted when a message of type 'pyout' is received.
91 # Emitted when a message of type 'pyout' is received.
113 pyout_received = QtCore.Signal(object)
92 pyout_received = QtCore.Signal(object)
114
93
115 # Emitted when a message of type 'pyerr' is received.
94 # Emitted when a message of type 'pyerr' is received.
116 pyerr_received = QtCore.Signal(object)
95 pyerr_received = QtCore.Signal(object)
117
96
118 # Emitted when a message of type 'display_data' is received
97 # Emitted when a message of type 'display_data' is received
119 display_data_received = QtCore.Signal(object)
98 display_data_received = QtCore.Signal(object)
120
99
121 # Emitted when a crash report message is received from the kernel's
100 # Emitted when a crash report message is received from the kernel's
122 # last-resort sys.excepthook.
101 # last-resort sys.excepthook.
123 crash_received = QtCore.Signal(object)
102 crash_received = QtCore.Signal(object)
124
103
125 # Emitted when a shutdown is noticed.
104 # Emitted when a shutdown is noticed.
126 shutdown_reply_received = QtCore.Signal(object)
105 shutdown_reply_received = QtCore.Signal(object)
127
106
128 #---------------------------------------------------------------------------
107 #---------------------------------------------------------------------------
129 # 'IOPubChannel' interface
108 # 'IOPubChannel' interface
130 #---------------------------------------------------------------------------
109 #---------------------------------------------------------------------------
131
110
132 def call_handlers(self, msg):
111 def call_handlers(self, msg):
133 """ Reimplemented to emit signals instead of making callbacks.
112 """ Reimplemented to emit signals instead of making callbacks.
134 """
113 """
135 # Emit the generic signal.
114 # Emit the generic signal.
136 self.message_received.emit(msg)
115 self.message_received.emit(msg)
137 # Emit signals for specialized message types.
116 # Emit signals for specialized message types.
138 msg_type = msg['header']['msg_type']
117 msg_type = msg['header']['msg_type']
139 signal = getattr(self, msg_type + '_received', None)
118 signal = getattr(self, msg_type + '_received', None)
140 if signal:
119 if signal:
141 signal.emit(msg)
120 signal.emit(msg)
142 elif msg_type in ('stdout', 'stderr'):
121 elif msg_type in ('stdout', 'stderr'):
143 self.stream_received.emit(msg)
122 self.stream_received.emit(msg)
144
123
145 def flush(self):
124 def flush(self):
146 """ Reimplemented to ensure that signals are dispatched immediately.
125 """ Reimplemented to ensure that signals are dispatched immediately.
147 """
126 """
148 super(QtIOPubChannelMixin, self).flush()
127 super(QtIOPubChannelMixin, self).flush()
149 QtCore.QCoreApplication.instance().processEvents()
128 QtCore.QCoreApplication.instance().processEvents()
150
129
151
130
152 class QtStdInChannelMixin(ChannelQObject):
131 class QtStdInChannelMixin(ChannelQObject):
153
132
154 # Emitted when any message is received.
133 # Emitted when any message is received.
155 message_received = QtCore.Signal(object)
134 message_received = QtCore.Signal(object)
156
135
157 # Emitted when an input request is received.
136 # Emitted when an input request is received.
158 input_requested = QtCore.Signal(object)
137 input_requested = QtCore.Signal(object)
159
138
160 #---------------------------------------------------------------------------
139 #---------------------------------------------------------------------------
161 # 'StdInChannel' interface
140 # 'StdInChannel' interface
162 #---------------------------------------------------------------------------
141 #---------------------------------------------------------------------------
163
142
164 def call_handlers(self, msg):
143 def call_handlers(self, msg):
165 """ Reimplemented to emit signals instead of making callbacks.
144 """ Reimplemented to emit signals instead of making callbacks.
166 """
145 """
167 # Emit the generic signal.
146 # Emit the generic signal.
168 self.message_received.emit(msg)
147 self.message_received.emit(msg)
169
148
170 # Emit signals for specialized message types.
149 # Emit signals for specialized message types.
171 msg_type = msg['header']['msg_type']
150 msg_type = msg['header']['msg_type']
172 if msg_type == 'input_request':
151 if msg_type == 'input_request':
173 self.input_requested.emit(msg)
152 self.input_requested.emit(msg)
174
153
175
154
176 class QtHBChannelMixin(ChannelQObject):
155 class QtHBChannelMixin(ChannelQObject):
177
156
178 # Emitted when the kernel has died.
157 # Emitted when the kernel has died.
179 kernel_died = QtCore.Signal(object)
158 kernel_died = QtCore.Signal(object)
180
159
181 #---------------------------------------------------------------------------
160 #---------------------------------------------------------------------------
182 # 'HBChannel' interface
161 # 'HBChannel' interface
183 #---------------------------------------------------------------------------
162 #---------------------------------------------------------------------------
184
163
185 def call_handlers(self, since_last_heartbeat):
164 def call_handlers(self, since_last_heartbeat):
186 """ Reimplemented to emit signals instead of making callbacks.
165 """ Reimplemented to emit signals instead of making callbacks.
187 """
166 """
188 # Emit the generic signal.
167 # Emit the generic signal.
189 self.kernel_died.emit(since_last_heartbeat)
168 self.kernel_died.emit(since_last_heartbeat)
190
169
191
170
192 class QtKernelManagerMixin(HasTraits, SuperQObject):
171 class QtKernelClientMixin(HasTraits, SuperQObject):
193 """ A KernelManager that provides signals and slots.
172 """ A KernelClient that provides signals and slots.
194 """
173 """
195
174
196 __metaclass__ = MetaQObjectHasTraits
175 __metaclass__ = MetaQObjectHasTraits
197
176
198 # Emitted when the kernel manager has started listening.
177 # Emitted when the kernel client has started listening.
199 started_kernel = QtCore.Signal()
200
201 # Emitted when the kernel manager has started listening.
202 started_channels = QtCore.Signal()
178 started_channels = QtCore.Signal()
203
179
204 # Emitted when the kernel manager has stopped listening.
180 # Emitted when the kernel client has stopped listening.
205 stopped_channels = QtCore.Signal()
181 stopped_channels = QtCore.Signal()
206
182
207 # Use Qt-specific channel classes that emit signals.
183 # Use Qt-specific channel classes that emit signals.
208 iopub_channel_class = Type(QtIOPubChannelMixin)
184 iopub_channel_class = Type(QtIOPubChannelMixin)
209 shell_channel_class = Type(QtShellChannelMixin)
185 shell_channel_class = Type(QtShellChannelMixin)
210 stdin_channel_class = Type(QtStdInChannelMixin)
186 stdin_channel_class = Type(QtStdInChannelMixin)
211 hb_channel_class = Type(QtHBChannelMixin)
187 hb_channel_class = Type(QtHBChannelMixin)
212
188
213 #---------------------------------------------------------------------------
189 #---------------------------------------------------------------------------
214 # 'KernelManager' interface
190 # 'KernelClient' interface
215 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
216
192
217 #------ Kernel process management ------------------------------------------
218
219 def start_kernel(self, *args, **kw):
220 """ Reimplemented for proper heartbeat management.
221 """
222 if self._shell_channel is not None:
223 self._shell_channel.reset_first_reply()
224 super(QtKernelManagerMixin, self).start_kernel(*args, **kw)
225 self.started_kernel.emit()
226
227 #------ Channel management -------------------------------------------------
193 #------ Channel management -------------------------------------------------
228
194
229 def start_channels(self, *args, **kw):
195 def start_channels(self, *args, **kw):
230 """ Reimplemented to emit signal.
196 """ Reimplemented to emit signal.
231 """
197 """
232 super(QtKernelManagerMixin, self).start_channels(*args, **kw)
198 super(QtKernelClientMixin, self).start_channels(*args, **kw)
233 self.started_channels.emit()
199 self.started_channels.emit()
234
200
235 def stop_channels(self):
201 def stop_channels(self):
236 """ Reimplemented to emit signal.
202 """ Reimplemented to emit signal.
237 """
203 """
238 super(QtKernelManagerMixin, self).stop_channels()
204 super(QtKernelClientMixin, self).stop_channels()
239 self.stopped_channels.emit()
205 self.stopped_channels.emit()
240
241 @property
242 def shell_channel(self):
243 """ Reimplemented for proper heartbeat management.
244 """
245 if self._shell_channel is None:
246 self._shell_channel = super(QtKernelManagerMixin,self).shell_channel
247 self._shell_channel.first_reply.connect(self._first_reply)
248 return self._shell_channel
249
250 #---------------------------------------------------------------------------
251 # Protected interface
252 #---------------------------------------------------------------------------
253
254 def _first_reply(self):
255 """ Unpauses the heartbeat channel when the first reply is received on
256 the execute channel. Note that this will *not* start the heartbeat
257 channel if it is not already running!
258 """
259 if self._hb_channel is not None:
260 self._hb_channel.unpause()
General Comments 0
You need to be logged in to leave comments. Login now