##// END OF EJS Templates
Fixed ANSI compliance issue in AnsiCodeProcessor....
epatters -
Show More
@@ -1,230 +1,233 b''
1 """ Utilities for processing ANSI escape codes and special ASCII characters.
1 """ Utilities for processing ANSI escape codes and special ASCII characters.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # Standard library imports
7 # Standard library imports
8 from collections import namedtuple
8 from collections import namedtuple
9 import re
9 import re
10
10
11 # System library imports
11 # System library imports
12 from PyQt4 import QtCore, QtGui
12 from PyQt4 import QtCore, QtGui
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Constants and datatypes
15 # Constants and datatypes
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # An action for erase requests (ED and EL commands).
18 # An action for erase requests (ED and EL commands).
19 EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to'])
19 EraseAction = namedtuple('EraseAction', ['action', 'area', 'erase_to'])
20
20
21 # An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP,
21 # An action for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL, CHA, CUP,
22 # and HVP commands).
22 # and HVP commands).
23 # FIXME: Not implemented in AnsiCodeProcessor.
23 # FIXME: Not implemented in AnsiCodeProcessor.
24 MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count'])
24 MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count'])
25
25
26 # An action for scroll requests (SU and ST) and form feeds.
26 # An action for scroll requests (SU and ST) and form feeds.
27 ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])
27 ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Classes
30 # Classes
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 class AnsiCodeProcessor(object):
33 class AnsiCodeProcessor(object):
34 """ Translates special ASCII characters and ANSI escape codes into readable
34 """ Translates special ASCII characters and ANSI escape codes into readable
35 attributes.
35 attributes.
36 """
36 """
37
37
38 # Whether to increase intensity or set boldness for SGR code 1.
38 # Whether to increase intensity or set boldness for SGR code 1.
39 # (Different terminals handle this in different ways.)
39 # (Different terminals handle this in different ways.)
40 bold_text_enabled = False
40 bold_text_enabled = False
41
41
42 # Protected class variables.
42 # Protected class variables.
43 _ansi_commands = 'ABCDEFGHJKSTfmnsu'
43 _ansi_commands = 'ABCDEFGHJKSTfmnsu'
44 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
44 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
45 _special_pattern = re.compile('([\f])')
45 _special_pattern = re.compile('([\f])')
46
46
47 #---------------------------------------------------------------------------
47 #---------------------------------------------------------------------------
48 # AnsiCodeProcessor interface
48 # AnsiCodeProcessor interface
49 #---------------------------------------------------------------------------
49 #---------------------------------------------------------------------------
50
50
51 def __init__(self):
51 def __init__(self):
52 self.actions = []
52 self.actions = []
53 self.reset_sgr()
53 self.reset_sgr()
54
54
55 def reset_sgr(self):
55 def reset_sgr(self):
56 """ Reset graphics attributs to their default values.
56 """ Reset graphics attributs to their default values.
57 """
57 """
58 self.intensity = 0
58 self.intensity = 0
59 self.italic = False
59 self.italic = False
60 self.bold = False
60 self.bold = False
61 self.underline = False
61 self.underline = False
62 self.foreground_color = None
62 self.foreground_color = None
63 self.background_color = None
63 self.background_color = None
64
64
65 def split_string(self, string):
65 def split_string(self, string):
66 """ Yields substrings for which the same escape code applies.
66 """ Yields substrings for which the same escape code applies.
67 """
67 """
68 self.actions = []
68 self.actions = []
69 start = 0
69 start = 0
70
70
71 for match in self._ansi_pattern.finditer(string):
71 for match in self._ansi_pattern.finditer(string):
72 raw = string[start:match.start()]
72 raw = string[start:match.start()]
73 substring = self._special_pattern.sub(self._replace_special, raw)
73 substring = self._special_pattern.sub(self._replace_special, raw)
74 if substring or self.actions:
74 if substring or self.actions:
75 yield substring
75 yield substring
76 start = match.end()
76 start = match.end()
77
77
78 self.actions = []
78 self.actions = []
79 try:
79 try:
80 params = []
80 params = []
81 for param in match.group(1).split(';'):
81 for param in match.group(1).split(';'):
82 if param:
82 if param:
83 params.append(int(param))
83 params.append(int(param))
84 except ValueError:
84 except ValueError:
85 # Silently discard badly formed escape codes.
85 # Silently discard badly formed escape codes.
86 pass
86 pass
87 else:
87 else:
88 self.set_csi_code(match.group(2), params)
88 self.set_csi_code(match.group(2), params)
89
89
90 raw = string[start:]
90 raw = string[start:]
91 substring = self._special_pattern.sub(self._replace_special, raw)
91 substring = self._special_pattern.sub(self._replace_special, raw)
92 if substring or self.actions:
92 if substring or self.actions:
93 yield substring
93 yield substring
94
94
95 def set_csi_code(self, command, params=[]):
95 def set_csi_code(self, command, params=[]):
96 """ Set attributes based on CSI (Control Sequence Introducer) code.
96 """ Set attributes based on CSI (Control Sequence Introducer) code.
97
97
98 Parameters
98 Parameters
99 ----------
99 ----------
100 command : str
100 command : str
101 The code identifier, i.e. the final character in the sequence.
101 The code identifier, i.e. the final character in the sequence.
102
102
103 params : sequence of integers, optional
103 params : sequence of integers, optional
104 The parameter codes for the command.
104 The parameter codes for the command.
105 """
105 """
106 if command == 'm': # SGR - Select Graphic Rendition
106 if command == 'm': # SGR - Select Graphic Rendition
107 for code in params:
107 if params:
108 self.set_sgr_code(code)
108 for code in params:
109 self.set_sgr_code(code)
110 else:
111 self.set_sgr_code(0)
109
112
110 elif (command == 'J' or # ED - Erase Data
113 elif (command == 'J' or # ED - Erase Data
111 command == 'K'): # EL - Erase in Line
114 command == 'K'): # EL - Erase in Line
112 code = params[0] if params else 0
115 code = params[0] if params else 0
113 if 0 <= code <= 2:
116 if 0 <= code <= 2:
114 area = 'screen' if command == 'J' else 'line'
117 area = 'screen' if command == 'J' else 'line'
115 if code == 0:
118 if code == 0:
116 erase_to = 'end'
119 erase_to = 'end'
117 elif code == 1:
120 elif code == 1:
118 erase_to = 'start'
121 erase_to = 'start'
119 elif code == 2:
122 elif code == 2:
120 erase_to = 'all'
123 erase_to = 'all'
121 self.actions.append(EraseAction('erase', area, erase_to))
124 self.actions.append(EraseAction('erase', area, erase_to))
122
125
123 elif (command == 'S' or # SU - Scroll Up
126 elif (command == 'S' or # SU - Scroll Up
124 command == 'T'): # SD - Scroll Down
127 command == 'T'): # SD - Scroll Down
125 dir = 'up' if command == 'S' else 'down'
128 dir = 'up' if command == 'S' else 'down'
126 count = params[0] if params else 1
129 count = params[0] if params else 1
127 self.actions.append(ScrollAction('scroll', dir, 'line', count))
130 self.actions.append(ScrollAction('scroll', dir, 'line', count))
128
131
129 def set_sgr_code(self, code):
132 def set_sgr_code(self, code):
130 """ Set attributes based on SGR (Select Graphic Rendition) code.
133 """ Set attributes based on SGR (Select Graphic Rendition) code.
131 """
134 """
132 if code == 0:
135 if code == 0:
133 self.reset_sgr()
136 self.reset_sgr()
134 elif code == 1:
137 elif code == 1:
135 if self.bold_text_enabled:
138 if self.bold_text_enabled:
136 self.bold = True
139 self.bold = True
137 else:
140 else:
138 self.intensity = 1
141 self.intensity = 1
139 elif code == 2:
142 elif code == 2:
140 self.intensity = 0
143 self.intensity = 0
141 elif code == 3:
144 elif code == 3:
142 self.italic = True
145 self.italic = True
143 elif code == 4:
146 elif code == 4:
144 self.underline = True
147 self.underline = True
145 elif code == 22:
148 elif code == 22:
146 self.intensity = 0
149 self.intensity = 0
147 self.bold = False
150 self.bold = False
148 elif code == 23:
151 elif code == 23:
149 self.italic = False
152 self.italic = False
150 elif code == 24:
153 elif code == 24:
151 self.underline = False
154 self.underline = False
152 elif code >= 30 and code <= 37:
155 elif code >= 30 and code <= 37:
153 self.foreground_color = code - 30
156 self.foreground_color = code - 30
154 elif code == 39:
157 elif code == 39:
155 self.foreground_color = None
158 self.foreground_color = None
156 elif code >= 40 and code <= 47:
159 elif code >= 40 and code <= 47:
157 self.background_color = code - 40
160 self.background_color = code - 40
158 elif code == 49:
161 elif code == 49:
159 self.background_color = None
162 self.background_color = None
160
163
161 #---------------------------------------------------------------------------
164 #---------------------------------------------------------------------------
162 # Protected interface
165 # Protected interface
163 #---------------------------------------------------------------------------
166 #---------------------------------------------------------------------------
164
167
165 def _replace_special(self, match):
168 def _replace_special(self, match):
166 special = match.group(1)
169 special = match.group(1)
167 if special == '\f':
170 if special == '\f':
168 self.actions.append(ScrollAction('scroll', 'down', 'page', 1))
171 self.actions.append(ScrollAction('scroll', 'down', 'page', 1))
169 return ''
172 return ''
170
173
171
174
172 class QtAnsiCodeProcessor(AnsiCodeProcessor):
175 class QtAnsiCodeProcessor(AnsiCodeProcessor):
173 """ Translates ANSI escape codes into QTextCharFormats.
176 """ Translates ANSI escape codes into QTextCharFormats.
174 """
177 """
175
178
176 # A map from color codes to RGB colors.
179 # A map from color codes to RGB colors.
177 default_map = (# Normal, Bright/Light ANSI color code
180 default_map = (# Normal, Bright/Light ANSI color code
178 ('black', 'grey'), # 0: black
181 ('black', 'grey'), # 0: black
179 ('darkred', 'red'), # 1: red
182 ('darkred', 'red'), # 1: red
180 ('darkgreen', 'lime'), # 2: green
183 ('darkgreen', 'lime'), # 2: green
181 ('brown', 'yellow'), # 3: yellow
184 ('brown', 'yellow'), # 3: yellow
182 ('darkblue', 'deepskyblue'), # 4: blue
185 ('darkblue', 'deepskyblue'), # 4: blue
183 ('darkviolet', 'magenta'), # 5: magenta
186 ('darkviolet', 'magenta'), # 5: magenta
184 ('steelblue', 'cyan'), # 6: cyan
187 ('steelblue', 'cyan'), # 6: cyan
185 ('grey', 'white')) # 7: white
188 ('grey', 'white')) # 7: white
186
189
187 def __init__(self):
190 def __init__(self):
188 super(QtAnsiCodeProcessor, self).__init__()
191 super(QtAnsiCodeProcessor, self).__init__()
189 self.color_map = self.default_map
192 self.color_map = self.default_map
190
193
191 def get_format(self):
194 def get_format(self):
192 """ Returns a QTextCharFormat that encodes the current style attributes.
195 """ Returns a QTextCharFormat that encodes the current style attributes.
193 """
196 """
194 format = QtGui.QTextCharFormat()
197 format = QtGui.QTextCharFormat()
195
198
196 # Set foreground color
199 # Set foreground color
197 if self.foreground_color is not None:
200 if self.foreground_color is not None:
198 color = self.color_map[self.foreground_color][self.intensity]
201 color = self.color_map[self.foreground_color][self.intensity]
199 format.setForeground(QtGui.QColor(color))
202 format.setForeground(QtGui.QColor(color))
200
203
201 # Set background color
204 # Set background color
202 if self.background_color is not None:
205 if self.background_color is not None:
203 color = self.color_map[self.background_color][self.intensity]
206 color = self.color_map[self.background_color][self.intensity]
204 format.setBackground(QtGui.QColor(color))
207 format.setBackground(QtGui.QColor(color))
205
208
206 # Set font weight/style options
209 # Set font weight/style options
207 if self.bold:
210 if self.bold:
208 format.setFontWeight(QtGui.QFont.Bold)
211 format.setFontWeight(QtGui.QFont.Bold)
209 else:
212 else:
210 format.setFontWeight(QtGui.QFont.Normal)
213 format.setFontWeight(QtGui.QFont.Normal)
211 format.setFontItalic(self.italic)
214 format.setFontItalic(self.italic)
212 format.setFontUnderline(self.underline)
215 format.setFontUnderline(self.underline)
213
216
214 return format
217 return format
215
218
216 def set_background_color(self, color):
219 def set_background_color(self, color):
217 """ Given a background color (a QColor), attempt to set a color map
220 """ Given a background color (a QColor), attempt to set a color map
218 that will be aesthetically pleasing.
221 that will be aesthetically pleasing.
219 """
222 """
220 if color.value() < 127:
223 if color.value() < 127:
221 # Colors appropriate for a terminal with a dark background.
224 # Colors appropriate for a terminal with a dark background.
222 self.color_map = self.default_map
225 self.color_map = self.default_map
223
226
224 else:
227 else:
225 # Colors appropriate for a terminal with a light background. For
228 # Colors appropriate for a terminal with a light background. For
226 # now, only use non-bright colors...
229 # now, only use non-bright colors...
227 self.color_map = [ (pair[0], pair[0]) for pair in self.default_map ]
230 self.color_map = [ (pair[0], pair[0]) for pair in self.default_map ]
228
231
229 # ...and replace white with black.
232 # ...and replace white with black.
230 self.color_map[7] = ('black', 'black')
233 self.color_map[7] = ('black', 'black')
@@ -1,524 +1,530 b''
1 # Standard library imports
1 # Standard library imports
2 from collections import namedtuple
2 from collections import namedtuple
3 import signal
3 import signal
4 import sys
4 import sys
5
5
6 # System library imports
6 # System library imports
7 from pygments.lexers import PythonLexer
7 from pygments.lexers import PythonLexer
8 from PyQt4 import QtCore, QtGui
8 from PyQt4 import QtCore, QtGui
9
9
10 # Local imports
10 # Local imports
11 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
11 from IPython.core.inputsplitter import InputSplitter, transform_classic_prompt
12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
13 from IPython.utils.io import raw_print
13 from IPython.utils.io import raw_print
14 from IPython.utils.traitlets import Bool
14 from IPython.utils.traitlets import Bool
15 from bracket_matcher import BracketMatcher
15 from bracket_matcher import BracketMatcher
16 from call_tip_widget import CallTipWidget
16 from call_tip_widget import CallTipWidget
17 from completion_lexer import CompletionLexer
17 from completion_lexer import CompletionLexer
18 from history_console_widget import HistoryConsoleWidget
18 from history_console_widget import HistoryConsoleWidget
19 from pygments_highlighter import PygmentsHighlighter
19 from pygments_highlighter import PygmentsHighlighter
20
20
21
21
22 class FrontendHighlighter(PygmentsHighlighter):
22 class FrontendHighlighter(PygmentsHighlighter):
23 """ A PygmentsHighlighter that can be turned on and off and that ignores
23 """ A PygmentsHighlighter that can be turned on and off and that ignores
24 prompts.
24 prompts.
25 """
25 """
26
26
27 def __init__(self, frontend):
27 def __init__(self, frontend):
28 super(FrontendHighlighter, self).__init__(frontend._control.document())
28 super(FrontendHighlighter, self).__init__(frontend._control.document())
29 self._current_offset = 0
29 self._current_offset = 0
30 self._frontend = frontend
30 self._frontend = frontend
31 self.highlighting_on = False
31 self.highlighting_on = False
32
32
33 def highlightBlock(self, qstring):
33 def highlightBlock(self, qstring):
34 """ Highlight a block of text. Reimplemented to highlight selectively.
34 """ Highlight a block of text. Reimplemented to highlight selectively.
35 """
35 """
36 if not self.highlighting_on:
36 if not self.highlighting_on:
37 return
37 return
38
38
39 # The input to this function is unicode string that may contain
39 # The input to this function is unicode string that may contain
40 # paragraph break characters, non-breaking spaces, etc. Here we acquire
40 # paragraph break characters, non-breaking spaces, etc. Here we acquire
41 # the string as plain text so we can compare it.
41 # the string as plain text so we can compare it.
42 current_block = self.currentBlock()
42 current_block = self.currentBlock()
43 string = self._frontend._get_block_plain_text(current_block)
43 string = self._frontend._get_block_plain_text(current_block)
44
44
45 # Decide whether to check for the regular or continuation prompt.
45 # Decide whether to check for the regular or continuation prompt.
46 if current_block.contains(self._frontend._prompt_pos):
46 if current_block.contains(self._frontend._prompt_pos):
47 prompt = self._frontend._prompt
47 prompt = self._frontend._prompt
48 else:
48 else:
49 prompt = self._frontend._continuation_prompt
49 prompt = self._frontend._continuation_prompt
50
50
51 # Don't highlight the part of the string that contains the prompt.
51 # Don't highlight the part of the string that contains the prompt.
52 if string.startswith(prompt):
52 if string.startswith(prompt):
53 self._current_offset = len(prompt)
53 self._current_offset = len(prompt)
54 qstring.remove(0, len(prompt))
54 qstring.remove(0, len(prompt))
55 else:
55 else:
56 self._current_offset = 0
56 self._current_offset = 0
57
57
58 PygmentsHighlighter.highlightBlock(self, qstring)
58 PygmentsHighlighter.highlightBlock(self, qstring)
59
59
60 def rehighlightBlock(self, block):
60 def rehighlightBlock(self, block):
61 """ Reimplemented to temporarily enable highlighting if disabled.
61 """ Reimplemented to temporarily enable highlighting if disabled.
62 """
62 """
63 old = self.highlighting_on
63 old = self.highlighting_on
64 self.highlighting_on = True
64 self.highlighting_on = True
65 super(FrontendHighlighter, self).rehighlightBlock(block)
65 super(FrontendHighlighter, self).rehighlightBlock(block)
66 self.highlighting_on = old
66 self.highlighting_on = old
67
67
68 def setFormat(self, start, count, format):
68 def setFormat(self, start, count, format):
69 """ Reimplemented to highlight selectively.
69 """ Reimplemented to highlight selectively.
70 """
70 """
71 start += self._current_offset
71 start += self._current_offset
72 PygmentsHighlighter.setFormat(self, start, count, format)
72 PygmentsHighlighter.setFormat(self, start, count, format)
73
73
74
74
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
75 class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):
76 """ A Qt frontend for a generic Python kernel.
76 """ A Qt frontend for a generic Python kernel.
77 """
77 """
78
78
79 # An option and corresponding signal for overriding the default kernel
79 # An option and corresponding signal for overriding the default kernel
80 # interrupt behavior.
80 # interrupt behavior.
81 custom_interrupt = Bool(False)
81 custom_interrupt = Bool(False)
82 custom_interrupt_requested = QtCore.pyqtSignal()
82 custom_interrupt_requested = QtCore.pyqtSignal()
83
83
84 # An option and corresponding signals for overriding the default kernel
84 # An option and corresponding signals for overriding the default kernel
85 # restart behavior.
85 # restart behavior.
86 custom_restart = Bool(False)
86 custom_restart = Bool(False)
87 custom_restart_kernel_died = QtCore.pyqtSignal(float)
87 custom_restart_kernel_died = QtCore.pyqtSignal(float)
88 custom_restart_requested = QtCore.pyqtSignal()
88 custom_restart_requested = QtCore.pyqtSignal()
89
89
90 # Emitted when an 'execute_reply' has been received from the kernel and
90 # Emitted when an 'execute_reply' has been received from the kernel and
91 # processed by the FrontendWidget.
91 # processed by the FrontendWidget.
92 executed = QtCore.pyqtSignal(object)
92 executed = QtCore.pyqtSignal(object)
93
93
94 # Emitted when an exit request has been received from the kernel.
94 # Emitted when an exit request has been received from the kernel.
95 exit_requested = QtCore.pyqtSignal()
95 exit_requested = QtCore.pyqtSignal()
96
96
97 # Protected class variables.
97 # Protected class variables.
98 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
98 _CallTipRequest = namedtuple('_CallTipRequest', ['id', 'pos'])
99 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
99 _CompletionRequest = namedtuple('_CompletionRequest', ['id', 'pos'])
100 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
100 _ExecutionRequest = namedtuple('_ExecutionRequest', ['id', 'kind'])
101 _input_splitter_class = InputSplitter
101 _input_splitter_class = InputSplitter
102
102
103 #---------------------------------------------------------------------------
103 #---------------------------------------------------------------------------
104 # 'object' interface
104 # 'object' interface
105 #---------------------------------------------------------------------------
105 #---------------------------------------------------------------------------
106
106
107 def __init__(self, *args, **kw):
107 def __init__(self, *args, **kw):
108 super(FrontendWidget, self).__init__(*args, **kw)
108 super(FrontendWidget, self).__init__(*args, **kw)
109
109
110 # FrontendWidget protected variables.
110 # FrontendWidget protected variables.
111 self._bracket_matcher = BracketMatcher(self._control)
111 self._bracket_matcher = BracketMatcher(self._control)
112 self._call_tip_widget = CallTipWidget(self._control)
112 self._call_tip_widget = CallTipWidget(self._control)
113 self._completion_lexer = CompletionLexer(PythonLexer())
113 self._completion_lexer = CompletionLexer(PythonLexer())
114 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
114 self._copy_raw_action = QtGui.QAction('Copy (Raw Text)', None)
115 self._hidden = False
115 self._hidden = False
116 self._highlighter = FrontendHighlighter(self)
116 self._highlighter = FrontendHighlighter(self)
117 self._input_splitter = self._input_splitter_class(input_mode='block')
117 self._input_splitter = self._input_splitter_class(input_mode='block')
118 self._kernel_manager = None
118 self._kernel_manager = None
119 self._possible_kernel_restart = False
119 self._possible_kernel_restart = False
120 self._request_info = {}
120 self._request_info = {}
121
121
122 # Configure the ConsoleWidget.
122 # Configure the ConsoleWidget.
123 self.tab_width = 4
123 self.tab_width = 4
124 self._set_continuation_prompt('... ')
124 self._set_continuation_prompt('... ')
125
125
126 # Configure actions.
126 # Configure actions.
127 action = self._copy_raw_action
127 action = self._copy_raw_action
128 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
128 key = QtCore.Qt.CTRL | QtCore.Qt.SHIFT | QtCore.Qt.Key_C
129 action.setEnabled(False)
129 action.setEnabled(False)
130 action.setShortcut(QtGui.QKeySequence(key))
130 action.setShortcut(QtGui.QKeySequence(key))
131 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
131 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
132 action.triggered.connect(self.copy_raw)
132 action.triggered.connect(self.copy_raw)
133 self.copy_available.connect(action.setEnabled)
133 self.copy_available.connect(action.setEnabled)
134 self.addAction(action)
134 self.addAction(action)
135
135
136 # Connect signal handlers.
136 # Connect signal handlers.
137 document = self._control.document()
137 document = self._control.document()
138 document.contentsChange.connect(self._document_contents_change)
138 document.contentsChange.connect(self._document_contents_change)
139
139
140 #---------------------------------------------------------------------------
140 #---------------------------------------------------------------------------
141 # 'ConsoleWidget' public interface
141 # 'ConsoleWidget' public interface
142 #---------------------------------------------------------------------------
142 #---------------------------------------------------------------------------
143
143
144 def copy(self):
144 def copy(self):
145 """ Copy the currently selected text to the clipboard, removing prompts.
145 """ Copy the currently selected text to the clipboard, removing prompts.
146 """
146 """
147 text = str(self._control.textCursor().selection().toPlainText())
147 text = str(self._control.textCursor().selection().toPlainText())
148 if text:
148 if text:
149 # Remove prompts.
149 # Remove prompts.
150 lines = map(transform_classic_prompt, text.splitlines())
150 lines = map(transform_classic_prompt, text.splitlines())
151 text = '\n'.join(lines)
151 text = '\n'.join(lines)
152 # Expand tabs so that we respect PEP-8.
152 # Expand tabs so that we respect PEP-8.
153 QtGui.QApplication.clipboard().setText(text.expandtabs(4))
153 QtGui.QApplication.clipboard().setText(text.expandtabs(4))
154
154
155 #---------------------------------------------------------------------------
155 #---------------------------------------------------------------------------
156 # 'ConsoleWidget' abstract interface
156 # 'ConsoleWidget' abstract interface
157 #---------------------------------------------------------------------------
157 #---------------------------------------------------------------------------
158
158
159 def _is_complete(self, source, interactive):
159 def _is_complete(self, source, interactive):
160 """ Returns whether 'source' can be completely processed and a new
160 """ Returns whether 'source' can be completely processed and a new
161 prompt created. When triggered by an Enter/Return key press,
161 prompt created. When triggered by an Enter/Return key press,
162 'interactive' is True; otherwise, it is False.
162 'interactive' is True; otherwise, it is False.
163 """
163 """
164 complete = self._input_splitter.push(source.expandtabs(4))
164 complete = self._input_splitter.push(source.expandtabs(4))
165 if interactive:
165 if interactive:
166 complete = not self._input_splitter.push_accepts_more()
166 complete = not self._input_splitter.push_accepts_more()
167 return complete
167 return complete
168
168
169 def _execute(self, source, hidden):
169 def _execute(self, source, hidden):
170 """ Execute 'source'. If 'hidden', do not show any output.
170 """ Execute 'source'. If 'hidden', do not show any output.
171
171
172 See parent class :meth:`execute` docstring for full details.
172 See parent class :meth:`execute` docstring for full details.
173 """
173 """
174 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
174 msg_id = self.kernel_manager.xreq_channel.execute(source, hidden)
175 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
175 self._request_info['execute'] = self._ExecutionRequest(msg_id, 'user')
176 self._hidden = hidden
176 self._hidden = hidden
177
177
178 def _prompt_started_hook(self):
178 def _prompt_started_hook(self):
179 """ Called immediately after a new prompt is displayed.
179 """ Called immediately after a new prompt is displayed.
180 """
180 """
181 if not self._reading:
181 if not self._reading:
182 self._highlighter.highlighting_on = True
182 self._highlighter.highlighting_on = True
183
183
184 def _prompt_finished_hook(self):
184 def _prompt_finished_hook(self):
185 """ Called immediately after a prompt is finished, i.e. when some input
185 """ Called immediately after a prompt is finished, i.e. when some input
186 will be processed and a new prompt displayed.
186 will be processed and a new prompt displayed.
187 """
187 """
188 if not self._reading:
188 if not self._reading:
189 self._highlighter.highlighting_on = False
189 self._highlighter.highlighting_on = False
190
190
191 def _tab_pressed(self):
191 def _tab_pressed(self):
192 """ Called when the tab key is pressed. Returns whether to continue
192 """ Called when the tab key is pressed. Returns whether to continue
193 processing the event.
193 processing the event.
194 """
194 """
195 # Perform tab completion if:
195 # Perform tab completion if:
196 # 1) The cursor is in the input buffer.
196 # 1) The cursor is in the input buffer.
197 # 2) There is a non-whitespace character before the cursor.
197 # 2) There is a non-whitespace character before the cursor.
198 text = self._get_input_buffer_cursor_line()
198 text = self._get_input_buffer_cursor_line()
199 if text is None:
199 if text is None:
200 return False
200 return False
201 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
201 complete = bool(text[:self._get_input_buffer_cursor_column()].strip())
202 if complete:
202 if complete:
203 self._complete()
203 self._complete()
204 return not complete
204 return not complete
205
205
206 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
207 # 'ConsoleWidget' protected interface
207 # 'ConsoleWidget' protected interface
208 #---------------------------------------------------------------------------
208 #---------------------------------------------------------------------------
209
209
210 def _context_menu_make(self, pos):
210 def _context_menu_make(self, pos):
211 """ Reimplemented to add an action for raw copy.
211 """ Reimplemented to add an action for raw copy.
212 """
212 """
213 menu = super(FrontendWidget, self)._context_menu_make(pos)
213 menu = super(FrontendWidget, self)._context_menu_make(pos)
214 for before_action in menu.actions():
214 for before_action in menu.actions():
215 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
215 if before_action.shortcut().matches(QtGui.QKeySequence.Paste) == \
216 QtGui.QKeySequence.ExactMatch:
216 QtGui.QKeySequence.ExactMatch:
217 menu.insertAction(before_action, self._copy_raw_action)
217 menu.insertAction(before_action, self._copy_raw_action)
218 break
218 break
219 return menu
219 return menu
220
220
221 def _event_filter_console_keypress(self, event):
221 def _event_filter_console_keypress(self, event):
222 """ Reimplemented to allow execution interruption.
222 """ Reimplemented to allow execution interruption.
223 """
223 """
224 key = event.key()
224 key = event.key()
225 if self._control_key_down(event.modifiers(), include_command=False):
225 if self._control_key_down(event.modifiers(), include_command=False):
226 if key == QtCore.Qt.Key_C and self._executing:
226 if key == QtCore.Qt.Key_C and self._executing:
227 self.interrupt_kernel()
227 self.interrupt_kernel()
228 return True
228 return True
229 elif key == QtCore.Qt.Key_Period:
229 elif key == QtCore.Qt.Key_Period:
230 message = 'Are you sure you want to restart the kernel?'
230 message = 'Are you sure you want to restart the kernel?'
231 self.restart_kernel(message, instant_death=False)
231 self.restart_kernel(message, instant_death=False)
232 return True
232 return True
233 return super(FrontendWidget, self)._event_filter_console_keypress(event)
233 return super(FrontendWidget, self)._event_filter_console_keypress(event)
234
234
235 def _insert_continuation_prompt(self, cursor):
235 def _insert_continuation_prompt(self, cursor):
236 """ Reimplemented for auto-indentation.
236 """ Reimplemented for auto-indentation.
237 """
237 """
238 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
238 super(FrontendWidget, self)._insert_continuation_prompt(cursor)
239 spaces = self._input_splitter.indent_spaces
239 spaces = self._input_splitter.indent_spaces
240 cursor.insertText('\t' * (spaces / self.tab_width))
240 cursor.insertText('\t' * (spaces / self.tab_width))
241 cursor.insertText(' ' * (spaces % self.tab_width))
241 cursor.insertText(' ' * (spaces % self.tab_width))
242
242
243 #---------------------------------------------------------------------------
243 #---------------------------------------------------------------------------
244 # 'BaseFrontendMixin' abstract interface
244 # 'BaseFrontendMixin' abstract interface
245 #---------------------------------------------------------------------------
245 #---------------------------------------------------------------------------
246
246
247 def _handle_complete_reply(self, rep):
247 def _handle_complete_reply(self, rep):
248 """ Handle replies for tab completion.
248 """ Handle replies for tab completion.
249 """
249 """
250 cursor = self._get_cursor()
250 cursor = self._get_cursor()
251 info = self._request_info.get('complete')
251 info = self._request_info.get('complete')
252 if info and info.id == rep['parent_header']['msg_id'] and \
252 if info and info.id == rep['parent_header']['msg_id'] and \
253 info.pos == cursor.position():
253 info.pos == cursor.position():
254 text = '.'.join(self._get_context())
254 text = '.'.join(self._get_context())
255 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
255 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
256 self._complete_with_items(cursor, rep['content']['matches'])
256 self._complete_with_items(cursor, rep['content']['matches'])
257
257
258 def _handle_execute_reply(self, msg):
258 def _handle_execute_reply(self, msg):
259 """ Handles replies for code execution.
259 """ Handles replies for code execution.
260 """
260 """
261 info = self._request_info.get('execute')
261 info = self._request_info.get('execute')
262 if info and info.id == msg['parent_header']['msg_id'] and \
262 if info and info.id == msg['parent_header']['msg_id'] and \
263 info.kind == 'user' and not self._hidden:
263 info.kind == 'user' and not self._hidden:
264 # Make sure that all output from the SUB channel has been processed
264 # Make sure that all output from the SUB channel has been processed
265 # before writing a new prompt.
265 # before writing a new prompt.
266 self.kernel_manager.sub_channel.flush()
266 self.kernel_manager.sub_channel.flush()
267
267
268 # Reset the ANSI style information to prevent bad text in stdout
269 # from messing up our colors. We're not a true terminal so we're
270 # allowed to do this.
271 if self.ansi_codes:
272 self._ansi_processor.reset_sgr()
273
268 content = msg['content']
274 content = msg['content']
269 status = content['status']
275 status = content['status']
270 if status == 'ok':
276 if status == 'ok':
271 self._process_execute_ok(msg)
277 self._process_execute_ok(msg)
272 elif status == 'error':
278 elif status == 'error':
273 self._process_execute_error(msg)
279 self._process_execute_error(msg)
274 elif status == 'abort':
280 elif status == 'abort':
275 self._process_execute_abort(msg)
281 self._process_execute_abort(msg)
276
282
277 self._show_interpreter_prompt_for_reply(msg)
283 self._show_interpreter_prompt_for_reply(msg)
278 self.executed.emit(msg)
284 self.executed.emit(msg)
279
285
280 def _handle_input_request(self, msg):
286 def _handle_input_request(self, msg):
281 """ Handle requests for raw_input.
287 """ Handle requests for raw_input.
282 """
288 """
283 if self._hidden:
289 if self._hidden:
284 raise RuntimeError('Request for raw input during hidden execution.')
290 raise RuntimeError('Request for raw input during hidden execution.')
285
291
286 # Make sure that all output from the SUB channel has been processed
292 # Make sure that all output from the SUB channel has been processed
287 # before entering readline mode.
293 # before entering readline mode.
288 self.kernel_manager.sub_channel.flush()
294 self.kernel_manager.sub_channel.flush()
289
295
290 def callback(line):
296 def callback(line):
291 self.kernel_manager.rep_channel.input(line)
297 self.kernel_manager.rep_channel.input(line)
292 self._readline(msg['content']['prompt'], callback=callback)
298 self._readline(msg['content']['prompt'], callback=callback)
293
299
294 def _handle_kernel_died(self, since_last_heartbeat):
300 def _handle_kernel_died(self, since_last_heartbeat):
295 """ Handle the kernel's death by asking if the user wants to restart.
301 """ Handle the kernel's death by asking if the user wants to restart.
296 """
302 """
297 message = 'The kernel heartbeat has been inactive for %.2f ' \
303 message = 'The kernel heartbeat has been inactive for %.2f ' \
298 'seconds. Do you want to restart the kernel? You may ' \
304 'seconds. Do you want to restart the kernel? You may ' \
299 'first want to check the network connection.' % \
305 'first want to check the network connection.' % \
300 since_last_heartbeat
306 since_last_heartbeat
301 if self.custom_restart:
307 if self.custom_restart:
302 self.custom_restart_kernel_died.emit(since_last_heartbeat)
308 self.custom_restart_kernel_died.emit(since_last_heartbeat)
303 else:
309 else:
304 self.restart_kernel(message, instant_death=True)
310 self.restart_kernel(message, instant_death=True)
305
311
306 def _handle_object_info_reply(self, rep):
312 def _handle_object_info_reply(self, rep):
307 """ Handle replies for call tips.
313 """ Handle replies for call tips.
308 """
314 """
309 cursor = self._get_cursor()
315 cursor = self._get_cursor()
310 info = self._request_info.get('call_tip')
316 info = self._request_info.get('call_tip')
311 if info and info.id == rep['parent_header']['msg_id'] and \
317 if info and info.id == rep['parent_header']['msg_id'] and \
312 info.pos == cursor.position():
318 info.pos == cursor.position():
313 doc = rep['content']['docstring']
319 doc = rep['content']['docstring']
314 if doc:
320 if doc:
315 self._call_tip_widget.show_docstring(doc)
321 self._call_tip_widget.show_docstring(doc)
316
322
317 def _handle_pyout(self, msg):
323 def _handle_pyout(self, msg):
318 """ Handle display hook output.
324 """ Handle display hook output.
319 """
325 """
320 if not self._hidden and self._is_from_this_session(msg):
326 if not self._hidden and self._is_from_this_session(msg):
321 self._append_plain_text(msg['content']['data'] + '\n')
327 self._append_plain_text(msg['content']['data'] + '\n')
322
328
323 def _handle_stream(self, msg):
329 def _handle_stream(self, msg):
324 """ Handle stdout, stderr, and stdin.
330 """ Handle stdout, stderr, and stdin.
325 """
331 """
326 if not self._hidden and self._is_from_this_session(msg):
332 if not self._hidden and self._is_from_this_session(msg):
327 # Most consoles treat tabs as being 8 space characters. Convert tabs
333 # Most consoles treat tabs as being 8 space characters. Convert tabs
328 # to spaces so that output looks as expected regardless of this
334 # to spaces so that output looks as expected regardless of this
329 # widget's tab width.
335 # widget's tab width.
330 text = msg['content']['data'].expandtabs(8)
336 text = msg['content']['data'].expandtabs(8)
331
337
332 self._append_plain_text(text)
338 self._append_plain_text(text)
333 self._control.moveCursor(QtGui.QTextCursor.End)
339 self._control.moveCursor(QtGui.QTextCursor.End)
334
340
335 def _started_channels(self):
341 def _started_channels(self):
336 """ Called when the KernelManager channels have started listening or
342 """ Called when the KernelManager channels have started listening or
337 when the frontend is assigned an already listening KernelManager.
343 when the frontend is assigned an already listening KernelManager.
338 """
344 """
339 self._control.clear()
345 self._control.clear()
340 self._append_plain_text(self._get_banner())
346 self._append_plain_text(self._get_banner())
341 self._show_interpreter_prompt()
347 self._show_interpreter_prompt()
342
348
343 def _stopped_channels(self):
349 def _stopped_channels(self):
344 """ Called when the KernelManager channels have stopped listening or
350 """ Called when the KernelManager channels have stopped listening or
345 when a listening KernelManager is removed from the frontend.
351 when a listening KernelManager is removed from the frontend.
346 """
352 """
347 self._executing = self._reading = False
353 self._executing = self._reading = False
348 self._highlighter.highlighting_on = False
354 self._highlighter.highlighting_on = False
349
355
350 #---------------------------------------------------------------------------
356 #---------------------------------------------------------------------------
351 # 'FrontendWidget' public interface
357 # 'FrontendWidget' public interface
352 #---------------------------------------------------------------------------
358 #---------------------------------------------------------------------------
353
359
354 def copy_raw(self):
360 def copy_raw(self):
355 """ Copy the currently selected text to the clipboard without attempting
361 """ Copy the currently selected text to the clipboard without attempting
356 to remove prompts or otherwise alter the text.
362 to remove prompts or otherwise alter the text.
357 """
363 """
358 self._control.copy()
364 self._control.copy()
359
365
360 def execute_file(self, path, hidden=False):
366 def execute_file(self, path, hidden=False):
361 """ Attempts to execute file with 'path'. If 'hidden', no output is
367 """ Attempts to execute file with 'path'. If 'hidden', no output is
362 shown.
368 shown.
363 """
369 """
364 self.execute('execfile("%s")' % path, hidden=hidden)
370 self.execute('execfile("%s")' % path, hidden=hidden)
365
371
366 def interrupt_kernel(self):
372 def interrupt_kernel(self):
367 """ Attempts to interrupt the running kernel.
373 """ Attempts to interrupt the running kernel.
368 """
374 """
369 if self.custom_interrupt:
375 if self.custom_interrupt:
370 self.custom_interrupt_requested.emit()
376 self.custom_interrupt_requested.emit()
371 elif self.kernel_manager.has_kernel:
377 elif self.kernel_manager.has_kernel:
372 self.kernel_manager.signal_kernel(signal.SIGINT)
378 self.kernel_manager.signal_kernel(signal.SIGINT)
373 else:
379 else:
374 self._append_plain_text('Kernel process is either remote or '
380 self._append_plain_text('Kernel process is either remote or '
375 'unspecified. Cannot interrupt.\n')
381 'unspecified. Cannot interrupt.\n')
376
382
377 def restart_kernel(self, message, instant_death=False):
383 def restart_kernel(self, message, instant_death=False):
378 """ Attempts to restart the running kernel.
384 """ Attempts to restart the running kernel.
379 """
385 """
380 # FIXME: instant_death should be configurable via a checkbox in the
386 # FIXME: instant_death should be configurable via a checkbox in the
381 # dialog. Right now at least the heartbeat path sets it to True and
387 # dialog. Right now at least the heartbeat path sets it to True and
382 # the manual restart to False. But those should just be the
388 # the manual restart to False. But those should just be the
383 # pre-selected states of a checkbox that the user could override if so
389 # pre-selected states of a checkbox that the user could override if so
384 # desired. But I don't know enough Qt to go implementing the checkbox
390 # desired. But I don't know enough Qt to go implementing the checkbox
385 # now.
391 # now.
386
392
387 # We want to make sure that if this dialog is already happening, that
393 # We want to make sure that if this dialog is already happening, that
388 # other signals don't trigger it again. This can happen when the
394 # other signals don't trigger it again. This can happen when the
389 # kernel_died heartbeat signal is emitted and the user is slow to
395 # kernel_died heartbeat signal is emitted and the user is slow to
390 # respond to the dialog.
396 # respond to the dialog.
391 if not self._possible_kernel_restart:
397 if not self._possible_kernel_restart:
392 if self.custom_restart:
398 if self.custom_restart:
393 self.custom_restart_requested.emit()
399 self.custom_restart_requested.emit()
394 elif self.kernel_manager.has_kernel:
400 elif self.kernel_manager.has_kernel:
395 # Setting this to True will prevent this logic from happening
401 # Setting this to True will prevent this logic from happening
396 # again until the current pass is completed.
402 # again until the current pass is completed.
397 self._possible_kernel_restart = True
403 self._possible_kernel_restart = True
398 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
404 buttons = QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
399 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
405 result = QtGui.QMessageBox.question(self, 'Restart kernel?',
400 message, buttons)
406 message, buttons)
401 if result == QtGui.QMessageBox.Yes:
407 if result == QtGui.QMessageBox.Yes:
402 try:
408 try:
403 self.kernel_manager.restart_kernel(
409 self.kernel_manager.restart_kernel(
404 instant_death=instant_death)
410 instant_death=instant_death)
405 except RuntimeError:
411 except RuntimeError:
406 message = 'Kernel started externally. Cannot restart.\n'
412 message = 'Kernel started externally. Cannot restart.\n'
407 self._append_plain_text(message)
413 self._append_plain_text(message)
408 else:
414 else:
409 self._stopped_channels()
415 self._stopped_channels()
410 self._append_plain_text('Kernel restarting...\n')
416 self._append_plain_text('Kernel restarting...\n')
411 self._show_interpreter_prompt()
417 self._show_interpreter_prompt()
412 # This might need to be moved to another location?
418 # This might need to be moved to another location?
413 self._possible_kernel_restart = False
419 self._possible_kernel_restart = False
414 else:
420 else:
415 self._append_plain_text('Kernel process is either remote or '
421 self._append_plain_text('Kernel process is either remote or '
416 'unspecified. Cannot restart.\n')
422 'unspecified. Cannot restart.\n')
417
423
418 #---------------------------------------------------------------------------
424 #---------------------------------------------------------------------------
419 # 'FrontendWidget' protected interface
425 # 'FrontendWidget' protected interface
420 #---------------------------------------------------------------------------
426 #---------------------------------------------------------------------------
421
427
422 def _call_tip(self):
428 def _call_tip(self):
423 """ Shows a call tip, if appropriate, at the current cursor location.
429 """ Shows a call tip, if appropriate, at the current cursor location.
424 """
430 """
425 # Decide if it makes sense to show a call tip
431 # Decide if it makes sense to show a call tip
426 cursor = self._get_cursor()
432 cursor = self._get_cursor()
427 cursor.movePosition(QtGui.QTextCursor.Left)
433 cursor.movePosition(QtGui.QTextCursor.Left)
428 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
434 if cursor.document().characterAt(cursor.position()).toAscii() != '(':
429 return False
435 return False
430 context = self._get_context(cursor)
436 context = self._get_context(cursor)
431 if not context:
437 if not context:
432 return False
438 return False
433
439
434 # Send the metadata request to the kernel
440 # Send the metadata request to the kernel
435 name = '.'.join(context)
441 name = '.'.join(context)
436 msg_id = self.kernel_manager.xreq_channel.object_info(name)
442 msg_id = self.kernel_manager.xreq_channel.object_info(name)
437 pos = self._get_cursor().position()
443 pos = self._get_cursor().position()
438 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
444 self._request_info['call_tip'] = self._CallTipRequest(msg_id, pos)
439 return True
445 return True
440
446
441 def _complete(self):
447 def _complete(self):
442 """ Performs completion at the current cursor location.
448 """ Performs completion at the current cursor location.
443 """
449 """
444 context = self._get_context()
450 context = self._get_context()
445 if context:
451 if context:
446 # Send the completion request to the kernel
452 # Send the completion request to the kernel
447 msg_id = self.kernel_manager.xreq_channel.complete(
453 msg_id = self.kernel_manager.xreq_channel.complete(
448 '.'.join(context), # text
454 '.'.join(context), # text
449 self._get_input_buffer_cursor_line(), # line
455 self._get_input_buffer_cursor_line(), # line
450 self._get_input_buffer_cursor_column(), # cursor_pos
456 self._get_input_buffer_cursor_column(), # cursor_pos
451 self.input_buffer) # block
457 self.input_buffer) # block
452 pos = self._get_cursor().position()
458 pos = self._get_cursor().position()
453 info = self._CompletionRequest(msg_id, pos)
459 info = self._CompletionRequest(msg_id, pos)
454 self._request_info['complete'] = info
460 self._request_info['complete'] = info
455
461
456 def _get_banner(self):
462 def _get_banner(self):
457 """ Gets a banner to display at the beginning of a session.
463 """ Gets a banner to display at the beginning of a session.
458 """
464 """
459 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
465 banner = 'Python %s on %s\nType "help", "copyright", "credits" or ' \
460 '"license" for more information.'
466 '"license" for more information.'
461 return banner % (sys.version, sys.platform)
467 return banner % (sys.version, sys.platform)
462
468
463 def _get_context(self, cursor=None):
469 def _get_context(self, cursor=None):
464 """ Gets the context for the specified cursor (or the current cursor
470 """ Gets the context for the specified cursor (or the current cursor
465 if none is specified).
471 if none is specified).
466 """
472 """
467 if cursor is None:
473 if cursor is None:
468 cursor = self._get_cursor()
474 cursor = self._get_cursor()
469 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
475 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
470 QtGui.QTextCursor.KeepAnchor)
476 QtGui.QTextCursor.KeepAnchor)
471 text = str(cursor.selection().toPlainText())
477 text = str(cursor.selection().toPlainText())
472 return self._completion_lexer.get_context(text)
478 return self._completion_lexer.get_context(text)
473
479
474 def _process_execute_abort(self, msg):
480 def _process_execute_abort(self, msg):
475 """ Process a reply for an aborted execution request.
481 """ Process a reply for an aborted execution request.
476 """
482 """
477 self._append_plain_text("ERROR: execution aborted\n")
483 self._append_plain_text("ERROR: execution aborted\n")
478
484
479 def _process_execute_error(self, msg):
485 def _process_execute_error(self, msg):
480 """ Process a reply for an execution request that resulted in an error.
486 """ Process a reply for an execution request that resulted in an error.
481 """
487 """
482 content = msg['content']
488 content = msg['content']
483 traceback = ''.join(content['traceback'])
489 traceback = ''.join(content['traceback'])
484 self._append_plain_text(traceback)
490 self._append_plain_text(traceback)
485
491
486 def _process_execute_ok(self, msg):
492 def _process_execute_ok(self, msg):
487 """ Process a reply for a successful execution equest.
493 """ Process a reply for a successful execution equest.
488 """
494 """
489 payload = msg['content']['payload']
495 payload = msg['content']['payload']
490 for item in payload:
496 for item in payload:
491 if not self._process_execute_payload(item):
497 if not self._process_execute_payload(item):
492 warning = 'Warning: received unknown payload of type %s'
498 warning = 'Warning: received unknown payload of type %s'
493 raw_print(warning % repr(item['source']))
499 raw_print(warning % repr(item['source']))
494
500
495 def _process_execute_payload(self, item):
501 def _process_execute_payload(self, item):
496 """ Process a single payload item from the list of payload items in an
502 """ Process a single payload item from the list of payload items in an
497 execution reply. Returns whether the payload was handled.
503 execution reply. Returns whether the payload was handled.
498 """
504 """
499 # The basic FrontendWidget doesn't handle payloads, as they are a
505 # The basic FrontendWidget doesn't handle payloads, as they are a
500 # mechanism for going beyond the standard Python interpreter model.
506 # mechanism for going beyond the standard Python interpreter model.
501 return False
507 return False
502
508
503 def _show_interpreter_prompt(self):
509 def _show_interpreter_prompt(self):
504 """ Shows a prompt for the interpreter.
510 """ Shows a prompt for the interpreter.
505 """
511 """
506 self._show_prompt('>>> ')
512 self._show_prompt('>>> ')
507
513
508 def _show_interpreter_prompt_for_reply(self, msg):
514 def _show_interpreter_prompt_for_reply(self, msg):
509 """ Shows a prompt for the interpreter given an 'execute_reply' message.
515 """ Shows a prompt for the interpreter given an 'execute_reply' message.
510 """
516 """
511 self._show_interpreter_prompt()
517 self._show_interpreter_prompt()
512
518
513 #------ Signal handlers ----------------------------------------------------
519 #------ Signal handlers ----------------------------------------------------
514
520
515 def _document_contents_change(self, position, removed, added):
521 def _document_contents_change(self, position, removed, added):
516 """ Called whenever the document's content changes. Display a call tip
522 """ Called whenever the document's content changes. Display a call tip
517 if appropriate.
523 if appropriate.
518 """
524 """
519 # Calculate where the cursor should be *after* the change:
525 # Calculate where the cursor should be *after* the change:
520 position += added
526 position += added
521
527
522 document = self._control.document()
528 document = self._control.document()
523 if position == self._get_cursor().position():
529 if position == self._get_cursor().position():
524 self._call_tip()
530 self._call_tip()
General Comments 0
You need to be logged in to leave comments. Login now