##// END OF EJS Templates
Merge pull request #7094 from helenst/qtconsole-no-banner...
Kyle Kelley -
r19192:7fa9c4cd merge
parent child Browse files
Show More
@@ -1,807 +1,808 b''
1 """Frontend widget for the Qt Console"""
1 """Frontend widget for the Qt Console"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from __future__ import print_function
6 from __future__ import print_function
7
7
8 from collections import namedtuple
8 from collections import namedtuple
9 import sys
9 import sys
10 import uuid
10 import uuid
11
11
12 from IPython.external import qt
12 from IPython.external import qt
13 from IPython.external.qt import QtCore, QtGui
13 from IPython.external.qt import QtCore, QtGui
14 from IPython.utils import py3compat
14 from IPython.utils import py3compat
15 from IPython.utils.importstring import import_item
15 from IPython.utils.importstring import import_item
16
16
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
17 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
18 from IPython.core.inputtransformer import classic_prompt
18 from IPython.core.inputtransformer import classic_prompt
19 from IPython.core.oinspect import call_tip
19 from IPython.core.oinspect import call_tip
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
20 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
21 from IPython.utils.traitlets import Any, Bool, Instance, Unicode, DottedObjectName
22 from .bracket_matcher import BracketMatcher
22 from .bracket_matcher import BracketMatcher
23 from .call_tip_widget import CallTipWidget
23 from .call_tip_widget import CallTipWidget
24 from .history_console_widget import HistoryConsoleWidget
24 from .history_console_widget import HistoryConsoleWidget
25 from .pygments_highlighter import PygmentsHighlighter
25 from .pygments_highlighter import PygmentsHighlighter
26
26
27
27
28 class FrontendHighlighter(PygmentsHighlighter):
28 class FrontendHighlighter(PygmentsHighlighter):
29 """ A PygmentsHighlighter that understands and ignores prompts.
29 """ A PygmentsHighlighter that understands and ignores prompts.
30 """
30 """
31
31
32 def __init__(self, frontend, lexer=None):
32 def __init__(self, frontend, lexer=None):
33 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
33 super(FrontendHighlighter, self).__init__(frontend._control.document(), lexer=lexer)
34 self._current_offset = 0
34 self._current_offset = 0
35 self._frontend = frontend
35 self._frontend = frontend
36 self.highlighting_on = False
36 self.highlighting_on = False
37
37
38 def highlightBlock(self, string):
38 def highlightBlock(self, string):
39 """ Highlight a block of text. Reimplemented to highlight selectively.
39 """ Highlight a block of text. Reimplemented to highlight selectively.
40 """
40 """
41 if not self.highlighting_on:
41 if not self.highlighting_on:
42 return
42 return
43
43
44 # The input to this function is a unicode string that may contain
44 # The input to this function is a unicode string that may contain
45 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 # paragraph break characters, non-breaking spaces, etc. Here we acquire
46 # the string as plain text so we can compare it.
46 # the string as plain text so we can compare it.
47 current_block = self.currentBlock()
47 current_block = self.currentBlock()
48 string = self._frontend._get_block_plain_text(current_block)
48 string = self._frontend._get_block_plain_text(current_block)
49
49
50 # Decide whether to check for the regular or continuation prompt.
50 # Decide whether to check for the regular or continuation prompt.
51 if current_block.contains(self._frontend._prompt_pos):
51 if current_block.contains(self._frontend._prompt_pos):
52 prompt = self._frontend._prompt
52 prompt = self._frontend._prompt
53 else:
53 else:
54 prompt = self._frontend._continuation_prompt
54 prompt = self._frontend._continuation_prompt
55
55
56 # Only highlight if we can identify a prompt, but make sure not to
56 # Only highlight if we can identify a prompt, but make sure not to
57 # highlight the prompt.
57 # highlight the prompt.
58 if string.startswith(prompt):
58 if string.startswith(prompt):
59 self._current_offset = len(prompt)
59 self._current_offset = len(prompt)
60 string = string[len(prompt):]
60 string = string[len(prompt):]
61 super(FrontendHighlighter, self).highlightBlock(string)
61 super(FrontendHighlighter, self).highlightBlock(string)
62
62
63 def rehighlightBlock(self, block):
63 def rehighlightBlock(self, block):
64 """ Reimplemented to temporarily enable highlighting if disabled.
64 """ Reimplemented to temporarily enable highlighting if disabled.
65 """
65 """
66 old = self.highlighting_on
66 old = self.highlighting_on
67 self.highlighting_on = True
67 self.highlighting_on = True
68 super(FrontendHighlighter, self).rehighlightBlock(block)
68 super(FrontendHighlighter, self).rehighlightBlock(block)
69 self.highlighting_on = old
69 self.highlighting_on = old
70
70
71 def setFormat(self, start, count, format):
71 def setFormat(self, start, count, format):
72 """ Reimplemented to highlight selectively.
72 """ Reimplemented to highlight selectively.
73 """
73 """
74 start += self._current_offset
74 start += self._current_offset
75 super(FrontendHighlighter, self).setFormat(start, count, format)
75 super(FrontendHighlighter, self).setFormat(start, count, format)
76
76
77
77
78 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
79 """ A Qt frontend for a generic Python kernel.
79 """ A Qt frontend for a generic Python kernel.
80 """
80 """
81
81
82 # The text to show when the kernel is (re)started.
82 # The text to show when the kernel is (re)started.
83 banner = Unicode(config=True)
83 banner = Unicode(config=True)
84 kernel_banner = Unicode()
84 kernel_banner = Unicode()
85
85
86 # An option and corresponding signal for overriding the default kernel
86 # An option and corresponding signal for overriding the default kernel
87 # interrupt behavior.
87 # interrupt behavior.
88 custom_interrupt = Bool(False)
88 custom_interrupt = Bool(False)
89 custom_interrupt_requested = QtCore.Signal()
89 custom_interrupt_requested = QtCore.Signal()
90
90
91 # An option and corresponding signals for overriding the default kernel
91 # An option and corresponding signals for overriding the default kernel
92 # restart behavior.
92 # restart behavior.
93 custom_restart = Bool(False)
93 custom_restart = Bool(False)
94 custom_restart_kernel_died = QtCore.Signal(float)
94 custom_restart_kernel_died = QtCore.Signal(float)
95 custom_restart_requested = QtCore.Signal()
95 custom_restart_requested = QtCore.Signal()
96
96
97 # Whether to automatically show calltips on open-parentheses.
97 # Whether to automatically show calltips on open-parentheses.
98 enable_calltips = Bool(True, config=True,
98 enable_calltips = Bool(True, config=True,
99 help="Whether to draw information calltips on open-parentheses.")
99 help="Whether to draw information calltips on open-parentheses.")
100
100
101 clear_on_kernel_restart = Bool(True, config=True,
101 clear_on_kernel_restart = Bool(True, config=True,
102 help="Whether to clear the console when the kernel is restarted")
102 help="Whether to clear the console when the kernel is restarted")
103
103
104 confirm_restart = Bool(True, config=True,
104 confirm_restart = Bool(True, config=True,
105 help="Whether to ask for user confirmation when restarting kernel")
105 help="Whether to ask for user confirmation when restarting kernel")
106
106
107 lexer_class = DottedObjectName(config=True,
107 lexer_class = DottedObjectName(config=True,
108 help="The pygments lexer class to use."
108 help="The pygments lexer class to use."
109 )
109 )
110 def _lexer_class_changed(self, name, old, new):
110 def _lexer_class_changed(self, name, old, new):
111 lexer_class = import_item(new)
111 lexer_class = import_item(new)
112 self.lexer = lexer_class()
112 self.lexer = lexer_class()
113
113
114 def _lexer_class_default(self):
114 def _lexer_class_default(self):
115 if py3compat.PY3:
115 if py3compat.PY3:
116 return 'pygments.lexers.Python3Lexer'
116 return 'pygments.lexers.Python3Lexer'
117 else:
117 else:
118 return 'pygments.lexers.PythonLexer'
118 return 'pygments.lexers.PythonLexer'
119
119
120 lexer = Any()
120 lexer = Any()
121 def _lexer_default(self):
121 def _lexer_default(self):
122 lexer_class = import_item(self.lexer_class)
122 lexer_class = import_item(self.lexer_class)
123 return lexer_class()
123 return lexer_class()
124
124
125 # Emitted when a user visible 'execute_request' has been submitted to the
125 # Emitted when a user visible 'execute_request' has been submitted to the
126 # kernel from the FrontendWidget. Contains the code to be executed.
126 # kernel from the FrontendWidget. Contains the code to be executed.
127 executing = QtCore.Signal(object)
127 executing = QtCore.Signal(object)
128
128
129 # Emitted when a user-visible 'execute_reply' has been received from the
129 # Emitted when a user-visible 'execute_reply' has been received from the
130 # kernel and processed by the FrontendWidget. Contains the response message.
130 # kernel and processed by the FrontendWidget. Contains the response message.
131 executed = QtCore.Signal(object)
131 executed = QtCore.Signal(object)
132
132
133 # Emitted when an exit request has been received from the kernel.
133 # Emitted when an exit request has been received from the kernel.
134 exit_requested = QtCore.Signal(object)
134 exit_requested = QtCore.Signal(object)
135
135
136 # Protected class variables.
136 # Protected class variables.
137 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
137 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
138 logical_line_transforms=[],
138 logical_line_transforms=[],
139 python_line_transforms=[],
139 python_line_transforms=[],
140 )
140 )
141 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
141 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
142 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
142 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
143 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
143 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
144 _input_splitter_class = InputSplitter
144 _input_splitter_class = InputSplitter
145 _local_kernel = False
145 _local_kernel = False
146 _highlighter = Instance(FrontendHighlighter)
146 _highlighter = Instance(FrontendHighlighter)
147
147
148 #---------------------------------------------------------------------------
148 #---------------------------------------------------------------------------
149 # 'object' interface
149 # 'object' interface
150 #---------------------------------------------------------------------------
150 #---------------------------------------------------------------------------
151
151
152 def __init__(self, *args, **kw):
152 def __init__(self, *args, **kw):
153 super(FrontendWidget, self).__init__(*args, **kw)
153 super(FrontendWidget, self).__init__(*args, **kw)
154 # FIXME: remove this when PySide min version is updated past 1.0.7
154 # FIXME: remove this when PySide min version is updated past 1.0.7
155 # forcefully disable calltips if PySide is < 1.0.7, because they crash
155 # forcefully disable calltips if PySide is < 1.0.7, because they crash
156 if qt.QT_API == qt.QT_API_PYSIDE:
156 if qt.QT_API == qt.QT_API_PYSIDE:
157 import PySide
157 import PySide
158 if PySide.__version_info__ < (1,0,7):
158 if PySide.__version_info__ < (1,0,7):
159 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
159 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
160 self.enable_calltips = False
160 self.enable_calltips = False
161
161
162 # FrontendWidget protected variables.
162 # FrontendWidget protected variables.
163 self._bracket_matcher = BracketMatcher(self._control)
163 self._bracket_matcher = BracketMatcher(self._control)
164 self._call_tip_widget = CallTipWidget(self._control)
164 self._call_tip_widget = CallTipWidget(self._control)
165 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
165 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
166 self._hidden = False
166 self._hidden = False
167 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
167 self._highlighter = FrontendHighlighter(self, lexer=self.lexer)
168 self._input_splitter = self._input_splitter_class()
168 self._input_splitter = self._input_splitter_class()
169 self._kernel_manager = None
169 self._kernel_manager = None
170 self._kernel_client = None
170 self._kernel_client = None
171 self._request_info = {}
171 self._request_info = {}
172 self._request_info['execute'] = {};
172 self._request_info['execute'] = {};
173 self._callback_dict = {}
173 self._callback_dict = {}
174
174
175 # Configure the ConsoleWidget.
175 # Configure the ConsoleWidget.
176 self.tab_width = 4
176 self.tab_width = 4
177 self._set_continuation_prompt('... ')
177 self._set_continuation_prompt('... ')
178
178
179 # Configure the CallTipWidget.
179 # Configure the CallTipWidget.
180 self._call_tip_widget.setFont(self.font)
180 self._call_tip_widget.setFont(self.font)
181 self.font_changed.connect(self._call_tip_widget.setFont)
181 self.font_changed.connect(self._call_tip_widget.setFont)
182
182
183 # Configure actions.
183 # Configure actions.
184 action = self._copy_raw_action
184 action = self._copy_raw_action
185 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
185 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
186 action.setEnabled(False)
186 action.setEnabled(False)
187 action.setShortcut(QtGui.QKeySequence(key))
187 action.setShortcut(QtGui.QKeySequence(key))
188 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
188 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
189 action.triggered.connect(self.copy_raw)
189 action.triggered.connect(self.copy_raw)
190 self.copy_available.connect(action.setEnabled)
190 self.copy_available.connect(action.setEnabled)
191 self.addAction(action)
191 self.addAction(action)
192
192
193 # Connect signal handlers.
193 # Connect signal handlers.
194 document = self._control.document()
194 document = self._control.document()
195 document.contentsChange.connect(self._document_contents_change)
195 document.contentsChange.connect(self._document_contents_change)
196
196
197 # Set flag for whether we are connected via localhost.
197 # Set flag for whether we are connected via localhost.
198 self._local_kernel = kw.get('local_kernel',
198 self._local_kernel = kw.get('local_kernel',
199 FrontendWidget._local_kernel)
199 FrontendWidget._local_kernel)
200
200
201 # Whether or not a clear_output call is pending new output.
201 # Whether or not a clear_output call is pending new output.
202 self._pending_clearoutput = False
202 self._pending_clearoutput = False
203
203
204 #---------------------------------------------------------------------------
204 #---------------------------------------------------------------------------
205 # 'ConsoleWidget' public interface
205 # 'ConsoleWidget' public interface
206 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
207
207
208 def copy(self):
208 def copy(self):
209 """ Copy the currently selected text to the clipboard, removing prompts.
209 """ Copy the currently selected text to the clipboard, removing prompts.
210 """
210 """
211 if self._page_control is not None and self._page_control.hasFocus():
211 if self._page_control is not None and self._page_control.hasFocus():
212 self._page_control.copy()
212 self._page_control.copy()
213 elif self._control.hasFocus():
213 elif self._control.hasFocus():
214 text = self._control.textCursor().selection().toPlainText()
214 text = self._control.textCursor().selection().toPlainText()
215 if text:
215 if text:
216 was_newline = text[-1] == '\n'
216 was_newline = text[-1] == '\n'
217 text = self._prompt_transformer.transform_cell(text)
217 text = self._prompt_transformer.transform_cell(text)
218 if not was_newline: # user doesn't need newline
218 if not was_newline: # user doesn't need newline
219 text = text[:-1]
219 text = text[:-1]
220 QtGui.QApplication.clipboard().setText(text)
220 QtGui.QApplication.clipboard().setText(text)
221 else:
221 else:
222 self.log.debug("frontend widget : unknown copy target")
222 self.log.debug("frontend widget : unknown copy target")
223
223
224 #---------------------------------------------------------------------------
224 #---------------------------------------------------------------------------
225 # 'ConsoleWidget' abstract interface
225 # 'ConsoleWidget' abstract interface
226 #---------------------------------------------------------------------------
226 #---------------------------------------------------------------------------
227
227
228 def _is_complete(self, source, interactive):
228 def _is_complete(self, source, interactive):
229 """ Returns whether 'source' can be completely processed and a new
229 """ Returns whether 'source' can be completely processed and a new
230 prompt created. When triggered by an Enter/Return key press,
230 prompt created. When triggered by an Enter/Return key press,
231 'interactive' is True; otherwise, it is False.
231 'interactive' is True; otherwise, it is False.
232 """
232 """
233 self._input_splitter.reset()
233 self._input_splitter.reset()
234 try:
234 try:
235 complete = self._input_splitter.push(source)
235 complete = self._input_splitter.push(source)
236 except SyntaxError:
236 except SyntaxError:
237 return True
237 return True
238 if interactive:
238 if interactive:
239 complete = not self._input_splitter.push_accepts_more()
239 complete = not self._input_splitter.push_accepts_more()
240 return complete
240 return complete
241
241
242 def _execute(self, source, hidden):
242 def _execute(self, source, hidden):
243 """ Execute 'source'. If 'hidden', do not show any output.
243 """ Execute 'source'. If 'hidden', do not show any output.
244
244
245 See parent class :meth:`execute` docstring for full details.
245 See parent class :meth:`execute` docstring for full details.
246 """
246 """
247 msg_id = self.kernel_client.execute(source, hidden)
247 msg_id = self.kernel_client.execute(source, hidden)
248 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
248 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
249 self._hidden = hidden
249 self._hidden = hidden
250 if not hidden:
250 if not hidden:
251 self.executing.emit(source)
251 self.executing.emit(source)
252
252
253 def _prompt_started_hook(self):
253 def _prompt_started_hook(self):
254 """ Called immediately after a new prompt is displayed.
254 """ Called immediately after a new prompt is displayed.
255 """
255 """
256 if not self._reading:
256 if not self._reading:
257 self._highlighter.highlighting_on = True
257 self._highlighter.highlighting_on = True
258
258
259 def _prompt_finished_hook(self):
259 def _prompt_finished_hook(self):
260 """ Called immediately after a prompt is finished, i.e. when some input
260 """ Called immediately after a prompt is finished, i.e. when some input
261 will be processed and a new prompt displayed.
261 will be processed and a new prompt displayed.
262 """
262 """
263 # Flush all state from the input splitter so the next round of
263 # Flush all state from the input splitter so the next round of
264 # reading input starts with a clean buffer.
264 # reading input starts with a clean buffer.
265 self._input_splitter.reset()
265 self._input_splitter.reset()
266
266
267 if not self._reading:
267 if not self._reading:
268 self._highlighter.highlighting_on = False
268 self._highlighter.highlighting_on = False
269
269
270 def _tab_pressed(self):
270 def _tab_pressed(self):
271 """ Called when the tab key is pressed. Returns whether to continue
271 """ Called when the tab key is pressed. Returns whether to continue
272 processing the event.
272 processing the event.
273 """
273 """
274 # Perform tab completion if:
274 # Perform tab completion if:
275 # 1) The cursor is in the input buffer.
275 # 1) The cursor is in the input buffer.
276 # 2) There is a non-whitespace character before the cursor.
276 # 2) There is a non-whitespace character before the cursor.
277 text = self._get_input_buffer_cursor_line()
277 text = self._get_input_buffer_cursor_line()
278 if text is None:
278 if text is None:
279 return False
279 return False
280 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
280 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
281 if complete:
281 if complete:
282 self._complete()
282 self._complete()
283 return not complete
283 return not complete
284
284
285 #---------------------------------------------------------------------------
285 #---------------------------------------------------------------------------
286 # 'ConsoleWidget' protected interface
286 # 'ConsoleWidget' protected interface
287 #---------------------------------------------------------------------------
287 #---------------------------------------------------------------------------
288
288
289 def _context_menu_make(self, pos):
289 def _context_menu_make(self, pos):
290 """ Reimplemented to add an action for raw copy.
290 """ Reimplemented to add an action for raw copy.
291 """
291 """
292 menu = super(FrontendWidget, self)._context_menu_make(pos)
292 menu = super(FrontendWidget, self)._context_menu_make(pos)
293 for before_action in menu.actions():
293 for before_action in menu.actions():
294 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
294 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
295 QtGui.QKeySequence.ExactMatch:
295 QtGui.QKeySequence.ExactMatch:
296 menu.insertAction(before_action, self._copy_raw_action)
296 menu.insertAction(before_action, self._copy_raw_action)
297 break
297 break
298 return menu
298 return menu
299
299
300 def request_interrupt_kernel(self):
300 def request_interrupt_kernel(self):
301 if self._executing:
301 if self._executing:
302 self.interrupt_kernel()
302 self.interrupt_kernel()
303
303
304 def request_restart_kernel(self):
304 def request_restart_kernel(self):
305 message = 'Are you sure you want to restart the kernel?'
305 message = 'Are you sure you want to restart the kernel?'
306 self.restart_kernel(message, now=False)
306 self.restart_kernel(message, now=False)
307
307
308 def _event_filter_console_keypress(self, event):
308 def _event_filter_console_keypress(self, event):
309 """ Reimplemented for execution interruption and smart backspace.
309 """ Reimplemented for execution interruption and smart backspace.
310 """
310 """
311 key = event.key()
311 key = event.key()
312 if self._control_key_down(event.modifiers(), include_command=False):
312 if self._control_key_down(event.modifiers(), include_command=False):
313
313
314 if key == QtCore.Qt.Key_C and self._executing:
314 if key == QtCore.Qt.Key_C and self._executing:
315 self.request_interrupt_kernel()
315 self.request_interrupt_kernel()
316 return True
316 return True
317
317
318 elif key == QtCore.Qt.Key_Period:
318 elif key == QtCore.Qt.Key_Period:
319 self.request_restart_kernel()
319 self.request_restart_kernel()
320 return True
320 return True
321
321
322 elif not event.modifiers() & QtCore.Qt.AltModifier:
322 elif not event.modifiers() & QtCore.Qt.AltModifier:
323
323
324 # Smart backspace: remove four characters in one backspace if:
324 # Smart backspace: remove four characters in one backspace if:
325 # 1) everything left of the cursor is whitespace
325 # 1) everything left of the cursor is whitespace
326 # 2) the four characters immediately left of the cursor are spaces
326 # 2) the four characters immediately left of the cursor are spaces
327 if key == QtCore.Qt.Key_Backspace:
327 if key == QtCore.Qt.Key_Backspace:
328 col = self._get_input_buffer_cursor_column()
328 col = self._get_input_buffer_cursor_column()
329 cursor = self._control.textCursor()
329 cursor = self._control.textCursor()
330 if col > 3 and not cursor.hasSelection():
330 if col > 3 and not cursor.hasSelection():
331 text = self._get_input_buffer_cursor_line()[:col]
331 text = self._get_input_buffer_cursor_line()[:col]
332 if text.endswith(' ') and not text.strip():
332 if text.endswith(' ') and not text.strip():
333 cursor.movePosition(QtGui.QTextCursor.Left,
333 cursor.movePosition(QtGui.QTextCursor.Left,
334 QtGui.QTextCursor.KeepAnchor, 4)
334 QtGui.QTextCursor.KeepAnchor, 4)
335 cursor.removeSelectedText()
335 cursor.removeSelectedText()
336 return True
336 return True
337
337
338 return super(FrontendWidget, self)._event_filter_console_keypress(event)
338 return super(FrontendWidget, self)._event_filter_console_keypress(event)
339
339
340 def _insert_continuation_prompt(self, cursor):
340 def _insert_continuation_prompt(self, cursor):
341 """ Reimplemented for auto-indentation.
341 """ Reimplemented for auto-indentation.
342 """
342 """
343 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
343 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
344 cursor.insertText(' ' * self._input_splitter.indent_spaces)
344 cursor.insertText(' ' * self._input_splitter.indent_spaces)
345
345
346 #---------------------------------------------------------------------------
346 #---------------------------------------------------------------------------
347 # 'BaseFrontendMixin' abstract interface
347 # 'BaseFrontendMixin' abstract interface
348 #---------------------------------------------------------------------------
348 #---------------------------------------------------------------------------
349 def _handle_clear_output(self, msg):
349 def _handle_clear_output(self, msg):
350 """Handle clear output messages."""
350 """Handle clear output messages."""
351 if include_output(msg):
351 if include_output(msg):
352 wait = msg['content'].get('wait', True)
352 wait = msg['content'].get('wait', True)
353 if wait:
353 if wait:
354 self._pending_clearoutput = True
354 self._pending_clearoutput = True
355 else:
355 else:
356 self.clear_output()
356 self.clear_output()
357
357
358 def _silent_exec_callback(self, expr, callback):
358 def _silent_exec_callback(self, expr, callback):
359 """Silently execute `expr` in the kernel and call `callback` with reply
359 """Silently execute `expr` in the kernel and call `callback` with reply
360
360
361 the `expr` is evaluated silently in the kernel (without) output in
361 the `expr` is evaluated silently in the kernel (without) output in
362 the frontend. Call `callback` with the
362 the frontend. Call `callback` with the
363 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
363 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
364
364
365 Parameters
365 Parameters
366 ----------
366 ----------
367 expr : string
367 expr : string
368 valid string to be executed by the kernel.
368 valid string to be executed by the kernel.
369 callback : function
369 callback : function
370 function accepting one argument, as a string. The string will be
370 function accepting one argument, as a string. The string will be
371 the `repr` of the result of evaluating `expr`
371 the `repr` of the result of evaluating `expr`
372
372
373 The `callback` is called with the `repr()` of the result of `expr` as
373 The `callback` is called with the `repr()` of the result of `expr` as
374 first argument. To get the object, do `eval()` on the passed value.
374 first argument. To get the object, do `eval()` on the passed value.
375
375
376 See Also
376 See Also
377 --------
377 --------
378 _handle_exec_callback : private method, deal with calling callback with reply
378 _handle_exec_callback : private method, deal with calling callback with reply
379
379
380 """
380 """
381
381
382 # generate uuid, which would be used as an indication of whether or
382 # generate uuid, which would be used as an indication of whether or
383 # not the unique request originated from here (can use msg id ?)
383 # not the unique request originated from here (can use msg id ?)
384 local_uuid = str(uuid.uuid1())
384 local_uuid = str(uuid.uuid1())
385 msg_id = self.kernel_client.execute('',
385 msg_id = self.kernel_client.execute('',
386 silent=True, user_expressions={ local_uuid:expr })
386 silent=True, user_expressions={ local_uuid:expr })
387 self._callback_dict[local_uuid] = callback
387 self._callback_dict[local_uuid] = callback
388 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
388 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
389
389
390 def _handle_exec_callback(self, msg):
390 def _handle_exec_callback(self, msg):
391 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
391 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
392
392
393 Parameters
393 Parameters
394 ----------
394 ----------
395 msg : raw message send by the kernel containing an `user_expressions`
395 msg : raw message send by the kernel containing an `user_expressions`
396 and having a 'silent_exec_callback' kind.
396 and having a 'silent_exec_callback' kind.
397
397
398 Notes
398 Notes
399 -----
399 -----
400 This function will look for a `callback` associated with the
400 This function will look for a `callback` associated with the
401 corresponding message id. Association has been made by
401 corresponding message id. Association has been made by
402 `_silent_exec_callback`. `callback` is then called with the `repr()`
402 `_silent_exec_callback`. `callback` is then called with the `repr()`
403 of the value of corresponding `user_expressions` as argument.
403 of the value of corresponding `user_expressions` as argument.
404 `callback` is then removed from the known list so that any message
404 `callback` is then removed from the known list so that any message
405 coming again with the same id won't trigger it.
405 coming again with the same id won't trigger it.
406
406
407 """
407 """
408
408
409 user_exp = msg['content'].get('user_expressions')
409 user_exp = msg['content'].get('user_expressions')
410 if not user_exp:
410 if not user_exp:
411 return
411 return
412 for expression in user_exp:
412 for expression in user_exp:
413 if expression in self._callback_dict:
413 if expression in self._callback_dict:
414 self._callback_dict.pop(expression)(user_exp[expression])
414 self._callback_dict.pop(expression)(user_exp[expression])
415
415
416 def _handle_execute_reply(self, msg):
416 def _handle_execute_reply(self, msg):
417 """ Handles replies for code execution.
417 """ Handles replies for code execution.
418 """
418 """
419 self.log.debug("execute: %s", msg.get('content', ''))
419 self.log.debug("execute: %s", msg.get('content', ''))
420 msg_id = msg['parent_header']['msg_id']
420 msg_id = msg['parent_header']['msg_id']
421 info = self._request_info['execute'].get(msg_id)
421 info = self._request_info['execute'].get(msg_id)
422 # unset reading flag, because if execute finished, raw_input can't
422 # unset reading flag, because if execute finished, raw_input can't
423 # still be pending.
423 # still be pending.
424 self._reading = False
424 self._reading = False
425 if info and info.kind == 'user' and not self._hidden:
425 if info and info.kind == 'user' and not self._hidden:
426 # Make sure that all output from the SUB channel has been processed
426 # Make sure that all output from the SUB channel has been processed
427 # before writing a new prompt.
427 # before writing a new prompt.
428 self.kernel_client.iopub_channel.flush()
428 self.kernel_client.iopub_channel.flush()
429
429
430 # Reset the ANSI style information to prevent bad text in stdout
430 # Reset the ANSI style information to prevent bad text in stdout
431 # from messing up our colors. We're not a true terminal so we're
431 # from messing up our colors. We're not a true terminal so we're
432 # allowed to do this.
432 # allowed to do this.
433 if self.ansi_codes:
433 if self.ansi_codes:
434 self._ansi_processor.reset_sgr()
434 self._ansi_processor.reset_sgr()
435
435
436 content = msg['content']
436 content = msg['content']
437 status = content['status']
437 status = content['status']
438 if status == 'ok':
438 if status == 'ok':
439 self._process_execute_ok(msg)
439 self._process_execute_ok(msg)
440 elif status == 'error':
440 elif status == 'error':
441 self._process_execute_error(msg)
441 self._process_execute_error(msg)
442 elif status == 'aborted':
442 elif status == 'aborted':
443 self._process_execute_abort(msg)
443 self._process_execute_abort(msg)
444
444
445 self._show_interpreter_prompt_for_reply(msg)
445 self._show_interpreter_prompt_for_reply(msg)
446 self.executed.emit(msg)
446 self.executed.emit(msg)
447 self._request_info['execute'].pop(msg_id)
447 self._request_info['execute'].pop(msg_id)
448 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
448 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
449 self._handle_exec_callback(msg)
449 self._handle_exec_callback(msg)
450 self._request_info['execute'].pop(msg_id)
450 self._request_info['execute'].pop(msg_id)
451 else:
451 else:
452 super(FrontendWidget, self)._handle_execute_reply(msg)
452 super(FrontendWidget, self)._handle_execute_reply(msg)
453
453
454 def _handle_input_request(self, msg):
454 def _handle_input_request(self, msg):
455 """ Handle requests for raw_input.
455 """ Handle requests for raw_input.
456 """
456 """
457 self.log.debug("input: %s", msg.get('content', ''))
457 self.log.debug("input: %s", msg.get('content', ''))
458 if self._hidden:
458 if self._hidden:
459 raise RuntimeError('Request for raw input during hidden execution.')
459 raise RuntimeError('Request for raw input during hidden execution.')
460
460
461 # Make sure that all output from the SUB channel has been processed
461 # Make sure that all output from the SUB channel has been processed
462 # before entering readline mode.
462 # before entering readline mode.
463 self.kernel_client.iopub_channel.flush()
463 self.kernel_client.iopub_channel.flush()
464
464
465 def callback(line):
465 def callback(line):
466 self.kernel_client.stdin_channel.input(line)
466 self.kernel_client.stdin_channel.input(line)
467 if self._reading:
467 if self._reading:
468 self.log.debug("Got second input request, assuming first was interrupted.")
468 self.log.debug("Got second input request, assuming first was interrupted.")
469 self._reading = False
469 self._reading = False
470 self._readline(msg['content']['prompt'], callback=callback)
470 self._readline(msg['content']['prompt'], callback=callback)
471
471
472 def _kernel_restarted_message(self, died=True):
472 def _kernel_restarted_message(self, died=True):
473 msg = "Kernel died, restarting" if died else "Kernel restarting"
473 msg = "Kernel died, restarting" if died else "Kernel restarting"
474 self._append_html("<br>%s<hr><br>" % msg,
474 self._append_html("<br>%s<hr><br>" % msg,
475 before_prompt=False
475 before_prompt=False
476 )
476 )
477
477
478 def _handle_kernel_died(self, since_last_heartbeat):
478 def _handle_kernel_died(self, since_last_heartbeat):
479 """Handle the kernel's death (if we do not own the kernel).
479 """Handle the kernel's death (if we do not own the kernel).
480 """
480 """
481 self.log.warn("kernel died: %s", since_last_heartbeat)
481 self.log.warn("kernel died: %s", since_last_heartbeat)
482 if self.custom_restart:
482 if self.custom_restart:
483 self.custom_restart_kernel_died.emit(since_last_heartbeat)
483 self.custom_restart_kernel_died.emit(since_last_heartbeat)
484 else:
484 else:
485 self._kernel_restarted_message(died=True)
485 self._kernel_restarted_message(died=True)
486 self.reset()
486 self.reset()
487
487
488 def _handle_kernel_restarted(self, died=True):
488 def _handle_kernel_restarted(self, died=True):
489 """Notice that the autorestarter restarted the kernel.
489 """Notice that the autorestarter restarted the kernel.
490
490
491 There's nothing to do but show a message.
491 There's nothing to do but show a message.
492 """
492 """
493 self.log.warn("kernel restarted")
493 self.log.warn("kernel restarted")
494 self._kernel_restarted_message(died=died)
494 self._kernel_restarted_message(died=died)
495 self.reset()
495 self.reset()
496
496
497 def _handle_inspect_reply(self, rep):
497 def _handle_inspect_reply(self, rep):
498 """Handle replies for call tips."""
498 """Handle replies for call tips."""
499 self.log.debug("oinfo: %s", rep.get('content', ''))
499 self.log.debug("oinfo: %s", rep.get('content', ''))
500 cursor = self._get_cursor()
500 cursor = self._get_cursor()
501 info = self._request_info.get('call_tip')
501 info = self._request_info.get('call_tip')
502 if info and info.id == rep['parent_header']['msg_id'] and \
502 if info and info.id == rep['parent_header']['msg_id'] and \
503 info.pos == cursor.position():
503 info.pos == cursor.position():
504 content = rep['content']
504 content = rep['content']
505 if content.get('status') == 'ok' and content.get('found', False):
505 if content.get('status') == 'ok' and content.get('found', False):
506 self._call_tip_widget.show_inspect_data(content)
506 self._call_tip_widget.show_inspect_data(content)
507
507
508 def _handle_execute_result(self, msg):
508 def _handle_execute_result(self, msg):
509 """ Handle display hook output.
509 """ Handle display hook output.
510 """
510 """
511 self.log.debug("execute_result: %s", msg.get('content', ''))
511 self.log.debug("execute_result: %s", msg.get('content', ''))
512 if self.include_output(msg):
512 if self.include_output(msg):
513 self.flush_clearoutput()
513 self.flush_clearoutput()
514 text = msg['content']['data']
514 text = msg['content']['data']
515 self._append_plain_text(text + '\n', before_prompt=True)
515 self._append_plain_text(text + '\n', before_prompt=True)
516
516
517 def _handle_stream(self, msg):
517 def _handle_stream(self, msg):
518 """ Handle stdout, stderr, and stdin.
518 """ Handle stdout, stderr, and stdin.
519 """
519 """
520 self.log.debug("stream: %s", msg.get('content', ''))
520 self.log.debug("stream: %s", msg.get('content', ''))
521 if self.include_output(msg):
521 if self.include_output(msg):
522 self.flush_clearoutput()
522 self.flush_clearoutput()
523 self.append_stream(msg['content']['text'])
523 self.append_stream(msg['content']['text'])
524
524
525 def _handle_shutdown_reply(self, msg):
525 def _handle_shutdown_reply(self, msg):
526 """ Handle shutdown signal, only if from other console.
526 """ Handle shutdown signal, only if from other console.
527 """
527 """
528 self.log.info("shutdown: %s", msg.get('content', ''))
528 self.log.info("shutdown: %s", msg.get('content', ''))
529 restart = msg.get('content', {}).get('restart', False)
529 restart = msg.get('content', {}).get('restart', False)
530 if not self._hidden and not self.from_here(msg):
530 if not self._hidden and not self.from_here(msg):
531 # got shutdown reply, request came from session other than ours
531 # got shutdown reply, request came from session other than ours
532 if restart:
532 if restart:
533 # someone restarted the kernel, handle it
533 # someone restarted the kernel, handle it
534 self._handle_kernel_restarted(died=False)
534 self._handle_kernel_restarted(died=False)
535 else:
535 else:
536 # kernel was shutdown permanently
536 # kernel was shutdown permanently
537 # this triggers exit_requested if the kernel was local,
537 # this triggers exit_requested if the kernel was local,
538 # and a dialog if the kernel was remote,
538 # and a dialog if the kernel was remote,
539 # so we don't suddenly clear the qtconsole without asking.
539 # so we don't suddenly clear the qtconsole without asking.
540 if self._local_kernel:
540 if self._local_kernel:
541 self.exit_requested.emit(self)
541 self.exit_requested.emit(self)
542 else:
542 else:
543 title = self.window().windowTitle()
543 title = self.window().windowTitle()
544 reply = QtGui.QMessageBox.question(self, title,
544 reply = QtGui.QMessageBox.question(self, title,
545 "Kernel has been shutdown permanently. "
545 "Kernel has been shutdown permanently. "
546 "Close the Console?",
546 "Close the Console?",
547 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
547 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
548 if reply == QtGui.QMessageBox.Yes:
548 if reply == QtGui.QMessageBox.Yes:
549 self.exit_requested.emit(self)
549 self.exit_requested.emit(self)
550
550
551 def _handle_status(self, msg):
551 def _handle_status(self, msg):
552 """Handle status message"""
552 """Handle status message"""
553 # This is where a busy/idle indicator would be triggered,
553 # This is where a busy/idle indicator would be triggered,
554 # when we make one.
554 # when we make one.
555 state = msg['content'].get('execution_state', '')
555 state = msg['content'].get('execution_state', '')
556 if state == 'starting':
556 if state == 'starting':
557 # kernel started while we were running
557 # kernel started while we were running
558 if self._executing:
558 if self._executing:
559 self._handle_kernel_restarted(died=True)
559 self._handle_kernel_restarted(died=True)
560 elif state == 'idle':
560 elif state == 'idle':
561 pass
561 pass
562 elif state == 'busy':
562 elif state == 'busy':
563 pass
563 pass
564
564
565 def _started_channels(self):
565 def _started_channels(self):
566 """ Called when the KernelManager channels have started listening or
566 """ Called when the KernelManager channels have started listening or
567 when the frontend is assigned an already listening KernelManager.
567 when the frontend is assigned an already listening KernelManager.
568 """
568 """
569 self.reset(clear=True)
569 self.reset(clear=True)
570
570
571 #---------------------------------------------------------------------------
571 #---------------------------------------------------------------------------
572 # 'FrontendWidget' public interface
572 # 'FrontendWidget' public interface
573 #---------------------------------------------------------------------------
573 #---------------------------------------------------------------------------
574
574
575 def copy_raw(self):
575 def copy_raw(self):
576 """ Copy the currently selected text to the clipboard without attempting
576 """ Copy the currently selected text to the clipboard without attempting
577 to remove prompts or otherwise alter the text.
577 to remove prompts or otherwise alter the text.
578 """
578 """
579 self._control.copy()
579 self._control.copy()
580
580
581 def execute_file(self, path, hidden=False):
581 def execute_file(self, path, hidden=False):
582 """ Attempts to execute file with 'path'. If 'hidden', no output is
582 """ Attempts to execute file with 'path'. If 'hidden', no output is
583 shown.
583 shown.
584 """
584 """
585 self.execute('execfile(%r)' % path, hidden=hidden)
585 self.execute('execfile(%r)' % path, hidden=hidden)
586
586
587 def interrupt_kernel(self):
587 def interrupt_kernel(self):
588 """ Attempts to interrupt the running kernel.
588 """ Attempts to interrupt the running kernel.
589
589
590 Also unsets _reading flag, to avoid runtime errors
590 Also unsets _reading flag, to avoid runtime errors
591 if raw_input is called again.
591 if raw_input is called again.
592 """
592 """
593 if self.custom_interrupt:
593 if self.custom_interrupt:
594 self._reading = False
594 self._reading = False
595 self.custom_interrupt_requested.emit()
595 self.custom_interrupt_requested.emit()
596 elif self.kernel_manager:
596 elif self.kernel_manager:
597 self._reading = False
597 self._reading = False
598 self.kernel_manager.interrupt_kernel()
598 self.kernel_manager.interrupt_kernel()
599 else:
599 else:
600 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
600 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
601
601
602 def reset(self, clear=False):
602 def reset(self, clear=False):
603 """ Resets the widget to its initial state if ``clear`` parameter
603 """ Resets the widget to its initial state if ``clear`` parameter
604 is True, otherwise
604 is True, otherwise
605 prints a visual indication of the fact that the kernel restarted, but
605 prints a visual indication of the fact that the kernel restarted, but
606 does not clear the traces from previous usage of the kernel before it
606 does not clear the traces from previous usage of the kernel before it
607 was restarted. With ``clear=True``, it is similar to ``%clear``, but
607 was restarted. With ``clear=True``, it is similar to ``%clear``, but
608 also re-writes the banner and aborts execution if necessary.
608 also re-writes the banner and aborts execution if necessary.
609 """
609 """
610 if self._executing:
610 if self._executing:
611 self._executing = False
611 self._executing = False
612 self._request_info['execute'] = {}
612 self._request_info['execute'] = {}
613 self._reading = False
613 self._reading = False
614 self._highlighter.highlighting_on = False
614 self._highlighter.highlighting_on = False
615
615
616 if clear:
616 if clear:
617 self._control.clear()
617 self._control.clear()
618 self._append_plain_text(self.banner)
618 if self._display_banner:
619 if self.kernel_banner:
619 self._append_plain_text(self.banner)
620 self._append_plain_text(self.kernel_banner)
620 if self.kernel_banner:
621
621 self._append_plain_text(self.kernel_banner)
622
622 # update output marker for stdout/stderr, so that startup
623 # update output marker for stdout/stderr, so that startup
623 # messages appear after banner:
624 # messages appear after banner:
624 self._append_before_prompt_pos = self._get_cursor().position()
625 self._append_before_prompt_pos = self._get_cursor().position()
625 self._show_interpreter_prompt()
626 self._show_interpreter_prompt()
626
627
627 def restart_kernel(self, message, now=False):
628 def restart_kernel(self, message, now=False):
628 """ Attempts to restart the running kernel.
629 """ Attempts to restart the running kernel.
629 """
630 """
630 # FIXME: now should be configurable via a checkbox in the dialog. Right
631 # FIXME: now should be configurable via a checkbox in the dialog. Right
631 # now at least the heartbeat path sets it to True and the manual restart
632 # now at least the heartbeat path sets it to True and the manual restart
632 # to False. But those should just be the pre-selected states of a
633 # to False. But those should just be the pre-selected states of a
633 # checkbox that the user could override if so desired. But I don't know
634 # checkbox that the user could override if so desired. But I don't know
634 # enough Qt to go implementing the checkbox now.
635 # enough Qt to go implementing the checkbox now.
635
636
636 if self.custom_restart:
637 if self.custom_restart:
637 self.custom_restart_requested.emit()
638 self.custom_restart_requested.emit()
638 return
639 return
639
640
640 if self.kernel_manager:
641 if self.kernel_manager:
641 # Pause the heart beat channel to prevent further warnings.
642 # Pause the heart beat channel to prevent further warnings.
642 self.kernel_client.hb_channel.pause()
643 self.kernel_client.hb_channel.pause()
643
644
644 # Prompt the user to restart the kernel. Un-pause the heartbeat if
645 # Prompt the user to restart the kernel. Un-pause the heartbeat if
645 # they decline. (If they accept, the heartbeat will be un-paused
646 # they decline. (If they accept, the heartbeat will be un-paused
646 # automatically when the kernel is restarted.)
647 # automatically when the kernel is restarted.)
647 if self.confirm_restart:
648 if self.confirm_restart:
648 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
649 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
649 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
650 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
650 message, buttons)
651 message, buttons)
651 do_restart = result == QtGui.QMessageBox.Yes
652 do_restart = result == QtGui.QMessageBox.Yes
652 else:
653 else:
653 # confirm_restart is False, so we don't need to ask user
654 # confirm_restart is False, so we don't need to ask user
654 # anything, just do the restart
655 # anything, just do the restart
655 do_restart = True
656 do_restart = True
656 if do_restart:
657 if do_restart:
657 try:
658 try:
658 self.kernel_manager.restart_kernel(now=now)
659 self.kernel_manager.restart_kernel(now=now)
659 except RuntimeError as e:
660 except RuntimeError as e:
660 self._append_plain_text(
661 self._append_plain_text(
661 'Error restarting kernel: %s\n' % e,
662 'Error restarting kernel: %s\n' % e,
662 before_prompt=True
663 before_prompt=True
663 )
664 )
664 else:
665 else:
665 self._append_html("<br>Restarting kernel...\n<hr><br>",
666 self._append_html("<br>Restarting kernel...\n<hr><br>",
666 before_prompt=True,
667 before_prompt=True,
667 )
668 )
668 else:
669 else:
669 self.kernel_client.hb_channel.unpause()
670 self.kernel_client.hb_channel.unpause()
670
671
671 else:
672 else:
672 self._append_plain_text(
673 self._append_plain_text(
673 'Cannot restart a Kernel I did not start\n',
674 'Cannot restart a Kernel I did not start\n',
674 before_prompt=True
675 before_prompt=True
675 )
676 )
676
677
677 def append_stream(self, text):
678 def append_stream(self, text):
678 """Appends text to the output stream."""
679 """Appends text to the output stream."""
679 # Most consoles treat tabs as being 8 space characters. Convert tabs
680 # Most consoles treat tabs as being 8 space characters. Convert tabs
680 # to spaces so that output looks as expected regardless of this
681 # to spaces so that output looks as expected regardless of this
681 # widget's tab width.
682 # widget's tab width.
682 text = text.expandtabs(8)
683 text = text.expandtabs(8)
683 self._append_plain_text(text, before_prompt=True)
684 self._append_plain_text(text, before_prompt=True)
684 self._control.moveCursor(QtGui.QTextCursor.End)
685 self._control.moveCursor(QtGui.QTextCursor.End)
685
686
686 def flush_clearoutput(self):
687 def flush_clearoutput(self):
687 """If a clearoutput is pending, execute it."""
688 """If a clearoutput is pending, execute it."""
688 if self._pending_clearoutput:
689 if self._pending_clearoutput:
689 self._pending_clearoutput = False
690 self._pending_clearoutput = False
690 self.clear_output()
691 self.clear_output()
691
692
692 def clear_output(self):
693 def clear_output(self):
693 """Clears the current line of output."""
694 """Clears the current line of output."""
694 cursor = self._control.textCursor()
695 cursor = self._control.textCursor()
695 cursor.beginEditBlock()
696 cursor.beginEditBlock()
696 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
697 cursor.movePosition(cursor.StartOfLine, cursor.KeepAnchor)
697 cursor.insertText('')
698 cursor.insertText('')
698 cursor.endEditBlock()
699 cursor.endEditBlock()
699
700
700 #---------------------------------------------------------------------------
701 #---------------------------------------------------------------------------
701 # 'FrontendWidget' protected interface
702 # 'FrontendWidget' protected interface
702 #---------------------------------------------------------------------------
703 #---------------------------------------------------------------------------
703
704
704 def _auto_call_tip(self):
705 def _auto_call_tip(self):
705 """Trigger call tip automatically on open parenthesis
706 """Trigger call tip automatically on open parenthesis
706
707
707 Call tips can be requested explcitly with `_call_tip`.
708 Call tips can be requested explcitly with `_call_tip`.
708 """
709 """
709 cursor = self._get_cursor()
710 cursor = self._get_cursor()
710 cursor.movePosition(QtGui.QTextCursor.Left)
711 cursor.movePosition(QtGui.QTextCursor.Left)
711 if cursor.document().characterAt(cursor.position()) == '(':
712 if cursor.document().characterAt(cursor.position()) == '(':
712 # trigger auto call tip on open paren
713 # trigger auto call tip on open paren
713 self._call_tip()
714 self._call_tip()
714
715
715 def _call_tip(self):
716 def _call_tip(self):
716 """Shows a call tip, if appropriate, at the current cursor location."""
717 """Shows a call tip, if appropriate, at the current cursor location."""
717 # Decide if it makes sense to show a call tip
718 # Decide if it makes sense to show a call tip
718 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
719 if not self.enable_calltips or not self.kernel_client.shell_channel.is_alive():
719 return False
720 return False
720 cursor_pos = self._get_input_buffer_cursor_pos()
721 cursor_pos = self._get_input_buffer_cursor_pos()
721 code = self.input_buffer
722 code = self.input_buffer
722 # Send the metadata request to the kernel
723 # Send the metadata request to the kernel
723 msg_id = self.kernel_client.inspect(code, cursor_pos)
724 msg_id = self.kernel_client.inspect(code, cursor_pos)
724 pos = self._get_cursor().position()
725 pos = self._get_cursor().position()
725 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
726 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
726 return True
727 return True
727
728
728 def _complete(self):
729 def _complete(self):
729 """ Performs completion at the current cursor location.
730 """ Performs completion at the current cursor location.
730 """
731 """
731 # Send the completion request to the kernel
732 # Send the completion request to the kernel
732 msg_id = self.kernel_client.complete(
733 msg_id = self.kernel_client.complete(
733 code=self.input_buffer,
734 code=self.input_buffer,
734 cursor_pos=self._get_input_buffer_cursor_pos(),
735 cursor_pos=self._get_input_buffer_cursor_pos(),
735 )
736 )
736 pos = self._get_cursor().position()
737 pos = self._get_cursor().position()
737 info = self._CompletionRequest(msg_id, pos)
738 info = self._CompletionRequest(msg_id, pos)
738 self._request_info['complete'] = info
739 self._request_info['complete'] = info
739
740
740 def _process_execute_abort(self, msg):
741 def _process_execute_abort(self, msg):
741 """ Process a reply for an aborted execution request.
742 """ Process a reply for an aborted execution request.
742 """
743 """
743 self._append_plain_text("ERROR: execution aborted\n")
744 self._append_plain_text("ERROR: execution aborted\n")
744
745
745 def _process_execute_error(self, msg):
746 def _process_execute_error(self, msg):
746 """ Process a reply for an execution request that resulted in an error.
747 """ Process a reply for an execution request that resulted in an error.
747 """
748 """
748 content = msg['content']
749 content = msg['content']
749 # If a SystemExit is passed along, this means exit() was called - also
750 # If a SystemExit is passed along, this means exit() was called - also
750 # all the ipython %exit magic syntax of '-k' to be used to keep
751 # all the ipython %exit magic syntax of '-k' to be used to keep
751 # the kernel running
752 # the kernel running
752 if content['ename']=='SystemExit':
753 if content['ename']=='SystemExit':
753 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
754 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
754 self._keep_kernel_on_exit = keepkernel
755 self._keep_kernel_on_exit = keepkernel
755 self.exit_requested.emit(self)
756 self.exit_requested.emit(self)
756 else:
757 else:
757 traceback = ''.join(content['traceback'])
758 traceback = ''.join(content['traceback'])
758 self._append_plain_text(traceback)
759 self._append_plain_text(traceback)
759
760
760 def _process_execute_ok(self, msg):
761 def _process_execute_ok(self, msg):
761 """ Process a reply for a successful execution request.
762 """ Process a reply for a successful execution request.
762 """
763 """
763 payload = msg['content']['payload']
764 payload = msg['content']['payload']
764 for item in payload:
765 for item in payload:
765 if not self._process_execute_payload(item):
766 if not self._process_execute_payload(item):
766 warning = 'Warning: received unknown payload of type %s'
767 warning = 'Warning: received unknown payload of type %s'
767 print(warning % repr(item['source']))
768 print(warning % repr(item['source']))
768
769
769 def _process_execute_payload(self, item):
770 def _process_execute_payload(self, item):
770 """ Process a single payload item from the list of payload items in an
771 """ Process a single payload item from the list of payload items in an
771 execution reply. Returns whether the payload was handled.
772 execution reply. Returns whether the payload was handled.
772 """
773 """
773 # The basic FrontendWidget doesn't handle payloads, as they are a
774 # The basic FrontendWidget doesn't handle payloads, as they are a
774 # mechanism for going beyond the standard Python interpreter model.
775 # mechanism for going beyond the standard Python interpreter model.
775 return False
776 return False
776
777
777 def _show_interpreter_prompt(self):
778 def _show_interpreter_prompt(self):
778 """ Shows a prompt for the interpreter.
779 """ Shows a prompt for the interpreter.
779 """
780 """
780 self._show_prompt('>>> ')
781 self._show_prompt('>>> ')
781
782
782 def _show_interpreter_prompt_for_reply(self, msg):
783 def _show_interpreter_prompt_for_reply(self, msg):
783 """ Shows a prompt for the interpreter given an 'execute_reply' message.
784 """ Shows a prompt for the interpreter given an 'execute_reply' message.
784 """
785 """
785 self._show_interpreter_prompt()
786 self._show_interpreter_prompt()
786
787
787 #------ Signal handlers ----------------------------------------------------
788 #------ Signal handlers ----------------------------------------------------
788
789
789 def _document_contents_change(self, position, removed, added):
790 def _document_contents_change(self, position, removed, added):
790 """ Called whenever the document's content changes. Display a call tip
791 """ Called whenever the document's content changes. Display a call tip
791 if appropriate.
792 if appropriate.
792 """
793 """
793 # Calculate where the cursor should be *after* the change:
794 # Calculate where the cursor should be *after* the change:
794 position += added
795 position += added
795
796
796 document = self._control.document()
797 document = self._control.document()
797 if position == self._get_cursor().position():
798 if position == self._get_cursor().position():
798 self._auto_call_tip()
799 self._auto_call_tip()
799
800
800 #------ Trait default initializers -----------------------------------------
801 #------ Trait default initializers -----------------------------------------
801
802
802 def _banner_default(self):
803 def _banner_default(self):
803 """ Returns the standard Python banner.
804 """ Returns the standard Python banner.
804 """
805 """
805 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
806 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
806 '"license" for more information.'
807 '"license" for more information.'
807 return banner % (sys.version, sys.platform)
808 return banner % (sys.version, sys.platform)
@@ -1,373 +1,384 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
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 # stdlib imports
14 # stdlib imports
15 import os
15 import os
16 import signal
16 import signal
17 import sys
17 import sys
18
18
19 # If run on Windows, install an exception hook which pops up a
19 # If run on Windows, install an exception hook which pops up a
20 # message box. Pythonw.exe hides the console, so without this
20 # message box. Pythonw.exe hides the console, so without this
21 # the application silently fails to load.
21 # the application silently fails to load.
22 #
22 #
23 # We always install this handler, because the expectation is for
23 # We always install this handler, because the expectation is for
24 # qtconsole to bring up a GUI even if called from the console.
24 # qtconsole to bring up a GUI even if called from the console.
25 # The old handler is called, so the exception is printed as well.
25 # The old handler is called, so the exception is printed as well.
26 # If desired, check for pythonw with an additional condition
26 # If desired, check for pythonw with an additional condition
27 # (sys.executable.lower().find('pythonw.exe') >= 0).
27 # (sys.executable.lower().find('pythonw.exe') >= 0).
28 if os.name == 'nt':
28 if os.name == 'nt':
29 old_excepthook = sys.excepthook
29 old_excepthook = sys.excepthook
30
30
31 # Exclude this from our autogenerated API docs.
31 # Exclude this from our autogenerated API docs.
32 undoc = lambda func: func
32 undoc = lambda func: func
33
33
34 @undoc
34 @undoc
35 def gui_excepthook(exctype, value, tb):
35 def gui_excepthook(exctype, value, tb):
36 try:
36 try:
37 import ctypes, traceback
37 import ctypes, traceback
38 MB_ICONERROR = 0x00000010
38 MB_ICONERROR = 0x00000010
39 title = u'Error starting IPython QtConsole'
39 title = u'Error starting IPython QtConsole'
40 msg = u''.join(traceback.format_exception(exctype, value, tb))
40 msg = u''.join(traceback.format_exception(exctype, value, tb))
41 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
41 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
42 finally:
42 finally:
43 # Also call the old exception hook to let it do
43 # Also call the old exception hook to let it do
44 # its thing too.
44 # its thing too.
45 old_excepthook(exctype, value, tb)
45 old_excepthook(exctype, value, tb)
46
46
47 sys.excepthook = gui_excepthook
47 sys.excepthook = gui_excepthook
48
48
49 # System library imports
49 # System library imports
50 from IPython.external.qt import QtCore, QtGui
50 from IPython.external.qt import QtCore, QtGui
51
51
52 # Local imports
52 # Local imports
53 from IPython.config.application import boolean_flag
53 from IPython.config.application import catch_config_error
54 from IPython.config.application import catch_config_error
54 from IPython.core.application import BaseIPythonApplication
55 from IPython.core.application import BaseIPythonApplication
55 from IPython.qt.console.ipython_widget import IPythonWidget
56 from IPython.qt.console.ipython_widget import IPythonWidget
56 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
57 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
57 from IPython.qt.console import styles
58 from IPython.qt.console import styles
58 from IPython.qt.console.mainwindow import MainWindow
59 from IPython.qt.console.mainwindow import MainWindow
59 from IPython.qt.client import QtKernelClient
60 from IPython.qt.client import QtKernelClient
60 from IPython.qt.manager import QtKernelManager
61 from IPython.qt.manager import QtKernelManager
61 from IPython.utils.traitlets import (
62 from IPython.utils.traitlets import (
62 Dict, Unicode, CBool, Any
63 Dict, Unicode, CBool, Any
63 )
64 )
64
65
65 from IPython.consoleapp import (
66 from IPython.consoleapp import (
66 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
67 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
67 )
68 )
68
69
69 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
70 # Network Constants
71 # Network Constants
71 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
72
73
73 from IPython.utils.localinterfaces import is_local_ip
74 from IPython.utils.localinterfaces import is_local_ip
74
75
75 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
76 # Globals
77 # Globals
77 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
78
79
79 _examples = """
80 _examples = """
80 ipython qtconsole # start the qtconsole
81 ipython qtconsole # start the qtconsole
81 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
82 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
82 """
83 """
83
84
84 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
85 # Aliases and Flags
86 # Aliases and Flags
86 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
87
88
88 # start with copy of flags
89 # start with copy of flags
89 flags = dict(flags)
90 flags = dict(flags)
90 qt_flags = {
91 qt_flags = {
91 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
92 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
92 "Disable rich text support."),
93 "Disable rich text support."),
93 }
94 }
95 qt_flags.update(boolean_flag(
96 'banner', 'IPythonQtConsoleApp.display_banner',
97 "Display a banner upon starting the QtConsole.",
98 "Don't display a banner upon starting the QtConsole."
99 ))
94
100
95 # and app_flags from the Console Mixin
101 # and app_flags from the Console Mixin
96 qt_flags.update(app_flags)
102 qt_flags.update(app_flags)
97 # add frontend flags to the full set
103 # add frontend flags to the full set
98 flags.update(qt_flags)
104 flags.update(qt_flags)
99
105
100 # start with copy of front&backend aliases list
106 # start with copy of front&backend aliases list
101 aliases = dict(aliases)
107 aliases = dict(aliases)
102 qt_aliases = dict(
108 qt_aliases = dict(
103 style = 'IPythonWidget.syntax_style',
109 style = 'IPythonWidget.syntax_style',
104 stylesheet = 'IPythonQtConsoleApp.stylesheet',
110 stylesheet = 'IPythonQtConsoleApp.stylesheet',
105 colors = 'ZMQInteractiveShell.colors',
111 colors = 'ZMQInteractiveShell.colors',
106
112
107 editor = 'IPythonWidget.editor',
113 editor = 'IPythonWidget.editor',
108 paging = 'ConsoleWidget.paging',
114 paging = 'ConsoleWidget.paging',
109 )
115 )
110 # and app_aliases from the Console Mixin
116 # and app_aliases from the Console Mixin
111 qt_aliases.update(app_aliases)
117 qt_aliases.update(app_aliases)
112 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
118 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
113 # add frontend aliases to the full set
119 # add frontend aliases to the full set
114 aliases.update(qt_aliases)
120 aliases.update(qt_aliases)
115
121
116 # get flags&aliases into sets, and remove a couple that
122 # get flags&aliases into sets, and remove a couple that
117 # shouldn't be scrubbed from backend flags:
123 # shouldn't be scrubbed from backend flags:
118 qt_aliases = set(qt_aliases.keys())
124 qt_aliases = set(qt_aliases.keys())
119 qt_aliases.remove('colors')
125 qt_aliases.remove('colors')
120 qt_flags = set(qt_flags.keys())
126 qt_flags = set(qt_flags.keys())
121
127
122 #-----------------------------------------------------------------------------
128 #-----------------------------------------------------------------------------
123 # Classes
129 # Classes
124 #-----------------------------------------------------------------------------
130 #-----------------------------------------------------------------------------
125
131
126 #-----------------------------------------------------------------------------
132 #-----------------------------------------------------------------------------
127 # IPythonQtConsole
133 # IPythonQtConsole
128 #-----------------------------------------------------------------------------
134 #-----------------------------------------------------------------------------
129
135
130
136
131 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
137 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
132 name = 'ipython-qtconsole'
138 name = 'ipython-qtconsole'
133
139
134 description = """
140 description = """
135 The IPython QtConsole.
141 The IPython QtConsole.
136
142
137 This launches a Console-style application using Qt. It is not a full
143 This launches a Console-style application using Qt. It is not a full
138 console, in that launched terminal subprocesses will not be able to accept
144 console, in that launched terminal subprocesses will not be able to accept
139 input.
145 input.
140
146
141 The QtConsole supports various extra features beyond the Terminal IPython
147 The QtConsole supports various extra features beyond the Terminal IPython
142 shell, such as inline plotting with matplotlib, via:
148 shell, such as inline plotting with matplotlib, via:
143
149
144 ipython qtconsole --matplotlib=inline
150 ipython qtconsole --matplotlib=inline
145
151
146 as well as saving your session as HTML, and printing the output.
152 as well as saving your session as HTML, and printing the output.
147
153
148 """
154 """
149 examples = _examples
155 examples = _examples
150
156
151 classes = [IPythonWidget] + IPythonConsoleApp.classes
157 classes = [IPythonWidget] + IPythonConsoleApp.classes
152 flags = Dict(flags)
158 flags = Dict(flags)
153 aliases = Dict(aliases)
159 aliases = Dict(aliases)
154 frontend_flags = Any(qt_flags)
160 frontend_flags = Any(qt_flags)
155 frontend_aliases = Any(qt_aliases)
161 frontend_aliases = Any(qt_aliases)
156 kernel_client_class = QtKernelClient
162 kernel_client_class = QtKernelClient
157 kernel_manager_class = QtKernelManager
163 kernel_manager_class = QtKernelManager
158
164
159 stylesheet = Unicode('', config=True,
165 stylesheet = Unicode('', config=True,
160 help="path to a custom CSS stylesheet")
166 help="path to a custom CSS stylesheet")
161
167
162 hide_menubar = CBool(False, config=True,
168 hide_menubar = CBool(False, config=True,
163 help="Start the console window with the menu bar hidden.")
169 help="Start the console window with the menu bar hidden.")
164
170
165 maximize = CBool(False, config=True,
171 maximize = CBool(False, config=True,
166 help="Start the console window maximized.")
172 help="Start the console window maximized.")
167
173
168 plain = CBool(False, config=True,
174 plain = CBool(False, config=True,
169 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).")
170
176
177 display_banner = CBool(True, config=True,
178 help="Whether to display a banner upon starting the QtConsole."
179 )
180
171 def _plain_changed(self, name, old, new):
181 def _plain_changed(self, name, old, new):
172 kind = 'plain' if new else 'rich'
182 kind = 'plain' if new else 'rich'
173 self.config.ConsoleWidget.kind = kind
183 self.config.ConsoleWidget.kind = kind
174 if new:
184 if new:
175 self.widget_factory = IPythonWidget
185 self.widget_factory = IPythonWidget
176 else:
186 else:
177 self.widget_factory = RichIPythonWidget
187 self.widget_factory = RichIPythonWidget
178
188
179 # the factory for creating a widget
189 # the factory for creating a widget
180 widget_factory = Any(RichIPythonWidget)
190 widget_factory = Any(RichIPythonWidget)
181
191
182 def parse_command_line(self, argv=None):
192 def parse_command_line(self, argv=None):
183 super(IPythonQtConsoleApp, self).parse_command_line(argv)
193 super(IPythonQtConsoleApp, self).parse_command_line(argv)
184 self.build_kernel_argv(argv)
194 self.build_kernel_argv(argv)
185
195
186
196
187 def new_frontend_master(self):
197 def new_frontend_master(self):
188 """ Create and return new frontend attached to new kernel, launched on localhost.
198 """ Create and return new frontend attached to new kernel, launched on localhost.
189 """
199 """
190 kernel_manager = self.kernel_manager_class(
200 kernel_manager = self.kernel_manager_class(
191 connection_file=self._new_connection_file(),
201 connection_file=self._new_connection_file(),
192 parent=self,
202 parent=self,
193 autorestart=True,
203 autorestart=True,
194 )
204 )
195 # start the kernel
205 # start the kernel
196 kwargs = {}
206 kwargs = {}
197 # FIXME: remove special treatment of IPython kernels
207 # FIXME: remove special treatment of IPython kernels
198 if self.kernel_manager.ipython_kernel:
208 if self.kernel_manager.ipython_kernel:
199 kwargs['extra_arguments'] = self.kernel_argv
209 kwargs['extra_arguments'] = self.kernel_argv
200 kernel_manager.start_kernel(**kwargs)
210 kernel_manager.start_kernel(**kwargs)
201 kernel_manager.client_factory = self.kernel_client_class
211 kernel_manager.client_factory = self.kernel_client_class
202 kernel_client = kernel_manager.client()
212 kernel_client = kernel_manager.client()
203 kernel_client.start_channels(shell=True, iopub=True)
213 kernel_client.start_channels(shell=True, iopub=True)
204 widget = self.widget_factory(config=self.config,
214 widget = self.widget_factory(config=self.config,
205 local_kernel=True)
215 local_kernel=True)
206 self.init_colors(widget)
216 self.init_colors(widget)
207 widget.kernel_manager = kernel_manager
217 widget.kernel_manager = kernel_manager
208 widget.kernel_client = kernel_client
218 widget.kernel_client = kernel_client
209 widget._existing = False
219 widget._existing = False
210 widget._may_close = True
220 widget._may_close = True
211 widget._confirm_exit = self.confirm_exit
221 widget._confirm_exit = self.confirm_exit
212 return widget
222 return widget
213
223
214 def new_frontend_slave(self, current_widget):
224 def new_frontend_slave(self, current_widget):
215 """Create and return a new frontend attached to an existing kernel.
225 """Create and return a new frontend attached to an existing kernel.
216
226
217 Parameters
227 Parameters
218 ----------
228 ----------
219 current_widget : IPythonWidget
229 current_widget : IPythonWidget
220 The IPythonWidget whose kernel this frontend is to share
230 The IPythonWidget whose kernel this frontend is to share
221 """
231 """
222 kernel_client = self.kernel_client_class(
232 kernel_client = self.kernel_client_class(
223 connection_file=current_widget.kernel_client.connection_file,
233 connection_file=current_widget.kernel_client.connection_file,
224 config = self.config,
234 config = self.config,
225 )
235 )
226 kernel_client.load_connection_file()
236 kernel_client.load_connection_file()
227 kernel_client.start_channels()
237 kernel_client.start_channels()
228 widget = self.widget_factory(config=self.config,
238 widget = self.widget_factory(config=self.config,
229 local_kernel=False)
239 local_kernel=False)
230 self.init_colors(widget)
240 self.init_colors(widget)
231 widget._existing = True
241 widget._existing = True
232 widget._may_close = False
242 widget._may_close = False
233 widget._confirm_exit = False
243 widget._confirm_exit = False
234 widget.kernel_client = kernel_client
244 widget.kernel_client = kernel_client
235 widget.kernel_manager = current_widget.kernel_manager
245 widget.kernel_manager = current_widget.kernel_manager
236 return widget
246 return widget
237
247
238 def init_qt_app(self):
248 def init_qt_app(self):
239 # separate from qt_elements, because it must run first
249 # separate from qt_elements, because it must run first
240 self.app = QtGui.QApplication([])
250 self.app = QtGui.QApplication([])
241
251
242 def init_qt_elements(self):
252 def init_qt_elements(self):
243 # Create the widget.
253 # Create the widget.
244
254
245 base_path = os.path.abspath(os.path.dirname(__file__))
255 base_path = os.path.abspath(os.path.dirname(__file__))
246 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
256 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
247 self.app.icon = QtGui.QIcon(icon_path)
257 self.app.icon = QtGui.QIcon(icon_path)
248 QtGui.QApplication.setWindowIcon(self.app.icon)
258 QtGui.QApplication.setWindowIcon(self.app.icon)
249
259
250 ip = self.ip
260 ip = self.ip
251 local_kernel = (not self.existing) or is_local_ip(ip)
261 local_kernel = (not self.existing) or is_local_ip(ip)
252 self.widget = self.widget_factory(config=self.config,
262 self.widget = self.widget_factory(config=self.config,
253 local_kernel=local_kernel)
263 local_kernel=local_kernel)
254 self.init_colors(self.widget)
264 self.init_colors(self.widget)
255 self.widget._existing = self.existing
265 self.widget._existing = self.existing
256 self.widget._may_close = not self.existing
266 self.widget._may_close = not self.existing
257 self.widget._confirm_exit = self.confirm_exit
267 self.widget._confirm_exit = self.confirm_exit
268 self.widget._display_banner = self.display_banner
258
269
259 self.widget.kernel_manager = self.kernel_manager
270 self.widget.kernel_manager = self.kernel_manager
260 self.widget.kernel_client = self.kernel_client
271 self.widget.kernel_client = self.kernel_client
261 self.window = MainWindow(self.app,
272 self.window = MainWindow(self.app,
262 confirm_exit=self.confirm_exit,
273 confirm_exit=self.confirm_exit,
263 new_frontend_factory=self.new_frontend_master,
274 new_frontend_factory=self.new_frontend_master,
264 slave_frontend_factory=self.new_frontend_slave,
275 slave_frontend_factory=self.new_frontend_slave,
265 )
276 )
266 self.window.log = self.log
277 self.window.log = self.log
267 self.window.add_tab_with_frontend(self.widget)
278 self.window.add_tab_with_frontend(self.widget)
268 self.window.init_magic_helper()
279 self.window.init_magic_helper()
269 self.window.init_menu_bar()
280 self.window.init_menu_bar()
270
281
271 # Ignore on OSX, where there is always a menu bar
282 # Ignore on OSX, where there is always a menu bar
272 if sys.platform != 'darwin' and self.hide_menubar:
283 if sys.platform != 'darwin' and self.hide_menubar:
273 self.window.menuBar().setVisible(False)
284 self.window.menuBar().setVisible(False)
274
285
275 self.window.setWindowTitle('IPython')
286 self.window.setWindowTitle('IPython')
276
287
277 def init_colors(self, widget):
288 def init_colors(self, widget):
278 """Configure the coloring of the widget"""
289 """Configure the coloring of the widget"""
279 # Note: This will be dramatically simplified when colors
290 # Note: This will be dramatically simplified when colors
280 # are removed from the backend.
291 # are removed from the backend.
281
292
282 # parse the colors arg down to current known labels
293 # parse the colors arg down to current known labels
283 cfg = self.config
294 cfg = self.config
284 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
295 colors = cfg.ZMQInteractiveShell.colors if 'ZMQInteractiveShell.colors' in cfg else None
285 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
296 style = cfg.IPythonWidget.syntax_style if 'IPythonWidget.syntax_style' in cfg else None
286 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
297 sheet = cfg.IPythonWidget.style_sheet if 'IPythonWidget.style_sheet' in cfg else None
287
298
288 # find the value for colors:
299 # find the value for colors:
289 if colors:
300 if colors:
290 colors=colors.lower()
301 colors=colors.lower()
291 if colors in ('lightbg', 'light'):
302 if colors in ('lightbg', 'light'):
292 colors='lightbg'
303 colors='lightbg'
293 elif colors in ('dark', 'linux'):
304 elif colors in ('dark', 'linux'):
294 colors='linux'
305 colors='linux'
295 else:
306 else:
296 colors='nocolor'
307 colors='nocolor'
297 elif style:
308 elif style:
298 if style=='bw':
309 if style=='bw':
299 colors='nocolor'
310 colors='nocolor'
300 elif styles.dark_style(style):
311 elif styles.dark_style(style):
301 colors='linux'
312 colors='linux'
302 else:
313 else:
303 colors='lightbg'
314 colors='lightbg'
304 else:
315 else:
305 colors=None
316 colors=None
306
317
307 # Configure the style
318 # Configure the style
308 if style:
319 if style:
309 widget.style_sheet = styles.sheet_from_template(style, colors)
320 widget.style_sheet = styles.sheet_from_template(style, colors)
310 widget.syntax_style = style
321 widget.syntax_style = style
311 widget._syntax_style_changed()
322 widget._syntax_style_changed()
312 widget._style_sheet_changed()
323 widget._style_sheet_changed()
313 elif colors:
324 elif colors:
314 # use a default dark/light/bw style
325 # use a default dark/light/bw style
315 widget.set_default_style(colors=colors)
326 widget.set_default_style(colors=colors)
316
327
317 if self.stylesheet:
328 if self.stylesheet:
318 # we got an explicit stylesheet
329 # we got an explicit stylesheet
319 if os.path.isfile(self.stylesheet):
330 if os.path.isfile(self.stylesheet):
320 with open(self.stylesheet) as f:
331 with open(self.stylesheet) as f:
321 sheet = f.read()
332 sheet = f.read()
322 else:
333 else:
323 raise IOError("Stylesheet %r not found." % self.stylesheet)
334 raise IOError("Stylesheet %r not found." % self.stylesheet)
324 if sheet:
335 if sheet:
325 widget.style_sheet = sheet
336 widget.style_sheet = sheet
326 widget._style_sheet_changed()
337 widget._style_sheet_changed()
327
338
328
339
329 def init_signal(self):
340 def init_signal(self):
330 """allow clean shutdown on sigint"""
341 """allow clean shutdown on sigint"""
331 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
342 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
332 # need a timer, so that QApplication doesn't block until a real
343 # need a timer, so that QApplication doesn't block until a real
333 # Qt event fires (can require mouse movement)
344 # Qt event fires (can require mouse movement)
334 # timer trick from http://stackoverflow.com/q/4938723/938949
345 # timer trick from http://stackoverflow.com/q/4938723/938949
335 timer = QtCore.QTimer()
346 timer = QtCore.QTimer()
336 # Let the interpreter run each 200 ms:
347 # Let the interpreter run each 200 ms:
337 timer.timeout.connect(lambda: None)
348 timer.timeout.connect(lambda: None)
338 timer.start(200)
349 timer.start(200)
339 # hold onto ref, so the timer doesn't get cleaned up
350 # hold onto ref, so the timer doesn't get cleaned up
340 self._sigint_timer = timer
351 self._sigint_timer = timer
341
352
342 @catch_config_error
353 @catch_config_error
343 def initialize(self, argv=None):
354 def initialize(self, argv=None):
344 self.init_qt_app()
355 self.init_qt_app()
345 super(IPythonQtConsoleApp, self).initialize(argv)
356 super(IPythonQtConsoleApp, self).initialize(argv)
346 IPythonConsoleApp.initialize(self,argv)
357 IPythonConsoleApp.initialize(self,argv)
347 self.init_qt_elements()
358 self.init_qt_elements()
348 self.init_signal()
359 self.init_signal()
349
360
350 def start(self):
361 def start(self):
351
362
352 # draw the window
363 # draw the window
353 if self.maximize:
364 if self.maximize:
354 self.window.showMaximized()
365 self.window.showMaximized()
355 else:
366 else:
356 self.window.show()
367 self.window.show()
357 self.window.raise_()
368 self.window.raise_()
358
369
359 # Start the application main loop.
370 # Start the application main loop.
360 self.app.exec_()
371 self.app.exec_()
361
372
362 #-----------------------------------------------------------------------------
373 #-----------------------------------------------------------------------------
363 # Main entry point
374 # Main entry point
364 #-----------------------------------------------------------------------------
375 #-----------------------------------------------------------------------------
365
376
366 def main():
377 def main():
367 app = IPythonQtConsoleApp()
378 app = IPythonQtConsoleApp()
368 app.initialize()
379 app.initialize()
369 app.start()
380 app.start()
370
381
371
382
372 if __name__ == '__main__':
383 if __name__ == '__main__':
373 main()
384 main()
General Comments 0
You need to be logged in to leave comments. Login now