##// END OF EJS Templates
The IPythonWidget now tries to be smart about choosing colors to use for ANSI color codes.
epatters -
Show More
@@ -1,177 +1,203 b''
1 # Standard library imports
1 # Standard library imports
2 import re
2 import re
3
3
4 # System library imports
4 # System library imports
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7
7
8 class AnsiAction(object):
8 class AnsiAction(object):
9 """ Represents an action requested by an ANSI escape sequence.
9 """ Represents an action requested by an ANSI escape sequence.
10 """
10 """
11 def __init__(self, kind):
11 def __init__(self, kind):
12 self.kind = kind
12 self.kind = kind
13
13
14 class MoveAction(AnsiAction):
14 class MoveAction(AnsiAction):
15 """ An AnsiAction for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL,
15 """ An AnsiAction for cursor move requests (CUU, CUD, CUF, CUB, CNL, CPL,
16 CHA, and CUP commands).
16 CHA, and CUP commands).
17 """
17 """
18 def __init__(self):
18 def __init__(self):
19 raise NotImplementedError
19 raise NotImplementedError
20
20
21 class EraseAction(AnsiAction):
21 class EraseAction(AnsiAction):
22 """ An AnsiAction for erase requests (ED and EL commands).
22 """ An AnsiAction for erase requests (ED and EL commands).
23 """
23 """
24 def __init__(self, area, erase_to):
24 def __init__(self, area, erase_to):
25 super(EraseAction, self).__init__('erase')
25 super(EraseAction, self).__init__('erase')
26 self.area = area
26 self.area = area
27 self.erase_to = erase_to
27 self.erase_to = erase_to
28
28
29
29
30 class AnsiCodeProcessor(object):
30 class AnsiCodeProcessor(object):
31 """ Translates ANSI escape codes into readable attributes.
31 """ Translates ANSI escape codes into readable attributes.
32 """
32 """
33
33
34 # Whether to increase intensity or set boldness for SGR code 1.
35 # (Different terminals handle this in different ways.)
36 bold_text_enabled = False
37
34 # Protected class variables.
38 # Protected class variables.
35 _ansi_commands = 'ABCDEFGHJKSTfmnsu'
39 _ansi_commands = 'ABCDEFGHJKSTfmnsu'
36 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
40 _ansi_pattern = re.compile('\x01?\x1b\[(.*?)([%s])\x02?' % _ansi_commands)
37
41
38 def __init__(self):
42 def __init__(self):
39 self.actions = []
43 self.actions = []
40 self.reset_sgr()
44 self.reset_sgr()
41
45
42 def reset_sgr(self):
46 def reset_sgr(self):
43 """ Reset graphics attributs to their default values.
47 """ Reset graphics attributs to their default values.
44 """
48 """
45 self.intensity = 0
49 self.intensity = 0
46 self.italic = False
50 self.italic = False
47 self.bold = False
51 self.bold = False
48 self.underline = False
52 self.underline = False
49 self.foreground_color = None
53 self.foreground_color = None
50 self.background_color = None
54 self.background_color = None
51
55
52 def split_string(self, string):
56 def split_string(self, string):
53 """ Yields substrings for which the same escape code applies.
57 """ Yields substrings for which the same escape code applies.
54 """
58 """
55 self.actions = []
59 self.actions = []
56 start = 0
60 start = 0
57
61
58 for match in self._ansi_pattern.finditer(string):
62 for match in self._ansi_pattern.finditer(string):
59 substring = string[start:match.start()]
63 substring = string[start:match.start()]
60 if substring or self.actions:
64 if substring or self.actions:
61 yield substring
65 yield substring
62 start = match.end()
66 start = match.end()
63
67
64 self.actions = []
68 self.actions = []
65 try:
69 try:
66 params = []
70 params = []
67 for param in match.group(1).split(';'):
71 for param in match.group(1).split(';'):
68 if param:
72 if param:
69 params.append(int(param))
73 params.append(int(param))
70 except ValueError:
74 except ValueError:
71 # Silently discard badly formed escape codes.
75 # Silently discard badly formed escape codes.
72 pass
76 pass
73 else:
77 else:
74 self.set_csi_code(match.group(2), params)
78 self.set_csi_code(match.group(2), params)
75
79
76 substring = string[start:]
80 substring = string[start:]
77 if substring or self.actions:
81 if substring or self.actions:
78 yield substring
82 yield substring
79
83
80 def set_csi_code(self, command, params=[]):
84 def set_csi_code(self, command, params=[]):
81 """ Set attributes based on CSI (Control Sequence Introducer) code.
85 """ Set attributes based on CSI (Control Sequence Introducer) code.
82
86
83 Parameters
87 Parameters
84 ----------
88 ----------
85 command : str
89 command : str
86 The code identifier, i.e. the final character in the sequence.
90 The code identifier, i.e. the final character in the sequence.
87
91
88 params : sequence of integers, optional
92 params : sequence of integers, optional
89 The parameter codes for the command.
93 The parameter codes for the command.
90 """
94 """
91 if command == 'm': # SGR - Select Graphic Rendition
95 if command == 'm': # SGR - Select Graphic Rendition
92 for code in params:
96 for code in params:
93 self.set_sgr_code(code)
97 self.set_sgr_code(code)
94
98
95 elif (command == 'J' or # ED - Erase Data
99 elif (command == 'J' or # ED - Erase Data
96 command == 'K'): # EL - Erase in Line
100 command == 'K'): # EL - Erase in Line
97 code = params[0] if params else 0
101 code = params[0] if params else 0
98 if 0 <= code <= 2:
102 if 0 <= code <= 2:
99 area = 'screen' if command == 'J' else 'line'
103 area = 'screen' if command == 'J' else 'line'
100 if code == 0:
104 if code == 0:
101 erase_to = 'end'
105 erase_to = 'end'
102 elif code == 1:
106 elif code == 1:
103 erase_to = 'start'
107 erase_to = 'start'
104 elif code == 2:
108 elif code == 2:
105 erase_to = 'all'
109 erase_to = 'all'
106 self.actions.append(EraseAction(area, erase_to))
110 self.actions.append(EraseAction(area, erase_to))
107
111
108 def set_sgr_code(self, code):
112 def set_sgr_code(self, code):
109 """ Set attributes based on SGR (Select Graphic Rendition) code.
113 """ Set attributes based on SGR (Select Graphic Rendition) code.
110 """
114 """
111 if code == 0:
115 if code == 0:
112 self.reset_sgr()
116 self.reset_sgr()
113 elif code == 1:
117 elif code == 1:
114 self.intensity = 1
118 if self.bold_text_enabled:
115 self.bold = True
119 self.bold = True
120 else:
121 self.intensity = 1
116 elif code == 2:
122 elif code == 2:
117 self.intensity = 0
123 self.intensity = 0
118 elif code == 3:
124 elif code == 3:
119 self.italic = True
125 self.italic = True
120 elif code == 4:
126 elif code == 4:
121 self.underline = True
127 self.underline = True
122 elif code == 22:
128 elif code == 22:
123 self.intensity = 0
129 self.intensity = 0
124 self.bold = False
130 self.bold = False
125 elif code == 23:
131 elif code == 23:
126 self.italic = False
132 self.italic = False
127 elif code == 24:
133 elif code == 24:
128 self.underline = False
134 self.underline = False
129 elif code >= 30 and code <= 37:
135 elif code >= 30 and code <= 37:
130 self.foreground_color = code - 30
136 self.foreground_color = code - 30
131 elif code == 39:
137 elif code == 39:
132 self.foreground_color = None
138 self.foreground_color = None
133 elif code >= 40 and code <= 47:
139 elif code >= 40 and code <= 47:
134 self.background_color = code - 40
140 self.background_color = code - 40
135 elif code == 49:
141 elif code == 49:
136 self.background_color = None
142 self.background_color = None
137
143
138
144
139 class QtAnsiCodeProcessor(AnsiCodeProcessor):
145 class QtAnsiCodeProcessor(AnsiCodeProcessor):
140 """ Translates ANSI escape codes into QTextCharFormats.
146 """ Translates ANSI escape codes into QTextCharFormats.
141 """
147 """
142
148
143 # A map from color codes to RGB colors.
149 # A map from color codes to RGB colors.
144 ansi_colors = (# Normal, Bright/Light ANSI color code
150 default_map = (# Normal, Bright/Light ANSI color code
145 ('black', 'grey'), # 0: black
151 ('black', 'grey'), # 0: black
146 ('darkred', 'red'), # 1: red
152 ('darkred', 'red'), # 1: red
147 ('darkgreen', 'green'), # 2: green
153 ('darkgreen', 'lime'), # 2: green
148 ('gold', 'yellow'), # 3: yellow
154 ('brown', 'yellow'), # 3: yellow
149 ('darkblue', 'blue'), # 4: blue
155 ('darkblue', 'deepskyblue'), # 4: blue
150 ('darkviolet', 'magenta'), # 5: magenta
156 ('darkviolet', 'magenta'), # 5: magenta
151 ('steelblue', 'cyan'), # 6: cyan
157 ('steelblue', 'cyan'), # 6: cyan
152 ('grey', 'white')) # 7: white
158 ('grey', 'white')) # 7: white
153
159
160 def __init__(self):
161 super(QtAnsiCodeProcessor, self).__init__()
162 self.color_map = self.default_map
163
154 def get_format(self):
164 def get_format(self):
155 """ Returns a QTextCharFormat that encodes the current style attributes.
165 """ Returns a QTextCharFormat that encodes the current style attributes.
156 """
166 """
157 format = QtGui.QTextCharFormat()
167 format = QtGui.QTextCharFormat()
158
168
159 # Set foreground color
169 # Set foreground color
160 if self.foreground_color is not None:
170 if self.foreground_color is not None:
161 color = self.ansi_colors[self.foreground_color][self.intensity]
171 color = self.color_map[self.foreground_color][self.intensity]
162 format.setForeground(QtGui.QColor(color))
172 format.setForeground(QtGui.QColor(color))
163
173
164 # Set background color
174 # Set background color
165 if self.background_color is not None:
175 if self.background_color is not None:
166 color = self.ansi_colors[self.background_color][self.intensity]
176 color = self.color_map[self.background_color][self.intensity]
167 format.setBackground(QtGui.QColor(color))
177 format.setBackground(QtGui.QColor(color))
168
178
169 # Set font weight/style options
179 # Set font weight/style options
170 if self.bold:
180 if self.bold:
171 format.setFontWeight(QtGui.QFont.Bold)
181 format.setFontWeight(QtGui.QFont.Bold)
172 else:
182 else:
173 format.setFontWeight(QtGui.QFont.Normal)
183 format.setFontWeight(QtGui.QFont.Normal)
174 format.setFontItalic(self.italic)
184 format.setFontItalic(self.italic)
175 format.setFontUnderline(self.underline)
185 format.setFontUnderline(self.underline)
176
186
177 return format
187 return format
188
189 def set_background_color(self, color):
190 """ Given a background color (a QColor), attempt to set a color map
191 that will be aesthetically pleasing.
192 """
193 if color.value() < 127:
194 # Colors appropriate for a terminal with a dark background.
195 self.color_map = self.default_map
196
197 else:
198 # Colors appropriate for a terminal with a light background. For
199 # now, only use non-bright colors...
200 self.color_map = [ (pair[0], pair[0]) for pair in self.default_map ]
201
202 # ...and replace white with black.
203 self.color_map[7] = ('black', 'black')
@@ -1,372 +1,375 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3
3
4 TODO: Add support for retrieving the system default editor. Requires code
4 TODO: Add support for retrieving the system default editor. Requires code
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 Linux (use the xdg system).
6 Linux (use the xdg system).
7 """
7 """
8
8
9 # Standard library imports
9 # Standard library imports
10 from subprocess import Popen
10 from subprocess import Popen
11
11
12 # System library imports
12 # System library imports
13 from PyQt4 import QtCore, QtGui
13 from PyQt4 import QtCore, QtGui
14
14
15 # Local imports
15 # Local imports
16 from IPython.core.inputsplitter import IPythonInputSplitter
16 from IPython.core.inputsplitter import IPythonInputSplitter
17 from IPython.core.usage import default_banner
17 from IPython.core.usage import default_banner
18 from frontend_widget import FrontendWidget
18 from frontend_widget import FrontendWidget
19
19
20
20
21 class IPythonPromptBlock(object):
21 class IPythonPromptBlock(object):
22 """ An internal storage object for IPythonWidget.
22 """ An internal storage object for IPythonWidget.
23 """
23 """
24 def __init__(self, block, length, number):
24 def __init__(self, block, length, number):
25 self.block = block
25 self.block = block
26 self.length = length
26 self.length = length
27 self.number = number
27 self.number = number
28
28
29
29
30 class IPythonWidget(FrontendWidget):
30 class IPythonWidget(FrontendWidget):
31 """ A FrontendWidget for an IPython kernel.
31 """ A FrontendWidget for an IPython kernel.
32 """
32 """
33
33
34 # Signal emitted when an editor is needed for a file and the editor has been
34 # Signal emitted when an editor is needed for a file and the editor has been
35 # specified as 'custom'. See 'set_editor' for more information.
35 # specified as 'custom'. See 'set_editor' for more information.
36 custom_edit_requested = QtCore.pyqtSignal(object, object)
36 custom_edit_requested = QtCore.pyqtSignal(object, object)
37
37
38 # The default stylesheet: black text on a white background.
38 # The default stylesheet: black text on a white background.
39 default_stylesheet = """
39 default_stylesheet = """
40 .error { color: red; }
40 .error { color: red; }
41 .in-prompt { color: navy; }
41 .in-prompt { color: navy; }
42 .in-prompt-number { font-weight: bold; }
42 .in-prompt-number { font-weight: bold; }
43 .out-prompt { color: darkred; }
43 .out-prompt { color: darkred; }
44 .out-prompt-number { font-weight: bold; }
44 .out-prompt-number { font-weight: bold; }
45 """
45 """
46
46
47 # A dark stylesheet: white text on a black background.
47 # A dark stylesheet: white text on a black background.
48 dark_stylesheet = """
48 dark_stylesheet = """
49 QPlainTextEdit, QTextEdit { background-color: black; color: white }
49 QPlainTextEdit, QTextEdit { background-color: black; color: white }
50 QFrame { border: 1px solid grey; }
50 QFrame { border: 1px solid grey; }
51 .error { color: red; }
51 .error { color: red; }
52 .in-prompt { color: lime; }
52 .in-prompt { color: lime; }
53 .in-prompt-number { color: lime; font-weight: bold; }
53 .in-prompt-number { color: lime; font-weight: bold; }
54 .out-prompt { color: red; }
54 .out-prompt { color: red; }
55 .out-prompt-number { color: red; font-weight: bold; }
55 .out-prompt-number { color: red; font-weight: bold; }
56 """
56 """
57
57
58 # Default prompts.
58 # Default prompts.
59 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
59 in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
60 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
60 out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
61
61
62 # FrontendWidget protected class variables.
62 # FrontendWidget protected class variables.
63 _input_splitter_class = IPythonInputSplitter
63 _input_splitter_class = IPythonInputSplitter
64
64
65 # IPythonWidget protected class variables.
65 # IPythonWidget protected class variables.
66 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
66 _payload_source_edit = 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic'
67 _payload_source_page = 'IPython.zmq.page.page'
67 _payload_source_page = 'IPython.zmq.page.page'
68
68
69 #---------------------------------------------------------------------------
69 #---------------------------------------------------------------------------
70 # 'object' interface
70 # 'object' interface
71 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
72
72
73 def __init__(self, *args, **kw):
73 def __init__(self, *args, **kw):
74 super(IPythonWidget, self).__init__(*args, **kw)
74 super(IPythonWidget, self).__init__(*args, **kw)
75
75
76 # IPythonWidget protected variables.
76 # IPythonWidget protected variables.
77 self._previous_prompt_obj = None
77 self._previous_prompt_obj = None
78
78
79 # Set a default editor and stylesheet.
79 # Set a default editor and stylesheet.
80 self.set_editor('default')
80 self.set_editor('default')
81 self.reset_styling()
81 self.reset_styling()
82
82
83 #---------------------------------------------------------------------------
83 #---------------------------------------------------------------------------
84 # 'BaseFrontendMixin' abstract interface
84 # 'BaseFrontendMixin' abstract interface
85 #---------------------------------------------------------------------------
85 #---------------------------------------------------------------------------
86
86
87 def _handle_complete_reply(self, rep):
87 def _handle_complete_reply(self, rep):
88 """ Reimplemented to support IPython's improved completion machinery.
88 """ Reimplemented to support IPython's improved completion machinery.
89 """
89 """
90 cursor = self._get_cursor()
90 cursor = self._get_cursor()
91 if rep['parent_header']['msg_id'] == self._complete_id and \
91 if rep['parent_header']['msg_id'] == self._complete_id and \
92 cursor.position() == self._complete_pos:
92 cursor.position() == self._complete_pos:
93 # The completer tells us what text was actually used for the
93 # The completer tells us what text was actually used for the
94 # matching, so we must move that many characters left to apply the
94 # matching, so we must move that many characters left to apply the
95 # completions.
95 # completions.
96 text = rep['content']['matched_text']
96 text = rep['content']['matched_text']
97 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
97 cursor.movePosition(QtGui.QTextCursor.Left, n=len(text))
98 self._complete_with_items(cursor, rep['content']['matches'])
98 self._complete_with_items(cursor, rep['content']['matches'])
99
99
100 def _handle_history_reply(self, msg):
100 def _handle_history_reply(self, msg):
101 """ Implemented to handle history replies, which are only supported by
101 """ Implemented to handle history replies, which are only supported by
102 the IPython kernel.
102 the IPython kernel.
103 """
103 """
104 history_dict = msg['content']['history']
104 history_dict = msg['content']['history']
105 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
105 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
106 self._set_history(items)
106 self._set_history(items)
107
107
108 def _handle_prompt_reply(self, msg):
108 def _handle_prompt_reply(self, msg):
109 """ Implemented to handle prompt number replies, which are only
109 """ Implemented to handle prompt number replies, which are only
110 supported by the IPython kernel.
110 supported by the IPython kernel.
111 """
111 """
112 content = msg['content']
112 content = msg['content']
113 self._show_interpreter_prompt(content['prompt_number'],
113 self._show_interpreter_prompt(content['prompt_number'],
114 content['input_sep'])
114 content['input_sep'])
115
115
116 def _handle_pyout(self, msg):
116 def _handle_pyout(self, msg):
117 """ Reimplemented for IPython-style "display hook".
117 """ Reimplemented for IPython-style "display hook".
118 """
118 """
119 if not self._hidden and self._is_from_this_session(msg):
119 if not self._hidden and self._is_from_this_session(msg):
120 content = msg['content']
120 content = msg['content']
121 prompt_number = content['prompt_number']
121 prompt_number = content['prompt_number']
122 self._append_plain_text(content['output_sep'])
122 self._append_plain_text(content['output_sep'])
123 self._append_html(self._make_out_prompt(prompt_number))
123 self._append_html(self._make_out_prompt(prompt_number))
124 self._append_plain_text(content['data'] + '\n' +
124 self._append_plain_text(content['data'] + '\n' +
125 content['output_sep2'])
125 content['output_sep2'])
126
126
127 def _started_channels(self):
127 def _started_channels(self):
128 """ Reimplemented to make a history request.
128 """ Reimplemented to make a history request.
129 """
129 """
130 super(IPythonWidget, self)._started_channels()
130 super(IPythonWidget, self)._started_channels()
131 # FIXME: Disabled until history requests are properly implemented.
131 # FIXME: Disabled until history requests are properly implemented.
132 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
132 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
133
133
134 #---------------------------------------------------------------------------
134 #---------------------------------------------------------------------------
135 # 'FrontendWidget' interface
135 # 'FrontendWidget' interface
136 #---------------------------------------------------------------------------
136 #---------------------------------------------------------------------------
137
137
138 def execute_file(self, path, hidden=False):
138 def execute_file(self, path, hidden=False):
139 """ Reimplemented to use the 'run' magic.
139 """ Reimplemented to use the 'run' magic.
140 """
140 """
141 self.execute('%%run %s' % path, hidden=hidden)
141 self.execute('%%run %s' % path, hidden=hidden)
142
142
143 #---------------------------------------------------------------------------
143 #---------------------------------------------------------------------------
144 # 'FrontendWidget' protected interface
144 # 'FrontendWidget' protected interface
145 #---------------------------------------------------------------------------
145 #---------------------------------------------------------------------------
146
146
147 def _complete(self):
147 def _complete(self):
148 """ Reimplemented to support IPython's improved completion machinery.
148 """ Reimplemented to support IPython's improved completion machinery.
149 """
149 """
150 # We let the kernel split the input line, so we *always* send an empty
150 # We let the kernel split the input line, so we *always* send an empty
151 # text field. Readline-based frontends do get a real text field which
151 # text field. Readline-based frontends do get a real text field which
152 # they can use.
152 # they can use.
153 text = ''
153 text = ''
154
154
155 # Send the completion request to the kernel
155 # Send the completion request to the kernel
156 self._complete_id = self.kernel_manager.xreq_channel.complete(
156 self._complete_id = self.kernel_manager.xreq_channel.complete(
157 text, # text
157 text, # text
158 self._get_input_buffer_cursor_line(), # line
158 self._get_input_buffer_cursor_line(), # line
159 self._get_input_buffer_cursor_column(), # cursor_pos
159 self._get_input_buffer_cursor_column(), # cursor_pos
160 self.input_buffer) # block
160 self.input_buffer) # block
161 self._complete_pos = self._get_cursor().position()
161 self._complete_pos = self._get_cursor().position()
162
162
163 def _get_banner(self):
163 def _get_banner(self):
164 """ Reimplemented to return IPython's default banner.
164 """ Reimplemented to return IPython's default banner.
165 """
165 """
166 return default_banner + '\n'
166 return default_banner + '\n'
167
167
168 def _process_execute_error(self, msg):
168 def _process_execute_error(self, msg):
169 """ Reimplemented for IPython-style traceback formatting.
169 """ Reimplemented for IPython-style traceback formatting.
170 """
170 """
171 content = msg['content']
171 content = msg['content']
172 traceback = '\n'.join(content['traceback']) + '\n'
172 traceback = '\n'.join(content['traceback']) + '\n'
173 if False:
173 if False:
174 # FIXME: For now, tracebacks come as plain text, so we can't use
174 # FIXME: For now, tracebacks come as plain text, so we can't use
175 # the html renderer yet. Once we refactor ultratb to produce
175 # the html renderer yet. Once we refactor ultratb to produce
176 # properly styled tracebacks, this branch should be the default
176 # properly styled tracebacks, this branch should be the default
177 traceback = traceback.replace(' ', '&nbsp;')
177 traceback = traceback.replace(' ', '&nbsp;')
178 traceback = traceback.replace('\n', '<br/>')
178 traceback = traceback.replace('\n', '<br/>')
179
179
180 ename = content['ename']
180 ename = content['ename']
181 ename_styled = '<span class="error">%s</span>' % ename
181 ename_styled = '<span class="error">%s</span>' % ename
182 traceback = traceback.replace(ename, ename_styled)
182 traceback = traceback.replace(ename, ename_styled)
183
183
184 self._append_html(traceback)
184 self._append_html(traceback)
185 else:
185 else:
186 # This is the fallback for now, using plain text with ansi escapes
186 # This is the fallback for now, using plain text with ansi escapes
187 self._append_plain_text(traceback)
187 self._append_plain_text(traceback)
188
188
189 def _process_execute_payload(self, item):
189 def _process_execute_payload(self, item):
190 """ Reimplemented to handle %edit and paging payloads.
190 """ Reimplemented to handle %edit and paging payloads.
191 """
191 """
192 if item['source'] == self._payload_source_edit:
192 if item['source'] == self._payload_source_edit:
193 self._edit(item['filename'], item['line_number'])
193 self._edit(item['filename'], item['line_number'])
194 return True
194 return True
195 elif item['source'] == self._payload_source_page:
195 elif item['source'] == self._payload_source_page:
196 self._page(item['data'])
196 self._page(item['data'])
197 return True
197 return True
198 else:
198 else:
199 return False
199 return False
200
200
201 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
201 def _show_interpreter_prompt(self, number=None, input_sep='\n'):
202 """ Reimplemented for IPython-style prompts.
202 """ Reimplemented for IPython-style prompts.
203 """
203 """
204 # If a number was not specified, make a prompt number request.
204 # If a number was not specified, make a prompt number request.
205 if number is None:
205 if number is None:
206 self.kernel_manager.xreq_channel.prompt()
206 self.kernel_manager.xreq_channel.prompt()
207 return
207 return
208
208
209 # Show a new prompt and save information about it so that it can be
209 # Show a new prompt and save information about it so that it can be
210 # updated later if the prompt number turns out to be wrong.
210 # updated later if the prompt number turns out to be wrong.
211 self._prompt_sep = input_sep
211 self._prompt_sep = input_sep
212 self._show_prompt(self._make_in_prompt(number), html=True)
212 self._show_prompt(self._make_in_prompt(number), html=True)
213 block = self._control.document().lastBlock()
213 block = self._control.document().lastBlock()
214 length = len(self._prompt)
214 length = len(self._prompt)
215 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
215 self._previous_prompt_obj = IPythonPromptBlock(block, length, number)
216
216
217 # Update continuation prompt to reflect (possibly) new prompt length.
217 # Update continuation prompt to reflect (possibly) new prompt length.
218 self._set_continuation_prompt(
218 self._set_continuation_prompt(
219 self._make_continuation_prompt(self._prompt), html=True)
219 self._make_continuation_prompt(self._prompt), html=True)
220
220
221 def _show_interpreter_prompt_for_reply(self, msg):
221 def _show_interpreter_prompt_for_reply(self, msg):
222 """ Reimplemented for IPython-style prompts.
222 """ Reimplemented for IPython-style prompts.
223 """
223 """
224 # Update the old prompt number if necessary.
224 # Update the old prompt number if necessary.
225 content = msg['content']
225 content = msg['content']
226 previous_prompt_number = content['prompt_number']
226 previous_prompt_number = content['prompt_number']
227 if self._previous_prompt_obj and \
227 if self._previous_prompt_obj and \
228 self._previous_prompt_obj.number != previous_prompt_number:
228 self._previous_prompt_obj.number != previous_prompt_number:
229 block = self._previous_prompt_obj.block
229 block = self._previous_prompt_obj.block
230
230
231 # Make sure the prompt block has not been erased.
231 # Make sure the prompt block has not been erased.
232 if block.isValid() and not block.text().isEmpty():
232 if block.isValid() and not block.text().isEmpty():
233
233
234 # Remove the old prompt and insert a new prompt.
234 # Remove the old prompt and insert a new prompt.
235 cursor = QtGui.QTextCursor(block)
235 cursor = QtGui.QTextCursor(block)
236 cursor.movePosition(QtGui.QTextCursor.Right,
236 cursor.movePosition(QtGui.QTextCursor.Right,
237 QtGui.QTextCursor.KeepAnchor,
237 QtGui.QTextCursor.KeepAnchor,
238 self._previous_prompt_obj.length)
238 self._previous_prompt_obj.length)
239 prompt = self._make_in_prompt(previous_prompt_number)
239 prompt = self._make_in_prompt(previous_prompt_number)
240 self._prompt = self._insert_html_fetching_plain_text(
240 self._prompt = self._insert_html_fetching_plain_text(
241 cursor, prompt)
241 cursor, prompt)
242
242
243 # When the HTML is inserted, Qt blows away the syntax
243 # When the HTML is inserted, Qt blows away the syntax
244 # highlighting for the line, so we need to rehighlight it.
244 # highlighting for the line, so we need to rehighlight it.
245 self._highlighter.rehighlightBlock(cursor.block())
245 self._highlighter.rehighlightBlock(cursor.block())
246
246
247 self._previous_prompt_obj = None
247 self._previous_prompt_obj = None
248
248
249 # Show a new prompt with the kernel's estimated prompt number.
249 # Show a new prompt with the kernel's estimated prompt number.
250 next_prompt = content['next_prompt']
250 next_prompt = content['next_prompt']
251 self._show_interpreter_prompt(next_prompt['prompt_number'],
251 self._show_interpreter_prompt(next_prompt['prompt_number'],
252 next_prompt['input_sep'])
252 next_prompt['input_sep'])
253
253
254 #---------------------------------------------------------------------------
254 #---------------------------------------------------------------------------
255 # 'IPythonWidget' interface
255 # 'IPythonWidget' interface
256 #---------------------------------------------------------------------------
256 #---------------------------------------------------------------------------
257
257
258 def reset_styling(self):
258 def reset_styling(self):
259 """ Restores the default IPythonWidget styling.
259 """ Restores the default IPythonWidget styling.
260 """
260 """
261 self.set_styling(self.default_stylesheet, syntax_style='default')
261 self.set_styling(self.default_stylesheet, syntax_style='default')
262 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
262 #self.set_styling(self.dark_stylesheet, syntax_style='monokai')
263
263
264 def set_editor(self, editor, line_editor=None):
264 def set_editor(self, editor, line_editor=None):
265 """ Sets the editor to use with the %edit magic.
265 """ Sets the editor to use with the %edit magic.
266
266
267 Parameters:
267 Parameters:
268 -----------
268 -----------
269 editor : str
269 editor : str
270 A command for invoking a system text editor. If the string contains
270 A command for invoking a system text editor. If the string contains
271 a {filename} format specifier, it will be used. Otherwise, the
271 a {filename} format specifier, it will be used. Otherwise, the
272 filename will be appended to the end the command.
272 filename will be appended to the end the command.
273
273
274 This parameter also takes a special value:
274 This parameter also takes a special value:
275 'custom' : Emit a 'custom_edit_requested(str, int)' signal
275 'custom' : Emit a 'custom_edit_requested(str, int)' signal
276 instead of opening an editor.
276 instead of opening an editor.
277
277
278 line_editor : str, optional
278 line_editor : str, optional
279 The editor command to use when a specific line number is
279 The editor command to use when a specific line number is
280 requested. The string should contain two format specifiers: {line}
280 requested. The string should contain two format specifiers: {line}
281 and {filename}. If this parameter is not specified, the line number
281 and {filename}. If this parameter is not specified, the line number
282 option to the %edit magic will be ignored.
282 option to the %edit magic will be ignored.
283 """
283 """
284 self._editor = editor
284 self._editor = editor
285 self._editor_line = line_editor
285 self._editor_line = line_editor
286
286
287 def set_styling(self, stylesheet, syntax_style=None):
287 def set_styling(self, stylesheet, syntax_style=None):
288 """ Sets the IPythonWidget styling.
288 """ Sets the IPythonWidget styling.
289
289
290 Parameters:
290 Parameters:
291 -----------
291 -----------
292 stylesheet : str
292 stylesheet : str
293 A CSS stylesheet. The stylesheet can contain classes for:
293 A CSS stylesheet. The stylesheet can contain classes for:
294 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
294 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
295 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
295 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
296 3. IPython: .error, .in-prompt, .out-prompt, etc.
296 3. IPython: .error, .in-prompt, .out-prompt, etc.
297
297
298 syntax_style : str or None [default None]
298 syntax_style : str or None [default None]
299 If specified, use the Pygments style with given name. Otherwise,
299 If specified, use the Pygments style with given name. Otherwise,
300 the stylesheet is queried for Pygments style information.
300 the stylesheet is queried for Pygments style information.
301 """
301 """
302 self.setStyleSheet(stylesheet)
302 self.setStyleSheet(stylesheet)
303 self._control.document().setDefaultStyleSheet(stylesheet)
303 self._control.document().setDefaultStyleSheet(stylesheet)
304 if self._page_control:
304 if self._page_control:
305 self._page_control.document().setDefaultStyleSheet(stylesheet)
305 self._page_control.document().setDefaultStyleSheet(stylesheet)
306
306
307 if syntax_style is None:
307 if syntax_style is None:
308 self._highlighter.set_style_sheet(stylesheet)
308 self._highlighter.set_style_sheet(stylesheet)
309 else:
309 else:
310 self._highlighter.set_style(syntax_style)
310 self._highlighter.set_style(syntax_style)
311
311
312 bg_color = self._control.palette().background().color()
313 self._ansi_processor.set_background_color(bg_color)
314
312 #---------------------------------------------------------------------------
315 #---------------------------------------------------------------------------
313 # 'IPythonWidget' protected interface
316 # 'IPythonWidget' protected interface
314 #---------------------------------------------------------------------------
317 #---------------------------------------------------------------------------
315
318
316 def _edit(self, filename, line=None):
319 def _edit(self, filename, line=None):
317 """ Opens a Python script for editing.
320 """ Opens a Python script for editing.
318
321
319 Parameters:
322 Parameters:
320 -----------
323 -----------
321 filename : str
324 filename : str
322 A path to a local system file.
325 A path to a local system file.
323
326
324 line : int, optional
327 line : int, optional
325 A line of interest in the file.
328 A line of interest in the file.
326 """
329 """
327 if self._editor == 'custom':
330 if self._editor == 'custom':
328 self.custom_edit_requested.emit(filename, line)
331 self.custom_edit_requested.emit(filename, line)
329 elif self._editor == 'default':
332 elif self._editor == 'default':
330 self._append_plain_text('No default editor available.\n')
333 self._append_plain_text('No default editor available.\n')
331 else:
334 else:
332 try:
335 try:
333 filename = '"%s"' % filename
336 filename = '"%s"' % filename
334 if line and self._editor_line:
337 if line and self._editor_line:
335 command = self._editor_line.format(filename=filename,
338 command = self._editor_line.format(filename=filename,
336 line=line)
339 line=line)
337 else:
340 else:
338 try:
341 try:
339 command = self._editor.format()
342 command = self._editor.format()
340 except KeyError:
343 except KeyError:
341 command = self._editor.format(filename=filename)
344 command = self._editor.format(filename=filename)
342 else:
345 else:
343 command += ' ' + filename
346 command += ' ' + filename
344 except KeyError:
347 except KeyError:
345 self._append_plain_text('Invalid editor command.\n')
348 self._append_plain_text('Invalid editor command.\n')
346 else:
349 else:
347 try:
350 try:
348 Popen(command, shell=True)
351 Popen(command, shell=True)
349 except OSError:
352 except OSError:
350 msg = 'Opening editor with command "%s" failed.\n'
353 msg = 'Opening editor with command "%s" failed.\n'
351 self._append_plain_text(msg % command)
354 self._append_plain_text(msg % command)
352
355
353 def _make_in_prompt(self, number):
356 def _make_in_prompt(self, number):
354 """ Given a prompt number, returns an HTML In prompt.
357 """ Given a prompt number, returns an HTML In prompt.
355 """
358 """
356 body = self.in_prompt % number
359 body = self.in_prompt % number
357 return '<span class="in-prompt">%s</span>' % body
360 return '<span class="in-prompt">%s</span>' % body
358
361
359 def _make_continuation_prompt(self, prompt):
362 def _make_continuation_prompt(self, prompt):
360 """ Given a plain text version of an In prompt, returns an HTML
363 """ Given a plain text version of an In prompt, returns an HTML
361 continuation prompt.
364 continuation prompt.
362 """
365 """
363 end_chars = '...: '
366 end_chars = '...: '
364 space_count = len(prompt.lstrip('\n')) - len(end_chars)
367 space_count = len(prompt.lstrip('\n')) - len(end_chars)
365 body = '&nbsp;' * space_count + end_chars
368 body = '&nbsp;' * space_count + end_chars
366 return '<span class="in-prompt">%s</span>' % body
369 return '<span class="in-prompt">%s</span>' % body
367
370
368 def _make_out_prompt(self, number):
371 def _make_out_prompt(self, number):
369 """ Given a prompt number, returns an HTML Out prompt.
372 """ Given a prompt number, returns an HTML Out prompt.
370 """
373 """
371 body = self.out_prompt % number
374 body = self.out_prompt % number
372 return '<span class="out-prompt">%s</span>' % body
375 return '<span class="out-prompt">%s</span>' % body
@@ -1,92 +1,88 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 """ A minimal application using the Qt console-style IPython frontend.
3 """ A minimal application using the Qt console-style IPython frontend.
4 """
4 """
5
5
6 # Systemm library imports
6 # Systemm library imports
7 from PyQt4 import QtCore, QtGui
7 from PyQt4 import QtCore, QtGui
8
8
9 # Local imports
9 # Local imports
10 from IPython.external.argparse import ArgumentParser
10 from IPython.external.argparse import ArgumentParser
11 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
11 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
12 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
12 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
13 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
13 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
14 from IPython.frontend.qt.kernelmanager import QtKernelManager
14 from IPython.frontend.qt.kernelmanager import QtKernelManager
15
15
16 # Constants
16 # Constants
17 LOCALHOST = '127.0.0.1'
17 LOCALHOST = '127.0.0.1'
18
18
19
19
20 def main():
20 def main():
21 """ Entry point for application.
21 """ Entry point for application.
22 """
22 """
23 # Parse command line arguments.
23 # Parse command line arguments.
24 parser = ArgumentParser()
24 parser = ArgumentParser()
25 parser.add_argument('-r', '--rich', action='store_true',
25 parser.add_argument('-r', '--rich', action='store_true',
26 help='use a rich text frontend')
26 help='use a rich text frontend')
27 parser.add_argument('-t', '--tab-simple', action='store_true',
27 parser.add_argument('-t', '--tab-simple', action='store_true',
28 help='do tab completion ala a Unix terminal')
28 help='do tab completion ala a Unix terminal')
29
29
30 parser.add_argument('--existing', action='store_true',
30 parser.add_argument('--existing', action='store_true',
31 help='connect to an existing kernel')
31 help='connect to an existing kernel')
32 parser.add_argument('--ip', type=str, default=LOCALHOST,
32 parser.add_argument('--ip', type=str, default=LOCALHOST,
33 help='set the kernel\'s IP address [default localhost]')
33 help='set the kernel\'s IP address [default localhost]')
34 parser.add_argument('--xreq', type=int, metavar='PORT', default=0,
34 parser.add_argument('--xreq', type=int, metavar='PORT', default=0,
35 help='set the XREQ channel port [default random]')
35 help='set the XREQ channel port [default random]')
36 parser.add_argument('--sub', type=int, metavar='PORT', default=0,
36 parser.add_argument('--sub', type=int, metavar='PORT', default=0,
37 help='set the SUB channel port [default random]')
37 help='set the SUB channel port [default random]')
38 parser.add_argument('--rep', type=int, metavar='PORT', default=0,
38 parser.add_argument('--rep', type=int, metavar='PORT', default=0,
39 help='set the REP channel port [default random]')
39 help='set the REP channel port [default random]')
40
40
41 group = parser.add_mutually_exclusive_group()
41 group = parser.add_mutually_exclusive_group()
42 group.add_argument('--pure', action='store_true', help = \
42 group.add_argument('--pure', action='store_true', help = \
43 'use a pure Python kernel instead of an IPython kernel')
43 'use a pure Python kernel instead of an IPython kernel')
44 group.add_argument('--pylab', action='store_true',
44 group.add_argument('--pylab', action='store_true',
45 help='use a kernel with PyLab enabled')
45 help='use a kernel with PyLab enabled')
46
46
47 args = parser.parse_args()
47 args = parser.parse_args()
48
48
49 # Don't let Qt or ZMQ swallow KeyboardInterupts.
49 # Don't let Qt or ZMQ swallow KeyboardInterupts.
50 import signal
50 import signal
51 signal.signal(signal.SIGINT, signal.SIG_DFL)
51 signal.signal(signal.SIGINT, signal.SIG_DFL)
52
52
53 # Create a KernelManager and start a kernel.
53 # Create a KernelManager and start a kernel.
54 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
54 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
55 sub_address=(args.ip, args.sub),
55 sub_address=(args.ip, args.sub),
56 rep_address=(args.ip, args.rep))
56 rep_address=(args.ip, args.rep))
57 if args.ip == LOCALHOST and not args.existing:
57 if args.ip == LOCALHOST and not args.existing:
58 if args.pure:
58 if args.pure:
59 kernel_manager.start_kernel(ipython=False)
59 kernel_manager.start_kernel(ipython=False)
60 elif args.pylab:
60 elif args.pylab:
61 if args.rich:
61 if args.rich:
62 kernel_manager.start_kernel(pylab='payload-svg')
62 kernel_manager.start_kernel(pylab='payload-svg')
63 else:
63 else:
64 kernel_manager.start_kernel(pylab='qt4')
64 kernel_manager.start_kernel(pylab='qt4')
65 else:
65 else:
66 kernel_manager.start_kernel()
66 kernel_manager.start_kernel()
67 kernel_manager.start_channels()
67 kernel_manager.start_channels()
68
68
69 # Create the widget.
69 # Create the widget.
70 app = QtGui.QApplication([])
70 app = QtGui.QApplication([])
71 if args.pure:
71 if args.pure:
72 kind = 'rich' if args.rich else 'plain'
72 kind = 'rich' if args.rich else 'plain'
73 widget = FrontendWidget(kind=kind)
73 widget = FrontendWidget(kind=kind)
74 elif args.rich:
74 elif args.rich:
75 widget = RichIPythonWidget()
75 widget = RichIPythonWidget()
76 else:
76 else:
77 widget = IPythonWidget()
77 widget = IPythonWidget()
78 widget.gui_completion = not args.tab_simple
78 widget.gui_completion = not args.tab_simple
79 widget.kernel_manager = kernel_manager
79 widget.kernel_manager = kernel_manager
80 widget.setWindowTitle('Python' if args.pure else 'IPython')
80 widget.setWindowTitle('Python' if args.pure else 'IPython')
81 widget.show()
81 widget.show()
82
82
83 # FIXME: This is a hack: set colors to lightbg by default in qt terminal
84 # unconditionally, regardless of user settings in config files.
85 widget.execute("%colors lightbg", hidden=True)
86
87 # Start the application main loop.
83 # Start the application main loop.
88 app.exec_()
84 app.exec_()
89
85
90
86
91 if __name__ == '__main__':
87 if __name__ == '__main__':
92 main()
88 main()
General Comments 0
You need to be logged in to leave comments. Login now