##// END OF EJS Templates
Remove unused imports in IPython.qt
Thomas Kluyver -
Show More
@@ -1,375 +1,375 b''
1 1 """ Utilities for processing ANSI escape codes and special ASCII characters.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Imports
5 5 #-----------------------------------------------------------------------------
6 6
7 7 # Standard library imports
8 8 from collections import namedtuple
9 9 import re
10 10
11 11 # System library imports
12 from IPython.external.qt import QtCore, QtGui
12 from IPython.external.qt import QtGui
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Constants and datatypes
16 16 #-----------------------------------------------------------------------------
17 17
18 18 # An action for erase requests (ED and EL commands).
19 19 EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to'])
20 20
21 21 # An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP,
22 22 # and HVP commands).
23 23 # FIXME: Not implemented in AnsiCodeProcessor.
24 24 MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count'])
25 25
26 26 # An action for scroll requests (SU and ST) and form feeds.
27 27 ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])
28 28
29 29 # An action for the carriage return character
30 30 CarriageReturnAction = namedtuple('CarriageReturnAction', ['action'])
31 31
32 32 # An action for the \n character
33 33 NewLineAction = namedtuple('NewLineAction', ['action'])
34 34
35 35 # An action for the beep character
36 36 BeepAction = namedtuple('BeepAction', ['action'])
37 37
38 38 # An action for backspace
39 39 BackSpaceAction = namedtuple('BackSpaceAction', ['action'])
40 40
41 41 # Regular expressions.
42 42 CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu'
43 43 CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS
44 44 OSC_SUBPATTERN = '\](.*?)[\x07\x1b]'
45 45 ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \
46 46 (CSI_SUBPATTERN, OSC_SUBPATTERN))
47 47 ANSI_OR_SPECIAL_PATTERN = re.compile('(\a|\b|\r(?!\n)|\r?\n)|(?:%s)' % ANSI_PATTERN)
48 48 SPECIAL_PATTERN = re.compile('([\f])')
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Classes
52 52 #-----------------------------------------------------------------------------
53 53
54 54 class AnsiCodeProcessor(object):
55 55 """ Translates special ASCII characters and ANSI escape codes into readable
56 56 attributes. It also supports a few non-standard, xterm-specific codes.
57 57 """
58 58
59 59 # Whether to increase intensity or set boldness for SGR code 1.
60 60 # (Different terminals handle this in different ways.)
61 61 bold_text_enabled = False
62 62
63 63 # We provide an empty default color map because subclasses will likely want
64 64 # to use a custom color format.
65 65 default_color_map = {}
66 66
67 67 #---------------------------------------------------------------------------
68 68 # AnsiCodeProcessor interface
69 69 #---------------------------------------------------------------------------
70 70
71 71 def __init__(self):
72 72 self.actions = []
73 73 self.color_map = self.default_color_map.copy()
74 74 self.reset_sgr()
75 75
76 76 def reset_sgr(self):
77 77 """ Reset graphics attributs to their default values.
78 78 """
79 79 self.intensity = 0
80 80 self.italic = False
81 81 self.bold = False
82 82 self.underline = False
83 83 self.foreground_color = None
84 84 self.background_color = None
85 85
86 86 def split_string(self, string):
87 87 """ Yields substrings for which the same escape code applies.
88 88 """
89 89 self.actions = []
90 90 start = 0
91 91
92 92 # strings ending with \r are assumed to be ending in \r\n since
93 93 # \n is appended to output strings automatically. Accounting
94 94 # for that, here.
95 95 last_char = '\n' if len(string) > 0 and string[-1] == '\n' else None
96 96 string = string[:-1] if last_char is not None else string
97 97
98 98 for match in ANSI_OR_SPECIAL_PATTERN.finditer(string):
99 99 raw = string[start:match.start()]
100 100 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
101 101 if substring or self.actions:
102 102 yield substring
103 103 self.actions = []
104 104 start = match.end()
105 105
106 106 groups = filter(lambda x: x is not None, match.groups())
107 107 g0 = groups[0]
108 108 if g0 == '\a':
109 109 self.actions.append(BeepAction('beep'))
110 110 yield None
111 111 self.actions = []
112 112 elif g0 == '\r':
113 113 self.actions.append(CarriageReturnAction('carriage-return'))
114 114 yield None
115 115 self.actions = []
116 116 elif g0 == '\b':
117 117 self.actions.append(BackSpaceAction('backspace'))
118 118 yield None
119 119 self.actions = []
120 120 elif g0 == '\n' or g0 == '\r\n':
121 121 self.actions.append(NewLineAction('newline'))
122 122 yield g0
123 123 self.actions = []
124 124 else:
125 125 params = [ param for param in groups[1].split(';') if param ]
126 126 if g0.startswith('['):
127 127 # Case 1: CSI code.
128 128 try:
129 129 params = map(int, params)
130 130 except ValueError:
131 131 # Silently discard badly formed codes.
132 132 pass
133 133 else:
134 134 self.set_csi_code(groups[2], params)
135 135
136 136 elif g0.startswith(']'):
137 137 # Case 2: OSC code.
138 138 self.set_osc_code(params)
139 139
140 140 raw = string[start:]
141 141 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
142 142 if substring or self.actions:
143 143 yield substring
144 144
145 145 if last_char is not None:
146 146 self.actions.append(NewLineAction('newline'))
147 147 yield last_char
148 148
149 149 def set_csi_code(self, command, params=[]):
150 150 """ Set attributes based on CSI (Control Sequence Introducer) code.
151 151
152 152 Parameters
153 153 ----------
154 154 command : str
155 155 The code identifier, i.e. the final character in the sequence.
156 156
157 157 params : sequence of integers, optional
158 158 The parameter codes for the command.
159 159 """
160 160 if command == 'm': # SGR - Select Graphic Rendition
161 161 if params:
162 162 self.set_sgr_code(params)
163 163 else:
164 164 self.set_sgr_code([0])
165 165
166 166 elif (command == 'J' or # ED - Erase Data
167 167 command == 'K'): # EL - Erase in Line
168 168 code = params[0] if params else 0
169 169 if 0 <= code <= 2:
170 170 area = 'screen' if command == 'J' else 'line'
171 171 if code == 0:
172 172 erase_to = 'end'
173 173 elif code == 1:
174 174 erase_to = 'start'
175 175 elif code == 2:
176 176 erase_to = 'all'
177 177 self.actions.append(EraseAction('erase', area, erase_to))
178 178
179 179 elif (command == 'S' or # SU - Scroll Up
180 180 command == 'T'): # SD - Scroll Down
181 181 dir = 'up' if command == 'S' else 'down'
182 182 count = params[0] if params else 1
183 183 self.actions.append(ScrollAction('scroll', dir, 'line', count))
184 184
185 185 def set_osc_code(self, params):
186 186 """ Set attributes based on OSC (Operating System Command) parameters.
187 187
188 188 Parameters
189 189 ----------
190 190 params : sequence of str
191 191 The parameters for the command.
192 192 """
193 193 try:
194 194 command = int(params.pop(0))
195 195 except (IndexError, ValueError):
196 196 return
197 197
198 198 if command == 4:
199 199 # xterm-specific: set color number to color spec.
200 200 try:
201 201 color = int(params.pop(0))
202 202 spec = params.pop(0)
203 203 self.color_map[color] = self._parse_xterm_color_spec(spec)
204 204 except (IndexError, ValueError):
205 205 pass
206 206
207 207 def set_sgr_code(self, params):
208 208 """ Set attributes based on SGR (Select Graphic Rendition) codes.
209 209
210 210 Parameters
211 211 ----------
212 212 params : sequence of ints
213 213 A list of SGR codes for one or more SGR commands. Usually this
214 214 sequence will have one element per command, although certain
215 215 xterm-specific commands requires multiple elements.
216 216 """
217 217 # Always consume the first parameter.
218 218 if not params:
219 219 return
220 220 code = params.pop(0)
221 221
222 222 if code == 0:
223 223 self.reset_sgr()
224 224 elif code == 1:
225 225 if self.bold_text_enabled:
226 226 self.bold = True
227 227 else:
228 228 self.intensity = 1
229 229 elif code == 2:
230 230 self.intensity = 0
231 231 elif code == 3:
232 232 self.italic = True
233 233 elif code == 4:
234 234 self.underline = True
235 235 elif code == 22:
236 236 self.intensity = 0
237 237 self.bold = False
238 238 elif code == 23:
239 239 self.italic = False
240 240 elif code == 24:
241 241 self.underline = False
242 242 elif code >= 30 and code <= 37:
243 243 self.foreground_color = code - 30
244 244 elif code == 38 and params and params.pop(0) == 5:
245 245 # xterm-specific: 256 color support.
246 246 if params:
247 247 self.foreground_color = params.pop(0)
248 248 elif code == 39:
249 249 self.foreground_color = None
250 250 elif code >= 40 and code <= 47:
251 251 self.background_color = code - 40
252 252 elif code == 48 and params and params.pop(0) == 5:
253 253 # xterm-specific: 256 color support.
254 254 if params:
255 255 self.background_color = params.pop(0)
256 256 elif code == 49:
257 257 self.background_color = None
258 258
259 259 # Recurse with unconsumed parameters.
260 260 self.set_sgr_code(params)
261 261
262 262 #---------------------------------------------------------------------------
263 263 # Protected interface
264 264 #---------------------------------------------------------------------------
265 265
266 266 def _parse_xterm_color_spec(self, spec):
267 267 if spec.startswith('rgb:'):
268 268 return tuple(map(lambda x: int(x, 16), spec[4:].split('/')))
269 269 elif spec.startswith('rgbi:'):
270 270 return tuple(map(lambda x: int(float(x) * 255),
271 271 spec[5:].split('/')))
272 272 elif spec == '?':
273 273 raise ValueError('Unsupported xterm color spec')
274 274 return spec
275 275
276 276 def _replace_special(self, match):
277 277 special = match.group(1)
278 278 if special == '\f':
279 279 self.actions.append(ScrollAction('scroll', 'down', 'page', 1))
280 280 return ''
281 281
282 282
283 283 class QtAnsiCodeProcessor(AnsiCodeProcessor):
284 284 """ Translates ANSI escape codes into QTextCharFormats.
285 285 """
286 286
287 287 # A map from ANSI color codes to SVG color names or RGB(A) tuples.
288 288 darkbg_color_map = {
289 289 0 : 'black', # black
290 290 1 : 'darkred', # red
291 291 2 : 'darkgreen', # green
292 292 3 : 'brown', # yellow
293 293 4 : 'darkblue', # blue
294 294 5 : 'darkviolet', # magenta
295 295 6 : 'steelblue', # cyan
296 296 7 : 'grey', # white
297 297 8 : 'grey', # black (bright)
298 298 9 : 'red', # red (bright)
299 299 10 : 'lime', # green (bright)
300 300 11 : 'yellow', # yellow (bright)
301 301 12 : 'deepskyblue', # blue (bright)
302 302 13 : 'magenta', # magenta (bright)
303 303 14 : 'cyan', # cyan (bright)
304 304 15 : 'white' } # white (bright)
305 305
306 306 # Set the default color map for super class.
307 307 default_color_map = darkbg_color_map.copy()
308 308
309 309 def get_color(self, color, intensity=0):
310 310 """ Returns a QColor for a given color code, or None if one cannot be
311 311 constructed.
312 312 """
313 313 if color is None:
314 314 return None
315 315
316 316 # Adjust for intensity, if possible.
317 317 if color < 8 and intensity > 0:
318 318 color += 8
319 319
320 320 constructor = self.color_map.get(color, None)
321 321 if isinstance(constructor, basestring):
322 322 # If this is an X11 color name, we just hope there is a close SVG
323 323 # color name. We could use QColor's static method
324 324 # 'setAllowX11ColorNames()', but this is global and only available
325 325 # on X11. It seems cleaner to aim for uniformity of behavior.
326 326 return QtGui.QColor(constructor)
327 327
328 328 elif isinstance(constructor, (tuple, list)):
329 329 return QtGui.QColor(*constructor)
330 330
331 331 return None
332 332
333 333 def get_format(self):
334 334 """ Returns a QTextCharFormat that encodes the current style attributes.
335 335 """
336 336 format = QtGui.QTextCharFormat()
337 337
338 338 # Set foreground color
339 339 qcolor = self.get_color(self.foreground_color, self.intensity)
340 340 if qcolor is not None:
341 341 format.setForeground(qcolor)
342 342
343 343 # Set background color
344 344 qcolor = self.get_color(self.background_color, self.intensity)
345 345 if qcolor is not None:
346 346 format.setBackground(qcolor)
347 347
348 348 # Set font weight/style options
349 349 if self.bold:
350 350 format.setFontWeight(QtGui.QFont.Bold)
351 351 else:
352 352 format.setFontWeight(QtGui.QFont.Normal)
353 353 format.setFontItalic(self.italic)
354 354 format.setFontUnderline(self.underline)
355 355
356 356 return format
357 357
358 358 def set_background_color(self, color):
359 359 """ Given a background color (a QColor), attempt to set a color map
360 360 that will be aesthetically pleasing.
361 361 """
362 362 # Set a new default color map.
363 363 self.default_color_map = self.darkbg_color_map.copy()
364 364
365 365 if color.value() >= 127:
366 366 # Colors appropriate for a terminal with a light background. For
367 367 # now, only use non-bright colors...
368 368 for i in xrange(8):
369 369 self.default_color_map[i + 8] = self.default_color_map[i]
370 370
371 371 # ...and replace white with black.
372 372 self.default_color_map[7] = self.default_color_map[15] = 'black'
373 373
374 374 # Update the current color map with the new defaults.
375 375 self.color_map.update(self.default_color_map)
@@ -1,785 +1,784 b''
1 1 from __future__ import print_function
2 2
3 3 # Standard library imports
4 4 from collections import namedtuple
5 5 import sys
6 import time
7 6 import uuid
8 7
9 8 # System library imports
10 9 from pygments.lexers import PythonLexer
11 10 from IPython.external import qt
12 11 from IPython.external.qt import QtCore, QtGui
13 12
14 13 # Local imports
15 14 from IPython.core.inputsplitter import InputSplitter, IPythonInputSplitter
16 15 from IPython.core.inputtransformer import classic_prompt
17 16 from IPython.core.oinspect import call_tip
18 17 from IPython.qt.base_frontend_mixin import BaseFrontendMixin
19 18 from IPython.utils.traitlets import Bool, Instance, Unicode
20 19 from bracket_matcher import BracketMatcher
21 20 from call_tip_widget import CallTipWidget
22 21 from completion_lexer import CompletionLexer
23 22 from history_console_widget import HistoryConsoleWidget
24 23 from pygments_highlighter import PygmentsHighlighter
25 24
26 25
27 26 class FrontendHighlighter(PygmentsHighlighter):
28 27 """ A PygmentsHighlighter that understands and ignores prompts.
29 28 """
30 29
31 30 def __init__(self, frontend):
32 31 super(FrontendHighlighter, self).__init__(frontend._control.document())
33 32 self._current_offset = 0
34 33 self._frontend = frontend
35 34 self.highlighting_on = False
36 35
37 36 def highlightBlock(self, string):
38 37 """ Highlight a block of text. Reimplemented to highlight selectively.
39 38 """
40 39 if not self.highlighting_on:
41 40 return
42 41
43 42 # The input to this function is a unicode string that may contain
44 43 # paragraph break characters, non-breaking spaces, etc. Here we acquire
45 44 # the string as plain text so we can compare it.
46 45 current_block = self.currentBlock()
47 46 string = self._frontend._get_block_plain_text(current_block)
48 47
49 48 # Decide whether to check for the regular or continuation prompt.
50 49 if current_block.contains(self._frontend._prompt_pos):
51 50 prompt = self._frontend._prompt
52 51 else:
53 52 prompt = self._frontend._continuation_prompt
54 53
55 54 # Only highlight if we can identify a prompt, but make sure not to
56 55 # highlight the prompt.
57 56 if string.startswith(prompt):
58 57 self._current_offset = len(prompt)
59 58 string = string[len(prompt):]
60 59 super(FrontendHighlighter, self).highlightBlock(string)
61 60
62 61 def rehighlightBlock(self, block):
63 62 """ Reimplemented to temporarily enable highlighting if disabled.
64 63 """
65 64 old = self.highlighting_on
66 65 self.highlighting_on = True
67 66 super(FrontendHighlighter, self).rehighlightBlock(block)
68 67 self.highlighting_on = old
69 68
70 69 def setFormat(self, start, count, format):
71 70 """ Reimplemented to highlight selectively.
72 71 """
73 72 start += self._current_offset
74 73 super(FrontendHighlighter, self).setFormat(start, count, format)
75 74
76 75
77 76 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
78 77 """ A Qt frontend for a generic Python kernel.
79 78 """
80 79
81 80 # The text to show when the kernel is (re)started.
82 81 banner = Unicode()
83 82
84 83 # An option and corresponding signal for overriding the default kernel
85 84 # interrupt behavior.
86 85 custom_interrupt = Bool(False)
87 86 custom_interrupt_requested = QtCore.Signal()
88 87
89 88 # An option and corresponding signals for overriding the default kernel
90 89 # restart behavior.
91 90 custom_restart = Bool(False)
92 91 custom_restart_kernel_died = QtCore.Signal(float)
93 92 custom_restart_requested = QtCore.Signal()
94 93
95 94 # Whether to automatically show calltips on open-parentheses.
96 95 enable_calltips = Bool(True, config=True,
97 96 help="Whether to draw information calltips on open-parentheses.")
98 97
99 98 clear_on_kernel_restart = Bool(True, config=True,
100 99 help="Whether to clear the console when the kernel is restarted")
101 100
102 101 confirm_restart = Bool(True, config=True,
103 102 help="Whether to ask for user confirmation when restarting kernel")
104 103
105 104 # Emitted when a user visible 'execute_request' has been submitted to the
106 105 # kernel from the FrontendWidget. Contains the code to be executed.
107 106 executing = QtCore.Signal(object)
108 107
109 108 # Emitted when a user-visible 'execute_reply' has been received from the
110 109 # kernel and processed by the FrontendWidget. Contains the response message.
111 110 executed = QtCore.Signal(object)
112 111
113 112 # Emitted when an exit request has been received from the kernel.
114 113 exit_requested = QtCore.Signal(object)
115 114
116 115 # Protected class variables.
117 116 _prompt_transformer = IPythonInputSplitter(physical_line_transforms=[classic_prompt()],
118 117 logical_line_transforms=[],
119 118 python_line_transforms=[],
120 119 )
121 120 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
122 121 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
123 122 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
124 123 _input_splitter_class = InputSplitter
125 124 _local_kernel = False
126 125 _highlighter = Instance(FrontendHighlighter)
127 126
128 127 #---------------------------------------------------------------------------
129 128 # 'object' interface
130 129 #---------------------------------------------------------------------------
131 130
132 131 def __init__(self, *args, **kw):
133 132 super(FrontendWidget, self).__init__(*args, **kw)
134 133 # FIXME: remove this when PySide min version is updated past 1.0.7
135 134 # forcefully disable calltips if PySide is < 1.0.7, because they crash
136 135 if qt.QT_API == qt.QT_API_PYSIDE:
137 136 import PySide
138 137 if PySide.__version_info__ < (1,0,7):
139 138 self.log.warn("PySide %s < 1.0.7 detected, disabling calltips" % PySide.__version__)
140 139 self.enable_calltips = False
141 140
142 141 # FrontendWidget protected variables.
143 142 self._bracket_matcher = BracketMatcher(self._control)
144 143 self._call_tip_widget = CallTipWidget(self._control)
145 144 self._completion_lexer = CompletionLexer(PythonLexer())
146 145 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
147 146 self._hidden = False
148 147 self._highlighter = FrontendHighlighter(self)
149 148 self._input_splitter = self._input_splitter_class()
150 149 self._kernel_manager = None
151 150 self._kernel_client = None
152 151 self._request_info = {}
153 152 self._request_info['execute'] = {};
154 153 self._callback_dict = {}
155 154
156 155 # Configure the ConsoleWidget.
157 156 self.tab_width = 4
158 157 self._set_continuation_prompt('... ')
159 158
160 159 # Configure the CallTipWidget.
161 160 self._call_tip_widget.setFont(self.font)
162 161 self.font_changed.connect(self._call_tip_widget.setFont)
163 162
164 163 # Configure actions.
165 164 action = self._copy_raw_action
166 165 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
167 166 action.setEnabled(False)
168 167 action.setShortcut(QtGui.QKeySequence(key))
169 168 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
170 169 action.triggered.connect(self.copy_raw)
171 170 self.copy_available.connect(action.setEnabled)
172 171 self.addAction(action)
173 172
174 173 # Connect signal handlers.
175 174 document = self._control.document()
176 175 document.contentsChange.connect(self._document_contents_change)
177 176
178 177 # Set flag for whether we are connected via localhost.
179 178 self._local_kernel = kw.get('local_kernel',
180 179 FrontendWidget._local_kernel)
181 180
182 181 #---------------------------------------------------------------------------
183 182 # 'ConsoleWidget' public interface
184 183 #---------------------------------------------------------------------------
185 184
186 185 def copy(self):
187 186 """ Copy the currently selected text to the clipboard, removing prompts.
188 187 """
189 188 if self._page_control is not None and self._page_control.hasFocus():
190 189 self._page_control.copy()
191 190 elif self._control.hasFocus():
192 191 text = self._control.textCursor().selection().toPlainText()
193 192 if text:
194 193 text = self._prompt_transformer.transform_cell(text)
195 194 QtGui.QApplication.clipboard().setText(text)
196 195 else:
197 196 self.log.debug("frontend widget : unknown copy target")
198 197
199 198 #---------------------------------------------------------------------------
200 199 # 'ConsoleWidget' abstract interface
201 200 #---------------------------------------------------------------------------
202 201
203 202 def _is_complete(self, source, interactive):
204 203 """ Returns whether 'source' can be completely processed and a new
205 204 prompt created. When triggered by an Enter/Return key press,
206 205 'interactive' is True; otherwise, it is False.
207 206 """
208 207 self._input_splitter.reset()
209 208 complete = self._input_splitter.push(source)
210 209 if interactive:
211 210 complete = not self._input_splitter.push_accepts_more()
212 211 return complete
213 212
214 213 def _execute(self, source, hidden):
215 214 """ Execute 'source'. If 'hidden', do not show any output.
216 215
217 216 See parent class :meth:`execute` docstring for full details.
218 217 """
219 218 msg_id = self.kernel_client.execute(source, hidden)
220 219 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'user')
221 220 self._hidden = hidden
222 221 if not hidden:
223 222 self.executing.emit(source)
224 223
225 224 def _prompt_started_hook(self):
226 225 """ Called immediately after a new prompt is displayed.
227 226 """
228 227 if not self._reading:
229 228 self._highlighter.highlighting_on = True
230 229
231 230 def _prompt_finished_hook(self):
232 231 """ Called immediately after a prompt is finished, i.e. when some input
233 232 will be processed and a new prompt displayed.
234 233 """
235 234 # Flush all state from the input splitter so the next round of
236 235 # reading input starts with a clean buffer.
237 236 self._input_splitter.reset()
238 237
239 238 if not self._reading:
240 239 self._highlighter.highlighting_on = False
241 240
242 241 def _tab_pressed(self):
243 242 """ Called when the tab key is pressed. Returns whether to continue
244 243 processing the event.
245 244 """
246 245 # Perform tab completion if:
247 246 # 1) The cursor is in the input buffer.
248 247 # 2) There is a non-whitespace character before the cursor.
249 248 text = self._get_input_buffer_cursor_line()
250 249 if text is None:
251 250 return False
252 251 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
253 252 if complete:
254 253 self._complete()
255 254 return not complete
256 255
257 256 #---------------------------------------------------------------------------
258 257 # 'ConsoleWidget' protected interface
259 258 #---------------------------------------------------------------------------
260 259
261 260 def _context_menu_make(self, pos):
262 261 """ Reimplemented to add an action for raw copy.
263 262 """
264 263 menu = super(FrontendWidget, self)._context_menu_make(pos)
265 264 for before_action in menu.actions():
266 265 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
267 266 QtGui.QKeySequence.ExactMatch:
268 267 menu.insertAction(before_action, self._copy_raw_action)
269 268 break
270 269 return menu
271 270
272 271 def request_interrupt_kernel(self):
273 272 if self._executing:
274 273 self.interrupt_kernel()
275 274
276 275 def request_restart_kernel(self):
277 276 message = 'Are you sure you want to restart the kernel?'
278 277 self.restart_kernel(message, now=False)
279 278
280 279 def _event_filter_console_keypress(self, event):
281 280 """ Reimplemented for execution interruption and smart backspace.
282 281 """
283 282 key = event.key()
284 283 if self._control_key_down(event.modifiers(), include_command=False):
285 284
286 285 if key == QtCore.Qt.Key_C and self._executing:
287 286 self.request_interrupt_kernel()
288 287 return True
289 288
290 289 elif key == QtCore.Qt.Key_Period:
291 290 self.request_restart_kernel()
292 291 return True
293 292
294 293 elif not event.modifiers() & QtCore.Qt.AltModifier:
295 294
296 295 # Smart backspace: remove four characters in one backspace if:
297 296 # 1) everything left of the cursor is whitespace
298 297 # 2) the four characters immediately left of the cursor are spaces
299 298 if key == QtCore.Qt.Key_Backspace:
300 299 col = self._get_input_buffer_cursor_column()
301 300 cursor = self._control.textCursor()
302 301 if col > 3 and not cursor.hasSelection():
303 302 text = self._get_input_buffer_cursor_line()[:col]
304 303 if text.endswith(' ') and not text.strip():
305 304 cursor.movePosition(QtGui.QTextCursor.Left,
306 305 QtGui.QTextCursor.KeepAnchor, 4)
307 306 cursor.removeSelectedText()
308 307 return True
309 308
310 309 return super(FrontendWidget, self)._event_filter_console_keypress(event)
311 310
312 311 def _insert_continuation_prompt(self, cursor):
313 312 """ Reimplemented for auto-indentation.
314 313 """
315 314 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
316 315 cursor.insertText(' ' * self._input_splitter.indent_spaces)
317 316
318 317 #---------------------------------------------------------------------------
319 318 # 'BaseFrontendMixin' abstract interface
320 319 #---------------------------------------------------------------------------
321 320
322 321 def _handle_complete_reply(self, rep):
323 322 """ Handle replies for tab completion.
324 323 """
325 324 self.log.debug("complete: %s", rep.get('content', ''))
326 325 cursor = self._get_cursor()
327 326 info = self._request_info.get('complete')
328 327 if info and info.id == rep['parent_header']['msg_id'] and \
329 328 info.pos == cursor.position():
330 329 text = '.'.join(self._get_context())
331 330 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
332 331 self._complete_with_items(cursor, rep['content']['matches'])
333 332
334 333 def _silent_exec_callback(self, expr, callback):
335 334 """Silently execute `expr` in the kernel and call `callback` with reply
336 335
337 336 the `expr` is evaluated silently in the kernel (without) output in
338 337 the frontend. Call `callback` with the
339 338 `repr <http://docs.python.org/library/functions.html#repr> `_ as first argument
340 339
341 340 Parameters
342 341 ----------
343 342 expr : string
344 343 valid string to be executed by the kernel.
345 344 callback : function
346 345 function accepting one argument, as a string. The string will be
347 346 the `repr` of the result of evaluating `expr`
348 347
349 348 The `callback` is called with the `repr()` of the result of `expr` as
350 349 first argument. To get the object, do `eval()` on the passed value.
351 350
352 351 See Also
353 352 --------
354 353 _handle_exec_callback : private method, deal with calling callback with reply
355 354
356 355 """
357 356
358 357 # generate uuid, which would be used as an indication of whether or
359 358 # not the unique request originated from here (can use msg id ?)
360 359 local_uuid = str(uuid.uuid1())
361 360 msg_id = self.kernel_client.execute('',
362 361 silent=True, user_expressions={ local_uuid:expr })
363 362 self._callback_dict[local_uuid] = callback
364 363 self._request_info['execute'][msg_id] = self._ExecutionRequest(msg_id, 'silent_exec_callback')
365 364
366 365 def _handle_exec_callback(self, msg):
367 366 """Execute `callback` corresponding to `msg` reply, after ``_silent_exec_callback``
368 367
369 368 Parameters
370 369 ----------
371 370 msg : raw message send by the kernel containing an `user_expressions`
372 371 and having a 'silent_exec_callback' kind.
373 372
374 373 Notes
375 374 -----
376 375 This function will look for a `callback` associated with the
377 376 corresponding message id. Association has been made by
378 377 `_silent_exec_callback`. `callback` is then called with the `repr()`
379 378 of the value of corresponding `user_expressions` as argument.
380 379 `callback` is then removed from the known list so that any message
381 380 coming again with the same id won't trigger it.
382 381
383 382 """
384 383
385 384 user_exp = msg['content'].get('user_expressions')
386 385 if not user_exp:
387 386 return
388 387 for expression in user_exp:
389 388 if expression in self._callback_dict:
390 389 self._callback_dict.pop(expression)(user_exp[expression])
391 390
392 391 def _handle_execute_reply(self, msg):
393 392 """ Handles replies for code execution.
394 393 """
395 394 self.log.debug("execute: %s", msg.get('content', ''))
396 395 msg_id = msg['parent_header']['msg_id']
397 396 info = self._request_info['execute'].get(msg_id)
398 397 # unset reading flag, because if execute finished, raw_input can't
399 398 # still be pending.
400 399 self._reading = False
401 400 if info and info.kind == 'user' and not self._hidden:
402 401 # Make sure that all output from the SUB channel has been processed
403 402 # before writing a new prompt.
404 403 self.kernel_client.iopub_channel.flush()
405 404
406 405 # Reset the ANSI style information to prevent bad text in stdout
407 406 # from messing up our colors. We're not a true terminal so we're
408 407 # allowed to do this.
409 408 if self.ansi_codes:
410 409 self._ansi_processor.reset_sgr()
411 410
412 411 content = msg['content']
413 412 status = content['status']
414 413 if status == 'ok':
415 414 self._process_execute_ok(msg)
416 415 elif status == 'error':
417 416 self._process_execute_error(msg)
418 417 elif status == 'aborted':
419 418 self._process_execute_abort(msg)
420 419
421 420 self._show_interpreter_prompt_for_reply(msg)
422 421 self.executed.emit(msg)
423 422 self._request_info['execute'].pop(msg_id)
424 423 elif info and info.kind == 'silent_exec_callback' and not self._hidden:
425 424 self._handle_exec_callback(msg)
426 425 self._request_info['execute'].pop(msg_id)
427 426 else:
428 427 super(FrontendWidget, self)._handle_execute_reply(msg)
429 428
430 429 def _handle_input_request(self, msg):
431 430 """ Handle requests for raw_input.
432 431 """
433 432 self.log.debug("input: %s", msg.get('content', ''))
434 433 if self._hidden:
435 434 raise RuntimeError('Request for raw input during hidden execution.')
436 435
437 436 # Make sure that all output from the SUB channel has been processed
438 437 # before entering readline mode.
439 438 self.kernel_client.iopub_channel.flush()
440 439
441 440 def callback(line):
442 441 self.kernel_client.stdin_channel.input(line)
443 442 if self._reading:
444 443 self.log.debug("Got second input request, assuming first was interrupted.")
445 444 self._reading = False
446 445 self._readline(msg['content']['prompt'], callback=callback)
447 446
448 447 def _kernel_restarted_message(self, died=True):
449 448 msg = "Kernel died, restarting" if died else "Kernel restarting"
450 449 self._append_html("<br>%s<hr><br>" % msg,
451 450 before_prompt=False
452 451 )
453 452
454 453 def _handle_kernel_died(self, since_last_heartbeat):
455 454 """Handle the kernel's death (if we do not own the kernel).
456 455 """
457 456 self.log.warn("kernel died: %s", since_last_heartbeat)
458 457 if self.custom_restart:
459 458 self.custom_restart_kernel_died.emit(since_last_heartbeat)
460 459 else:
461 460 self._kernel_restarted_message(died=True)
462 461 self.reset()
463 462
464 463 def _handle_kernel_restarted(self, died=True):
465 464 """Notice that the autorestarter restarted the kernel.
466 465
467 466 There's nothing to do but show a message.
468 467 """
469 468 self.log.warn("kernel restarted")
470 469 self._kernel_restarted_message(died=died)
471 470 self.reset()
472 471
473 472 def _handle_object_info_reply(self, rep):
474 473 """ Handle replies for call tips.
475 474 """
476 475 self.log.debug("oinfo: %s", rep.get('content', ''))
477 476 cursor = self._get_cursor()
478 477 info = self._request_info.get('call_tip')
479 478 if info and info.id == rep['parent_header']['msg_id'] and \
480 479 info.pos == cursor.position():
481 480 # Get the information for a call tip. For now we format the call
482 481 # line as string, later we can pass False to format_call and
483 482 # syntax-highlight it ourselves for nicer formatting in the
484 483 # calltip.
485 484 content = rep['content']
486 485 # if this is from pykernel, 'docstring' will be the only key
487 486 if content.get('ismagic', False):
488 487 # Don't generate a call-tip for magics. Ideally, we should
489 488 # generate a tooltip, but not on ( like we do for actual
490 489 # callables.
491 490 call_info, doc = None, None
492 491 else:
493 492 call_info, doc = call_tip(content, format_call=True)
494 493 if call_info or doc:
495 494 self._call_tip_widget.show_call_info(call_info, doc)
496 495
497 496 def _handle_pyout(self, msg):
498 497 """ Handle display hook output.
499 498 """
500 499 self.log.debug("pyout: %s", msg.get('content', ''))
501 500 if not self._hidden and self._is_from_this_session(msg):
502 501 text = msg['content']['data']
503 502 self._append_plain_text(text + '\n', before_prompt=True)
504 503
505 504 def _handle_stream(self, msg):
506 505 """ Handle stdout, stderr, and stdin.
507 506 """
508 507 self.log.debug("stream: %s", msg.get('content', ''))
509 508 if not self._hidden and self._is_from_this_session(msg):
510 509 # Most consoles treat tabs as being 8 space characters. Convert tabs
511 510 # to spaces so that output looks as expected regardless of this
512 511 # widget's tab width.
513 512 text = msg['content']['data'].expandtabs(8)
514 513
515 514 self._append_plain_text(text, before_prompt=True)
516 515 self._control.moveCursor(QtGui.QTextCursor.End)
517 516
518 517 def _handle_shutdown_reply(self, msg):
519 518 """ Handle shutdown signal, only if from other console.
520 519 """
521 520 self.log.warn("shutdown: %s", msg.get('content', ''))
522 521 restart = msg.get('content', {}).get('restart', False)
523 522 if not self._hidden and not self._is_from_this_session(msg):
524 523 # got shutdown reply, request came from session other than ours
525 524 if restart:
526 525 # someone restarted the kernel, handle it
527 526 self._handle_kernel_restarted(died=False)
528 527 else:
529 528 # kernel was shutdown permanently
530 529 # this triggers exit_requested if the kernel was local,
531 530 # and a dialog if the kernel was remote,
532 531 # so we don't suddenly clear the qtconsole without asking.
533 532 if self._local_kernel:
534 533 self.exit_requested.emit(self)
535 534 else:
536 535 title = self.window().windowTitle()
537 536 reply = QtGui.QMessageBox.question(self, title,
538 537 "Kernel has been shutdown permanently. "
539 538 "Close the Console?",
540 539 QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
541 540 if reply == QtGui.QMessageBox.Yes:
542 541 self.exit_requested.emit(self)
543 542
544 543 def _handle_status(self, msg):
545 544 """Handle status message"""
546 545 # This is where a busy/idle indicator would be triggered,
547 546 # when we make one.
548 547 state = msg['content'].get('execution_state', '')
549 548 if state == 'starting':
550 549 # kernel started while we were running
551 550 if self._executing:
552 551 self._handle_kernel_restarted(died=True)
553 552 elif state == 'idle':
554 553 pass
555 554 elif state == 'busy':
556 555 pass
557 556
558 557 def _started_channels(self):
559 558 """ Called when the KernelManager channels have started listening or
560 559 when the frontend is assigned an already listening KernelManager.
561 560 """
562 561 self.reset(clear=True)
563 562
564 563 #---------------------------------------------------------------------------
565 564 # 'FrontendWidget' public interface
566 565 #---------------------------------------------------------------------------
567 566
568 567 def copy_raw(self):
569 568 """ Copy the currently selected text to the clipboard without attempting
570 569 to remove prompts or otherwise alter the text.
571 570 """
572 571 self._control.copy()
573 572
574 573 def execute_file(self, path, hidden=False):
575 574 """ Attempts to execute file with 'path'. If 'hidden', no output is
576 575 shown.
577 576 """
578 577 self.execute('execfile(%r)' % path, hidden=hidden)
579 578
580 579 def interrupt_kernel(self):
581 580 """ Attempts to interrupt the running kernel.
582 581
583 582 Also unsets _reading flag, to avoid runtime errors
584 583 if raw_input is called again.
585 584 """
586 585 if self.custom_interrupt:
587 586 self._reading = False
588 587 self.custom_interrupt_requested.emit()
589 588 elif self.kernel_manager:
590 589 self._reading = False
591 590 self.kernel_manager.interrupt_kernel()
592 591 else:
593 592 self._append_plain_text('Cannot interrupt a kernel I did not start.\n')
594 593
595 594 def reset(self, clear=False):
596 595 """ Resets the widget to its initial state if ``clear`` parameter
597 596 is True, otherwise
598 597 prints a visual indication of the fact that the kernel restarted, but
599 598 does not clear the traces from previous usage of the kernel before it
600 599 was restarted. With ``clear=True``, it is similar to ``%clear``, but
601 600 also re-writes the banner and aborts execution if necessary.
602 601 """
603 602 if self._executing:
604 603 self._executing = False
605 604 self._request_info['execute'] = {}
606 605 self._reading = False
607 606 self._highlighter.highlighting_on = False
608 607
609 608 if clear:
610 609 self._control.clear()
611 610 self._append_plain_text(self.banner)
612 611 # update output marker for stdout/stderr, so that startup
613 612 # messages appear after banner:
614 613 self._append_before_prompt_pos = self._get_cursor().position()
615 614 self._show_interpreter_prompt()
616 615
617 616 def restart_kernel(self, message, now=False):
618 617 """ Attempts to restart the running kernel.
619 618 """
620 619 # FIXME: now should be configurable via a checkbox in the dialog. Right
621 620 # now at least the heartbeat path sets it to True and the manual restart
622 621 # to False. But those should just be the pre-selected states of a
623 622 # checkbox that the user could override if so desired. But I don't know
624 623 # enough Qt to go implementing the checkbox now.
625 624
626 625 if self.custom_restart:
627 626 self.custom_restart_requested.emit()
628 627 return
629 628
630 629 if self.kernel_manager:
631 630 # Pause the heart beat channel to prevent further warnings.
632 631 self.kernel_client.hb_channel.pause()
633 632
634 633 # Prompt the user to restart the kernel. Un-pause the heartbeat if
635 634 # they decline. (If they accept, the heartbeat will be un-paused
636 635 # automatically when the kernel is restarted.)
637 636 if self.confirm_restart:
638 637 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
639 638 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
640 639 message, buttons)
641 640 do_restart = result == QtGui.QMessageBox.Yes
642 641 else:
643 642 # confirm_restart is False, so we don't need to ask user
644 643 # anything, just do the restart
645 644 do_restart = True
646 645 if do_restart:
647 646 try:
648 647 self.kernel_manager.restart_kernel(now=now)
649 648 except RuntimeError as e:
650 649 self._append_plain_text(
651 650 'Error restarting kernel: %s\n' % e,
652 651 before_prompt=True
653 652 )
654 653 else:
655 654 self._append_html("<br>Restarting kernel...\n<hr><br>",
656 655 before_prompt=True,
657 656 )
658 657 else:
659 658 self.kernel_client.hb_channel.unpause()
660 659
661 660 else:
662 661 self._append_plain_text(
663 662 'Cannot restart a Kernel I did not start\n',
664 663 before_prompt=True
665 664 )
666 665
667 666 #---------------------------------------------------------------------------
668 667 # 'FrontendWidget' protected interface
669 668 #---------------------------------------------------------------------------
670 669
671 670 def _call_tip(self):
672 671 """ Shows a call tip, if appropriate, at the current cursor location.
673 672 """
674 673 # Decide if it makes sense to show a call tip
675 674 if not self.enable_calltips:
676 675 return False
677 676 cursor = self._get_cursor()
678 677 cursor.movePosition(QtGui.QTextCursor.Left)
679 678 if cursor.document().characterAt(cursor.position()) != '(':
680 679 return False
681 680 context = self._get_context(cursor)
682 681 if not context:
683 682 return False
684 683
685 684 # Send the metadata request to the kernel
686 685 name = '.'.join(context)
687 686 msg_id = self.kernel_client.object_info(name)
688 687 pos = self._get_cursor().position()
689 688 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
690 689 return True
691 690
692 691 def _complete(self):
693 692 """ Performs completion at the current cursor location.
694 693 """
695 694 context = self._get_context()
696 695 if context:
697 696 # Send the completion request to the kernel
698 697 msg_id = self.kernel_client.complete(
699 698 '.'.join(context), # text
700 699 self._get_input_buffer_cursor_line(), # line
701 700 self._get_input_buffer_cursor_column(), # cursor_pos
702 701 self.input_buffer) # block
703 702 pos = self._get_cursor().position()
704 703 info = self._CompletionRequest(msg_id, pos)
705 704 self._request_info['complete'] = info
706 705
707 706 def _get_context(self, cursor=None):
708 707 """ Gets the context for the specified cursor (or the current cursor
709 708 if none is specified).
710 709 """
711 710 if cursor is None:
712 711 cursor = self._get_cursor()
713 712 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
714 713 QtGui.QTextCursor.KeepAnchor)
715 714 text = cursor.selection().toPlainText()
716 715 return self._completion_lexer.get_context(text)
717 716
718 717 def _process_execute_abort(self, msg):
719 718 """ Process a reply for an aborted execution request.
720 719 """
721 720 self._append_plain_text("ERROR: execution aborted\n")
722 721
723 722 def _process_execute_error(self, msg):
724 723 """ Process a reply for an execution request that resulted in an error.
725 724 """
726 725 content = msg['content']
727 726 # If a SystemExit is passed along, this means exit() was called - also
728 727 # all the ipython %exit magic syntax of '-k' to be used to keep
729 728 # the kernel running
730 729 if content['ename']=='SystemExit':
731 730 keepkernel = content['evalue']=='-k' or content['evalue']=='True'
732 731 self._keep_kernel_on_exit = keepkernel
733 732 self.exit_requested.emit(self)
734 733 else:
735 734 traceback = ''.join(content['traceback'])
736 735 self._append_plain_text(traceback)
737 736
738 737 def _process_execute_ok(self, msg):
739 738 """ Process a reply for a successful execution request.
740 739 """
741 740 payload = msg['content']['payload']
742 741 for item in payload:
743 742 if not self._process_execute_payload(item):
744 743 warning = 'Warning: received unknown payload of type %s'
745 744 print(warning % repr(item['source']))
746 745
747 746 def _process_execute_payload(self, item):
748 747 """ Process a single payload item from the list of payload items in an
749 748 execution reply. Returns whether the payload was handled.
750 749 """
751 750 # The basic FrontendWidget doesn't handle payloads, as they are a
752 751 # mechanism for going beyond the standard Python interpreter model.
753 752 return False
754 753
755 754 def _show_interpreter_prompt(self):
756 755 """ Shows a prompt for the interpreter.
757 756 """
758 757 self._show_prompt('>>> ')
759 758
760 759 def _show_interpreter_prompt_for_reply(self, msg):
761 760 """ Shows a prompt for the interpreter given an 'execute_reply' message.
762 761 """
763 762 self._show_interpreter_prompt()
764 763
765 764 #------ Signal handlers ----------------------------------------------------
766 765
767 766 def _document_contents_change(self, position, removed, added):
768 767 """ Called whenever the document's content changes. Display a call tip
769 768 if appropriate.
770 769 """
771 770 # Calculate where the cursor should be *after* the change:
772 771 position += added
773 772
774 773 document = self._control.document()
775 774 if position == self._get_cursor().position():
776 775 self._call_tip()
777 776
778 777 #------ Trait default initializers -----------------------------------------
779 778
780 779 def _banner_default(self):
781 780 """ Returns the standard Python banner.
782 781 """
783 782 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
784 783 '"license" for more information.'
785 784 return banner % (sys.version, sys.platform)
@@ -1,388 +1,385 b''
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import os
24 24 import signal
25 25 import sys
26 26
27 27 # If run on Windows, install an exception hook which pops up a
28 28 # message box. Pythonw.exe hides the console, so without this
29 29 # the application silently fails to load.
30 30 #
31 31 # We always install this handler, because the expectation is for
32 32 # qtconsole to bring up a GUI even if called from the console.
33 33 # The old handler is called, so the exception is printed as well.
34 34 # If desired, check for pythonw with an additional condition
35 35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 36 if os.name == 'nt':
37 37 old_excepthook = sys.excepthook
38 38
39 39 def gui_excepthook(exctype, value, tb):
40 40 try:
41 41 import ctypes, traceback
42 42 MB_ICONERROR = 0x00000010L
43 43 title = u'Error starting IPython QtConsole'
44 44 msg = u''.join(traceback.format_exception(exctype, value, tb))
45 45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
46 46 finally:
47 47 # Also call the old exception hook to let it do
48 48 # its thing too.
49 49 old_excepthook(exctype, value, tb)
50 50
51 51 sys.excepthook = gui_excepthook
52 52
53 53 # System library imports
54 54 from IPython.external.qt import QtCore, QtGui
55 55
56 56 # Local imports
57 from IPython.config.application import boolean_flag, catch_config_error
57 from IPython.config.application import catch_config_error
58 58 from IPython.core.application import BaseIPythonApplication
59 from IPython.core.profiledir import ProfileDir
60 59 from IPython.qt.console.ipython_widget import IPythonWidget
61 60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
62 61 from IPython.qt.console import styles
63 62 from IPython.qt.console.mainwindow import MainWindow
64 63 from IPython.qt.client import QtKernelClient
65 64 from IPython.qt.manager import QtKernelManager
66 from IPython.kernel import tunnel_to_kernel, find_connection_file
67 65 from IPython.utils.traitlets import (
68 Dict, List, Unicode, CBool, Any
66 Dict, Unicode, CBool, Any
69 67 )
70 from IPython.kernel.zmq.session import default_secure
71 68
72 69 from IPython.consoleapp import (
73 70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
74 71 )
75 72
76 73 #-----------------------------------------------------------------------------
77 74 # Network Constants
78 75 #-----------------------------------------------------------------------------
79 76
80 77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
81 78
82 79 #-----------------------------------------------------------------------------
83 80 # Globals
84 81 #-----------------------------------------------------------------------------
85 82
86 83 _examples = """
87 84 ipython qtconsole # start the qtconsole
88 85 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
89 86 """
90 87
91 88 #-----------------------------------------------------------------------------
92 89 # Aliases and Flags
93 90 #-----------------------------------------------------------------------------
94 91
95 92 # start with copy of flags
96 93 flags = dict(flags)
97 94 qt_flags = {
98 95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
99 96 "Disable rich text support."),
100 97 }
101 98
102 99 # and app_flags from the Console Mixin
103 100 qt_flags.update(app_flags)
104 101 # add frontend flags to the full set
105 102 flags.update(qt_flags)
106 103
107 104 # start with copy of front&backend aliases list
108 105 aliases = dict(aliases)
109 106 qt_aliases = dict(
110 107 style = 'IPythonWidget.syntax_style',
111 108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
112 109 colors = 'ZMQInteractiveShell.colors',
113 110
114 111 editor = 'IPythonWidget.editor',
115 112 paging = 'ConsoleWidget.paging',
116 113 )
117 114 # and app_aliases from the Console Mixin
118 115 qt_aliases.update(app_aliases)
119 116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
120 117 # add frontend aliases to the full set
121 118 aliases.update(qt_aliases)
122 119
123 120 # get flags&aliases into sets, and remove a couple that
124 121 # shouldn't be scrubbed from backend flags:
125 122 qt_aliases = set(qt_aliases.keys())
126 123 qt_aliases.remove('colors')
127 124 qt_flags = set(qt_flags.keys())
128 125
129 126 #-----------------------------------------------------------------------------
130 127 # Classes
131 128 #-----------------------------------------------------------------------------
132 129
133 130 #-----------------------------------------------------------------------------
134 131 # IPythonQtConsole
135 132 #-----------------------------------------------------------------------------
136 133
137 134
138 135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
139 136 name = 'ipython-qtconsole'
140 137
141 138 description = """
142 139 The IPython QtConsole.
143 140
144 141 This launches a Console-style application using Qt. It is not a full
145 142 console, in that launched terminal subprocesses will not be able to accept
146 143 input.
147 144
148 145 The QtConsole supports various extra features beyond the Terminal IPython
149 146 shell, such as inline plotting with matplotlib, via:
150 147
151 148 ipython qtconsole --pylab=inline
152 149
153 150 as well as saving your session as HTML, and printing the output.
154 151
155 152 """
156 153 examples = _examples
157 154
158 155 classes = [IPythonWidget] + IPythonConsoleApp.classes
159 156 flags = Dict(flags)
160 157 aliases = Dict(aliases)
161 158 frontend_flags = Any(qt_flags)
162 159 frontend_aliases = Any(qt_aliases)
163 160 kernel_client_class = QtKernelClient
164 161 kernel_manager_class = QtKernelManager
165 162
166 163 stylesheet = Unicode('', config=True,
167 164 help="path to a custom CSS stylesheet")
168 165
169 166 hide_menubar = CBool(False, config=True,
170 167 help="Start the console window with the menu bar hidden.")
171 168
172 169 maximize = CBool(False, config=True,
173 170 help="Start the console window maximized.")
174 171
175 172 plain = CBool(False, config=True,
176 173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
177 174
178 175 def _plain_changed(self, name, old, new):
179 176 kind = 'plain' if new else 'rich'
180 177 self.config.ConsoleWidget.kind = kind
181 178 if new:
182 179 self.widget_factory = IPythonWidget
183 180 else:
184 181 self.widget_factory = RichIPythonWidget
185 182
186 183 # the factory for creating a widget
187 184 widget_factory = Any(RichIPythonWidget)
188 185
189 186 def parse_command_line(self, argv=None):
190 187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
191 188 self.build_kernel_argv(argv)
192 189
193 190
194 191 def new_frontend_master(self):
195 192 """ Create and return new frontend attached to new kernel, launched on localhost.
196 193 """
197 194 kernel_manager = self.kernel_manager_class(
198 195 connection_file=self._new_connection_file(),
199 196 parent=self,
200 197 autorestart=True,
201 198 )
202 199 # start the kernel
203 200 kwargs = dict()
204 201 kwargs['extra_arguments'] = self.kernel_argv
205 202 kernel_manager.start_kernel(**kwargs)
206 203 kernel_manager.client_factory = self.kernel_client_class
207 204 kernel_client = kernel_manager.client()
208 205 kernel_client.start_channels(shell=True, iopub=True)
209 206 widget = self.widget_factory(config=self.config,
210 207 local_kernel=True)
211 208 self.init_colors(widget)
212 209 widget.kernel_manager = kernel_manager
213 210 widget.kernel_client = kernel_client
214 211 widget._existing = False
215 212 widget._may_close = True
216 213 widget._confirm_exit = self.confirm_exit
217 214 return widget
218 215
219 216 def new_frontend_slave(self, current_widget):
220 217 """Create and return a new frontend attached to an existing kernel.
221 218
222 219 Parameters
223 220 ----------
224 221 current_widget : IPythonWidget
225 222 The IPythonWidget whose kernel this frontend is to share
226 223 """
227 224 kernel_client = self.kernel_client_class(
228 225 connection_file=current_widget.kernel_client.connection_file,
229 226 config = self.config,
230 227 )
231 228 kernel_client.load_connection_file()
232 229 kernel_client.start_channels()
233 230 widget = self.widget_factory(config=self.config,
234 231 local_kernel=False)
235 232 self.init_colors(widget)
236 233 widget._existing = True
237 234 widget._may_close = False
238 235 widget._confirm_exit = False
239 236 widget.kernel_client = kernel_client
240 237 widget.kernel_manager = current_widget.kernel_manager
241 238 return widget
242 239
243 240 def init_qt_app(self):
244 241 # separate from qt_elements, because it must run first
245 242 self.app = QtGui.QApplication([])
246 243
247 244 def init_qt_elements(self):
248 245 # Create the widget.
249 246
250 247 base_path = os.path.abspath(os.path.dirname(__file__))
251 248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
252 249 self.app.icon = QtGui.QIcon(icon_path)
253 250 QtGui.QApplication.setWindowIcon(self.app.icon)
254 251
255 252 try:
256 253 ip = self.config.KernelManager.ip
257 254 except AttributeError:
258 255 ip = LOCALHOST
259 256 local_kernel = (not self.existing) or ip in LOCAL_IPS
260 257 self.widget = self.widget_factory(config=self.config,
261 258 local_kernel=local_kernel)
262 259 self.init_colors(self.widget)
263 260 self.widget._existing = self.existing
264 261 self.widget._may_close = not self.existing
265 262 self.widget._confirm_exit = self.confirm_exit
266 263
267 264 self.widget.kernel_manager = self.kernel_manager
268 265 self.widget.kernel_client = self.kernel_client
269 266 self.window = MainWindow(self.app,
270 267 confirm_exit=self.confirm_exit,
271 268 new_frontend_factory=self.new_frontend_master,
272 269 slave_frontend_factory=self.new_frontend_slave,
273 270 )
274 271 self.window.log = self.log
275 272 self.window.add_tab_with_frontend(self.widget)
276 273 self.window.init_menu_bar()
277 274
278 275 # Ignore on OSX, where there is always a menu bar
279 276 if sys.platform != 'darwin' and self.hide_menubar:
280 277 self.window.menuBar().setVisible(False)
281 278
282 279 self.window.setWindowTitle('IPython')
283 280
284 281 def init_colors(self, widget):
285 282 """Configure the coloring of the widget"""
286 283 # Note: This will be dramatically simplified when colors
287 284 # are removed from the backend.
288 285
289 286 # parse the colors arg down to current known labels
290 287 try:
291 288 colors = self.config.ZMQInteractiveShell.colors
292 289 except AttributeError:
293 290 colors = None
294 291 try:
295 292 style = self.config.IPythonWidget.syntax_style
296 293 except AttributeError:
297 294 style = None
298 295 try:
299 296 sheet = self.config.IPythonWidget.style_sheet
300 297 except AttributeError:
301 298 sheet = None
302 299
303 300 # find the value for colors:
304 301 if colors:
305 302 colors=colors.lower()
306 303 if colors in ('lightbg', 'light'):
307 304 colors='lightbg'
308 305 elif colors in ('dark', 'linux'):
309 306 colors='linux'
310 307 else:
311 308 colors='nocolor'
312 309 elif style:
313 310 if style=='bw':
314 311 colors='nocolor'
315 312 elif styles.dark_style(style):
316 313 colors='linux'
317 314 else:
318 315 colors='lightbg'
319 316 else:
320 317 colors=None
321 318
322 319 # Configure the style
323 320 if style:
324 321 widget.style_sheet = styles.sheet_from_template(style, colors)
325 322 widget.syntax_style = style
326 323 widget._syntax_style_changed()
327 324 widget._style_sheet_changed()
328 325 elif colors:
329 326 # use a default dark/light/bw style
330 327 widget.set_default_style(colors=colors)
331 328
332 329 if self.stylesheet:
333 330 # we got an explicit stylesheet
334 331 if os.path.isfile(self.stylesheet):
335 332 with open(self.stylesheet) as f:
336 333 sheet = f.read()
337 334 else:
338 335 raise IOError("Stylesheet %r not found." % self.stylesheet)
339 336 if sheet:
340 337 widget.style_sheet = sheet
341 338 widget._style_sheet_changed()
342 339
343 340
344 341 def init_signal(self):
345 342 """allow clean shutdown on sigint"""
346 343 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
347 344 # need a timer, so that QApplication doesn't block until a real
348 345 # Qt event fires (can require mouse movement)
349 346 # timer trick from http://stackoverflow.com/q/4938723/938949
350 347 timer = QtCore.QTimer()
351 348 # Let the interpreter run each 200 ms:
352 349 timer.timeout.connect(lambda: None)
353 350 timer.start(200)
354 351 # hold onto ref, so the timer doesn't get cleaned up
355 352 self._sigint_timer = timer
356 353
357 354 @catch_config_error
358 355 def initialize(self, argv=None):
359 356 self.init_qt_app()
360 357 super(IPythonQtConsoleApp, self).initialize(argv)
361 358 IPythonConsoleApp.initialize(self,argv)
362 359 self.init_qt_elements()
363 360 self.init_signal()
364 361
365 362 def start(self):
366 363
367 364 # draw the window
368 365 if self.maximize:
369 366 self.window.showMaximized()
370 367 else:
371 368 self.window.show()
372 369 self.window.raise_()
373 370
374 371 # Start the application main loop.
375 372 self.app.exec_()
376 373
377 374 #-----------------------------------------------------------------------------
378 375 # Main entry point
379 376 #-----------------------------------------------------------------------------
380 377
381 378 def main():
382 379 app = IPythonQtConsoleApp()
383 380 app.initialize()
384 381 app.start()
385 382
386 383
387 384 if __name__ == '__main__':
388 385 main()
@@ -1,85 +1,85 b''
1 1 # Standard library imports
2 2 import unittest
3 3
4 4 # System library imports
5 from IPython.external.qt import QtCore, QtGui
5 from IPython.external.qt import QtGui
6 6
7 7 # Local imports
8 8 from IPython.qt.console.kill_ring import KillRing, QtKillRing
9 9
10 10
11 11 class TestKillRing(unittest.TestCase):
12 12
13 13 @classmethod
14 14 def setUpClass(cls):
15 15 """ Create the application for the test case.
16 16 """
17 17 cls._app = QtGui.QApplication.instance()
18 18 if cls._app is None:
19 19 cls._app = QtGui.QApplication([])
20 20 cls._app.setQuitOnLastWindowClosed(False)
21 21
22 22 @classmethod
23 23 def tearDownClass(cls):
24 24 """ Exit the application.
25 25 """
26 26 QtGui.QApplication.quit()
27 27
28 28 def test_generic(self):
29 29 """ Does the generic kill ring work?
30 30 """
31 31 ring = KillRing()
32 32 self.assertTrue(ring.yank() is None)
33 33 self.assertTrue(ring.rotate() is None)
34 34
35 35 ring.kill('foo')
36 36 self.assertEqual(ring.yank(), 'foo')
37 37 self.assertTrue(ring.rotate() is None)
38 38 self.assertEqual(ring.yank(), 'foo')
39 39
40 40 ring.kill('bar')
41 41 self.assertEqual(ring.yank(), 'bar')
42 42 self.assertEqual(ring.rotate(), 'foo')
43 43
44 44 ring.clear()
45 45 self.assertTrue(ring.yank() is None)
46 46 self.assertTrue(ring.rotate() is None)
47 47
48 48 def test_qt_basic(self):
49 49 """ Does the Qt kill ring work?
50 50 """
51 51 text_edit = QtGui.QPlainTextEdit()
52 52 ring = QtKillRing(text_edit)
53 53
54 54 ring.kill('foo')
55 55 ring.kill('bar')
56 56 ring.yank()
57 57 ring.rotate()
58 58 ring.yank()
59 59 self.assertEqual(text_edit.toPlainText(), 'foobar')
60 60
61 61 text_edit.clear()
62 62 ring.kill('baz')
63 63 ring.yank()
64 64 ring.rotate()
65 65 ring.rotate()
66 66 ring.rotate()
67 67 self.assertEqual(text_edit.toPlainText(), 'foo')
68 68
69 69 def test_qt_cursor(self):
70 70 """ Does the Qt kill ring maintain state with cursor movement?
71 71 """
72 72 text_edit = QtGui.QPlainTextEdit()
73 73 ring = QtKillRing(text_edit)
74 74
75 75 ring.kill('foo')
76 76 ring.kill('bar')
77 77 ring.yank()
78 78 text_edit.moveCursor(QtGui.QTextCursor.Left)
79 79 ring.rotate()
80 80 self.assertEqual(text_edit.toPlainText(), 'bar')
81 81
82 82
83 83 if __name__ == '__main__':
84 84 import nose
85 85 nose.main()
@@ -1,52 +1,52 b''
1 1 """ Defines a KernelClient that provides signals and slots.
2 2 """
3 3
4 4 from IPython.external.qt import QtCore
5 5
6 6 # Local imports
7 from IPython.utils.traitlets import Bool, Instance
7 from IPython.utils.traitlets import Bool
8 8
9 9 from IPython.kernel import KernelManager
10 10 from IPython.kernel.restarter import KernelRestarter
11 11
12 12 from .kernel_mixins import QtKernelManagerMixin, QtKernelRestarterMixin
13 13
14 14
15 15 class QtKernelRestarter(KernelRestarter, QtKernelRestarterMixin):
16 16
17 17 def start(self):
18 18 if self._timer is None:
19 19 self._timer = QtCore.QTimer()
20 20 self._timer.timeout.connect(self.poll)
21 21 self._timer.start(self.time_to_dead * 1000)
22 22
23 23 def stop(self):
24 24 self._timer.stop()
25 25
26 26 def poll(self):
27 27 super(QtKernelRestarter, self).poll()
28 28
29 29
30 30 class QtKernelManager(KernelManager, QtKernelManagerMixin):
31 31 """A KernelManager with Qt signals for restart"""
32 32
33 33 autorestart = Bool(True, config=True)
34 34
35 35 def start_restarter(self):
36 36 if self.autorestart and self.has_kernel:
37 37 if self._restarter is None:
38 38 self._restarter = QtKernelRestarter(
39 39 kernel_manager=self,
40 40 parent=self,
41 41 log=self.log,
42 42 )
43 43 self._restarter.add_callback(self._handle_kernel_restarted)
44 44 self._restarter.start()
45 45
46 46 def stop_restarter(self):
47 47 if self.autorestart:
48 48 if self._restarter is not None:
49 49 self._restarter.stop()
50 50
51 51 def _handle_kernel_restarted(self):
52 52 self.kernel_restarted.emit()
General Comments 0
You need to be logged in to leave comments. Login now