##// END OF EJS Templates
added --colors flag to ipythonqt
MinRK -
Show More
@@ -1,456 +1,460 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 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 # Standard library imports
13 # Standard library imports
14 from collections import namedtuple
14 from collections import namedtuple
15 import re
15 import re
16 from subprocess import Popen
16 from subprocess import Popen
17 from textwrap import dedent
17 from textwrap import dedent
18
18
19 # System library imports
19 # System library imports
20 from PyQt4 import QtCore, QtGui
20 from PyQt4 import QtCore, QtGui
21
21
22 # Local imports
22 # Local imports
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
24 transform_ipy_prompt
24 transform_ipy_prompt
25 from IPython.core.usage import default_gui_banner
25 from IPython.core.usage import default_gui_banner
26 from IPython.utils.traitlets import Bool, Str
26 from IPython.utils.traitlets import Bool, Str
27 from frontend_widget import FrontendWidget
27 from frontend_widget import FrontendWidget
28 from styles import (default_light_style_sheet, default_dark_style_sheet,
28 from styles import (default_light_style_sheet, default_light_syntax_style,
29 default_light_syntax_style, default_dark_syntax_style)
29 default_dark_style_sheet, default_dark_syntax_style,
30 default_bw_style_sheet, default_bw_syntax_style)
30
31
31 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
32 # Constants
33 # Constants
33 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
34
35
35 # Default strings to build and display input and output prompts (and separators
36 # Default strings to build and display input and output prompts (and separators
36 # in between)
37 # in between)
37 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
38 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
38 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
39 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
39 default_input_sep = '\n'
40 default_input_sep = '\n'
40 default_output_sep = ''
41 default_output_sep = ''
41 default_output_sep2 = ''
42 default_output_sep2 = ''
42
43
43 # Base path for most payload sources.
44 # Base path for most payload sources.
44 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
45 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
45
46
46 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
47 # IPythonWidget class
48 # IPythonWidget class
48 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
49
50
50 class IPythonWidget(FrontendWidget):
51 class IPythonWidget(FrontendWidget):
51 """ A FrontendWidget for an IPython kernel.
52 """ A FrontendWidget for an IPython kernel.
52 """
53 """
53
54
54 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 # settings.
57 # settings.
57 custom_edit = Bool(False)
58 custom_edit = Bool(False)
58 custom_edit_requested = QtCore.pyqtSignal(object, object)
59 custom_edit_requested = QtCore.pyqtSignal(object, object)
59
60
60 # A command for invoking a system text editor. If the string contains a
61 # A command for invoking a system text editor. If the string contains a
61 # {filename} format specifier, it will be used. Otherwise, the filename will
62 # {filename} format specifier, it will be used. Otherwise, the filename will
62 # be appended to the end the command.
63 # be appended to the end the command.
63 editor = Str('default', config=True)
64 editor = Str('default', config=True)
64
65
65 # The editor command to use when a specific line number is requested. The
66 # The editor command to use when a specific line number is requested. The
66 # string should contain two format specifiers: {line} and {filename}. If
67 # string should contain two format specifiers: {line} and {filename}. If
67 # this parameter is not specified, the line number option to the %edit magic
68 # this parameter is not specified, the line number option to the %edit magic
68 # will be ignored.
69 # will be ignored.
69 editor_line = Str(config=True)
70 editor_line = Str(config=True)
70
71
71 # A CSS stylesheet. The stylesheet can contain classes for:
72 # A CSS stylesheet. The stylesheet can contain classes for:
72 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
73 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
73 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
74 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
74 # 3. IPython: .error, .in-prompt, .out-prompt, etc
75 # 3. IPython: .error, .in-prompt, .out-prompt, etc
75 style_sheet = Str(config=True)
76 style_sheet = Str(config=True)
76
77
77 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
78 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
78 # the style sheet is queried for Pygments style information.
79 # the style sheet is queried for Pygments style information.
79 syntax_style = Str(config=True)
80 syntax_style = Str(config=True)
80
81
81 # Prompts.
82 # Prompts.
82 in_prompt = Str(default_in_prompt, config=True)
83 in_prompt = Str(default_in_prompt, config=True)
83 out_prompt = Str(default_out_prompt, config=True)
84 out_prompt = Str(default_out_prompt, config=True)
84 input_sep = Str(default_input_sep, config=True)
85 input_sep = Str(default_input_sep, config=True)
85 output_sep = Str(default_output_sep, config=True)
86 output_sep = Str(default_output_sep, config=True)
86 output_sep2 = Str(default_output_sep2, config=True)
87 output_sep2 = Str(default_output_sep2, config=True)
87
88
88 # FrontendWidget protected class variables.
89 # FrontendWidget protected class variables.
89 _input_splitter_class = IPythonInputSplitter
90 _input_splitter_class = IPythonInputSplitter
90
91
91 # IPythonWidget protected class variables.
92 # IPythonWidget protected class variables.
92 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
93 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
93 _payload_source_edit = zmq_shell_source + '.edit_magic'
94 _payload_source_edit = zmq_shell_source + '.edit_magic'
94 _payload_source_exit = zmq_shell_source + '.ask_exit'
95 _payload_source_exit = zmq_shell_source + '.ask_exit'
95 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
96 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
96 _payload_source_page = 'IPython.zmq.page.page'
97 _payload_source_page = 'IPython.zmq.page.page'
97
98
98 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
99 # 'object' interface
100 # 'object' interface
100 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
101
102
102 def __init__(self, *args, **kw):
103 def __init__(self, *args, **kw):
103 super(IPythonWidget, self).__init__(*args, **kw)
104 super(IPythonWidget, self).__init__(*args, **kw)
104
105
105 # IPythonWidget protected variables.
106 # IPythonWidget protected variables.
106 self._code_to_load = None
107 self._code_to_load = None
107 self._payload_handlers = {
108 self._payload_handlers = {
108 self._payload_source_edit : self._handle_payload_edit,
109 self._payload_source_edit : self._handle_payload_edit,
109 self._payload_source_exit : self._handle_payload_exit,
110 self._payload_source_exit : self._handle_payload_exit,
110 self._payload_source_page : self._handle_payload_page,
111 self._payload_source_page : self._handle_payload_page,
111 self._payload_source_loadpy : self._handle_payload_loadpy }
112 self._payload_source_loadpy : self._handle_payload_loadpy }
112 self._previous_prompt_obj = None
113 self._previous_prompt_obj = None
113
114
114 # Initialize widget styling.
115 # Initialize widget styling.
115 if self.style_sheet:
116 if self.style_sheet:
116 self._style_sheet_changed()
117 self._style_sheet_changed()
117 self._syntax_style_changed()
118 self._syntax_style_changed()
118 else:
119 else:
119 self.set_default_style()
120 self.set_default_style()
120
121
121 #---------------------------------------------------------------------------
122 #---------------------------------------------------------------------------
122 # 'BaseFrontendMixin' abstract interface
123 # 'BaseFrontendMixin' abstract interface
123 #---------------------------------------------------------------------------
124 #---------------------------------------------------------------------------
124
125
125 def _handle_complete_reply(self, rep):
126 def _handle_complete_reply(self, rep):
126 """ Reimplemented to support IPython's improved completion machinery.
127 """ Reimplemented to support IPython's improved completion machinery.
127 """
128 """
128 cursor = self._get_cursor()
129 cursor = self._get_cursor()
129 info = self._request_info.get('complete')
130 info = self._request_info.get('complete')
130 if info and info.id == rep['parent_header']['msg_id'] and \
131 if info and info.id == rep['parent_header']['msg_id'] and \
131 info.pos == cursor.position():
132 info.pos == cursor.position():
132 matches = rep['content']['matches']
133 matches = rep['content']['matches']
133 text = rep['content']['matched_text']
134 text = rep['content']['matched_text']
134 offset = len(text)
135 offset = len(text)
135
136
136 # Clean up matches with period and path separators if the matched
137 # Clean up matches with period and path separators if the matched
137 # text has not been transformed. This is done by truncating all
138 # text has not been transformed. This is done by truncating all
138 # but the last component and then suitably decreasing the offset
139 # but the last component and then suitably decreasing the offset
139 # between the current cursor position and the start of completion.
140 # between the current cursor position and the start of completion.
140 if len(matches) > 1 and matches[0][:offset] == text:
141 if len(matches) > 1 and matches[0][:offset] == text:
141 parts = re.split(r'[./\\]', text)
142 parts = re.split(r'[./\\]', text)
142 sep_count = len(parts) - 1
143 sep_count = len(parts) - 1
143 if sep_count:
144 if sep_count:
144 chop_length = sum(map(len, parts[:sep_count])) + sep_count
145 chop_length = sum(map(len, parts[:sep_count])) + sep_count
145 matches = [ match[chop_length:] for match in matches ]
146 matches = [ match[chop_length:] for match in matches ]
146 offset -= chop_length
147 offset -= chop_length
147
148
148 # Move the cursor to the start of the match and complete.
149 # Move the cursor to the start of the match and complete.
149 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
150 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
150 self._complete_with_items(cursor, matches)
151 self._complete_with_items(cursor, matches)
151
152
152 def _handle_execute_reply(self, msg):
153 def _handle_execute_reply(self, msg):
153 """ Reimplemented to support prompt requests.
154 """ Reimplemented to support prompt requests.
154 """
155 """
155 info = self._request_info.get('execute')
156 info = self._request_info.get('execute')
156 if info and info.id == msg['parent_header']['msg_id']:
157 if info and info.id == msg['parent_header']['msg_id']:
157 if info.kind == 'prompt':
158 if info.kind == 'prompt':
158 number = msg['content']['execution_count'] + 1
159 number = msg['content']['execution_count'] + 1
159 self._show_interpreter_prompt(number)
160 self._show_interpreter_prompt(number)
160 else:
161 else:
161 super(IPythonWidget, self)._handle_execute_reply(msg)
162 super(IPythonWidget, self)._handle_execute_reply(msg)
162
163
163 def _handle_history_reply(self, msg):
164 def _handle_history_reply(self, msg):
164 """ Implemented to handle history replies, which are only supported by
165 """ Implemented to handle history replies, which are only supported by
165 the IPython kernel.
166 the IPython kernel.
166 """
167 """
167 history_dict = msg['content']['history']
168 history_dict = msg['content']['history']
168 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
169 items = [ history_dict[key] for key in sorted(history_dict.keys()) ]
169 self._set_history(items)
170 self._set_history(items)
170
171
171 def _handle_pyout(self, msg):
172 def _handle_pyout(self, msg):
172 """ Reimplemented for IPython-style "display hook".
173 """ Reimplemented for IPython-style "display hook".
173 """
174 """
174 if not self._hidden and self._is_from_this_session(msg):
175 if not self._hidden and self._is_from_this_session(msg):
175 content = msg['content']
176 content = msg['content']
176 prompt_number = content['execution_count']
177 prompt_number = content['execution_count']
177 self._append_plain_text(self.output_sep)
178 self._append_plain_text(self.output_sep)
178 self._append_html(self._make_out_prompt(prompt_number))
179 self._append_html(self._make_out_prompt(prompt_number))
179 self._append_plain_text(content['data']+self.output_sep2)
180 self._append_plain_text(content['data']+self.output_sep2)
180
181
181 def _started_channels(self):
182 def _started_channels(self):
182 """ Reimplemented to make a history request.
183 """ Reimplemented to make a history request.
183 """
184 """
184 super(IPythonWidget, self)._started_channels()
185 super(IPythonWidget, self)._started_channels()
185 # FIXME: Disabled until history requests are properly implemented.
186 # FIXME: Disabled until history requests are properly implemented.
186 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
187 #self.kernel_manager.xreq_channel.history(raw=True, output=False)
187
188
188 #---------------------------------------------------------------------------
189 #---------------------------------------------------------------------------
189 # 'ConsoleWidget' public interface
190 # 'ConsoleWidget' public interface
190 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
191
192
192 def copy(self):
193 def copy(self):
193 """ Copy the currently selected text to the clipboard, removing prompts
194 """ Copy the currently selected text to the clipboard, removing prompts
194 if possible.
195 if possible.
195 """
196 """
196 text = unicode(self._control.textCursor().selection().toPlainText())
197 text = unicode(self._control.textCursor().selection().toPlainText())
197 if text:
198 if text:
198 lines = map(transform_ipy_prompt, text.splitlines())
199 lines = map(transform_ipy_prompt, text.splitlines())
199 text = '\n'.join(lines)
200 text = '\n'.join(lines)
200 QtGui.QApplication.clipboard().setText(text)
201 QtGui.QApplication.clipboard().setText(text)
201
202
202 #---------------------------------------------------------------------------
203 #---------------------------------------------------------------------------
203 # 'FrontendWidget' public interface
204 # 'FrontendWidget' public interface
204 #---------------------------------------------------------------------------
205 #---------------------------------------------------------------------------
205
206
206 def execute_file(self, path, hidden=False):
207 def execute_file(self, path, hidden=False):
207 """ Reimplemented to use the 'run' magic.
208 """ Reimplemented to use the 'run' magic.
208 """
209 """
209 self.execute('%%run %s' % path, hidden=hidden)
210 self.execute('%%run %s' % path, hidden=hidden)
210
211
211 #---------------------------------------------------------------------------
212 #---------------------------------------------------------------------------
212 # 'FrontendWidget' protected interface
213 # 'FrontendWidget' protected interface
213 #---------------------------------------------------------------------------
214 #---------------------------------------------------------------------------
214
215
215 def _complete(self):
216 def _complete(self):
216 """ Reimplemented to support IPython's improved completion machinery.
217 """ Reimplemented to support IPython's improved completion machinery.
217 """
218 """
218 # We let the kernel split the input line, so we *always* send an empty
219 # We let the kernel split the input line, so we *always* send an empty
219 # text field. Readline-based frontends do get a real text field which
220 # text field. Readline-based frontends do get a real text field which
220 # they can use.
221 # they can use.
221 text = ''
222 text = ''
222
223
223 # Send the completion request to the kernel
224 # Send the completion request to the kernel
224 msg_id = self.kernel_manager.xreq_channel.complete(
225 msg_id = self.kernel_manager.xreq_channel.complete(
225 text, # text
226 text, # text
226 self._get_input_buffer_cursor_line(), # line
227 self._get_input_buffer_cursor_line(), # line
227 self._get_input_buffer_cursor_column(), # cursor_pos
228 self._get_input_buffer_cursor_column(), # cursor_pos
228 self.input_buffer) # block
229 self.input_buffer) # block
229 pos = self._get_cursor().position()
230 pos = self._get_cursor().position()
230 info = self._CompletionRequest(msg_id, pos)
231 info = self._CompletionRequest(msg_id, pos)
231 self._request_info['complete'] = info
232 self._request_info['complete'] = info
232
233
233 def _get_banner(self):
234 def _get_banner(self):
234 """ Reimplemented to return IPython's default banner.
235 """ Reimplemented to return IPython's default banner.
235 """
236 """
236 return default_gui_banner
237 return default_gui_banner
237
238
238 def _process_execute_error(self, msg):
239 def _process_execute_error(self, msg):
239 """ Reimplemented for IPython-style traceback formatting.
240 """ Reimplemented for IPython-style traceback formatting.
240 """
241 """
241 content = msg['content']
242 content = msg['content']
242 traceback = '\n'.join(content['traceback']) + '\n'
243 traceback = '\n'.join(content['traceback']) + '\n'
243 if False:
244 if False:
244 # FIXME: For now, tracebacks come as plain text, so we can't use
245 # FIXME: For now, tracebacks come as plain text, so we can't use
245 # the html renderer yet. Once we refactor ultratb to produce
246 # the html renderer yet. Once we refactor ultratb to produce
246 # properly styled tracebacks, this branch should be the default
247 # properly styled tracebacks, this branch should be the default
247 traceback = traceback.replace(' ', '&nbsp;')
248 traceback = traceback.replace(' ', '&nbsp;')
248 traceback = traceback.replace('\n', '<br/>')
249 traceback = traceback.replace('\n', '<br/>')
249
250
250 ename = content['ename']
251 ename = content['ename']
251 ename_styled = '<span class="error">%s</span>' % ename
252 ename_styled = '<span class="error">%s</span>' % ename
252 traceback = traceback.replace(ename, ename_styled)
253 traceback = traceback.replace(ename, ename_styled)
253
254
254 self._append_html(traceback)
255 self._append_html(traceback)
255 else:
256 else:
256 # This is the fallback for now, using plain text with ansi escapes
257 # This is the fallback for now, using plain text with ansi escapes
257 self._append_plain_text(traceback)
258 self._append_plain_text(traceback)
258
259
259 def _process_execute_payload(self, item):
260 def _process_execute_payload(self, item):
260 """ Reimplemented to dispatch payloads to handler methods.
261 """ Reimplemented to dispatch payloads to handler methods.
261 """
262 """
262 handler = self._payload_handlers.get(item['source'])
263 handler = self._payload_handlers.get(item['source'])
263 if handler is None:
264 if handler is None:
264 # We have no handler for this type of payload, simply ignore it
265 # We have no handler for this type of payload, simply ignore it
265 return False
266 return False
266 else:
267 else:
267 handler(item)
268 handler(item)
268 return True
269 return True
269
270
270 def _show_interpreter_prompt(self, number=None):
271 def _show_interpreter_prompt(self, number=None):
271 """ Reimplemented for IPython-style prompts.
272 """ Reimplemented for IPython-style prompts.
272 """
273 """
273 # If a number was not specified, make a prompt number request.
274 # If a number was not specified, make a prompt number request.
274 if number is None:
275 if number is None:
275 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
276 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
276 info = self._ExecutionRequest(msg_id, 'prompt')
277 info = self._ExecutionRequest(msg_id, 'prompt')
277 self._request_info['execute'] = info
278 self._request_info['execute'] = info
278 return
279 return
279
280
280 # Show a new prompt and save information about it so that it can be
281 # Show a new prompt and save information about it so that it can be
281 # updated later if the prompt number turns out to be wrong.
282 # updated later if the prompt number turns out to be wrong.
282 self._prompt_sep = self.input_sep
283 self._prompt_sep = self.input_sep
283 self._show_prompt(self._make_in_prompt(number), html=True)
284 self._show_prompt(self._make_in_prompt(number), html=True)
284 block = self._control.document().lastBlock()
285 block = self._control.document().lastBlock()
285 length = len(self._prompt)
286 length = len(self._prompt)
286 self._previous_prompt_obj = self._PromptBlock(block, length, number)
287 self._previous_prompt_obj = self._PromptBlock(block, length, number)
287
288
288 # Update continuation prompt to reflect (possibly) new prompt length.
289 # Update continuation prompt to reflect (possibly) new prompt length.
289 self._set_continuation_prompt(
290 self._set_continuation_prompt(
290 self._make_continuation_prompt(self._prompt), html=True)
291 self._make_continuation_prompt(self._prompt), html=True)
291
292
292 # Load code from the %loadpy magic, if necessary.
293 # Load code from the %loadpy magic, if necessary.
293 if self._code_to_load is not None:
294 if self._code_to_load is not None:
294 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
295 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
295 self._code_to_load = None
296 self._code_to_load = None
296
297
297 def _show_interpreter_prompt_for_reply(self, msg):
298 def _show_interpreter_prompt_for_reply(self, msg):
298 """ Reimplemented for IPython-style prompts.
299 """ Reimplemented for IPython-style prompts.
299 """
300 """
300 # Update the old prompt number if necessary.
301 # Update the old prompt number if necessary.
301 content = msg['content']
302 content = msg['content']
302 previous_prompt_number = content['execution_count']
303 previous_prompt_number = content['execution_count']
303 if self._previous_prompt_obj and \
304 if self._previous_prompt_obj and \
304 self._previous_prompt_obj.number != previous_prompt_number:
305 self._previous_prompt_obj.number != previous_prompt_number:
305 block = self._previous_prompt_obj.block
306 block = self._previous_prompt_obj.block
306
307
307 # Make sure the prompt block has not been erased.
308 # Make sure the prompt block has not been erased.
308 if block.isValid() and not block.text().isEmpty():
309 if block.isValid() and not block.text().isEmpty():
309
310
310 # Remove the old prompt and insert a new prompt.
311 # Remove the old prompt and insert a new prompt.
311 cursor = QtGui.QTextCursor(block)
312 cursor = QtGui.QTextCursor(block)
312 cursor.movePosition(QtGui.QTextCursor.Right,
313 cursor.movePosition(QtGui.QTextCursor.Right,
313 QtGui.QTextCursor.KeepAnchor,
314 QtGui.QTextCursor.KeepAnchor,
314 self._previous_prompt_obj.length)
315 self._previous_prompt_obj.length)
315 prompt = self._make_in_prompt(previous_prompt_number)
316 prompt = self._make_in_prompt(previous_prompt_number)
316 self._prompt = self._insert_html_fetching_plain_text(
317 self._prompt = self._insert_html_fetching_plain_text(
317 cursor, prompt)
318 cursor, prompt)
318
319
319 # When the HTML is inserted, Qt blows away the syntax
320 # When the HTML is inserted, Qt blows away the syntax
320 # highlighting for the line, so we need to rehighlight it.
321 # highlighting for the line, so we need to rehighlight it.
321 self._highlighter.rehighlightBlock(cursor.block())
322 self._highlighter.rehighlightBlock(cursor.block())
322
323
323 self._previous_prompt_obj = None
324 self._previous_prompt_obj = None
324
325
325 # Show a new prompt with the kernel's estimated prompt number.
326 # Show a new prompt with the kernel's estimated prompt number.
326 self._show_interpreter_prompt(previous_prompt_number + 1)
327 self._show_interpreter_prompt(previous_prompt_number + 1)
327
328
328 #---------------------------------------------------------------------------
329 #---------------------------------------------------------------------------
329 # 'IPythonWidget' interface
330 # 'IPythonWidget' interface
330 #---------------------------------------------------------------------------
331 #---------------------------------------------------------------------------
331
332
332 def set_default_style(self, lightbg=True):
333 def set_default_style(self, colors='light'):
333 """ Sets the widget style to the class defaults.
334 """ Sets the widget style to the class defaults.
334
335
335 Parameters:
336 Parameters:
336 -----------
337 -----------
337 lightbg : bool, optional (default True)
338 colors : str, optional (default light)
338 Whether to use the default IPython light background or dark
339 Whether to use the default IPython light background or dark
339 background style.
340 background or B&W style.
340 """
341 """
341 if lightbg:
342 if colors=='light':
342 self.style_sheet = default_light_style_sheet
343 self.style_sheet = default_light_style_sheet
343 self.syntax_style = default_light_syntax_style
344 self.syntax_style = default_light_syntax_style
344 else:
345 elif colors=='dark':
345 self.style_sheet = default_dark_style_sheet
346 self.style_sheet = default_dark_style_sheet
346 self.syntax_style = default_dark_syntax_style
347 self.syntax_style = default_dark_syntax_style
348 elif colors=='bw':
349 self.style_sheet = default_bw_style_sheet
350 self.syntax_style = default_bw_syntax_style
347
351
348 #---------------------------------------------------------------------------
352 #---------------------------------------------------------------------------
349 # 'IPythonWidget' protected interface
353 # 'IPythonWidget' protected interface
350 #---------------------------------------------------------------------------
354 #---------------------------------------------------------------------------
351
355
352 def _edit(self, filename, line=None):
356 def _edit(self, filename, line=None):
353 """ Opens a Python script for editing.
357 """ Opens a Python script for editing.
354
358
355 Parameters:
359 Parameters:
356 -----------
360 -----------
357 filename : str
361 filename : str
358 A path to a local system file.
362 A path to a local system file.
359
363
360 line : int, optional
364 line : int, optional
361 A line of interest in the file.
365 A line of interest in the file.
362 """
366 """
363 if self.custom_edit:
367 if self.custom_edit:
364 self.custom_edit_requested.emit(filename, line)
368 self.custom_edit_requested.emit(filename, line)
365 elif self.editor == 'default':
369 elif self.editor == 'default':
366 self._append_plain_text('No default editor available.\n')
370 self._append_plain_text('No default editor available.\n')
367 else:
371 else:
368 try:
372 try:
369 filename = '"%s"' % filename
373 filename = '"%s"' % filename
370 if line and self.editor_line:
374 if line and self.editor_line:
371 command = self.editor_line.format(filename=filename,
375 command = self.editor_line.format(filename=filename,
372 line=line)
376 line=line)
373 else:
377 else:
374 try:
378 try:
375 command = self.editor.format()
379 command = self.editor.format()
376 except KeyError:
380 except KeyError:
377 command = self.editor.format(filename=filename)
381 command = self.editor.format(filename=filename)
378 else:
382 else:
379 command += ' ' + filename
383 command += ' ' + filename
380 except KeyError:
384 except KeyError:
381 self._append_plain_text('Invalid editor command.\n')
385 self._append_plain_text('Invalid editor command.\n')
382 else:
386 else:
383 try:
387 try:
384 Popen(command, shell=True)
388 Popen(command, shell=True)
385 except OSError:
389 except OSError:
386 msg = 'Opening editor with command "%s" failed.\n'
390 msg = 'Opening editor with command "%s" failed.\n'
387 self._append_plain_text(msg % command)
391 self._append_plain_text(msg % command)
388
392
389 def _make_in_prompt(self, number):
393 def _make_in_prompt(self, number):
390 """ Given a prompt number, returns an HTML In prompt.
394 """ Given a prompt number, returns an HTML In prompt.
391 """
395 """
392 body = self.in_prompt % number
396 body = self.in_prompt % number
393 return '<span class="in-prompt">%s</span>' % body
397 return '<span class="in-prompt">%s</span>' % body
394
398
395 def _make_continuation_prompt(self, prompt):
399 def _make_continuation_prompt(self, prompt):
396 """ Given a plain text version of an In prompt, returns an HTML
400 """ Given a plain text version of an In prompt, returns an HTML
397 continuation prompt.
401 continuation prompt.
398 """
402 """
399 end_chars = '...: '
403 end_chars = '...: '
400 space_count = len(prompt.lstrip('\n')) - len(end_chars)
404 space_count = len(prompt.lstrip('\n')) - len(end_chars)
401 body = '&nbsp;' * space_count + end_chars
405 body = '&nbsp;' * space_count + end_chars
402 return '<span class="in-prompt">%s</span>' % body
406 return '<span class="in-prompt">%s</span>' % body
403
407
404 def _make_out_prompt(self, number):
408 def _make_out_prompt(self, number):
405 """ Given a prompt number, returns an HTML Out prompt.
409 """ Given a prompt number, returns an HTML Out prompt.
406 """
410 """
407 body = self.out_prompt % number
411 body = self.out_prompt % number
408 return '<span class="out-prompt">%s</span>' % body
412 return '<span class="out-prompt">%s</span>' % body
409
413
410 #------ Payload handlers --------------------------------------------------
414 #------ Payload handlers --------------------------------------------------
411
415
412 # Payload handlers with a generic interface: each takes the opaque payload
416 # Payload handlers with a generic interface: each takes the opaque payload
413 # dict, unpacks it and calls the underlying functions with the necessary
417 # dict, unpacks it and calls the underlying functions with the necessary
414 # arguments.
418 # arguments.
415
419
416 def _handle_payload_edit(self, item):
420 def _handle_payload_edit(self, item):
417 self._edit(item['filename'], item['line_number'])
421 self._edit(item['filename'], item['line_number'])
418
422
419 def _handle_payload_exit(self, item):
423 def _handle_payload_exit(self, item):
420 self.exit_requested.emit()
424 self.exit_requested.emit()
421
425
422 def _handle_payload_loadpy(self, item):
426 def _handle_payload_loadpy(self, item):
423 # Simple save the text of the .py file for later. The text is written
427 # Simple save the text of the .py file for later. The text is written
424 # to the buffer when _prompt_started_hook is called.
428 # to the buffer when _prompt_started_hook is called.
425 self._code_to_load = item['text']
429 self._code_to_load = item['text']
426
430
427 def _handle_payload_page(self, item):
431 def _handle_payload_page(self, item):
428 # Since the plain text widget supports only a very small subset of HTML
432 # Since the plain text widget supports only a very small subset of HTML
429 # and we have no control over the HTML source, we only page HTML
433 # and we have no control over the HTML source, we only page HTML
430 # payloads in the rich text widget.
434 # payloads in the rich text widget.
431 if item['html'] and self.kind == 'rich':
435 if item['html'] and self.kind == 'rich':
432 self._page(item['html'], html=True)
436 self._page(item['html'], html=True)
433 else:
437 else:
434 self._page(item['text'], html=False)
438 self._page(item['text'], html=False)
435
439
436 #------ Trait change handlers ---------------------------------------------
440 #------ Trait change handlers ---------------------------------------------
437
441
438 def _style_sheet_changed(self):
442 def _style_sheet_changed(self):
439 """ Set the style sheets of the underlying widgets.
443 """ Set the style sheets of the underlying widgets.
440 """
444 """
441 self.setStyleSheet(self.style_sheet)
445 self.setStyleSheet(self.style_sheet)
442 self._control.document().setDefaultStyleSheet(self.style_sheet)
446 self._control.document().setDefaultStyleSheet(self.style_sheet)
443 if self._page_control:
447 if self._page_control:
444 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
448 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
445
449
446 bg_color = self._control.palette().background().color()
450 bg_color = self._control.palette().background().color()
447 self._ansi_processor.set_background_color(bg_color)
451 self._ansi_processor.set_background_color(bg_color)
448
452
449 def _syntax_style_changed(self):
453 def _syntax_style_changed(self):
450 """ Set the style for the syntax highlighter.
454 """ Set the style for the syntax highlighter.
451 """
455 """
452 if self.syntax_style:
456 if self.syntax_style:
453 self._highlighter.set_style(self.syntax_style)
457 self._highlighter.set_style(self.syntax_style)
454 else:
458 else:
455 self._highlighter.set_style_sheet(self.style_sheet)
459 self._highlighter.set_style_sheet(self.style_sheet)
456
460
@@ -1,235 +1,247 b''
1 """ A minimal application using the Qt console-style IPython frontend.
1 """ A minimal application using the Qt console-style IPython frontend.
2 """
2 """
3
3
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Imports
5 # Imports
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7
7
8 # Systemm library imports
8 # Systemm library imports
9 from PyQt4 import QtGui
9 from PyQt4 import QtGui
10 from pygments.styles import get_all_styles
10 from pygments.styles import get_all_styles
11 # Local imports
11 # Local imports
12 from IPython.external.argparse import ArgumentParser
12 from IPython.external.argparse import ArgumentParser
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
13 from IPython.frontend.qt.console.frontend_widget import FrontendWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
14 from IPython.frontend.qt.console.ipython_widget import IPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
15 from IPython.frontend.qt.console.rich_ipython_widget import RichIPythonWidget
16 from IPython.frontend.qt.console import styles
16 from IPython.frontend.qt.console import styles
17 from IPython.frontend.qt.kernelmanager import QtKernelManager
17 from IPython.frontend.qt.kernelmanager import QtKernelManager
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Network Constants
20 # Network Constants
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
23 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
24
24
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 # Classes
26 # Classes
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28
28
29 class MainWindow(QtGui.QMainWindow):
29 class MainWindow(QtGui.QMainWindow):
30
30
31 #---------------------------------------------------------------------------
31 #---------------------------------------------------------------------------
32 # 'object' interface
32 # 'object' interface
33 #---------------------------------------------------------------------------
33 #---------------------------------------------------------------------------
34
34
35 def __init__(self, app, frontend, existing=False, may_close=True):
35 def __init__(self, app, frontend, existing=False, may_close=True):
36 """ Create a MainWindow for the specified FrontendWidget.
36 """ Create a MainWindow for the specified FrontendWidget.
37
37
38 The app is passed as an argument to allow for different
38 The app is passed as an argument to allow for different
39 closing behavior depending on whether we are the Kernel's parent.
39 closing behavior depending on whether we are the Kernel's parent.
40
40
41 If existing is True, then this Console does not own the Kernel.
41 If existing is True, then this Console does not own the Kernel.
42
42
43 If may_close is True, then this Console is permitted to close the kernel
43 If may_close is True, then this Console is permitted to close the kernel
44 """
44 """
45 super(MainWindow, self).__init__()
45 super(MainWindow, self).__init__()
46 self._app = app
46 self._app = app
47 self._frontend = frontend
47 self._frontend = frontend
48 self._existing = existing
48 self._existing = existing
49 if existing:
49 if existing:
50 self._may_close = may_close
50 self._may_close = may_close
51 else:
51 else:
52 self._may_close = True
52 self._may_close = True
53 self._frontend.exit_requested.connect(self.close)
53 self._frontend.exit_requested.connect(self.close)
54 self.setCentralWidget(frontend)
54 self.setCentralWidget(frontend)
55
55
56 #---------------------------------------------------------------------------
56 #---------------------------------------------------------------------------
57 # QWidget interface
57 # QWidget interface
58 #---------------------------------------------------------------------------
58 #---------------------------------------------------------------------------
59
59
60 def closeEvent(self, event):
60 def closeEvent(self, event):
61 """ Reimplemented to prompt the user and close the kernel cleanly.
61 """ Reimplemented to prompt the user and close the kernel cleanly.
62 """
62 """
63 kernel_manager = self._frontend.kernel_manager
63 kernel_manager = self._frontend.kernel_manager
64 if kernel_manager and kernel_manager.channels_running:
64 if kernel_manager and kernel_manager.channels_running:
65 title = self.window().windowTitle()
65 title = self.window().windowTitle()
66 cancel = QtGui.QMessageBox.Cancel
66 cancel = QtGui.QMessageBox.Cancel
67 okay = QtGui.QMessageBox.Ok
67 okay = QtGui.QMessageBox.Ok
68 if self._may_close:
68 if self._may_close:
69 msg = "You are closing this Console window."
69 msg = "You are closing this Console window."
70 info = "Would you like to quit the Kernel and all attached Consoles as well?"
70 info = "Would you like to quit the Kernel and all attached Consoles as well?"
71 justthis = QtGui.QPushButton("&No, just this Console", self)
71 justthis = QtGui.QPushButton("&No, just this Console", self)
72 justthis.setShortcut('N')
72 justthis.setShortcut('N')
73 closeall = QtGui.QPushButton("&Yes, quit everything", self)
73 closeall = QtGui.QPushButton("&Yes, quit everything", self)
74 closeall.setShortcut('Y')
74 closeall.setShortcut('Y')
75 box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
75 box = QtGui.QMessageBox(QtGui.QMessageBox.Question, title, msg)
76 box.setInformativeText(info)
76 box.setInformativeText(info)
77 box.addButton(cancel)
77 box.addButton(cancel)
78 box.addButton(justthis, QtGui.QMessageBox.NoRole)
78 box.addButton(justthis, QtGui.QMessageBox.NoRole)
79 box.addButton(closeall, QtGui.QMessageBox.YesRole)
79 box.addButton(closeall, QtGui.QMessageBox.YesRole)
80 box.setDefaultButton(closeall)
80 box.setDefaultButton(closeall)
81 box.setEscapeButton(cancel)
81 box.setEscapeButton(cancel)
82 reply = box.exec_()
82 reply = box.exec_()
83 if reply == 1: # close All
83 if reply == 1: # close All
84 kernel_manager.shutdown_kernel()
84 kernel_manager.shutdown_kernel()
85 #kernel_manager.stop_channels()
85 #kernel_manager.stop_channels()
86 event.accept()
86 event.accept()
87 elif reply == 0: # close Console
87 elif reply == 0: # close Console
88 if not self._existing:
88 if not self._existing:
89 # I have the kernel: don't quit, just close the window
89 # I have the kernel: don't quit, just close the window
90 self._app.setQuitOnLastWindowClosed(False)
90 self._app.setQuitOnLastWindowClosed(False)
91 self.deleteLater()
91 self.deleteLater()
92 event.accept()
92 event.accept()
93 else:
93 else:
94 event.ignore()
94 event.ignore()
95 else:
95 else:
96 reply = QtGui.QMessageBox.question(self, title,
96 reply = QtGui.QMessageBox.question(self, title,
97 "Are you sure you want to close this Console?"+
97 "Are you sure you want to close this Console?"+
98 "\nThe Kernel and other Consoles will remain active.",
98 "\nThe Kernel and other Consoles will remain active.",
99 okay|cancel,
99 okay|cancel,
100 defaultButton=okay
100 defaultButton=okay
101 )
101 )
102 if reply == okay:
102 if reply == okay:
103 event.accept()
103 event.accept()
104 else:
104 else:
105 event.ignore()
105 event.ignore()
106
106
107
107
108 #-----------------------------------------------------------------------------
108 #-----------------------------------------------------------------------------
109 # Main entry point
109 # Main entry point
110 #-----------------------------------------------------------------------------
110 #-----------------------------------------------------------------------------
111
111
112 def main():
112 def main():
113 """ Entry point for application.
113 """ Entry point for application.
114 """
114 """
115 # Parse command line arguments.
115 # Parse command line arguments.
116 parser = ArgumentParser()
116 parser = ArgumentParser()
117 kgroup = parser.add_argument_group('kernel options')
117 kgroup = parser.add_argument_group('kernel options')
118 kgroup.add_argument('-e', '--existing', action='store_true',
118 kgroup.add_argument('-e', '--existing', action='store_true',
119 help='connect to an existing kernel')
119 help='connect to an existing kernel')
120 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
120 kgroup.add_argument('--ip', type=str, default=LOCALHOST,
121 help=\
121 help=\
122 "set the kernel\'s IP address [default localhost].\
122 "set the kernel\'s IP address [default localhost].\
123 If the IP address is something other than localhost, then \
123 If the IP address is something other than localhost, then \
124 Consoles on other machines will be able to connect\
124 Consoles on other machines will be able to connect\
125 to the Kernel, so be careful!")
125 to the Kernel, so be careful!")
126 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
126 kgroup.add_argument('--xreq', type=int, metavar='PORT', default=0,
127 help='set the XREQ channel port [default random]')
127 help='set the XREQ channel port [default random]')
128 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
128 kgroup.add_argument('--sub', type=int, metavar='PORT', default=0,
129 help='set the SUB channel port [default random]')
129 help='set the SUB channel port [default random]')
130 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
130 kgroup.add_argument('--rep', type=int, metavar='PORT', default=0,
131 help='set the REP channel port [default random]')
131 help='set the REP channel port [default random]')
132 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
132 kgroup.add_argument('--hb', type=int, metavar='PORT', default=0,
133 help='set the heartbeat port [default random]')
133 help='set the heartbeat port [default random]')
134
134
135 egroup = kgroup.add_mutually_exclusive_group()
135 egroup = kgroup.add_mutually_exclusive_group()
136 egroup.add_argument('--pure', action='store_true', help = \
136 egroup.add_argument('--pure', action='store_true', help = \
137 'use a pure Python kernel instead of an IPython kernel')
137 'use a pure Python kernel instead of an IPython kernel')
138 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
138 egroup.add_argument('--pylab', type=str, metavar='GUI', nargs='?',
139 const='auto', help = \
139 const='auto', help = \
140 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
140 "Pre-load matplotlib and numpy for interactive use. If GUI is not \
141 given, the GUI backend is matplotlib's, otherwise use one of: \
141 given, the GUI backend is matplotlib's, otherwise use one of: \
142 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
142 ['tk', 'gtk', 'qt', 'wx', 'inline'].")
143
143
144 wgroup = parser.add_argument_group('widget options')
144 wgroup = parser.add_argument_group('widget options')
145 wgroup.add_argument('--paging', type=str, default='inside',
145 wgroup.add_argument('--paging', type=str, default='inside',
146 choices = ['inside', 'hsplit', 'vsplit', 'none'],
146 choices = ['inside', 'hsplit', 'vsplit', 'none'],
147 help='set the paging style [default inside]')
147 help='set the paging style [default inside]')
148 wgroup.add_argument('--rich', action='store_true',
148 wgroup.add_argument('--rich', action='store_true',
149 help='enable rich text support')
149 help='enable rich text support')
150 wgroup.add_argument('--gui-completion', action='store_true',
150 wgroup.add_argument('--gui-completion', action='store_true',
151 help='use a GUI widget for tab completion')
151 help='use a GUI widget for tab completion')
152 wgroup.add_argument('--style', type=str,
152 wgroup.add_argument('--style', type=str,
153 help='specify a pygments style by name. \
153 choices = list(get_all_styles()),
154 Valid are: %s'%(list(get_all_styles())))
154 help='specify a pygments style for by name.')
155 wgroup.add_argument('--stylesheet', type=str,
155 wgroup.add_argument('--stylesheet', type=str,
156 help="path to a custom CSS stylesheet.")
156 help="path to a custom CSS stylesheet.")
157 wgroup.add_argument('--dark', action='store_true',
157 wgroup.add_argument('--colors', type=str,
158 help="use the dark style template instead of lightbg.\
158 help="Set the color scheme (light, dark, or bw). This is guessed\
159 If --style is not specified, the default dark style is used.")
159 based on the pygments style if not set.")
160
160
161 args = parser.parse_args()
161 args = parser.parse_args()
162
162
163 # Don't let Qt or ZMQ swallow KeyboardInterupts.
163 # Don't let Qt or ZMQ swallow KeyboardInterupts.
164 import signal
164 import signal
165 signal.signal(signal.SIGINT, signal.SIG_DFL)
165 signal.signal(signal.SIGINT, signal.SIG_DFL)
166
166
167 # Create a KernelManager and start a kernel.
167 # Create a KernelManager and start a kernel.
168 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
168 kernel_manager = QtKernelManager(xreq_address=(args.ip, args.xreq),
169 sub_address=(args.ip, args.sub),
169 sub_address=(args.ip, args.sub),
170 rep_address=(args.ip, args.rep),
170 rep_address=(args.ip, args.rep),
171 hb_address=(args.ip, args.hb))
171 hb_address=(args.ip, args.hb))
172 if not args.existing:
172 if not args.existing:
173 # if not args.ip in LOCAL_IPS+ALL_ALIAS:
173 # if not args.ip in LOCAL_IPS+ALL_ALIAS:
174 # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
174 # raise ValueError("Must bind a local ip, such as: %s"%LOCAL_IPS)
175
175
176 kwargs = dict(ip=args.ip)
176 kwargs = dict(ip=args.ip)
177 if args.pure:
177 if args.pure:
178 kwargs['ipython']=False
178 kwargs['ipython']=False
179 elif args.pylab:
179 elif args.pylab:
180 kwargs['pylab']=args.pylab
180 kwargs['pylab']=args.pylab
181
181
182 kernel_manager.start_kernel(**kwargs)
182 kernel_manager.start_kernel(**kwargs)
183 kernel_manager.start_channels()
183 kernel_manager.start_channels()
184
184
185 local_kernel = (not args.existing) or args.ip in LOCAL_IPS
185 local_kernel = (not args.existing) or args.ip in LOCAL_IPS
186 # Create the widget.
186 # Create the widget.
187 app = QtGui.QApplication([])
187 app = QtGui.QApplication([])
188 if args.pure:
188 if args.pure:
189 kind = 'rich' if args.rich else 'plain'
189 kind = 'rich' if args.rich else 'plain'
190 widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
190 widget = FrontendWidget(kind=kind, paging=args.paging, local_kernel=local_kernel)
191 elif args.rich or args.pylab:
191 elif args.rich or args.pylab:
192 widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
192 widget = RichIPythonWidget(paging=args.paging, local_kernel=local_kernel)
193 else:
193 else:
194 widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
194 widget = IPythonWidget(paging=args.paging, local_kernel=local_kernel)
195 widget.gui_completion = args.gui_completion
195 widget.gui_completion = args.gui_completion
196 widget.kernel_manager = kernel_manager
196 widget.kernel_manager = kernel_manager
197
197
198 # configure the style:
198 # configure the style:
199 if not args.pure: # only IPythonWidget supports styles
199 if not args.pure: # only IPythonWidget supports styles
200 # parse the colors arg down to current known labels
201 if args.colors:
202 colors=args.colors.lower()
203 if colors in ('lightbg', 'light'):
204 colors='light'
205 elif colors in ('dark', 'linux'):
206 colors='dark'
207 else:
208 colors='nocolor'
209 else:
210 colors=None
211 lightbg = colors != 'linux'
212
200 if args.style:
213 if args.style:
201 # guess whether it's a dark style:
214 # guess whether it's a dark style:
202 dark = args.dark or styles.dark_style(args.style)
203 widget.syntax_style = args.style
215 widget.syntax_style = args.style
204 widget.style_sheet = styles.sheet_from_template(args.style, not dark)
216 widget.style_sheet = styles.sheet_from_template(args.style, lightbg)
205 widget._syntax_style_changed()
217 widget._syntax_style_changed()
206 widget._style_sheet_changed()
218 widget._style_sheet_changed()
207 elif args.dark:
219 elif colors:
208 # use default dark style
220 # use a default style
209 widget.set_default_style(lightbg=False)
221 widget.set_default_style(colors=colors)
210 else:
222 else:
211 # this is redundant for now, but allows the widget's
223 # this is redundant for now, but allows the widget's
212 # defaults to change
224 # defaults to change
213 widget.set_default_style(lightbg=True)
225 widget.set_default_style()
214
226
215 if args.stylesheet:
227 if args.stylesheet:
216 # we got an expicit stylesheet
228 # we got an expicit stylesheet
217 if os.path.isfile(args.stylesheet):
229 if os.path.isfile(args.stylesheet):
218 with open(args.stylesheet) as f:
230 with open(args.stylesheet) as f:
219 sheet = f.read()
231 sheet = f.read()
220 widget.style_sheet = sheet
232 widget.style_sheet = sheet
221 widget._style_sheet_changed()
233 widget._style_sheet_changed()
222 else:
234 else:
223 raise IOError("Stylesheet %r not found."%args.stylesheet)
235 raise IOError("Stylesheet %r not found."%args.stylesheet)
224
236
225 # Create the main window.
237 # Create the main window.
226 window = MainWindow(app, widget, args.existing, may_close=local_kernel)
238 window = MainWindow(app, widget, args.existing, may_close=local_kernel)
227 window.setWindowTitle('Python' if args.pure else 'IPython')
239 window.setWindowTitle('Python' if args.pure else 'IPython')
228 window.show()
240 window.show()
229
241
230 # Start the application main loop.
242 # Start the application main loop.
231 app.exec_()
243 app.exec_()
232
244
233
245
234 if __name__ == '__main__':
246 if __name__ == '__main__':
235 main()
247 main()
@@ -1,102 +1,119 b''
1 """ Style utilities, templates, and defaults for syntax highlighting widgets.
1 """ Style utilities, templates, and defaults for syntax highlighting widgets.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 from colorsys import rgb_to_hls
7 from colorsys import rgb_to_hls
8 from pygments.styles import get_style_by_name
8 from pygments.styles import get_style_by_name
9 from pygments.token import Token
9 from pygments.token import Token
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Constants
12 # Constants
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 # The default light style sheet: black text on a white background.
15 # The default light style sheet: black text on a white background.
16 default_light_style_template = '''
16 default_light_style_template = '''
17 QPlainTextEdit, QTextEdit { background-color: %(bgcolor)s;
17 QPlainTextEdit, QTextEdit { background-color: %(bgcolor)s;
18 color: %(fgcolor)s ;
18 color: %(fgcolor)s ;
19 selection-background-color: %(select)s}
19 selection-background-color: %(select)s}
20 .error { color: red; }
20 .error { color: red; }
21 .in-prompt { color: navy; }
21 .in-prompt { color: navy; }
22 .in-prompt-number { font-weight: bold; }
22 .in-prompt-number { font-weight: bold; }
23 .out-prompt { color: darkred; }
23 .out-prompt { color: darkred; }
24 .out-prompt-number { font-weight: bold; }
24 .out-prompt-number { font-weight: bold; }
25 '''
25 '''
26 default_light_style_sheet = default_light_style_template%dict(
26 default_light_style_sheet = default_light_style_template%dict(
27 bgcolor='white', fgcolor='black', select="#ccc")
27 bgcolor='white', fgcolor='black', select="#ccc")
28 default_light_syntax_style = 'default'
28 default_light_syntax_style = 'default'
29
29
30 # The default dark style sheet: white text on a black background.
30 # The default dark style sheet: white text on a black background.
31 default_dark_style_template = '''
31 default_dark_style_template = '''
32 QPlainTextEdit, QTextEdit { background-color: %(bgcolor)s;
32 QPlainTextEdit, QTextEdit { background-color: %(bgcolor)s;
33 color: %(fgcolor)s ;
33 color: %(fgcolor)s ;
34 selection-background-color: %(select)s}
34 selection-background-color: %(select)s}
35 QFrame { border: 1px solid grey; }
35 QFrame { border: 1px solid grey; }
36 .error { color: red; }
36 .error { color: red; }
37 .in-prompt { color: lime; }
37 .in-prompt { color: lime; }
38 .in-prompt-number { color: lime; font-weight: bold; }
38 .in-prompt-number { color: lime; font-weight: bold; }
39 .out-prompt { color: red; }
39 .out-prompt { color: red; }
40 .out-prompt-number { color: red; font-weight: bold; }
40 .out-prompt-number { color: red; font-weight: bold; }
41 '''
41 '''
42 default_dark_style_sheet = default_dark_style_template%dict(
42 default_dark_style_sheet = default_dark_style_template%dict(
43 bgcolor='black', fgcolor='white', select="#555")
43 bgcolor='black', fgcolor='white', select="#555")
44 default_dark_syntax_style = 'monokai'
44 default_dark_syntax_style = 'monokai'
45
45
46 # The default monochrome
47 default_bw_style_sheet = '''
48 QPlainTextEdit, QTextEdit { background-color: white;
49 color: black ;
50 selection-background-color: #cccccc}
51 .in-prompt-number { font-weight: bold; }
52 .out-prompt-number { font-weight: bold; }
53 '''
54 default_bw_syntax_style = 'bw'
55
56
46 def hex_to_rgb(color):
57 def hex_to_rgb(color):
47 """Convert a hex color to rgb integer tuple."""
58 """Convert a hex color to rgb integer tuple."""
48 if color.startswith('#'):
59 if color.startswith('#'):
49 color = color[1:]
60 color = color[1:]
50 if len(color) == 3:
61 if len(color) == 3:
51 color = ''.join([c*2 for c in color])
62 color = ''.join([c*2 for c in color])
52 if len(color) != 6:
63 if len(color) != 6:
53 return False
64 return False
54 try:
65 try:
55 r = int(color[:2],16)
66 r = int(color[:2],16)
56 g = int(color[:2],16)
67 g = int(color[:2],16)
57 b = int(color[:2],16)
68 b = int(color[:2],16)
58 except ValueError:
69 except ValueError:
59 return False
70 return False
60 else:
71 else:
61 return r,g,b
72 return r,g,b
62
73
63 def dark_color(color):
74 def dark_color(color):
64 """Check whether a color is 'dark'.
75 """Check whether a color is 'dark'.
65
76
66 Currently, this is simply whether the luminance is <50%"""
77 Currently, this is simply whether the luminance is <50%"""
67 rgb = hex_to_rgb(color)
78 rgb = hex_to_rgb(color)
68 if rgb:
79 if rgb:
69 return rgb_to_hls(*rgb)[1] < 128
80 return rgb_to_hls(*rgb)[1] < 128
70 else: # default to False
81 else: # default to False
71 return False
82 return False
72
83
73 def dark_style(stylename):
84 def dark_style(stylename):
74 """Guess whether the background of the style with name 'stylename'
85 """Guess whether the background of the style with name 'stylename'
75 counts as 'dark'."""
86 counts as 'dark'."""
76 return dark_color(get_style_by_name(stylename).background_color)
87 return dark_color(get_style_by_name(stylename).background_color)
77
88
78 def get_colors(stylename):
89 def get_colors(stylename):
79 """Construct the keys to be used building the base stylesheet."""
90 """Construct the keys to be used building the base stylesheet
91 from a templatee."""
80 style = get_style_by_name(stylename)
92 style = get_style_by_name(stylename)
81 fgcolor = style.style_for_token(Token.Text)['color'] or ''
93 fgcolor = style.style_for_token(Token.Text)['color'] or ''
82 if len(fgcolor) in (3,6):
94 if len(fgcolor) in (3,6):
83 # could be 'abcdef' or 'ace' hex, which needs '#' prefix
95 # could be 'abcdef' or 'ace' hex, which needs '#' prefix
84 try:
96 try:
85 int(fgcolor, 16)
97 int(fgcolor, 16)
86 except TypeError:
98 except TypeError:
87 pass
99 pass
88 else:
100 else:
89 fgcolor = "#"+fgcolor
101 fgcolor = "#"+fgcolor
90
102
91 return dict(
103 return dict(
92 bgcolor = style.background_color,
104 bgcolor = style.background_color,
93 select = style.highlight_color,
105 select = style.highlight_color,
94 fgcolor = fgcolor
106 fgcolor = fgcolor
95 )
107 )
96
108
97 def sheet_from_template(name, lightbg=True):
109 def sheet_from_template(name, colors='light'):
98 """Use one of the base templates, and set bg/fg/select colors."""
110 """Use one of the base templates, and set bg/fg/select colors."""
99 if lightbg:
111 colors = colors.lower()
112 if colors=='light':
100 return default_light_style_template%get_colors(name)
113 return default_light_style_template%get_colors(name)
114 elif colors=='dark':
115 return default_dark_style_template%get_colors(name)
116 elif colors=='nocolor':
117 return default_bw_style_sheet
101 else:
118 else:
102 return default_dark_style_template%get_colors(name) No newline at end of file
119 raise KeyError("No such color scheme: %s"%colors)
General Comments 0
You need to be logged in to leave comments. Login now