##// END OF EJS Templates
Merge pull request #1318 from ivanov/qt-console-ctrl-d...
Min RK -
r5989:426a9864 merge
parent child Browse files
Show More
@@ -1,534 +1,535 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Usage information for the main IPython applications.
2 """Usage information for the main IPython applications.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (C) 2008-2011 The IPython Development Team
5 # Copyright (C) 2008-2011 The IPython Development Team
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
6 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
7 #
7 #
8 # Distributed under the terms of the BSD License. The full license is in
8 # Distributed under the terms of the BSD License. The full license is in
9 # the file COPYING, distributed as part of this software.
9 # the file COPYING, distributed as part of this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 import sys
12 import sys
13 from IPython.core import release
13 from IPython.core import release
14
14
15 cl_usage = """\
15 cl_usage = """\
16 =========
16 =========
17 IPython
17 IPython
18 =========
18 =========
19
19
20 Tools for Interactive Computing in Python
20 Tools for Interactive Computing in Python
21 =========================================
21 =========================================
22
22
23 A Python shell with automatic history (input and output), dynamic object
23 A Python shell with automatic history (input and output), dynamic object
24 introspection, easier configuration, command completion, access to the
24 introspection, easier configuration, command completion, access to the
25 system shell and more. IPython can also be embedded in running programs.
25 system shell and more. IPython can also be embedded in running programs.
26
26
27
27
28 Usage
28 Usage
29
29
30 ipython [subcommand] [options] [files]
30 ipython [subcommand] [options] [files]
31
31
32 If invoked with no options, it executes all the files listed in sequence
32 If invoked with no options, it executes all the files listed in sequence
33 and exits, use -i to enter interactive mode after running the files. Files
33 and exits, use -i to enter interactive mode after running the files. Files
34 ending in .py will be treated as normal Python, but files ending in .ipy
34 ending in .py will be treated as normal Python, but files ending in .ipy
35 can contain special IPython syntax (magic commands, shell expansions, etc.)
35 can contain special IPython syntax (magic commands, shell expansions, etc.)
36
36
37 Almost all configuration in IPython is available via the command-line. Do
37 Almost all configuration in IPython is available via the command-line. Do
38 `ipython --help-all` to see all available options. For persistent
38 `ipython --help-all` to see all available options. For persistent
39 configuration, look into your `ipython_config.py` configuration file for
39 configuration, look into your `ipython_config.py` configuration file for
40 details.
40 details.
41
41
42 This file is typically installed in the `IPYTHON_DIR` directory, and there
42 This file is typically installed in the `IPYTHON_DIR` directory, and there
43 is a separate configuration directory for each profile. The default profile
43 is a separate configuration directory for each profile. The default profile
44 directory will be located in $IPYTHON_DIR/profile_default. For Linux users,
44 directory will be located in $IPYTHON_DIR/profile_default. For Linux users,
45 IPYTHON_DIR defaults to `$HOME/.config/ipython`, and for other Unix systems
45 IPYTHON_DIR defaults to `$HOME/.config/ipython`, and for other Unix systems
46 to `$HOME/.ipython`. For Windows users, $HOME resolves to C:\\Documents
46 to `$HOME/.ipython`. For Windows users, $HOME resolves to C:\\Documents
47 and Settings\\YourUserName in most instances.
47 and Settings\\YourUserName in most instances.
48
48
49 To initialize a profile with the default configuration file, do::
49 To initialize a profile with the default configuration file, do::
50
50
51 $> ipython profile create
51 $> ipython profile create
52
52
53 and start editing `IPYTHON_DIR/profile_default/ipython_config.py`
53 and start editing `IPYTHON_DIR/profile_default/ipython_config.py`
54
54
55 In IPython's documentation, we will refer to this directory as
55 In IPython's documentation, we will refer to this directory as
56 `IPYTHON_DIR`, you can change its default location by creating an
56 `IPYTHON_DIR`, you can change its default location by creating an
57 environment variable with this name and setting it to the desired path.
57 environment variable with this name and setting it to the desired path.
58
58
59 For more information, see the manual available in HTML and PDF in your
59 For more information, see the manual available in HTML and PDF in your
60 installation, or online at http://ipython.org/documentation.html.
60 installation, or online at http://ipython.org/documentation.html.
61 """
61 """
62
62
63 interactive_usage = """
63 interactive_usage = """
64 IPython -- An enhanced Interactive Python
64 IPython -- An enhanced Interactive Python
65 =========================================
65 =========================================
66
66
67 IPython offers a combination of convenient shell features, special commands
67 IPython offers a combination of convenient shell features, special commands
68 and a history mechanism for both input (command history) and output (results
68 and a history mechanism for both input (command history) and output (results
69 caching, similar to Mathematica). It is intended to be a fully compatible
69 caching, similar to Mathematica). It is intended to be a fully compatible
70 replacement for the standard Python interpreter, while offering vastly
70 replacement for the standard Python interpreter, while offering vastly
71 improved functionality and flexibility.
71 improved functionality and flexibility.
72
72
73 At your system command line, type 'ipython -h' to see the command line
73 At your system command line, type 'ipython -h' to see the command line
74 options available. This document only describes interactive features.
74 options available. This document only describes interactive features.
75
75
76 MAIN FEATURES
76 MAIN FEATURES
77
77
78 * Access to the standard Python help. As of Python 2.1, a help system is
78 * Access to the standard Python help. As of Python 2.1, a help system is
79 available with access to object docstrings and the Python manuals. Simply
79 available with access to object docstrings and the Python manuals. Simply
80 type 'help' (no quotes) to access it.
80 type 'help' (no quotes) to access it.
81
81
82 * Magic commands: type %magic for information on the magic subsystem.
82 * Magic commands: type %magic for information on the magic subsystem.
83
83
84 * System command aliases, via the %alias command or the configuration file(s).
84 * System command aliases, via the %alias command or the configuration file(s).
85
85
86 * Dynamic object information:
86 * Dynamic object information:
87
87
88 Typing ?word or word? prints detailed information about an object. If
88 Typing ?word or word? prints detailed information about an object. If
89 certain strings in the object are too long (docstrings, code, etc.) they get
89 certain strings in the object are too long (docstrings, code, etc.) they get
90 snipped in the center for brevity.
90 snipped in the center for brevity.
91
91
92 Typing ??word or word?? gives access to the full information without
92 Typing ??word or word?? gives access to the full information without
93 snipping long strings. Long strings are sent to the screen through the less
93 snipping long strings. Long strings are sent to the screen through the less
94 pager if longer than the screen, printed otherwise.
94 pager if longer than the screen, printed otherwise.
95
95
96 The ?/?? system gives access to the full source code for any object (if
96 The ?/?? system gives access to the full source code for any object (if
97 available), shows function prototypes and other useful information.
97 available), shows function prototypes and other useful information.
98
98
99 If you just want to see an object's docstring, type '%pdoc object' (without
99 If you just want to see an object's docstring, type '%pdoc object' (without
100 quotes, and without % if you have automagic on).
100 quotes, and without % if you have automagic on).
101
101
102 Both %pdoc and ?/?? give you access to documentation even on things which are
102 Both %pdoc and ?/?? give you access to documentation even on things which are
103 not explicitely defined. Try for example typing {}.get? or after import os,
103 not explicitely defined. Try for example typing {}.get? or after import os,
104 type os.path.abspath??. The magic functions %pdef, %source and %file operate
104 type os.path.abspath??. The magic functions %pdef, %source and %file operate
105 similarly.
105 similarly.
106
106
107 * Completion in the local namespace, by typing TAB at the prompt.
107 * Completion in the local namespace, by typing TAB at the prompt.
108
108
109 At any time, hitting tab will complete any available python commands or
109 At any time, hitting tab will complete any available python commands or
110 variable names, and show you a list of the possible completions if there's
110 variable names, and show you a list of the possible completions if there's
111 no unambiguous one. It will also complete filenames in the current directory.
111 no unambiguous one. It will also complete filenames in the current directory.
112
112
113 This feature requires the readline and rlcomplete modules, so it won't work
113 This feature requires the readline and rlcomplete modules, so it won't work
114 if your Python lacks readline support (such as under Windows).
114 if your Python lacks readline support (such as under Windows).
115
115
116 * Search previous command history in two ways (also requires readline):
116 * Search previous command history in two ways (also requires readline):
117
117
118 - Start typing, and then use Ctrl-p (previous,up) and Ctrl-n (next,down) to
118 - Start typing, and then use Ctrl-p (previous,up) and Ctrl-n (next,down) to
119 search through only the history items that match what you've typed so
119 search through only the history items that match what you've typed so
120 far. If you use Ctrl-p/Ctrl-n at a blank prompt, they just behave like
120 far. If you use Ctrl-p/Ctrl-n at a blank prompt, they just behave like
121 normal arrow keys.
121 normal arrow keys.
122
122
123 - Hit Ctrl-r: opens a search prompt. Begin typing and the system searches
123 - Hit Ctrl-r: opens a search prompt. Begin typing and the system searches
124 your history for lines that match what you've typed so far, completing as
124 your history for lines that match what you've typed so far, completing as
125 much as it can.
125 much as it can.
126
126
127 - %hist: search history by index (this does *not* require readline).
127 - %hist: search history by index (this does *not* require readline).
128
128
129 * Persistent command history across sessions.
129 * Persistent command history across sessions.
130
130
131 * Logging of input with the ability to save and restore a working session.
131 * Logging of input with the ability to save and restore a working session.
132
132
133 * System escape with !. Typing !ls will run 'ls' in the current directory.
133 * System escape with !. Typing !ls will run 'ls' in the current directory.
134
134
135 * The reload command does a 'deep' reload of a module: changes made to the
135 * The reload command does a 'deep' reload of a module: changes made to the
136 module since you imported will actually be available without having to exit.
136 module since you imported will actually be available without having to exit.
137
137
138 * Verbose and colored exception traceback printouts. See the magic xmode and
138 * Verbose and colored exception traceback printouts. See the magic xmode and
139 xcolor functions for details (just type %magic).
139 xcolor functions for details (just type %magic).
140
140
141 * Input caching system:
141 * Input caching system:
142
142
143 IPython offers numbered prompts (In/Out) with input and output caching. All
143 IPython offers numbered prompts (In/Out) with input and output caching. All
144 input is saved and can be retrieved as variables (besides the usual arrow
144 input is saved and can be retrieved as variables (besides the usual arrow
145 key recall).
145 key recall).
146
146
147 The following GLOBAL variables always exist (so don't overwrite them!):
147 The following GLOBAL variables always exist (so don't overwrite them!):
148 _i: stores previous input.
148 _i: stores previous input.
149 _ii: next previous.
149 _ii: next previous.
150 _iii: next-next previous.
150 _iii: next-next previous.
151 _ih : a list of all input _ih[n] is the input from line n.
151 _ih : a list of all input _ih[n] is the input from line n.
152
152
153 Additionally, global variables named _i<n> are dynamically created (<n>
153 Additionally, global variables named _i<n> are dynamically created (<n>
154 being the prompt counter), such that _i<n> == _ih[<n>]
154 being the prompt counter), such that _i<n> == _ih[<n>]
155
155
156 For example, what you typed at prompt 14 is available as _i14 and _ih[14].
156 For example, what you typed at prompt 14 is available as _i14 and _ih[14].
157
157
158 You can create macros which contain multiple input lines from this history,
158 You can create macros which contain multiple input lines from this history,
159 for later re-execution, with the %macro function.
159 for later re-execution, with the %macro function.
160
160
161 The history function %hist allows you to see any part of your input history
161 The history function %hist allows you to see any part of your input history
162 by printing a range of the _i variables. Note that inputs which contain
162 by printing a range of the _i variables. Note that inputs which contain
163 magic functions (%) appear in the history with a prepended comment. This is
163 magic functions (%) appear in the history with a prepended comment. This is
164 because they aren't really valid Python code, so you can't exec them.
164 because they aren't really valid Python code, so you can't exec them.
165
165
166 * Output caching system:
166 * Output caching system:
167
167
168 For output that is returned from actions, a system similar to the input
168 For output that is returned from actions, a system similar to the input
169 cache exists but using _ instead of _i. Only actions that produce a result
169 cache exists but using _ instead of _i. Only actions that produce a result
170 (NOT assignments, for example) are cached. If you are familiar with
170 (NOT assignments, for example) are cached. If you are familiar with
171 Mathematica, IPython's _ variables behave exactly like Mathematica's %
171 Mathematica, IPython's _ variables behave exactly like Mathematica's %
172 variables.
172 variables.
173
173
174 The following GLOBAL variables always exist (so don't overwrite them!):
174 The following GLOBAL variables always exist (so don't overwrite them!):
175 _ (one underscore): previous output.
175 _ (one underscore): previous output.
176 __ (two underscores): next previous.
176 __ (two underscores): next previous.
177 ___ (three underscores): next-next previous.
177 ___ (three underscores): next-next previous.
178
178
179 Global variables named _<n> are dynamically created (<n> being the prompt
179 Global variables named _<n> are dynamically created (<n> being the prompt
180 counter), such that the result of output <n> is always available as _<n>.
180 counter), such that the result of output <n> is always available as _<n>.
181
181
182 Finally, a global dictionary named _oh exists with entries for all lines
182 Finally, a global dictionary named _oh exists with entries for all lines
183 which generated output.
183 which generated output.
184
184
185 * Directory history:
185 * Directory history:
186
186
187 Your history of visited directories is kept in the global list _dh, and the
187 Your history of visited directories is kept in the global list _dh, and the
188 magic %cd command can be used to go to any entry in that list.
188 magic %cd command can be used to go to any entry in that list.
189
189
190 * Auto-parentheses and auto-quotes (adapted from Nathan Gray's LazyPython)
190 * Auto-parentheses and auto-quotes (adapted from Nathan Gray's LazyPython)
191
191
192 1. Auto-parentheses
192 1. Auto-parentheses
193 Callable objects (i.e. functions, methods, etc) can be invoked like
193 Callable objects (i.e. functions, methods, etc) can be invoked like
194 this (notice the commas between the arguments):
194 this (notice the commas between the arguments):
195 In [1]: callable_ob arg1, arg2, arg3
195 In [1]: callable_ob arg1, arg2, arg3
196 and the input will be translated to this:
196 and the input will be translated to this:
197 ------> callable_ob(arg1, arg2, arg3)
197 ------> callable_ob(arg1, arg2, arg3)
198 This feature is off by default (in rare cases it can produce
198 This feature is off by default (in rare cases it can produce
199 undesirable side-effects), but you can activate it at the command-line
199 undesirable side-effects), but you can activate it at the command-line
200 by starting IPython with `--autocall 1`, set it permanently in your
200 by starting IPython with `--autocall 1`, set it permanently in your
201 configuration file, or turn on at runtime with `%autocall 1`.
201 configuration file, or turn on at runtime with `%autocall 1`.
202
202
203 You can force auto-parentheses by using '/' as the first character
203 You can force auto-parentheses by using '/' as the first character
204 of a line. For example:
204 of a line. For example:
205 In [1]: /globals # becomes 'globals()'
205 In [1]: /globals # becomes 'globals()'
206 Note that the '/' MUST be the first character on the line! This
206 Note that the '/' MUST be the first character on the line! This
207 won't work:
207 won't work:
208 In [2]: print /globals # syntax error
208 In [2]: print /globals # syntax error
209
209
210 In most cases the automatic algorithm should work, so you should
210 In most cases the automatic algorithm should work, so you should
211 rarely need to explicitly invoke /. One notable exception is if you
211 rarely need to explicitly invoke /. One notable exception is if you
212 are trying to call a function with a list of tuples as arguments (the
212 are trying to call a function with a list of tuples as arguments (the
213 parenthesis will confuse IPython):
213 parenthesis will confuse IPython):
214 In [1]: zip (1,2,3),(4,5,6) # won't work
214 In [1]: zip (1,2,3),(4,5,6) # won't work
215 but this will work:
215 but this will work:
216 In [2]: /zip (1,2,3),(4,5,6)
216 In [2]: /zip (1,2,3),(4,5,6)
217 ------> zip ((1,2,3),(4,5,6))
217 ------> zip ((1,2,3),(4,5,6))
218 Out[2]= [(1, 4), (2, 5), (3, 6)]
218 Out[2]= [(1, 4), (2, 5), (3, 6)]
219
219
220 IPython tells you that it has altered your command line by
220 IPython tells you that it has altered your command line by
221 displaying the new command line preceded by -->. e.g.:
221 displaying the new command line preceded by -->. e.g.:
222 In [18]: callable list
222 In [18]: callable list
223 -------> callable (list)
223 -------> callable (list)
224
224
225 2. Auto-Quoting
225 2. Auto-Quoting
226 You can force auto-quoting of a function's arguments by using ',' as
226 You can force auto-quoting of a function's arguments by using ',' as
227 the first character of a line. For example:
227 the first character of a line. For example:
228 In [1]: ,my_function /home/me # becomes my_function("/home/me")
228 In [1]: ,my_function /home/me # becomes my_function("/home/me")
229
229
230 If you use ';' instead, the whole argument is quoted as a single
230 If you use ';' instead, the whole argument is quoted as a single
231 string (while ',' splits on whitespace):
231 string (while ',' splits on whitespace):
232 In [2]: ,my_function a b c # becomes my_function("a","b","c")
232 In [2]: ,my_function a b c # becomes my_function("a","b","c")
233 In [3]: ;my_function a b c # becomes my_function("a b c")
233 In [3]: ;my_function a b c # becomes my_function("a b c")
234
234
235 Note that the ',' MUST be the first character on the line! This
235 Note that the ',' MUST be the first character on the line! This
236 won't work:
236 won't work:
237 In [4]: x = ,my_function /home/me # syntax error
237 In [4]: x = ,my_function /home/me # syntax error
238 """
238 """
239
239
240 interactive_usage_min = """\
240 interactive_usage_min = """\
241 An enhanced console for Python.
241 An enhanced console for Python.
242 Some of its features are:
242 Some of its features are:
243 - Readline support if the readline library is present.
243 - Readline support if the readline library is present.
244 - Tab completion in the local namespace.
244 - Tab completion in the local namespace.
245 - Logging of input, see command-line options.
245 - Logging of input, see command-line options.
246 - System shell escape via ! , eg !ls.
246 - System shell escape via ! , eg !ls.
247 - Magic commands, starting with a % (like %ls, %pwd, %cd, etc.)
247 - Magic commands, starting with a % (like %ls, %pwd, %cd, etc.)
248 - Keeps track of locally defined variables via %who, %whos.
248 - Keeps track of locally defined variables via %who, %whos.
249 - Show object information with a ? eg ?x or x? (use ?? for more info).
249 - Show object information with a ? eg ?x or x? (use ?? for more info).
250 """
250 """
251
251
252 quick_reference = r"""
252 quick_reference = r"""
253 IPython -- An enhanced Interactive Python - Quick Reference Card
253 IPython -- An enhanced Interactive Python - Quick Reference Card
254 ================================================================
254 ================================================================
255
255
256 obj?, obj?? : Get help, or more help for object (also works as
256 obj?, obj?? : Get help, or more help for object (also works as
257 ?obj, ??obj).
257 ?obj, ??obj).
258 ?foo.*abc* : List names in 'foo' containing 'abc' in them.
258 ?foo.*abc* : List names in 'foo' containing 'abc' in them.
259 %magic : Information about IPython's 'magic' % functions.
259 %magic : Information about IPython's 'magic' % functions.
260
260
261 Magic functions are prefixed by %, and typically take their arguments without
261 Magic functions are prefixed by %, and typically take their arguments without
262 parentheses, quotes or even commas for convenience.
262 parentheses, quotes or even commas for convenience.
263
263
264 Example magic function calls:
264 Example magic function calls:
265
265
266 %alias d ls -F : 'd' is now an alias for 'ls -F'
266 %alias d ls -F : 'd' is now an alias for 'ls -F'
267 alias d ls -F : Works if 'alias' not a python name
267 alias d ls -F : Works if 'alias' not a python name
268 alist = %alias : Get list of aliases to 'alist'
268 alist = %alias : Get list of aliases to 'alist'
269 cd /usr/share : Obvious. cd -<tab> to choose from visited dirs.
269 cd /usr/share : Obvious. cd -<tab> to choose from visited dirs.
270 %cd?? : See help AND source for magic %cd
270 %cd?? : See help AND source for magic %cd
271
271
272 System commands:
272 System commands:
273
273
274 !cp a.txt b/ : System command escape, calls os.system()
274 !cp a.txt b/ : System command escape, calls os.system()
275 cp a.txt b/ : after %rehashx, most system commands work without !
275 cp a.txt b/ : after %rehashx, most system commands work without !
276 cp ${f}.txt $bar : Variable expansion in magics and system commands
276 cp ${f}.txt $bar : Variable expansion in magics and system commands
277 files = !ls /usr : Capture sytem command output
277 files = !ls /usr : Capture sytem command output
278 files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc'
278 files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc'
279
279
280 History:
280 History:
281
281
282 _i, _ii, _iii : Previous, next previous, next next previous input
282 _i, _ii, _iii : Previous, next previous, next next previous input
283 _i4, _ih[2:5] : Input history line 4, lines 2-4
283 _i4, _ih[2:5] : Input history line 4, lines 2-4
284 exec _i81 : Execute input history line #81 again
284 exec _i81 : Execute input history line #81 again
285 %rep 81 : Edit input history line #81
285 %rep 81 : Edit input history line #81
286 _, __, ___ : previous, next previous, next next previous output
286 _, __, ___ : previous, next previous, next next previous output
287 _dh : Directory history
287 _dh : Directory history
288 _oh : Output history
288 _oh : Output history
289 %hist : Command history. '%hist -g foo' search history for 'foo'
289 %hist : Command history. '%hist -g foo' search history for 'foo'
290
290
291 Autocall:
291 Autocall:
292
292
293 f 1,2 : f(1,2) # Off by default, enable with %autocall magic.
293 f 1,2 : f(1,2) # Off by default, enable with %autocall magic.
294 /f 1,2 : f(1,2) (forced autoparen)
294 /f 1,2 : f(1,2) (forced autoparen)
295 ,f 1 2 : f("1","2")
295 ,f 1 2 : f("1","2")
296 ;f 1 2 : f("1 2")
296 ;f 1 2 : f("1 2")
297
297
298 Remember: TAB completion works in many contexts, not just file names
298 Remember: TAB completion works in many contexts, not just file names
299 or python names.
299 or python names.
300
300
301 The following magic functions are currently available:
301 The following magic functions are currently available:
302
302
303 """
303 """
304
304
305 gui_reference = """\
305 gui_reference = """\
306 ===============================
306 ===============================
307 The graphical IPython console
307 The graphical IPython console
308 ===============================
308 ===============================
309
309
310 This console is designed to emulate the look, feel and workflow of a terminal
310 This console is designed to emulate the look, feel and workflow of a terminal
311 environment, while adding a number of enhancements that are simply not possible
311 environment, while adding a number of enhancements that are simply not possible
312 in a real terminal, such as inline syntax highlighting, true multiline editing,
312 in a real terminal, such as inline syntax highlighting, true multiline editing,
313 inline graphics and much more.
313 inline graphics and much more.
314
314
315 This quick reference document contains the basic information you'll need to
315 This quick reference document contains the basic information you'll need to
316 know to make the most efficient use of it. For the various command line
316 know to make the most efficient use of it. For the various command line
317 options available at startup, type ``ipython qtconsole --help`` at the command line.
317 options available at startup, type ``ipython qtconsole --help`` at the command line.
318
318
319
319
320 Multiline editing
320 Multiline editing
321 =================
321 =================
322
322
323 The graphical console is capable of true multiline editing, but it also tries
323 The graphical console is capable of true multiline editing, but it also tries
324 to behave intuitively like a terminal when possible. If you are used to
324 to behave intuitively like a terminal when possible. If you are used to
325 IPyhton's old terminal behavior, you should find the transition painless, and
325 IPyhton's old terminal behavior, you should find the transition painless, and
326 once you learn a few basic keybindings it will be a much more efficient
326 once you learn a few basic keybindings it will be a much more efficient
327 environment.
327 environment.
328
328
329 For single expressions or indented blocks, the console behaves almost like the
329 For single expressions or indented blocks, the console behaves almost like the
330 terminal IPython: single expressions are immediately evaluated, and indented
330 terminal IPython: single expressions are immediately evaluated, and indented
331 blocks are evaluated once a single blank line is entered::
331 blocks are evaluated once a single blank line is entered::
332
332
333 In [1]: print "Hello IPython!" # Enter was pressed at the end of the line
333 In [1]: print "Hello IPython!" # Enter was pressed at the end of the line
334 Hello IPython!
334 Hello IPython!
335
335
336 In [2]: for i in range(10):
336 In [2]: for i in range(10):
337 ...: print i,
337 ...: print i,
338 ...:
338 ...:
339 0 1 2 3 4 5 6 7 8 9
339 0 1 2 3 4 5 6 7 8 9
340
340
341 If you want to enter more than one expression in a single input block
341 If you want to enter more than one expression in a single input block
342 (something not possible in the terminal), you can use ``Control-Enter`` at the
342 (something not possible in the terminal), you can use ``Control-Enter`` at the
343 end of your first line instead of ``Enter``. At that point the console goes
343 end of your first line instead of ``Enter``. At that point the console goes
344 into 'cell mode' and even if your inputs are not indented, it will continue
344 into 'cell mode' and even if your inputs are not indented, it will continue
345 accepting arbitrarily many lines until either you enter an extra blank line or
345 accepting arbitrarily many lines until either you enter an extra blank line or
346 you hit ``Shift-Enter`` (the key binding that forces execution). When a
346 you hit ``Shift-Enter`` (the key binding that forces execution). When a
347 multiline cell is entered, IPython analyzes it and executes its code producing
347 multiline cell is entered, IPython analyzes it and executes its code producing
348 an ``Out[n]`` prompt only for the last expression in it, while the rest of the
348 an ``Out[n]`` prompt only for the last expression in it, while the rest of the
349 cell is executed as if it was a script. An example should clarify this::
349 cell is executed as if it was a script. An example should clarify this::
350
350
351 In [3]: x=1 # Hit C-Enter here
351 In [3]: x=1 # Hit C-Enter here
352 ...: y=2 # from now on, regular Enter is sufficient
352 ...: y=2 # from now on, regular Enter is sufficient
353 ...: z=3
353 ...: z=3
354 ...: x**2 # This does *not* produce an Out[] value
354 ...: x**2 # This does *not* produce an Out[] value
355 ...: x+y+z # Only the last expression does
355 ...: x+y+z # Only the last expression does
356 ...:
356 ...:
357 Out[3]: 6
357 Out[3]: 6
358
358
359 The behavior where an extra blank line forces execution is only active if you
359 The behavior where an extra blank line forces execution is only active if you
360 are actually typing at the keyboard each line, and is meant to make it mimic
360 are actually typing at the keyboard each line, and is meant to make it mimic
361 the IPython terminal behavior. If you paste a long chunk of input (for example
361 the IPython terminal behavior. If you paste a long chunk of input (for example
362 a long script copied form an editor or web browser), it can contain arbitrarily
362 a long script copied form an editor or web browser), it can contain arbitrarily
363 many intermediate blank lines and they won't cause any problems. As always,
363 many intermediate blank lines and they won't cause any problems. As always,
364 you can then make it execute by appending a blank line *at the end* or hitting
364 you can then make it execute by appending a blank line *at the end* or hitting
365 ``Shift-Enter`` anywhere within the cell.
365 ``Shift-Enter`` anywhere within the cell.
366
366
367 With the up arrow key, you can retrieve previous blocks of input that contain
367 With the up arrow key, you can retrieve previous blocks of input that contain
368 multiple lines. You can move inside of a multiline cell like you would in any
368 multiple lines. You can move inside of a multiline cell like you would in any
369 text editor. When you want it executed, the simplest thing to do is to hit the
369 text editor. When you want it executed, the simplest thing to do is to hit the
370 force execution key, ``Shift-Enter`` (though you can also navigate to the end
370 force execution key, ``Shift-Enter`` (though you can also navigate to the end
371 and append a blank line by using ``Enter`` twice).
371 and append a blank line by using ``Enter`` twice).
372
372
373 If you've edited a multiline cell and accidentally navigate out of it with the
373 If you've edited a multiline cell and accidentally navigate out of it with the
374 up or down arrow keys, IPython will clear the cell and replace it with the
374 up or down arrow keys, IPython will clear the cell and replace it with the
375 contents of the one above or below that you navigated to. If this was an
375 contents of the one above or below that you navigated to. If this was an
376 accident and you want to retrieve the cell you were editing, use the Undo
376 accident and you want to retrieve the cell you were editing, use the Undo
377 keybinding, ``Control-z``.
377 keybinding, ``Control-z``.
378
378
379
379
380 Key bindings
380 Key bindings
381 ============
381 ============
382
382
383 The IPython console supports most of the basic Emacs line-oriented keybindings,
383 The IPython console supports most of the basic Emacs line-oriented keybindings,
384 in addition to some of its own.
384 in addition to some of its own.
385
385
386 The keybinding prefixes mean:
386 The keybinding prefixes mean:
387
387
388 - ``C``: Control
388 - ``C``: Control
389 - ``S``: Shift
389 - ``S``: Shift
390 - ``M``: Meta (typically the Alt key)
390 - ``M``: Meta (typically the Alt key)
391
391
392 The keybindings themselves are:
392 The keybindings themselves are:
393
393
394 - ``Enter``: insert new line (may cause execution, see above).
394 - ``Enter``: insert new line (may cause execution, see above).
395 - ``C-Enter``: *force* new line, *never* causes execution.
395 - ``C-Enter``: *force* new line, *never* causes execution.
396 - ``S-Enter``: *force* execution regardless of where cursor is, no newline added.
396 - ``S-Enter``: *force* execution regardless of where cursor is, no newline added.
397 - ``Up``: step backwards through the history.
397 - ``Up``: step backwards through the history.
398 - ``Down``: step forwards through the history.
398 - ``Down``: step forwards through the history.
399 - ``S-Up``: search backwards through the history (like ``C-r`` in bash).
399 - ``S-Up``: search backwards through the history (like ``C-r`` in bash).
400 - ``S-Down``: search forwards through the history.
400 - ``S-Down``: search forwards through the history.
401 - ``C-c``: copy highlighted text to clipboard (prompts are automatically stripped).
401 - ``C-c``: copy highlighted text to clipboard (prompts are automatically stripped).
402 - ``C-S-c``: copy highlighted text to clipboard (prompts are not stripped).
402 - ``C-S-c``: copy highlighted text to clipboard (prompts are not stripped).
403 - ``C-v``: paste text from clipboard.
403 - ``C-v``: paste text from clipboard.
404 - ``C-z``: undo (retrieves lost text if you move out of a cell with the arrows).
404 - ``C-z``: undo (retrieves lost text if you move out of a cell with the arrows).
405 - ``C-S-z``: redo.
405 - ``C-S-z``: redo.
406 - ``C-o``: move to 'other' area, between pager and terminal.
406 - ``C-o``: move to 'other' area, between pager and terminal.
407 - ``C-l``: clear terminal.
407 - ``C-l``: clear terminal.
408 - ``C-a``: go to beginning of line.
408 - ``C-a``: go to beginning of line.
409 - ``C-e``: go to end of line.
409 - ``C-e``: go to end of line.
410 - ``C-u``: kill from cursor to the begining of the line.
410 - ``C-k``: kill from cursor to the end of the line.
411 - ``C-k``: kill from cursor to the end of the line.
411 - ``C-y``: yank (paste)
412 - ``C-y``: yank (paste)
412 - ``C-p``: previous line (like up arrow)
413 - ``C-p``: previous line (like up arrow)
413 - ``C-n``: next line (like down arrow)
414 - ``C-n``: next line (like down arrow)
414 - ``C-f``: forward (like right arrow)
415 - ``C-f``: forward (like right arrow)
415 - ``C-b``: back (like left arrow)
416 - ``C-b``: back (like left arrow)
416 - ``C-d``: delete next character.
417 - ``C-d``: delete next character, or exits if input is empty
417 - ``M-<``: move to the beginning of the input region.
418 - ``M-<``: move to the beginning of the input region.
418 - ``M->``: move to the end of the input region.
419 - ``M->``: move to the end of the input region.
419 - ``M-d``: delete next word.
420 - ``M-d``: delete next word.
420 - ``M-Backspace``: delete previous word.
421 - ``M-Backspace``: delete previous word.
421 - ``C-.``: force a kernel restart (a confirmation dialog appears).
422 - ``C-.``: force a kernel restart (a confirmation dialog appears).
422 - ``C-+``: increase font size.
423 - ``C-+``: increase font size.
423 - ``C--``: decrease font size.
424 - ``C--``: decrease font size.
424 - ``C-M-Space``: toggle full screen. (Command-Control-Space on Mac OS X)
425 - ``C-M-Space``: toggle full screen. (Command-Control-Space on Mac OS X)
425
426
426 The IPython pager
427 The IPython pager
427 =================
428 =================
428
429
429 IPython will show long blocks of text from many sources using a builtin pager.
430 IPython will show long blocks of text from many sources using a builtin pager.
430 You can control where this pager appears with the ``--paging`` command-line
431 You can control where this pager appears with the ``--paging`` command-line
431 flag:
432 flag:
432
433
433 - ``inside`` [default]: the pager is overlaid on top of the main terminal. You
434 - ``inside`` [default]: the pager is overlaid on top of the main terminal. You
434 must quit the pager to get back to the terminal (similar to how a pager such
435 must quit the pager to get back to the terminal (similar to how a pager such
435 as ``less`` or ``more`` works).
436 as ``less`` or ``more`` works).
436
437
437 - ``vsplit``: the console is made double-tall, and the pager appears on the
438 - ``vsplit``: the console is made double-tall, and the pager appears on the
438 bottom area when needed. You can view its contents while using the terminal.
439 bottom area when needed. You can view its contents while using the terminal.
439
440
440 - ``hsplit``: the console is made double-wide, and the pager appears on the
441 - ``hsplit``: the console is made double-wide, and the pager appears on the
441 right area when needed. You can view its contents while using the terminal.
442 right area when needed. You can view its contents while using the terminal.
442
443
443 - ``none``: the console never pages output.
444 - ``none``: the console never pages output.
444
445
445 If you use the vertical or horizontal paging modes, you can navigate between
446 If you use the vertical or horizontal paging modes, you can navigate between
446 terminal and pager as follows:
447 terminal and pager as follows:
447
448
448 - Tab key: goes from pager to terminal (but not the other way around).
449 - Tab key: goes from pager to terminal (but not the other way around).
449 - Control-o: goes from one to another always.
450 - Control-o: goes from one to another always.
450 - Mouse: click on either.
451 - Mouse: click on either.
451
452
452 In all cases, the ``q`` or ``Escape`` keys quit the pager (when used with the
453 In all cases, the ``q`` or ``Escape`` keys quit the pager (when used with the
453 focus on the pager area).
454 focus on the pager area).
454
455
455 Running subprocesses
456 Running subprocesses
456 ====================
457 ====================
457
458
458 The graphical IPython console uses the ``pexpect`` module to run subprocesses
459 The graphical IPython console uses the ``pexpect`` module to run subprocesses
459 when you type ``!command``. This has a number of advantages (true asynchronous
460 when you type ``!command``. This has a number of advantages (true asynchronous
460 output from subprocesses as well as very robust termination of rogue
461 output from subprocesses as well as very robust termination of rogue
461 subprocesses with ``Control-C``), as well as some limitations. The main
462 subprocesses with ``Control-C``), as well as some limitations. The main
462 limitation is that you can *not* interact back with the subprocess, so anything
463 limitation is that you can *not* interact back with the subprocess, so anything
463 that invokes a pager or expects you to type input into it will block and hang
464 that invokes a pager or expects you to type input into it will block and hang
464 (you can kill it with ``Control-C``).
465 (you can kill it with ``Control-C``).
465
466
466 We have provided as magics ``%less`` to page files (aliased to ``%more``),
467 We have provided as magics ``%less`` to page files (aliased to ``%more``),
467 ``%clear`` to clear the terminal, and ``%man`` on Linux/OSX. These cover the
468 ``%clear`` to clear the terminal, and ``%man`` on Linux/OSX. These cover the
468 most common commands you'd want to call in your subshell and that would cause
469 most common commands you'd want to call in your subshell and that would cause
469 problems if invoked via ``!cmd``, but you need to be aware of this limitation.
470 problems if invoked via ``!cmd``, but you need to be aware of this limitation.
470
471
471 Display
472 Display
472 =======
473 =======
473
474
474 The IPython console can now display objects in a variety of formats, including
475 The IPython console can now display objects in a variety of formats, including
475 HTML, PNG and SVG. This is accomplished using the display functions in
476 HTML, PNG and SVG. This is accomplished using the display functions in
476 ``IPython.core.display``::
477 ``IPython.core.display``::
477
478
478 In [4]: from IPython.core.display import display, display_html
479 In [4]: from IPython.core.display import display, display_html
479
480
480 In [5]: from IPython.core.display import display_png, display_svg
481 In [5]: from IPython.core.display import display_png, display_svg
481
482
482 Python objects can simply be passed to these functions and the appropriate
483 Python objects can simply be passed to these functions and the appropriate
483 representations will be displayed in the console as long as the objects know
484 representations will be displayed in the console as long as the objects know
484 how to compute those representations. The easiest way of teaching objects how
485 how to compute those representations. The easiest way of teaching objects how
485 to format themselves in various representations is to define special methods
486 to format themselves in various representations is to define special methods
486 such as: ``_repr_html_``, ``_repr_svg_`` and ``_repr_png_``. IPython's display formatters
487 such as: ``_repr_html_``, ``_repr_svg_`` and ``_repr_png_``. IPython's display formatters
487 can also be given custom formatter functions for various types::
488 can also be given custom formatter functions for various types::
488
489
489 In [6]: ip = get_ipython()
490 In [6]: ip = get_ipython()
490
491
491 In [7]: html_formatter = ip.display_formatter.formatters['text/html']
492 In [7]: html_formatter = ip.display_formatter.formatters['text/html']
492
493
493 In [8]: html_formatter.for_type(Foo, foo_to_html)
494 In [8]: html_formatter.for_type(Foo, foo_to_html)
494
495
495 For further details, see ``IPython.core.formatters``.
496 For further details, see ``IPython.core.formatters``.
496
497
497 Inline matplotlib graphics
498 Inline matplotlib graphics
498 ==========================
499 ==========================
499
500
500 The IPython console is capable of displaying matplotlib figures inline, in SVG
501 The IPython console is capable of displaying matplotlib figures inline, in SVG
501 or PNG format. If started with the ``pylab=inline``, then all figures are
502 or PNG format. If started with the ``pylab=inline``, then all figures are
502 rendered inline automatically (PNG by default). If started with ``--pylab``
503 rendered inline automatically (PNG by default). If started with ``--pylab``
503 or ``pylab=<your backend>``, then a GUI backend will be used, but IPython's
504 or ``pylab=<your backend>``, then a GUI backend will be used, but IPython's
504 ``display()`` and ``getfigs()`` functions can be used to view plots inline::
505 ``display()`` and ``getfigs()`` functions can be used to view plots inline::
505
506
506 In [9]: display(*getfigs()) # display all figures inline
507 In [9]: display(*getfigs()) # display all figures inline
507
508
508 In[10]: display(*getfigs(1,2)) # display figures 1 and 2 inline
509 In[10]: display(*getfigs(1,2)) # display figures 1 and 2 inline
509 """
510 """
510
511
511
512
512 quick_guide = """\
513 quick_guide = """\
513 ? -> Introduction and overview of IPython's features.
514 ? -> Introduction and overview of IPython's features.
514 %quickref -> Quick reference.
515 %quickref -> Quick reference.
515 help -> Python's own help system.
516 help -> Python's own help system.
516 object? -> Details about 'object', use 'object??' for extra details.
517 object? -> Details about 'object', use 'object??' for extra details.
517 """
518 """
518
519
519 gui_note = """\
520 gui_note = """\
520 %guiref -> A brief reference about the graphical user interface.
521 %guiref -> A brief reference about the graphical user interface.
521 """
522 """
522
523
523 default_banner_parts = [
524 default_banner_parts = [
524 'Python %s\n' % (sys.version.split('\n')[0],),
525 'Python %s\n' % (sys.version.split('\n')[0],),
525 'Type "copyright", "credits" or "license" for more information.\n\n',
526 'Type "copyright", "credits" or "license" for more information.\n\n',
526 'IPython %s -- An enhanced Interactive Python.\n' % (release.version,),
527 'IPython %s -- An enhanced Interactive Python.\n' % (release.version,),
527 quick_guide
528 quick_guide
528 ]
529 ]
529
530
530 default_gui_banner_parts = default_banner_parts + [gui_note]
531 default_gui_banner_parts = default_banner_parts + [gui_note]
531
532
532 default_banner = ''.join(default_banner_parts)
533 default_banner = ''.join(default_banner_parts)
533
534
534 default_gui_banner = ''.join(default_gui_banner_parts)
535 default_gui_banner = ''.join(default_gui_banner_parts)
@@ -1,1843 +1,1852 b''
1 """ An abstract base class for console-type widgets.
1 """ An abstract base class for console-type widgets.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7 # Standard library imports
7 # Standard library imports
8 import os
8 import os
9 from os.path import commonprefix
9 from os.path import commonprefix
10 import re
10 import re
11 import sys
11 import sys
12 from textwrap import dedent
12 from textwrap import dedent
13 from unicodedata import category
13 from unicodedata import category
14
14
15 # System library imports
15 # System library imports
16 from IPython.external.qt import QtCore, QtGui
16 from IPython.external.qt import QtCore, QtGui
17
17
18 # Local imports
18 # Local imports
19 from IPython.config.configurable import LoggingConfigurable
19 from IPython.config.configurable import LoggingConfigurable
20 from IPython.frontend.qt.rich_text import HtmlExporter
20 from IPython.frontend.qt.rich_text import HtmlExporter
21 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
21 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
22 from IPython.utils.text import columnize
22 from IPython.utils.text import columnize
23 from IPython.utils.traitlets import Bool, Enum, Integer, Unicode
23 from IPython.utils.traitlets import Bool, Enum, Integer, Unicode
24 from ansi_code_processor import QtAnsiCodeProcessor
24 from ansi_code_processor import QtAnsiCodeProcessor
25 from completion_widget import CompletionWidget
25 from completion_widget import CompletionWidget
26 from kill_ring import QtKillRing
26 from kill_ring import QtKillRing
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Functions
29 # Functions
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 def is_letter_or_number(char):
32 def is_letter_or_number(char):
33 """ Returns whether the specified unicode character is a letter or a number.
33 """ Returns whether the specified unicode character is a letter or a number.
34 """
34 """
35 cat = category(char)
35 cat = category(char)
36 return cat.startswith('L') or cat.startswith('N')
36 return cat.startswith('L') or cat.startswith('N')
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Classes
39 # Classes
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):
42 class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):
43 """ An abstract base class for console-type widgets. This class has
43 """ An abstract base class for console-type widgets. This class has
44 functionality for:
44 functionality for:
45
45
46 * Maintaining a prompt and editing region
46 * Maintaining a prompt and editing region
47 * Providing the traditional Unix-style console keyboard shortcuts
47 * Providing the traditional Unix-style console keyboard shortcuts
48 * Performing tab completion
48 * Performing tab completion
49 * Paging text
49 * Paging text
50 * Handling ANSI escape codes
50 * Handling ANSI escape codes
51
51
52 ConsoleWidget also provides a number of utility methods that will be
52 ConsoleWidget also provides a number of utility methods that will be
53 convenient to implementors of a console-style widget.
53 convenient to implementors of a console-style widget.
54 """
54 """
55 __metaclass__ = MetaQObjectHasTraits
55 __metaclass__ = MetaQObjectHasTraits
56
56
57 #------ Configuration ------------------------------------------------------
57 #------ Configuration ------------------------------------------------------
58
58
59 ansi_codes = Bool(True, config=True,
59 ansi_codes = Bool(True, config=True,
60 help="Whether to process ANSI escape codes."
60 help="Whether to process ANSI escape codes."
61 )
61 )
62 buffer_size = Integer(500, config=True,
62 buffer_size = Integer(500, config=True,
63 help="""
63 help="""
64 The maximum number of lines of text before truncation. Specifying a
64 The maximum number of lines of text before truncation. Specifying a
65 non-positive number disables text truncation (not recommended).
65 non-positive number disables text truncation (not recommended).
66 """
66 """
67 )
67 )
68 gui_completion = Bool(False, config=True,
68 gui_completion = Bool(False, config=True,
69 help="""
69 help="""
70 Use a list widget instead of plain text output for tab completion.
70 Use a list widget instead of plain text output for tab completion.
71 """
71 """
72 )
72 )
73 # NOTE: this value can only be specified during initialization.
73 # NOTE: this value can only be specified during initialization.
74 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
74 kind = Enum(['plain', 'rich'], default_value='plain', config=True,
75 help="""
75 help="""
76 The type of underlying text widget to use. Valid values are 'plain',
76 The type of underlying text widget to use. Valid values are 'plain',
77 which specifies a QPlainTextEdit, and 'rich', which specifies a
77 which specifies a QPlainTextEdit, and 'rich', which specifies a
78 QTextEdit.
78 QTextEdit.
79 """
79 """
80 )
80 )
81 # NOTE: this value can only be specified during initialization.
81 # NOTE: this value can only be specified during initialization.
82 paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
82 paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
83 default_value='inside', config=True,
83 default_value='inside', config=True,
84 help="""
84 help="""
85 The type of paging to use. Valid values are:
85 The type of paging to use. Valid values are:
86
86
87 'inside' : The widget pages like a traditional terminal.
87 'inside' : The widget pages like a traditional terminal.
88 'hsplit' : When paging is requested, the widget is split
88 'hsplit' : When paging is requested, the widget is split
89 horizontally. The top pane contains the console, and the
89 horizontally. The top pane contains the console, and the
90 bottom pane contains the paged text.
90 bottom pane contains the paged text.
91 'vsplit' : Similar to 'hsplit', except that a vertical splitter
91 'vsplit' : Similar to 'hsplit', except that a vertical splitter
92 used.
92 used.
93 'custom' : No action is taken by the widget beyond emitting a
93 'custom' : No action is taken by the widget beyond emitting a
94 'custom_page_requested(str)' signal.
94 'custom_page_requested(str)' signal.
95 'none' : The text is written directly to the console.
95 'none' : The text is written directly to the console.
96 """)
96 """)
97
97
98 font_family = Unicode(config=True,
98 font_family = Unicode(config=True,
99 help="""The font family to use for the console.
99 help="""The font family to use for the console.
100 On OSX this defaults to Monaco, on Windows the default is
100 On OSX this defaults to Monaco, on Windows the default is
101 Consolas with fallback of Courier, and on other platforms
101 Consolas with fallback of Courier, and on other platforms
102 the default is Monospace.
102 the default is Monospace.
103 """)
103 """)
104 def _font_family_default(self):
104 def _font_family_default(self):
105 if sys.platform == 'win32':
105 if sys.platform == 'win32':
106 # Consolas ships with Vista/Win7, fallback to Courier if needed
106 # Consolas ships with Vista/Win7, fallback to Courier if needed
107 return 'Consolas'
107 return 'Consolas'
108 elif sys.platform == 'darwin':
108 elif sys.platform == 'darwin':
109 # OSX always has Monaco, no need for a fallback
109 # OSX always has Monaco, no need for a fallback
110 return 'Monaco'
110 return 'Monaco'
111 else:
111 else:
112 # Monospace should always exist, no need for a fallback
112 # Monospace should always exist, no need for a fallback
113 return 'Monospace'
113 return 'Monospace'
114
114
115 font_size = Integer(config=True,
115 font_size = Integer(config=True,
116 help="""The font size. If unconfigured, Qt will be entrusted
116 help="""The font size. If unconfigured, Qt will be entrusted
117 with the size of the font.
117 with the size of the font.
118 """)
118 """)
119
119
120 # Whether to override ShortcutEvents for the keybindings defined by this
120 # Whether to override ShortcutEvents for the keybindings defined by this
121 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
121 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
122 # priority (when it has focus) over, e.g., window-level menu shortcuts.
122 # priority (when it has focus) over, e.g., window-level menu shortcuts.
123 override_shortcuts = Bool(False)
123 override_shortcuts = Bool(False)
124
124
125 #------ Signals ------------------------------------------------------------
125 #------ Signals ------------------------------------------------------------
126
126
127 # Signals that indicate ConsoleWidget state.
127 # Signals that indicate ConsoleWidget state.
128 copy_available = QtCore.Signal(bool)
128 copy_available = QtCore.Signal(bool)
129 redo_available = QtCore.Signal(bool)
129 redo_available = QtCore.Signal(bool)
130 undo_available = QtCore.Signal(bool)
130 undo_available = QtCore.Signal(bool)
131
131
132 # Signal emitted when paging is needed and the paging style has been
132 # Signal emitted when paging is needed and the paging style has been
133 # specified as 'custom'.
133 # specified as 'custom'.
134 custom_page_requested = QtCore.Signal(object)
134 custom_page_requested = QtCore.Signal(object)
135
135
136 # Signal emitted when the font is changed.
136 # Signal emitted when the font is changed.
137 font_changed = QtCore.Signal(QtGui.QFont)
137 font_changed = QtCore.Signal(QtGui.QFont)
138
138
139 #------ Protected class variables ------------------------------------------
139 #------ Protected class variables ------------------------------------------
140
140
141 # When the control key is down, these keys are mapped.
141 # When the control key is down, these keys are mapped.
142 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
142 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
143 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
143 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
144 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
144 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
145 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
145 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
146 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
146 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
147 QtCore.Qt.Key_H : QtCore.Qt.Key_Backspace,
147 QtCore.Qt.Key_H : QtCore.Qt.Key_Backspace, }
148 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
149 if not sys.platform == 'darwin':
148 if not sys.platform == 'darwin':
150 # On OS X, Ctrl-E already does the right thing, whereas End moves the
149 # On OS X, Ctrl-E already does the right thing, whereas End moves the
151 # cursor to the bottom of the buffer.
150 # cursor to the bottom of the buffer.
152 _ctrl_down_remap[QtCore.Qt.Key_E] = QtCore.Qt.Key_End
151 _ctrl_down_remap[QtCore.Qt.Key_E] = QtCore.Qt.Key_End
153
152
154 # The shortcuts defined by this widget. We need to keep track of these to
153 # The shortcuts defined by this widget. We need to keep track of these to
155 # support 'override_shortcuts' above.
154 # support 'override_shortcuts' above.
156 _shortcuts = set(_ctrl_down_remap.keys() +
155 _shortcuts = set(_ctrl_down_remap.keys() +
157 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
156 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
158 QtCore.Qt.Key_V ])
157 QtCore.Qt.Key_V ])
159
158
160 #---------------------------------------------------------------------------
159 #---------------------------------------------------------------------------
161 # 'QObject' interface
160 # 'QObject' interface
162 #---------------------------------------------------------------------------
161 #---------------------------------------------------------------------------
163
162
164 def __init__(self, parent=None, **kw):
163 def __init__(self, parent=None, **kw):
165 """ Create a ConsoleWidget.
164 """ Create a ConsoleWidget.
166
165
167 Parameters:
166 Parameters:
168 -----------
167 -----------
169 parent : QWidget, optional [default None]
168 parent : QWidget, optional [default None]
170 The parent for this widget.
169 The parent for this widget.
171 """
170 """
172 QtGui.QWidget.__init__(self, parent)
171 QtGui.QWidget.__init__(self, parent)
173 LoggingConfigurable.__init__(self, **kw)
172 LoggingConfigurable.__init__(self, **kw)
174
173
175 # While scrolling the pager on Mac OS X, it tears badly. The
174 # While scrolling the pager on Mac OS X, it tears badly. The
176 # NativeGesture is platform and perhaps build-specific hence
175 # NativeGesture is platform and perhaps build-specific hence
177 # we take adequate precautions here.
176 # we take adequate precautions here.
178 self._pager_scroll_events = [QtCore.QEvent.Wheel]
177 self._pager_scroll_events = [QtCore.QEvent.Wheel]
179 if hasattr(QtCore.QEvent, 'NativeGesture'):
178 if hasattr(QtCore.QEvent, 'NativeGesture'):
180 self._pager_scroll_events.append(QtCore.QEvent.NativeGesture)
179 self._pager_scroll_events.append(QtCore.QEvent.NativeGesture)
181
180
182 # Create the layout and underlying text widget.
181 # Create the layout and underlying text widget.
183 layout = QtGui.QStackedLayout(self)
182 layout = QtGui.QStackedLayout(self)
184 layout.setContentsMargins(0, 0, 0, 0)
183 layout.setContentsMargins(0, 0, 0, 0)
185 self._control = self._create_control()
184 self._control = self._create_control()
186 self._page_control = None
185 self._page_control = None
187 self._splitter = None
186 self._splitter = None
188 if self.paging in ('hsplit', 'vsplit'):
187 if self.paging in ('hsplit', 'vsplit'):
189 self._splitter = QtGui.QSplitter()
188 self._splitter = QtGui.QSplitter()
190 if self.paging == 'hsplit':
189 if self.paging == 'hsplit':
191 self._splitter.setOrientation(QtCore.Qt.Horizontal)
190 self._splitter.setOrientation(QtCore.Qt.Horizontal)
192 else:
191 else:
193 self._splitter.setOrientation(QtCore.Qt.Vertical)
192 self._splitter.setOrientation(QtCore.Qt.Vertical)
194 self._splitter.addWidget(self._control)
193 self._splitter.addWidget(self._control)
195 layout.addWidget(self._splitter)
194 layout.addWidget(self._splitter)
196 else:
195 else:
197 layout.addWidget(self._control)
196 layout.addWidget(self._control)
198
197
199 # Create the paging widget, if necessary.
198 # Create the paging widget, if necessary.
200 if self.paging in ('inside', 'hsplit', 'vsplit'):
199 if self.paging in ('inside', 'hsplit', 'vsplit'):
201 self._page_control = self._create_page_control()
200 self._page_control = self._create_page_control()
202 if self._splitter:
201 if self._splitter:
203 self._page_control.hide()
202 self._page_control.hide()
204 self._splitter.addWidget(self._page_control)
203 self._splitter.addWidget(self._page_control)
205 else:
204 else:
206 layout.addWidget(self._page_control)
205 layout.addWidget(self._page_control)
207
206
208 # Initialize protected variables. Some variables contain useful state
207 # Initialize protected variables. Some variables contain useful state
209 # information for subclasses; they should be considered read-only.
208 # information for subclasses; they should be considered read-only.
210 self._append_before_prompt_pos = 0
209 self._append_before_prompt_pos = 0
211 self._ansi_processor = QtAnsiCodeProcessor()
210 self._ansi_processor = QtAnsiCodeProcessor()
212 self._completion_widget = CompletionWidget(self._control)
211 self._completion_widget = CompletionWidget(self._control)
213 self._continuation_prompt = '> '
212 self._continuation_prompt = '> '
214 self._continuation_prompt_html = None
213 self._continuation_prompt_html = None
215 self._executing = False
214 self._executing = False
216 self._filter_drag = False
215 self._filter_drag = False
217 self._filter_resize = False
216 self._filter_resize = False
218 self._html_exporter = HtmlExporter(self._control)
217 self._html_exporter = HtmlExporter(self._control)
219 self._input_buffer_executing = ''
218 self._input_buffer_executing = ''
220 self._input_buffer_pending = ''
219 self._input_buffer_pending = ''
221 self._kill_ring = QtKillRing(self._control)
220 self._kill_ring = QtKillRing(self._control)
222 self._prompt = ''
221 self._prompt = ''
223 self._prompt_html = None
222 self._prompt_html = None
224 self._prompt_pos = 0
223 self._prompt_pos = 0
225 self._prompt_sep = ''
224 self._prompt_sep = ''
226 self._reading = False
225 self._reading = False
227 self._reading_callback = None
226 self._reading_callback = None
228 self._tab_width = 8
227 self._tab_width = 8
229 self._text_completing_pos = 0
228 self._text_completing_pos = 0
230
229
231 # Set a monospaced font.
230 # Set a monospaced font.
232 self.reset_font()
231 self.reset_font()
233
232
234 # Configure actions.
233 # Configure actions.
235 action = QtGui.QAction('Print', None)
234 action = QtGui.QAction('Print', None)
236 action.setEnabled(True)
235 action.setEnabled(True)
237 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
236 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
238 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
237 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
239 # Only override the default if there is a collision.
238 # Only override the default if there is a collision.
240 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
239 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
241 printkey = "Ctrl+Shift+P"
240 printkey = "Ctrl+Shift+P"
242 action.setShortcut(printkey)
241 action.setShortcut(printkey)
243 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
242 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
244 action.triggered.connect(self.print_)
243 action.triggered.connect(self.print_)
245 self.addAction(action)
244 self.addAction(action)
246 self.print_action = action
245 self.print_action = action
247
246
248 action = QtGui.QAction('Save as HTML/XML', None)
247 action = QtGui.QAction('Save as HTML/XML', None)
249 action.setShortcut(QtGui.QKeySequence.Save)
248 action.setShortcut(QtGui.QKeySequence.Save)
250 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
249 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
251 action.triggered.connect(self.export_html)
250 action.triggered.connect(self.export_html)
252 self.addAction(action)
251 self.addAction(action)
253 self.export_action = action
252 self.export_action = action
254
253
255 action = QtGui.QAction('Select All', None)
254 action = QtGui.QAction('Select All', None)
256 action.setEnabled(True)
255 action.setEnabled(True)
257 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
256 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
258 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
257 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
259 # Only override the default if there is a collision.
258 # Only override the default if there is a collision.
260 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
259 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
261 selectall = "Ctrl+Shift+A"
260 selectall = "Ctrl+Shift+A"
262 action.setShortcut(selectall)
261 action.setShortcut(selectall)
263 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
262 action.setShortcutContext(QtCore.Qt.WidgetWithChildrenShortcut)
264 action.triggered.connect(self.select_all)
263 action.triggered.connect(self.select_all)
265 self.addAction(action)
264 self.addAction(action)
266 self.select_all_action = action
265 self.select_all_action = action
267
266
268 self.increase_font_size = QtGui.QAction("Bigger Font",
267 self.increase_font_size = QtGui.QAction("Bigger Font",
269 self,
268 self,
270 shortcut=QtGui.QKeySequence.ZoomIn,
269 shortcut=QtGui.QKeySequence.ZoomIn,
271 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
270 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
272 statusTip="Increase the font size by one point",
271 statusTip="Increase the font size by one point",
273 triggered=self._increase_font_size)
272 triggered=self._increase_font_size)
274 self.addAction(self.increase_font_size)
273 self.addAction(self.increase_font_size)
275
274
276 self.decrease_font_size = QtGui.QAction("Smaller Font",
275 self.decrease_font_size = QtGui.QAction("Smaller Font",
277 self,
276 self,
278 shortcut=QtGui.QKeySequence.ZoomOut,
277 shortcut=QtGui.QKeySequence.ZoomOut,
279 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
278 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
280 statusTip="Decrease the font size by one point",
279 statusTip="Decrease the font size by one point",
281 triggered=self._decrease_font_size)
280 triggered=self._decrease_font_size)
282 self.addAction(self.decrease_font_size)
281 self.addAction(self.decrease_font_size)
283
282
284 self.reset_font_size = QtGui.QAction("Normal Font",
283 self.reset_font_size = QtGui.QAction("Normal Font",
285 self,
284 self,
286 shortcut="Ctrl+0",
285 shortcut="Ctrl+0",
287 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
286 shortcutContext=QtCore.Qt.WidgetWithChildrenShortcut,
288 statusTip="Restore the Normal font size",
287 statusTip="Restore the Normal font size",
289 triggered=self.reset_font)
288 triggered=self.reset_font)
290 self.addAction(self.reset_font_size)
289 self.addAction(self.reset_font_size)
291
290
292
291
293
292
294 def eventFilter(self, obj, event):
293 def eventFilter(self, obj, event):
295 """ Reimplemented to ensure a console-like behavior in the underlying
294 """ Reimplemented to ensure a console-like behavior in the underlying
296 text widgets.
295 text widgets.
297 """
296 """
298 etype = event.type()
297 etype = event.type()
299 if etype == QtCore.QEvent.KeyPress:
298 if etype == QtCore.QEvent.KeyPress:
300
299
301 # Re-map keys for all filtered widgets.
300 # Re-map keys for all filtered widgets.
302 key = event.key()
301 key = event.key()
303 if self._control_key_down(event.modifiers()) and \
302 if self._control_key_down(event.modifiers()) and \
304 key in self._ctrl_down_remap:
303 key in self._ctrl_down_remap:
305 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
304 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
306 self._ctrl_down_remap[key],
305 self._ctrl_down_remap[key],
307 QtCore.Qt.NoModifier)
306 QtCore.Qt.NoModifier)
308 QtGui.qApp.sendEvent(obj, new_event)
307 QtGui.qApp.sendEvent(obj, new_event)
309 return True
308 return True
310
309
311 elif obj == self._control:
310 elif obj == self._control:
312 return self._event_filter_console_keypress(event)
311 return self._event_filter_console_keypress(event)
313
312
314 elif obj == self._page_control:
313 elif obj == self._page_control:
315 return self._event_filter_page_keypress(event)
314 return self._event_filter_page_keypress(event)
316
315
317 # Make middle-click paste safe.
316 # Make middle-click paste safe.
318 elif etype == QtCore.QEvent.MouseButtonRelease and \
317 elif etype == QtCore.QEvent.MouseButtonRelease and \
319 event.button() == QtCore.Qt.MidButton and \
318 event.button() == QtCore.Qt.MidButton and \
320 obj == self._control.viewport():
319 obj == self._control.viewport():
321 cursor = self._control.cursorForPosition(event.pos())
320 cursor = self._control.cursorForPosition(event.pos())
322 self._control.setTextCursor(cursor)
321 self._control.setTextCursor(cursor)
323 self.paste(QtGui.QClipboard.Selection)
322 self.paste(QtGui.QClipboard.Selection)
324 return True
323 return True
325
324
326 # Manually adjust the scrollbars *after* a resize event is dispatched.
325 # Manually adjust the scrollbars *after* a resize event is dispatched.
327 elif etype == QtCore.QEvent.Resize and not self._filter_resize:
326 elif etype == QtCore.QEvent.Resize and not self._filter_resize:
328 self._filter_resize = True
327 self._filter_resize = True
329 QtGui.qApp.sendEvent(obj, event)
328 QtGui.qApp.sendEvent(obj, event)
330 self._adjust_scrollbars()
329 self._adjust_scrollbars()
331 self._filter_resize = False
330 self._filter_resize = False
332 return True
331 return True
333
332
334 # Override shortcuts for all filtered widgets.
333 # Override shortcuts for all filtered widgets.
335 elif etype == QtCore.QEvent.ShortcutOverride and \
334 elif etype == QtCore.QEvent.ShortcutOverride and \
336 self.override_shortcuts and \
335 self.override_shortcuts and \
337 self._control_key_down(event.modifiers()) and \
336 self._control_key_down(event.modifiers()) and \
338 event.key() in self._shortcuts:
337 event.key() in self._shortcuts:
339 event.accept()
338 event.accept()
340
339
341 # Ensure that drags are safe. The problem is that the drag starting
340 # Ensure that drags are safe. The problem is that the drag starting
342 # logic, which determines whether the drag is a Copy or Move, is locked
341 # logic, which determines whether the drag is a Copy or Move, is locked
343 # down in QTextControl. If the widget is editable, which it must be if
342 # down in QTextControl. If the widget is editable, which it must be if
344 # we're not executing, the drag will be a Move. The following hack
343 # we're not executing, the drag will be a Move. The following hack
345 # prevents QTextControl from deleting the text by clearing the selection
344 # prevents QTextControl from deleting the text by clearing the selection
346 # when a drag leave event originating from this widget is dispatched.
345 # when a drag leave event originating from this widget is dispatched.
347 # The fact that we have to clear the user's selection is unfortunate,
346 # The fact that we have to clear the user's selection is unfortunate,
348 # but the alternative--trying to prevent Qt from using its hardwired
347 # but the alternative--trying to prevent Qt from using its hardwired
349 # drag logic and writing our own--is worse.
348 # drag logic and writing our own--is worse.
350 elif etype == QtCore.QEvent.DragEnter and \
349 elif etype == QtCore.QEvent.DragEnter and \
351 obj == self._control.viewport() and \
350 obj == self._control.viewport() and \
352 event.source() == self._control.viewport():
351 event.source() == self._control.viewport():
353 self._filter_drag = True
352 self._filter_drag = True
354 elif etype == QtCore.QEvent.DragLeave and \
353 elif etype == QtCore.QEvent.DragLeave and \
355 obj == self._control.viewport() and \
354 obj == self._control.viewport() and \
356 self._filter_drag:
355 self._filter_drag:
357 cursor = self._control.textCursor()
356 cursor = self._control.textCursor()
358 cursor.clearSelection()
357 cursor.clearSelection()
359 self._control.setTextCursor(cursor)
358 self._control.setTextCursor(cursor)
360 self._filter_drag = False
359 self._filter_drag = False
361
360
362 # Ensure that drops are safe.
361 # Ensure that drops are safe.
363 elif etype == QtCore.QEvent.Drop and obj == self._control.viewport():
362 elif etype == QtCore.QEvent.Drop and obj == self._control.viewport():
364 cursor = self._control.cursorForPosition(event.pos())
363 cursor = self._control.cursorForPosition(event.pos())
365 if self._in_buffer(cursor.position()):
364 if self._in_buffer(cursor.position()):
366 text = event.mimeData().text()
365 text = event.mimeData().text()
367 self._insert_plain_text_into_buffer(cursor, text)
366 self._insert_plain_text_into_buffer(cursor, text)
368
367
369 # Qt is expecting to get something here--drag and drop occurs in its
368 # Qt is expecting to get something here--drag and drop occurs in its
370 # own event loop. Send a DragLeave event to end it.
369 # own event loop. Send a DragLeave event to end it.
371 QtGui.qApp.sendEvent(obj, QtGui.QDragLeaveEvent())
370 QtGui.qApp.sendEvent(obj, QtGui.QDragLeaveEvent())
372 return True
371 return True
373
372
374 # Handle scrolling of the vsplit pager. This hack attempts to solve
373 # Handle scrolling of the vsplit pager. This hack attempts to solve
375 # problems with tearing of the help text inside the pager window. This
374 # problems with tearing of the help text inside the pager window. This
376 # happens only on Mac OS X with both PySide and PyQt. This fix isn't
375 # happens only on Mac OS X with both PySide and PyQt. This fix isn't
377 # perfect but makes the pager more usable.
376 # perfect but makes the pager more usable.
378 elif etype in self._pager_scroll_events and \
377 elif etype in self._pager_scroll_events and \
379 obj == self._page_control:
378 obj == self._page_control:
380 self._page_control.repaint()
379 self._page_control.repaint()
381 return True
380 return True
382 return super(ConsoleWidget, self).eventFilter(obj, event)
381 return super(ConsoleWidget, self).eventFilter(obj, event)
383
382
384 #---------------------------------------------------------------------------
383 #---------------------------------------------------------------------------
385 # 'QWidget' interface
384 # 'QWidget' interface
386 #---------------------------------------------------------------------------
385 #---------------------------------------------------------------------------
387
386
388 def sizeHint(self):
387 def sizeHint(self):
389 """ Reimplemented to suggest a size that is 80 characters wide and
388 """ Reimplemented to suggest a size that is 80 characters wide and
390 25 lines high.
389 25 lines high.
391 """
390 """
392 font_metrics = QtGui.QFontMetrics(self.font)
391 font_metrics = QtGui.QFontMetrics(self.font)
393 margin = (self._control.frameWidth() +
392 margin = (self._control.frameWidth() +
394 self._control.document().documentMargin()) * 2
393 self._control.document().documentMargin()) * 2
395 style = self.style()
394 style = self.style()
396 splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth)
395 splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth)
397
396
398 # Note 1: Despite my best efforts to take the various margins into
397 # Note 1: Despite my best efforts to take the various margins into
399 # account, the width is still coming out a bit too small, so we include
398 # account, the width is still coming out a bit too small, so we include
400 # a fudge factor of one character here.
399 # a fudge factor of one character here.
401 # Note 2: QFontMetrics.maxWidth is not used here or anywhere else due
400 # Note 2: QFontMetrics.maxWidth is not used here or anywhere else due
402 # to a Qt bug on certain Mac OS systems where it returns 0.
401 # to a Qt bug on certain Mac OS systems where it returns 0.
403 width = font_metrics.width(' ') * 81 + margin
402 width = font_metrics.width(' ') * 81 + margin
404 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
403 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
405 if self.paging == 'hsplit':
404 if self.paging == 'hsplit':
406 width = width * 2 + splitwidth
405 width = width * 2 + splitwidth
407
406
408 height = font_metrics.height() * 25 + margin
407 height = font_metrics.height() * 25 + margin
409 if self.paging == 'vsplit':
408 if self.paging == 'vsplit':
410 height = height * 2 + splitwidth
409 height = height * 2 + splitwidth
411
410
412 return QtCore.QSize(width, height)
411 return QtCore.QSize(width, height)
413
412
414 #---------------------------------------------------------------------------
413 #---------------------------------------------------------------------------
415 # 'ConsoleWidget' public interface
414 # 'ConsoleWidget' public interface
416 #---------------------------------------------------------------------------
415 #---------------------------------------------------------------------------
417
416
418 def can_copy(self):
417 def can_copy(self):
419 """ Returns whether text can be copied to the clipboard.
418 """ Returns whether text can be copied to the clipboard.
420 """
419 """
421 return self._control.textCursor().hasSelection()
420 return self._control.textCursor().hasSelection()
422
421
423 def can_cut(self):
422 def can_cut(self):
424 """ Returns whether text can be cut to the clipboard.
423 """ Returns whether text can be cut to the clipboard.
425 """
424 """
426 cursor = self._control.textCursor()
425 cursor = self._control.textCursor()
427 return (cursor.hasSelection() and
426 return (cursor.hasSelection() and
428 self._in_buffer(cursor.anchor()) and
427 self._in_buffer(cursor.anchor()) and
429 self._in_buffer(cursor.position()))
428 self._in_buffer(cursor.position()))
430
429
431 def can_paste(self):
430 def can_paste(self):
432 """ Returns whether text can be pasted from the clipboard.
431 """ Returns whether text can be pasted from the clipboard.
433 """
432 """
434 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
433 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
435 return bool(QtGui.QApplication.clipboard().text())
434 return bool(QtGui.QApplication.clipboard().text())
436 return False
435 return False
437
436
438 def clear(self, keep_input=True):
437 def clear(self, keep_input=True):
439 """ Clear the console.
438 """ Clear the console.
440
439
441 Parameters:
440 Parameters:
442 -----------
441 -----------
443 keep_input : bool, optional (default True)
442 keep_input : bool, optional (default True)
444 If set, restores the old input buffer if a new prompt is written.
443 If set, restores the old input buffer if a new prompt is written.
445 """
444 """
446 if self._executing:
445 if self._executing:
447 self._control.clear()
446 self._control.clear()
448 else:
447 else:
449 if keep_input:
448 if keep_input:
450 input_buffer = self.input_buffer
449 input_buffer = self.input_buffer
451 self._control.clear()
450 self._control.clear()
452 self._show_prompt()
451 self._show_prompt()
453 if keep_input:
452 if keep_input:
454 self.input_buffer = input_buffer
453 self.input_buffer = input_buffer
455
454
456 def copy(self):
455 def copy(self):
457 """ Copy the currently selected text to the clipboard.
456 """ Copy the currently selected text to the clipboard.
458 """
457 """
459 self.layout().currentWidget().copy()
458 self.layout().currentWidget().copy()
460
459
461 def cut(self):
460 def cut(self):
462 """ Copy the currently selected text to the clipboard and delete it
461 """ Copy the currently selected text to the clipboard and delete it
463 if it's inside the input buffer.
462 if it's inside the input buffer.
464 """
463 """
465 self.copy()
464 self.copy()
466 if self.can_cut():
465 if self.can_cut():
467 self._control.textCursor().removeSelectedText()
466 self._control.textCursor().removeSelectedText()
468
467
469 def execute(self, source=None, hidden=False, interactive=False):
468 def execute(self, source=None, hidden=False, interactive=False):
470 """ Executes source or the input buffer, possibly prompting for more
469 """ Executes source or the input buffer, possibly prompting for more
471 input.
470 input.
472
471
473 Parameters:
472 Parameters:
474 -----------
473 -----------
475 source : str, optional
474 source : str, optional
476
475
477 The source to execute. If not specified, the input buffer will be
476 The source to execute. If not specified, the input buffer will be
478 used. If specified and 'hidden' is False, the input buffer will be
477 used. If specified and 'hidden' is False, the input buffer will be
479 replaced with the source before execution.
478 replaced with the source before execution.
480
479
481 hidden : bool, optional (default False)
480 hidden : bool, optional (default False)
482
481
483 If set, no output will be shown and the prompt will not be modified.
482 If set, no output will be shown and the prompt will not be modified.
484 In other words, it will be completely invisible to the user that
483 In other words, it will be completely invisible to the user that
485 an execution has occurred.
484 an execution has occurred.
486
485
487 interactive : bool, optional (default False)
486 interactive : bool, optional (default False)
488
487
489 Whether the console is to treat the source as having been manually
488 Whether the console is to treat the source as having been manually
490 entered by the user. The effect of this parameter depends on the
489 entered by the user. The effect of this parameter depends on the
491 subclass implementation.
490 subclass implementation.
492
491
493 Raises:
492 Raises:
494 -------
493 -------
495 RuntimeError
494 RuntimeError
496 If incomplete input is given and 'hidden' is True. In this case,
495 If incomplete input is given and 'hidden' is True. In this case,
497 it is not possible to prompt for more input.
496 it is not possible to prompt for more input.
498
497
499 Returns:
498 Returns:
500 --------
499 --------
501 A boolean indicating whether the source was executed.
500 A boolean indicating whether the source was executed.
502 """
501 """
503 # WARNING: The order in which things happen here is very particular, in
502 # WARNING: The order in which things happen here is very particular, in
504 # large part because our syntax highlighting is fragile. If you change
503 # large part because our syntax highlighting is fragile. If you change
505 # something, test carefully!
504 # something, test carefully!
506
505
507 # Decide what to execute.
506 # Decide what to execute.
508 if source is None:
507 if source is None:
509 source = self.input_buffer
508 source = self.input_buffer
510 if not hidden:
509 if not hidden:
511 # A newline is appended later, but it should be considered part
510 # A newline is appended later, but it should be considered part
512 # of the input buffer.
511 # of the input buffer.
513 source += '\n'
512 source += '\n'
514 elif not hidden:
513 elif not hidden:
515 self.input_buffer = source
514 self.input_buffer = source
516
515
517 # Execute the source or show a continuation prompt if it is incomplete.
516 # Execute the source or show a continuation prompt if it is incomplete.
518 complete = self._is_complete(source, interactive)
517 complete = self._is_complete(source, interactive)
519 if hidden:
518 if hidden:
520 if complete:
519 if complete:
521 self._execute(source, hidden)
520 self._execute(source, hidden)
522 else:
521 else:
523 error = 'Incomplete noninteractive input: "%s"'
522 error = 'Incomplete noninteractive input: "%s"'
524 raise RuntimeError(error % source)
523 raise RuntimeError(error % source)
525 else:
524 else:
526 if complete:
525 if complete:
527 self._append_plain_text('\n')
526 self._append_plain_text('\n')
528 self._input_buffer_executing = self.input_buffer
527 self._input_buffer_executing = self.input_buffer
529 self._executing = True
528 self._executing = True
530 self._prompt_finished()
529 self._prompt_finished()
531
530
532 # The maximum block count is only in effect during execution.
531 # The maximum block count is only in effect during execution.
533 # This ensures that _prompt_pos does not become invalid due to
532 # This ensures that _prompt_pos does not become invalid due to
534 # text truncation.
533 # text truncation.
535 self._control.document().setMaximumBlockCount(self.buffer_size)
534 self._control.document().setMaximumBlockCount(self.buffer_size)
536
535
537 # Setting a positive maximum block count will automatically
536 # Setting a positive maximum block count will automatically
538 # disable the undo/redo history, but just to be safe:
537 # disable the undo/redo history, but just to be safe:
539 self._control.setUndoRedoEnabled(False)
538 self._control.setUndoRedoEnabled(False)
540
539
541 # Perform actual execution.
540 # Perform actual execution.
542 self._execute(source, hidden)
541 self._execute(source, hidden)
543
542
544 else:
543 else:
545 # Do this inside an edit block so continuation prompts are
544 # Do this inside an edit block so continuation prompts are
546 # removed seamlessly via undo/redo.
545 # removed seamlessly via undo/redo.
547 cursor = self._get_end_cursor()
546 cursor = self._get_end_cursor()
548 cursor.beginEditBlock()
547 cursor.beginEditBlock()
549 cursor.insertText('\n')
548 cursor.insertText('\n')
550 self._insert_continuation_prompt(cursor)
549 self._insert_continuation_prompt(cursor)
551 cursor.endEditBlock()
550 cursor.endEditBlock()
552
551
553 # Do not do this inside the edit block. It works as expected
552 # Do not do this inside the edit block. It works as expected
554 # when using a QPlainTextEdit control, but does not have an
553 # when using a QPlainTextEdit control, but does not have an
555 # effect when using a QTextEdit. I believe this is a Qt bug.
554 # effect when using a QTextEdit. I believe this is a Qt bug.
556 self._control.moveCursor(QtGui.QTextCursor.End)
555 self._control.moveCursor(QtGui.QTextCursor.End)
557
556
558 return complete
557 return complete
559
558
560 def export_html(self):
559 def export_html(self):
561 """ Shows a dialog to export HTML/XML in various formats.
560 """ Shows a dialog to export HTML/XML in various formats.
562 """
561 """
563 self._html_exporter.export()
562 self._html_exporter.export()
564
563
565 def _get_input_buffer(self, force=False):
564 def _get_input_buffer(self, force=False):
566 """ The text that the user has entered entered at the current prompt.
565 """ The text that the user has entered entered at the current prompt.
567
566
568 If the console is currently executing, the text that is executing will
567 If the console is currently executing, the text that is executing will
569 always be returned.
568 always be returned.
570 """
569 """
571 # If we're executing, the input buffer may not even exist anymore due to
570 # If we're executing, the input buffer may not even exist anymore due to
572 # the limit imposed by 'buffer_size'. Therefore, we store it.
571 # the limit imposed by 'buffer_size'. Therefore, we store it.
573 if self._executing and not force:
572 if self._executing and not force:
574 return self._input_buffer_executing
573 return self._input_buffer_executing
575
574
576 cursor = self._get_end_cursor()
575 cursor = self._get_end_cursor()
577 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
576 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
578 input_buffer = cursor.selection().toPlainText()
577 input_buffer = cursor.selection().toPlainText()
579
578
580 # Strip out continuation prompts.
579 # Strip out continuation prompts.
581 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
580 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
582
581
583 def _set_input_buffer(self, string):
582 def _set_input_buffer(self, string):
584 """ Sets the text in the input buffer.
583 """ Sets the text in the input buffer.
585
584
586 If the console is currently executing, this call has no *immediate*
585 If the console is currently executing, this call has no *immediate*
587 effect. When the execution is finished, the input buffer will be updated
586 effect. When the execution is finished, the input buffer will be updated
588 appropriately.
587 appropriately.
589 """
588 """
590 # If we're executing, store the text for later.
589 # If we're executing, store the text for later.
591 if self._executing:
590 if self._executing:
592 self._input_buffer_pending = string
591 self._input_buffer_pending = string
593 return
592 return
594
593
595 # Remove old text.
594 # Remove old text.
596 cursor = self._get_end_cursor()
595 cursor = self._get_end_cursor()
597 cursor.beginEditBlock()
596 cursor.beginEditBlock()
598 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
597 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
599 cursor.removeSelectedText()
598 cursor.removeSelectedText()
600
599
601 # Insert new text with continuation prompts.
600 # Insert new text with continuation prompts.
602 self._insert_plain_text_into_buffer(self._get_prompt_cursor(), string)
601 self._insert_plain_text_into_buffer(self._get_prompt_cursor(), string)
603 cursor.endEditBlock()
602 cursor.endEditBlock()
604 self._control.moveCursor(QtGui.QTextCursor.End)
603 self._control.moveCursor(QtGui.QTextCursor.End)
605
604
606 input_buffer = property(_get_input_buffer, _set_input_buffer)
605 input_buffer = property(_get_input_buffer, _set_input_buffer)
607
606
608 def _get_font(self):
607 def _get_font(self):
609 """ The base font being used by the ConsoleWidget.
608 """ The base font being used by the ConsoleWidget.
610 """
609 """
611 return self._control.document().defaultFont()
610 return self._control.document().defaultFont()
612
611
613 def _set_font(self, font):
612 def _set_font(self, font):
614 """ Sets the base font for the ConsoleWidget to the specified QFont.
613 """ Sets the base font for the ConsoleWidget to the specified QFont.
615 """
614 """
616 font_metrics = QtGui.QFontMetrics(font)
615 font_metrics = QtGui.QFontMetrics(font)
617 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
616 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
618
617
619 self._completion_widget.setFont(font)
618 self._completion_widget.setFont(font)
620 self._control.document().setDefaultFont(font)
619 self._control.document().setDefaultFont(font)
621 if self._page_control:
620 if self._page_control:
622 self._page_control.document().setDefaultFont(font)
621 self._page_control.document().setDefaultFont(font)
623
622
624 self.font_changed.emit(font)
623 self.font_changed.emit(font)
625
624
626 font = property(_get_font, _set_font)
625 font = property(_get_font, _set_font)
627
626
628 def paste(self, mode=QtGui.QClipboard.Clipboard):
627 def paste(self, mode=QtGui.QClipboard.Clipboard):
629 """ Paste the contents of the clipboard into the input region.
628 """ Paste the contents of the clipboard into the input region.
630
629
631 Parameters:
630 Parameters:
632 -----------
631 -----------
633 mode : QClipboard::Mode, optional [default QClipboard::Clipboard]
632 mode : QClipboard::Mode, optional [default QClipboard::Clipboard]
634
633
635 Controls which part of the system clipboard is used. This can be
634 Controls which part of the system clipboard is used. This can be
636 used to access the selection clipboard in X11 and the Find buffer
635 used to access the selection clipboard in X11 and the Find buffer
637 in Mac OS. By default, the regular clipboard is used.
636 in Mac OS. By default, the regular clipboard is used.
638 """
637 """
639 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
638 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
640 # Make sure the paste is safe.
639 # Make sure the paste is safe.
641 self._keep_cursor_in_buffer()
640 self._keep_cursor_in_buffer()
642 cursor = self._control.textCursor()
641 cursor = self._control.textCursor()
643
642
644 # Remove any trailing newline, which confuses the GUI and forces the
643 # Remove any trailing newline, which confuses the GUI and forces the
645 # user to backspace.
644 # user to backspace.
646 text = QtGui.QApplication.clipboard().text(mode).rstrip()
645 text = QtGui.QApplication.clipboard().text(mode).rstrip()
647 self._insert_plain_text_into_buffer(cursor, dedent(text))
646 self._insert_plain_text_into_buffer(cursor, dedent(text))
648
647
649 def print_(self, printer = None):
648 def print_(self, printer = None):
650 """ Print the contents of the ConsoleWidget to the specified QPrinter.
649 """ Print the contents of the ConsoleWidget to the specified QPrinter.
651 """
650 """
652 if (not printer):
651 if (not printer):
653 printer = QtGui.QPrinter()
652 printer = QtGui.QPrinter()
654 if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
653 if(QtGui.QPrintDialog(printer).exec_() != QtGui.QDialog.Accepted):
655 return
654 return
656 self._control.print_(printer)
655 self._control.print_(printer)
657
656
658 def prompt_to_top(self):
657 def prompt_to_top(self):
659 """ Moves the prompt to the top of the viewport.
658 """ Moves the prompt to the top of the viewport.
660 """
659 """
661 if not self._executing:
660 if not self._executing:
662 prompt_cursor = self._get_prompt_cursor()
661 prompt_cursor = self._get_prompt_cursor()
663 if self._get_cursor().blockNumber() < prompt_cursor.blockNumber():
662 if self._get_cursor().blockNumber() < prompt_cursor.blockNumber():
664 self._set_cursor(prompt_cursor)
663 self._set_cursor(prompt_cursor)
665 self._set_top_cursor(prompt_cursor)
664 self._set_top_cursor(prompt_cursor)
666
665
667 def redo(self):
666 def redo(self):
668 """ Redo the last operation. If there is no operation to redo, nothing
667 """ Redo the last operation. If there is no operation to redo, nothing
669 happens.
668 happens.
670 """
669 """
671 self._control.redo()
670 self._control.redo()
672
671
673 def reset_font(self):
672 def reset_font(self):
674 """ Sets the font to the default fixed-width font for this platform.
673 """ Sets the font to the default fixed-width font for this platform.
675 """
674 """
676 if sys.platform == 'win32':
675 if sys.platform == 'win32':
677 # Consolas ships with Vista/Win7, fallback to Courier if needed
676 # Consolas ships with Vista/Win7, fallback to Courier if needed
678 fallback = 'Courier'
677 fallback = 'Courier'
679 elif sys.platform == 'darwin':
678 elif sys.platform == 'darwin':
680 # OSX always has Monaco
679 # OSX always has Monaco
681 fallback = 'Monaco'
680 fallback = 'Monaco'
682 else:
681 else:
683 # Monospace should always exist
682 # Monospace should always exist
684 fallback = 'Monospace'
683 fallback = 'Monospace'
685 font = get_font(self.font_family, fallback)
684 font = get_font(self.font_family, fallback)
686 if self.font_size:
685 if self.font_size:
687 font.setPointSize(self.font_size)
686 font.setPointSize(self.font_size)
688 else:
687 else:
689 font.setPointSize(QtGui.qApp.font().pointSize())
688 font.setPointSize(QtGui.qApp.font().pointSize())
690 font.setStyleHint(QtGui.QFont.TypeWriter)
689 font.setStyleHint(QtGui.QFont.TypeWriter)
691 self._set_font(font)
690 self._set_font(font)
692
691
693 def change_font_size(self, delta):
692 def change_font_size(self, delta):
694 """Change the font size by the specified amount (in points).
693 """Change the font size by the specified amount (in points).
695 """
694 """
696 font = self.font
695 font = self.font
697 size = max(font.pointSize() + delta, 1) # minimum 1 point
696 size = max(font.pointSize() + delta, 1) # minimum 1 point
698 font.setPointSize(size)
697 font.setPointSize(size)
699 self._set_font(font)
698 self._set_font(font)
700
699
701 def _increase_font_size(self):
700 def _increase_font_size(self):
702 self.change_font_size(1)
701 self.change_font_size(1)
703
702
704 def _decrease_font_size(self):
703 def _decrease_font_size(self):
705 self.change_font_size(-1)
704 self.change_font_size(-1)
706
705
707 def select_all(self):
706 def select_all(self):
708 """ Selects all the text in the buffer.
707 """ Selects all the text in the buffer.
709 """
708 """
710 self._control.selectAll()
709 self._control.selectAll()
711
710
712 def _get_tab_width(self):
711 def _get_tab_width(self):
713 """ The width (in terms of space characters) for tab characters.
712 """ The width (in terms of space characters) for tab characters.
714 """
713 """
715 return self._tab_width
714 return self._tab_width
716
715
717 def _set_tab_width(self, tab_width):
716 def _set_tab_width(self, tab_width):
718 """ Sets the width (in terms of space characters) for tab characters.
717 """ Sets the width (in terms of space characters) for tab characters.
719 """
718 """
720 font_metrics = QtGui.QFontMetrics(self.font)
719 font_metrics = QtGui.QFontMetrics(self.font)
721 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
720 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
722
721
723 self._tab_width = tab_width
722 self._tab_width = tab_width
724
723
725 tab_width = property(_get_tab_width, _set_tab_width)
724 tab_width = property(_get_tab_width, _set_tab_width)
726
725
727 def undo(self):
726 def undo(self):
728 """ Undo the last operation. If there is no operation to undo, nothing
727 """ Undo the last operation. If there is no operation to undo, nothing
729 happens.
728 happens.
730 """
729 """
731 self._control.undo()
730 self._control.undo()
732
731
733 #---------------------------------------------------------------------------
732 #---------------------------------------------------------------------------
734 # 'ConsoleWidget' abstract interface
733 # 'ConsoleWidget' abstract interface
735 #---------------------------------------------------------------------------
734 #---------------------------------------------------------------------------
736
735
737 def _is_complete(self, source, interactive):
736 def _is_complete(self, source, interactive):
738 """ Returns whether 'source' can be executed. When triggered by an
737 """ Returns whether 'source' can be executed. When triggered by an
739 Enter/Return key press, 'interactive' is True; otherwise, it is
738 Enter/Return key press, 'interactive' is True; otherwise, it is
740 False.
739 False.
741 """
740 """
742 raise NotImplementedError
741 raise NotImplementedError
743
742
744 def _execute(self, source, hidden):
743 def _execute(self, source, hidden):
745 """ Execute 'source'. If 'hidden', do not show any output.
744 """ Execute 'source'. If 'hidden', do not show any output.
746 """
745 """
747 raise NotImplementedError
746 raise NotImplementedError
748
747
749 def _prompt_started_hook(self):
748 def _prompt_started_hook(self):
750 """ Called immediately after a new prompt is displayed.
749 """ Called immediately after a new prompt is displayed.
751 """
750 """
752 pass
751 pass
753
752
754 def _prompt_finished_hook(self):
753 def _prompt_finished_hook(self):
755 """ Called immediately after a prompt is finished, i.e. when some input
754 """ Called immediately after a prompt is finished, i.e. when some input
756 will be processed and a new prompt displayed.
755 will be processed and a new prompt displayed.
757 """
756 """
758 pass
757 pass
759
758
760 def _up_pressed(self, shift_modifier):
759 def _up_pressed(self, shift_modifier):
761 """ Called when the up key is pressed. Returns whether to continue
760 """ Called when the up key is pressed. Returns whether to continue
762 processing the event.
761 processing the event.
763 """
762 """
764 return True
763 return True
765
764
766 def _down_pressed(self, shift_modifier):
765 def _down_pressed(self, shift_modifier):
767 """ Called when the down key is pressed. Returns whether to continue
766 """ Called when the down key is pressed. Returns whether to continue
768 processing the event.
767 processing the event.
769 """
768 """
770 return True
769 return True
771
770
772 def _tab_pressed(self):
771 def _tab_pressed(self):
773 """ Called when the tab key is pressed. Returns whether to continue
772 """ Called when the tab key is pressed. Returns whether to continue
774 processing the event.
773 processing the event.
775 """
774 """
776 return False
775 return False
777
776
778 #--------------------------------------------------------------------------
777 #--------------------------------------------------------------------------
779 # 'ConsoleWidget' protected interface
778 # 'ConsoleWidget' protected interface
780 #--------------------------------------------------------------------------
779 #--------------------------------------------------------------------------
781
780
782 def _append_custom(self, insert, input, before_prompt=False):
781 def _append_custom(self, insert, input, before_prompt=False):
783 """ A low-level method for appending content to the end of the buffer.
782 """ A low-level method for appending content to the end of the buffer.
784
783
785 If 'before_prompt' is enabled, the content will be inserted before the
784 If 'before_prompt' is enabled, the content will be inserted before the
786 current prompt, if there is one.
785 current prompt, if there is one.
787 """
786 """
788 # Determine where to insert the content.
787 # Determine where to insert the content.
789 cursor = self._control.textCursor()
788 cursor = self._control.textCursor()
790 if before_prompt and (self._reading or not self._executing):
789 if before_prompt and (self._reading or not self._executing):
791 cursor.setPosition(self._append_before_prompt_pos)
790 cursor.setPosition(self._append_before_prompt_pos)
792 else:
791 else:
793 cursor.movePosition(QtGui.QTextCursor.End)
792 cursor.movePosition(QtGui.QTextCursor.End)
794 start_pos = cursor.position()
793 start_pos = cursor.position()
795
794
796 # Perform the insertion.
795 # Perform the insertion.
797 result = insert(cursor, input)
796 result = insert(cursor, input)
798
797
799 # Adjust the prompt position if we have inserted before it. This is safe
798 # Adjust the prompt position if we have inserted before it. This is safe
800 # because buffer truncation is disabled when not executing.
799 # because buffer truncation is disabled when not executing.
801 if before_prompt and not self._executing:
800 if before_prompt and not self._executing:
802 diff = cursor.position() - start_pos
801 diff = cursor.position() - start_pos
803 self._append_before_prompt_pos += diff
802 self._append_before_prompt_pos += diff
804 self._prompt_pos += diff
803 self._prompt_pos += diff
805
804
806 return result
805 return result
807
806
808 def _append_html(self, html, before_prompt=False):
807 def _append_html(self, html, before_prompt=False):
809 """ Appends HTML at the end of the console buffer.
808 """ Appends HTML at the end of the console buffer.
810 """
809 """
811 self._append_custom(self._insert_html, html, before_prompt)
810 self._append_custom(self._insert_html, html, before_prompt)
812
811
813 def _append_html_fetching_plain_text(self, html, before_prompt=False):
812 def _append_html_fetching_plain_text(self, html, before_prompt=False):
814 """ Appends HTML, then returns the plain text version of it.
813 """ Appends HTML, then returns the plain text version of it.
815 """
814 """
816 return self._append_custom(self._insert_html_fetching_plain_text,
815 return self._append_custom(self._insert_html_fetching_plain_text,
817 html, before_prompt)
816 html, before_prompt)
818
817
819 def _append_plain_text(self, text, before_prompt=False):
818 def _append_plain_text(self, text, before_prompt=False):
820 """ Appends plain text, processing ANSI codes if enabled.
819 """ Appends plain text, processing ANSI codes if enabled.
821 """
820 """
822 self._append_custom(self._insert_plain_text, text, before_prompt)
821 self._append_custom(self._insert_plain_text, text, before_prompt)
823
822
824 def _cancel_text_completion(self):
823 def _cancel_text_completion(self):
825 """ If text completion is progress, cancel it.
824 """ If text completion is progress, cancel it.
826 """
825 """
827 if self._text_completing_pos:
826 if self._text_completing_pos:
828 self._clear_temporary_buffer()
827 self._clear_temporary_buffer()
829 self._text_completing_pos = 0
828 self._text_completing_pos = 0
830
829
831 def _clear_temporary_buffer(self):
830 def _clear_temporary_buffer(self):
832 """ Clears the "temporary text" buffer, i.e. all the text following
831 """ Clears the "temporary text" buffer, i.e. all the text following
833 the prompt region.
832 the prompt region.
834 """
833 """
835 # Select and remove all text below the input buffer.
834 # Select and remove all text below the input buffer.
836 cursor = self._get_prompt_cursor()
835 cursor = self._get_prompt_cursor()
837 prompt = self._continuation_prompt.lstrip()
836 prompt = self._continuation_prompt.lstrip()
838 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
837 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
839 temp_cursor = QtGui.QTextCursor(cursor)
838 temp_cursor = QtGui.QTextCursor(cursor)
840 temp_cursor.select(QtGui.QTextCursor.BlockUnderCursor)
839 temp_cursor.select(QtGui.QTextCursor.BlockUnderCursor)
841 text = temp_cursor.selection().toPlainText().lstrip()
840 text = temp_cursor.selection().toPlainText().lstrip()
842 if not text.startswith(prompt):
841 if not text.startswith(prompt):
843 break
842 break
844 else:
843 else:
845 # We've reached the end of the input buffer and no text follows.
844 # We've reached the end of the input buffer and no text follows.
846 return
845 return
847 cursor.movePosition(QtGui.QTextCursor.Left) # Grab the newline.
846 cursor.movePosition(QtGui.QTextCursor.Left) # Grab the newline.
848 cursor.movePosition(QtGui.QTextCursor.End,
847 cursor.movePosition(QtGui.QTextCursor.End,
849 QtGui.QTextCursor.KeepAnchor)
848 QtGui.QTextCursor.KeepAnchor)
850 cursor.removeSelectedText()
849 cursor.removeSelectedText()
851
850
852 # After doing this, we have no choice but to clear the undo/redo
851 # After doing this, we have no choice but to clear the undo/redo
853 # history. Otherwise, the text is not "temporary" at all, because it
852 # history. Otherwise, the text is not "temporary" at all, because it
854 # can be recalled with undo/redo. Unfortunately, Qt does not expose
853 # can be recalled with undo/redo. Unfortunately, Qt does not expose
855 # fine-grained control to the undo/redo system.
854 # fine-grained control to the undo/redo system.
856 if self._control.isUndoRedoEnabled():
855 if self._control.isUndoRedoEnabled():
857 self._control.setUndoRedoEnabled(False)
856 self._control.setUndoRedoEnabled(False)
858 self._control.setUndoRedoEnabled(True)
857 self._control.setUndoRedoEnabled(True)
859
858
860 def _complete_with_items(self, cursor, items):
859 def _complete_with_items(self, cursor, items):
861 """ Performs completion with 'items' at the specified cursor location.
860 """ Performs completion with 'items' at the specified cursor location.
862 """
861 """
863 self._cancel_text_completion()
862 self._cancel_text_completion()
864
863
865 if len(items) == 1:
864 if len(items) == 1:
866 cursor.setPosition(self._control.textCursor().position(),
865 cursor.setPosition(self._control.textCursor().position(),
867 QtGui.QTextCursor.KeepAnchor)
866 QtGui.QTextCursor.KeepAnchor)
868 cursor.insertText(items[0])
867 cursor.insertText(items[0])
869
868
870 elif len(items) > 1:
869 elif len(items) > 1:
871 current_pos = self._control.textCursor().position()
870 current_pos = self._control.textCursor().position()
872 prefix = commonprefix(items)
871 prefix = commonprefix(items)
873 if prefix:
872 if prefix:
874 cursor.setPosition(current_pos, QtGui.QTextCursor.KeepAnchor)
873 cursor.setPosition(current_pos, QtGui.QTextCursor.KeepAnchor)
875 cursor.insertText(prefix)
874 cursor.insertText(prefix)
876 current_pos = cursor.position()
875 current_pos = cursor.position()
877
876
878 if self.gui_completion:
877 if self.gui_completion:
879 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
878 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
880 self._completion_widget.show_items(cursor, items)
879 self._completion_widget.show_items(cursor, items)
881 else:
880 else:
882 cursor.beginEditBlock()
881 cursor.beginEditBlock()
883 self._append_plain_text('\n')
882 self._append_plain_text('\n')
884 self._page(self._format_as_columns(items))
883 self._page(self._format_as_columns(items))
885 cursor.endEditBlock()
884 cursor.endEditBlock()
886
885
887 cursor.setPosition(current_pos)
886 cursor.setPosition(current_pos)
888 self._control.moveCursor(QtGui.QTextCursor.End)
887 self._control.moveCursor(QtGui.QTextCursor.End)
889 self._control.setTextCursor(cursor)
888 self._control.setTextCursor(cursor)
890 self._text_completing_pos = current_pos
889 self._text_completing_pos = current_pos
891
890
892 def _context_menu_make(self, pos):
891 def _context_menu_make(self, pos):
893 """ Creates a context menu for the given QPoint (in widget coordinates).
892 """ Creates a context menu for the given QPoint (in widget coordinates).
894 """
893 """
895 menu = QtGui.QMenu(self)
894 menu = QtGui.QMenu(self)
896
895
897 self.cut_action = menu.addAction('Cut', self.cut)
896 self.cut_action = menu.addAction('Cut', self.cut)
898 self.cut_action.setEnabled(self.can_cut())
897 self.cut_action.setEnabled(self.can_cut())
899 self.cut_action.setShortcut(QtGui.QKeySequence.Cut)
898 self.cut_action.setShortcut(QtGui.QKeySequence.Cut)
900
899
901 self.copy_action = menu.addAction('Copy', self.copy)
900 self.copy_action = menu.addAction('Copy', self.copy)
902 self.copy_action.setEnabled(self.can_copy())
901 self.copy_action.setEnabled(self.can_copy())
903 self.copy_action.setShortcut(QtGui.QKeySequence.Copy)
902 self.copy_action.setShortcut(QtGui.QKeySequence.Copy)
904
903
905 self.paste_action = menu.addAction('Paste', self.paste)
904 self.paste_action = menu.addAction('Paste', self.paste)
906 self.paste_action.setEnabled(self.can_paste())
905 self.paste_action.setEnabled(self.can_paste())
907 self.paste_action.setShortcut(QtGui.QKeySequence.Paste)
906 self.paste_action.setShortcut(QtGui.QKeySequence.Paste)
908
907
909 menu.addSeparator()
908 menu.addSeparator()
910 menu.addAction(self.select_all_action)
909 menu.addAction(self.select_all_action)
911
910
912 menu.addSeparator()
911 menu.addSeparator()
913 menu.addAction(self.export_action)
912 menu.addAction(self.export_action)
914 menu.addAction(self.print_action)
913 menu.addAction(self.print_action)
915
914
916 return menu
915 return menu
917
916
918 def _control_key_down(self, modifiers, include_command=False):
917 def _control_key_down(self, modifiers, include_command=False):
919 """ Given a KeyboardModifiers flags object, return whether the Control
918 """ Given a KeyboardModifiers flags object, return whether the Control
920 key is down.
919 key is down.
921
920
922 Parameters:
921 Parameters:
923 -----------
922 -----------
924 include_command : bool, optional (default True)
923 include_command : bool, optional (default True)
925 Whether to treat the Command key as a (mutually exclusive) synonym
924 Whether to treat the Command key as a (mutually exclusive) synonym
926 for Control when in Mac OS.
925 for Control when in Mac OS.
927 """
926 """
928 # Note that on Mac OS, ControlModifier corresponds to the Command key
927 # Note that on Mac OS, ControlModifier corresponds to the Command key
929 # while MetaModifier corresponds to the Control key.
928 # while MetaModifier corresponds to the Control key.
930 if sys.platform == 'darwin':
929 if sys.platform == 'darwin':
931 down = include_command and (modifiers & QtCore.Qt.ControlModifier)
930 down = include_command and (modifiers & QtCore.Qt.ControlModifier)
932 return bool(down) ^ bool(modifiers & QtCore.Qt.MetaModifier)
931 return bool(down) ^ bool(modifiers & QtCore.Qt.MetaModifier)
933 else:
932 else:
934 return bool(modifiers & QtCore.Qt.ControlModifier)
933 return bool(modifiers & QtCore.Qt.ControlModifier)
935
934
936 def _create_control(self):
935 def _create_control(self):
937 """ Creates and connects the underlying text widget.
936 """ Creates and connects the underlying text widget.
938 """
937 """
939 # Create the underlying control.
938 # Create the underlying control.
940 if self.kind == 'plain':
939 if self.kind == 'plain':
941 control = QtGui.QPlainTextEdit()
940 control = QtGui.QPlainTextEdit()
942 elif self.kind == 'rich':
941 elif self.kind == 'rich':
943 control = QtGui.QTextEdit()
942 control = QtGui.QTextEdit()
944 control.setAcceptRichText(False)
943 control.setAcceptRichText(False)
945
944
946 # Install event filters. The filter on the viewport is needed for
945 # Install event filters. The filter on the viewport is needed for
947 # mouse events and drag events.
946 # mouse events and drag events.
948 control.installEventFilter(self)
947 control.installEventFilter(self)
949 control.viewport().installEventFilter(self)
948 control.viewport().installEventFilter(self)
950
949
951 # Connect signals.
950 # Connect signals.
952 control.cursorPositionChanged.connect(self._cursor_position_changed)
951 control.cursorPositionChanged.connect(self._cursor_position_changed)
953 control.customContextMenuRequested.connect(
952 control.customContextMenuRequested.connect(
954 self._custom_context_menu_requested)
953 self._custom_context_menu_requested)
955 control.copyAvailable.connect(self.copy_available)
954 control.copyAvailable.connect(self.copy_available)
956 control.redoAvailable.connect(self.redo_available)
955 control.redoAvailable.connect(self.redo_available)
957 control.undoAvailable.connect(self.undo_available)
956 control.undoAvailable.connect(self.undo_available)
958
957
959 # Hijack the document size change signal to prevent Qt from adjusting
958 # Hijack the document size change signal to prevent Qt from adjusting
960 # the viewport's scrollbar. We are relying on an implementation detail
959 # the viewport's scrollbar. We are relying on an implementation detail
961 # of Q(Plain)TextEdit here, which is potentially dangerous, but without
960 # of Q(Plain)TextEdit here, which is potentially dangerous, but without
962 # this functionality we cannot create a nice terminal interface.
961 # this functionality we cannot create a nice terminal interface.
963 layout = control.document().documentLayout()
962 layout = control.document().documentLayout()
964 layout.documentSizeChanged.disconnect()
963 layout.documentSizeChanged.disconnect()
965 layout.documentSizeChanged.connect(self._adjust_scrollbars)
964 layout.documentSizeChanged.connect(self._adjust_scrollbars)
966
965
967 # Configure the control.
966 # Configure the control.
968 control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
967 control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
969 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
968 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
970 control.setReadOnly(True)
969 control.setReadOnly(True)
971 control.setUndoRedoEnabled(False)
970 control.setUndoRedoEnabled(False)
972 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
971 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
973 return control
972 return control
974
973
975 def _create_page_control(self):
974 def _create_page_control(self):
976 """ Creates and connects the underlying paging widget.
975 """ Creates and connects the underlying paging widget.
977 """
976 """
978 if self.kind == 'plain':
977 if self.kind == 'plain':
979 control = QtGui.QPlainTextEdit()
978 control = QtGui.QPlainTextEdit()
980 elif self.kind == 'rich':
979 elif self.kind == 'rich':
981 control = QtGui.QTextEdit()
980 control = QtGui.QTextEdit()
982 control.installEventFilter(self)
981 control.installEventFilter(self)
983 viewport = control.viewport()
982 viewport = control.viewport()
984 viewport.installEventFilter(self)
983 viewport.installEventFilter(self)
985 control.setReadOnly(True)
984 control.setReadOnly(True)
986 control.setUndoRedoEnabled(False)
985 control.setUndoRedoEnabled(False)
987 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
986 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
988 return control
987 return control
989
988
990 def _event_filter_console_keypress(self, event):
989 def _event_filter_console_keypress(self, event):
991 """ Filter key events for the underlying text widget to create a
990 """ Filter key events for the underlying text widget to create a
992 console-like interface.
991 console-like interface.
993 """
992 """
994 intercepted = False
993 intercepted = False
995 cursor = self._control.textCursor()
994 cursor = self._control.textCursor()
996 position = cursor.position()
995 position = cursor.position()
997 key = event.key()
996 key = event.key()
998 ctrl_down = self._control_key_down(event.modifiers())
997 ctrl_down = self._control_key_down(event.modifiers())
999 alt_down = event.modifiers() & QtCore.Qt.AltModifier
998 alt_down = event.modifiers() & QtCore.Qt.AltModifier
1000 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
999 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
1001
1000
1002 #------ Special sequences ----------------------------------------------
1001 #------ Special sequences ----------------------------------------------
1003
1002
1004 if event.matches(QtGui.QKeySequence.Copy):
1003 if event.matches(QtGui.QKeySequence.Copy):
1005 self.copy()
1004 self.copy()
1006 intercepted = True
1005 intercepted = True
1007
1006
1008 elif event.matches(QtGui.QKeySequence.Cut):
1007 elif event.matches(QtGui.QKeySequence.Cut):
1009 self.cut()
1008 self.cut()
1010 intercepted = True
1009 intercepted = True
1011
1010
1012 elif event.matches(QtGui.QKeySequence.Paste):
1011 elif event.matches(QtGui.QKeySequence.Paste):
1013 self.paste()
1012 self.paste()
1014 intercepted = True
1013 intercepted = True
1015
1014
1016 #------ Special modifier logic -----------------------------------------
1015 #------ Special modifier logic -----------------------------------------
1017
1016
1018 elif key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
1017 elif key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
1019 intercepted = True
1018 intercepted = True
1020
1019
1021 # Special handling when tab completing in text mode.
1020 # Special handling when tab completing in text mode.
1022 self._cancel_text_completion()
1021 self._cancel_text_completion()
1023
1022
1024 if self._in_buffer(position):
1023 if self._in_buffer(position):
1025 # Special handling when a reading a line of raw input.
1024 # Special handling when a reading a line of raw input.
1026 if self._reading:
1025 if self._reading:
1027 self._append_plain_text('\n')
1026 self._append_plain_text('\n')
1028 self._reading = False
1027 self._reading = False
1029 if self._reading_callback:
1028 if self._reading_callback:
1030 self._reading_callback()
1029 self._reading_callback()
1031
1030
1032 # If the input buffer is a single line or there is only
1031 # If the input buffer is a single line or there is only
1033 # whitespace after the cursor, execute. Otherwise, split the
1032 # whitespace after the cursor, execute. Otherwise, split the
1034 # line with a continuation prompt.
1033 # line with a continuation prompt.
1035 elif not self._executing:
1034 elif not self._executing:
1036 cursor.movePosition(QtGui.QTextCursor.End,
1035 cursor.movePosition(QtGui.QTextCursor.End,
1037 QtGui.QTextCursor.KeepAnchor)
1036 QtGui.QTextCursor.KeepAnchor)
1038 at_end = len(cursor.selectedText().strip()) == 0
1037 at_end = len(cursor.selectedText().strip()) == 0
1039 single_line = (self._get_end_cursor().blockNumber() ==
1038 single_line = (self._get_end_cursor().blockNumber() ==
1040 self._get_prompt_cursor().blockNumber())
1039 self._get_prompt_cursor().blockNumber())
1041 if (at_end or shift_down or single_line) and not ctrl_down:
1040 if (at_end or shift_down or single_line) and not ctrl_down:
1042 self.execute(interactive = not shift_down)
1041 self.execute(interactive = not shift_down)
1043 else:
1042 else:
1044 # Do this inside an edit block for clean undo/redo.
1043 # Do this inside an edit block for clean undo/redo.
1045 cursor.beginEditBlock()
1044 cursor.beginEditBlock()
1046 cursor.setPosition(position)
1045 cursor.setPosition(position)
1047 cursor.insertText('\n')
1046 cursor.insertText('\n')
1048 self._insert_continuation_prompt(cursor)
1047 self._insert_continuation_prompt(cursor)
1049 cursor.endEditBlock()
1048 cursor.endEditBlock()
1050
1049
1051 # Ensure that the whole input buffer is visible.
1050 # Ensure that the whole input buffer is visible.
1052 # FIXME: This will not be usable if the input buffer is
1051 # FIXME: This will not be usable if the input buffer is
1053 # taller than the console widget.
1052 # taller than the console widget.
1054 self._control.moveCursor(QtGui.QTextCursor.End)
1053 self._control.moveCursor(QtGui.QTextCursor.End)
1055 self._control.setTextCursor(cursor)
1054 self._control.setTextCursor(cursor)
1056
1055
1057 #------ Control/Cmd modifier -------------------------------------------
1056 #------ Control/Cmd modifier -------------------------------------------
1058
1057
1059 elif ctrl_down:
1058 elif ctrl_down:
1060 if key == QtCore.Qt.Key_G:
1059 if key == QtCore.Qt.Key_G:
1061 self._keyboard_quit()
1060 self._keyboard_quit()
1062 intercepted = True
1061 intercepted = True
1063
1062
1064 elif key == QtCore.Qt.Key_K:
1063 elif key == QtCore.Qt.Key_K:
1065 if self._in_buffer(position):
1064 if self._in_buffer(position):
1066 cursor.clearSelection()
1065 cursor.clearSelection()
1067 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
1066 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
1068 QtGui.QTextCursor.KeepAnchor)
1067 QtGui.QTextCursor.KeepAnchor)
1069 if not cursor.hasSelection():
1068 if not cursor.hasSelection():
1070 # Line deletion (remove continuation prompt)
1069 # Line deletion (remove continuation prompt)
1071 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1070 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1072 QtGui.QTextCursor.KeepAnchor)
1071 QtGui.QTextCursor.KeepAnchor)
1073 cursor.movePosition(QtGui.QTextCursor.Right,
1072 cursor.movePosition(QtGui.QTextCursor.Right,
1074 QtGui.QTextCursor.KeepAnchor,
1073 QtGui.QTextCursor.KeepAnchor,
1075 len(self._continuation_prompt))
1074 len(self._continuation_prompt))
1076 self._kill_ring.kill_cursor(cursor)
1075 self._kill_ring.kill_cursor(cursor)
1077 self._set_cursor(cursor)
1076 self._set_cursor(cursor)
1078 intercepted = True
1077 intercepted = True
1079
1078
1080 elif key == QtCore.Qt.Key_L:
1079 elif key == QtCore.Qt.Key_L:
1081 self.prompt_to_top()
1080 self.prompt_to_top()
1082 intercepted = True
1081 intercepted = True
1083
1082
1084 elif key == QtCore.Qt.Key_O:
1083 elif key == QtCore.Qt.Key_O:
1085 if self._page_control and self._page_control.isVisible():
1084 if self._page_control and self._page_control.isVisible():
1086 self._page_control.setFocus()
1085 self._page_control.setFocus()
1087 intercepted = True
1086 intercepted = True
1088
1087
1089 elif key == QtCore.Qt.Key_U:
1088 elif key == QtCore.Qt.Key_U:
1090 if self._in_buffer(position):
1089 if self._in_buffer(position):
1091 cursor.clearSelection()
1090 cursor.clearSelection()
1092 start_line = cursor.blockNumber()
1091 start_line = cursor.blockNumber()
1093 if start_line == self._get_prompt_cursor().blockNumber():
1092 if start_line == self._get_prompt_cursor().blockNumber():
1094 offset = len(self._prompt)
1093 offset = len(self._prompt)
1095 else:
1094 else:
1096 offset = len(self._continuation_prompt)
1095 offset = len(self._continuation_prompt)
1097 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1096 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1098 QtGui.QTextCursor.KeepAnchor)
1097 QtGui.QTextCursor.KeepAnchor)
1099 cursor.movePosition(QtGui.QTextCursor.Right,
1098 cursor.movePosition(QtGui.QTextCursor.Right,
1100 QtGui.QTextCursor.KeepAnchor, offset)
1099 QtGui.QTextCursor.KeepAnchor, offset)
1101 self._kill_ring.kill_cursor(cursor)
1100 self._kill_ring.kill_cursor(cursor)
1102 self._set_cursor(cursor)
1101 self._set_cursor(cursor)
1103 intercepted = True
1102 intercepted = True
1104
1103
1105 elif key == QtCore.Qt.Key_Y:
1104 elif key == QtCore.Qt.Key_Y:
1106 self._keep_cursor_in_buffer()
1105 self._keep_cursor_in_buffer()
1107 self._kill_ring.yank()
1106 self._kill_ring.yank()
1108 intercepted = True
1107 intercepted = True
1109
1108
1110 elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
1109 elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
1111 if key == QtCore.Qt.Key_Backspace:
1110 if key == QtCore.Qt.Key_Backspace:
1112 cursor = self._get_word_start_cursor(position)
1111 cursor = self._get_word_start_cursor(position)
1113 else: # key == QtCore.Qt.Key_Delete
1112 else: # key == QtCore.Qt.Key_Delete
1114 cursor = self._get_word_end_cursor(position)
1113 cursor = self._get_word_end_cursor(position)
1115 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1114 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1116 self._kill_ring.kill_cursor(cursor)
1115 self._kill_ring.kill_cursor(cursor)
1117 intercepted = True
1116 intercepted = True
1118
1117
1118 elif key == QtCore.Qt.Key_D:
1119 if len(self.input_buffer) == 0:
1120 self.exit_requested.emit(self)
1121 else:
1122 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1123 QtCore.Qt.Key_Delete,
1124 QtCore.Qt.NoModifier)
1125 QtGui.qApp.sendEvent(self._control, new_event)
1126 intercepted = True
1127
1119 #------ Alt modifier ---------------------------------------------------
1128 #------ Alt modifier ---------------------------------------------------
1120
1129
1121 elif alt_down:
1130 elif alt_down:
1122 if key == QtCore.Qt.Key_B:
1131 if key == QtCore.Qt.Key_B:
1123 self._set_cursor(self._get_word_start_cursor(position))
1132 self._set_cursor(self._get_word_start_cursor(position))
1124 intercepted = True
1133 intercepted = True
1125
1134
1126 elif key == QtCore.Qt.Key_F:
1135 elif key == QtCore.Qt.Key_F:
1127 self._set_cursor(self._get_word_end_cursor(position))
1136 self._set_cursor(self._get_word_end_cursor(position))
1128 intercepted = True
1137 intercepted = True
1129
1138
1130 elif key == QtCore.Qt.Key_Y:
1139 elif key == QtCore.Qt.Key_Y:
1131 self._kill_ring.rotate()
1140 self._kill_ring.rotate()
1132 intercepted = True
1141 intercepted = True
1133
1142
1134 elif key == QtCore.Qt.Key_Backspace:
1143 elif key == QtCore.Qt.Key_Backspace:
1135 cursor = self._get_word_start_cursor(position)
1144 cursor = self._get_word_start_cursor(position)
1136 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1145 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1137 self._kill_ring.kill_cursor(cursor)
1146 self._kill_ring.kill_cursor(cursor)
1138 intercepted = True
1147 intercepted = True
1139
1148
1140 elif key == QtCore.Qt.Key_D:
1149 elif key == QtCore.Qt.Key_D:
1141 cursor = self._get_word_end_cursor(position)
1150 cursor = self._get_word_end_cursor(position)
1142 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1151 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
1143 self._kill_ring.kill_cursor(cursor)
1152 self._kill_ring.kill_cursor(cursor)
1144 intercepted = True
1153 intercepted = True
1145
1154
1146 elif key == QtCore.Qt.Key_Delete:
1155 elif key == QtCore.Qt.Key_Delete:
1147 intercepted = True
1156 intercepted = True
1148
1157
1149 elif key == QtCore.Qt.Key_Greater:
1158 elif key == QtCore.Qt.Key_Greater:
1150 self._control.moveCursor(QtGui.QTextCursor.End)
1159 self._control.moveCursor(QtGui.QTextCursor.End)
1151 intercepted = True
1160 intercepted = True
1152
1161
1153 elif key == QtCore.Qt.Key_Less:
1162 elif key == QtCore.Qt.Key_Less:
1154 self._control.setTextCursor(self._get_prompt_cursor())
1163 self._control.setTextCursor(self._get_prompt_cursor())
1155 intercepted = True
1164 intercepted = True
1156
1165
1157 #------ No modifiers ---------------------------------------------------
1166 #------ No modifiers ---------------------------------------------------
1158
1167
1159 else:
1168 else:
1160 if shift_down:
1169 if shift_down:
1161 anchormode = QtGui.QTextCursor.KeepAnchor
1170 anchormode = QtGui.QTextCursor.KeepAnchor
1162 else:
1171 else:
1163 anchormode = QtGui.QTextCursor.MoveAnchor
1172 anchormode = QtGui.QTextCursor.MoveAnchor
1164
1173
1165 if key == QtCore.Qt.Key_Escape:
1174 if key == QtCore.Qt.Key_Escape:
1166 self._keyboard_quit()
1175 self._keyboard_quit()
1167 intercepted = True
1176 intercepted = True
1168
1177
1169 elif key == QtCore.Qt.Key_Up:
1178 elif key == QtCore.Qt.Key_Up:
1170 if self._reading or not self._up_pressed(shift_down):
1179 if self._reading or not self._up_pressed(shift_down):
1171 intercepted = True
1180 intercepted = True
1172 else:
1181 else:
1173 prompt_line = self._get_prompt_cursor().blockNumber()
1182 prompt_line = self._get_prompt_cursor().blockNumber()
1174 intercepted = cursor.blockNumber() <= prompt_line
1183 intercepted = cursor.blockNumber() <= prompt_line
1175
1184
1176 elif key == QtCore.Qt.Key_Down:
1185 elif key == QtCore.Qt.Key_Down:
1177 if self._reading or not self._down_pressed(shift_down):
1186 if self._reading or not self._down_pressed(shift_down):
1178 intercepted = True
1187 intercepted = True
1179 else:
1188 else:
1180 end_line = self._get_end_cursor().blockNumber()
1189 end_line = self._get_end_cursor().blockNumber()
1181 intercepted = cursor.blockNumber() == end_line
1190 intercepted = cursor.blockNumber() == end_line
1182
1191
1183 elif key == QtCore.Qt.Key_Tab:
1192 elif key == QtCore.Qt.Key_Tab:
1184 if not self._reading:
1193 if not self._reading:
1185 if self._tab_pressed():
1194 if self._tab_pressed():
1186 # real tab-key, insert four spaces
1195 # real tab-key, insert four spaces
1187 cursor.insertText(' '*4)
1196 cursor.insertText(' '*4)
1188 intercepted = True
1197 intercepted = True
1189
1198
1190 elif key == QtCore.Qt.Key_Left:
1199 elif key == QtCore.Qt.Key_Left:
1191
1200
1192 # Move to the previous line
1201 # Move to the previous line
1193 line, col = cursor.blockNumber(), cursor.columnNumber()
1202 line, col = cursor.blockNumber(), cursor.columnNumber()
1194 if line > self._get_prompt_cursor().blockNumber() and \
1203 if line > self._get_prompt_cursor().blockNumber() and \
1195 col == len(self._continuation_prompt):
1204 col == len(self._continuation_prompt):
1196 self._control.moveCursor(QtGui.QTextCursor.PreviousBlock,
1205 self._control.moveCursor(QtGui.QTextCursor.PreviousBlock,
1197 mode=anchormode)
1206 mode=anchormode)
1198 self._control.moveCursor(QtGui.QTextCursor.EndOfBlock,
1207 self._control.moveCursor(QtGui.QTextCursor.EndOfBlock,
1199 mode=anchormode)
1208 mode=anchormode)
1200 intercepted = True
1209 intercepted = True
1201
1210
1202 # Regular left movement
1211 # Regular left movement
1203 else:
1212 else:
1204 intercepted = not self._in_buffer(position - 1)
1213 intercepted = not self._in_buffer(position - 1)
1205
1214
1206 elif key == QtCore.Qt.Key_Right:
1215 elif key == QtCore.Qt.Key_Right:
1207 original_block_number = cursor.blockNumber()
1216 original_block_number = cursor.blockNumber()
1208 cursor.movePosition(QtGui.QTextCursor.Right,
1217 cursor.movePosition(QtGui.QTextCursor.Right,
1209 mode=anchormode)
1218 mode=anchormode)
1210 if cursor.blockNumber() != original_block_number:
1219 if cursor.blockNumber() != original_block_number:
1211 cursor.movePosition(QtGui.QTextCursor.Right,
1220 cursor.movePosition(QtGui.QTextCursor.Right,
1212 n=len(self._continuation_prompt),
1221 n=len(self._continuation_prompt),
1213 mode=anchormode)
1222 mode=anchormode)
1214 self._set_cursor(cursor)
1223 self._set_cursor(cursor)
1215 intercepted = True
1224 intercepted = True
1216
1225
1217 elif key == QtCore.Qt.Key_Home:
1226 elif key == QtCore.Qt.Key_Home:
1218 start_line = cursor.blockNumber()
1227 start_line = cursor.blockNumber()
1219 if start_line == self._get_prompt_cursor().blockNumber():
1228 if start_line == self._get_prompt_cursor().blockNumber():
1220 start_pos = self._prompt_pos
1229 start_pos = self._prompt_pos
1221 else:
1230 else:
1222 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1231 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1223 QtGui.QTextCursor.KeepAnchor)
1232 QtGui.QTextCursor.KeepAnchor)
1224 start_pos = cursor.position()
1233 start_pos = cursor.position()
1225 start_pos += len(self._continuation_prompt)
1234 start_pos += len(self._continuation_prompt)
1226 cursor.setPosition(position)
1235 cursor.setPosition(position)
1227 if shift_down and self._in_buffer(position):
1236 if shift_down and self._in_buffer(position):
1228 cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor)
1237 cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor)
1229 else:
1238 else:
1230 cursor.setPosition(start_pos)
1239 cursor.setPosition(start_pos)
1231 self._set_cursor(cursor)
1240 self._set_cursor(cursor)
1232 intercepted = True
1241 intercepted = True
1233
1242
1234 elif key == QtCore.Qt.Key_Backspace:
1243 elif key == QtCore.Qt.Key_Backspace:
1235
1244
1236 # Line deletion (remove continuation prompt)
1245 # Line deletion (remove continuation prompt)
1237 line, col = cursor.blockNumber(), cursor.columnNumber()
1246 line, col = cursor.blockNumber(), cursor.columnNumber()
1238 if not self._reading and \
1247 if not self._reading and \
1239 col == len(self._continuation_prompt) and \
1248 col == len(self._continuation_prompt) and \
1240 line > self._get_prompt_cursor().blockNumber():
1249 line > self._get_prompt_cursor().blockNumber():
1241 cursor.beginEditBlock()
1250 cursor.beginEditBlock()
1242 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1251 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1243 QtGui.QTextCursor.KeepAnchor)
1252 QtGui.QTextCursor.KeepAnchor)
1244 cursor.removeSelectedText()
1253 cursor.removeSelectedText()
1245 cursor.deletePreviousChar()
1254 cursor.deletePreviousChar()
1246 cursor.endEditBlock()
1255 cursor.endEditBlock()
1247 intercepted = True
1256 intercepted = True
1248
1257
1249 # Regular backwards deletion
1258 # Regular backwards deletion
1250 else:
1259 else:
1251 anchor = cursor.anchor()
1260 anchor = cursor.anchor()
1252 if anchor == position:
1261 if anchor == position:
1253 intercepted = not self._in_buffer(position - 1)
1262 intercepted = not self._in_buffer(position - 1)
1254 else:
1263 else:
1255 intercepted = not self._in_buffer(min(anchor, position))
1264 intercepted = not self._in_buffer(min(anchor, position))
1256
1265
1257 elif key == QtCore.Qt.Key_Delete:
1266 elif key == QtCore.Qt.Key_Delete:
1258
1267
1259 # Line deletion (remove continuation prompt)
1268 # Line deletion (remove continuation prompt)
1260 if not self._reading and self._in_buffer(position) and \
1269 if not self._reading and self._in_buffer(position) and \
1261 cursor.atBlockEnd() and not cursor.hasSelection():
1270 cursor.atBlockEnd() and not cursor.hasSelection():
1262 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1271 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1263 QtGui.QTextCursor.KeepAnchor)
1272 QtGui.QTextCursor.KeepAnchor)
1264 cursor.movePosition(QtGui.QTextCursor.Right,
1273 cursor.movePosition(QtGui.QTextCursor.Right,
1265 QtGui.QTextCursor.KeepAnchor,
1274 QtGui.QTextCursor.KeepAnchor,
1266 len(self._continuation_prompt))
1275 len(self._continuation_prompt))
1267 cursor.removeSelectedText()
1276 cursor.removeSelectedText()
1268 intercepted = True
1277 intercepted = True
1269
1278
1270 # Regular forwards deletion:
1279 # Regular forwards deletion:
1271 else:
1280 else:
1272 anchor = cursor.anchor()
1281 anchor = cursor.anchor()
1273 intercepted = (not self._in_buffer(anchor) or
1282 intercepted = (not self._in_buffer(anchor) or
1274 not self._in_buffer(position))
1283 not self._in_buffer(position))
1275
1284
1276 # Don't move the cursor if Control/Cmd is pressed to allow copy-paste
1285 # Don't move the cursor if Control/Cmd is pressed to allow copy-paste
1277 # using the keyboard in any part of the buffer. Also, permit scrolling
1286 # using the keyboard in any part of the buffer. Also, permit scrolling
1278 # with Page Up/Down keys. Finally, if we're executing, don't move the
1287 # with Page Up/Down keys. Finally, if we're executing, don't move the
1279 # cursor (if even this made sense, we can't guarantee that the prompt
1288 # cursor (if even this made sense, we can't guarantee that the prompt
1280 # position is still valid due to text truncation).
1289 # position is still valid due to text truncation).
1281 if not (self._control_key_down(event.modifiers(), include_command=True)
1290 if not (self._control_key_down(event.modifiers(), include_command=True)
1282 or key in (QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown)
1291 or key in (QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown)
1283 or (self._executing and not self._reading)):
1292 or (self._executing and not self._reading)):
1284 self._keep_cursor_in_buffer()
1293 self._keep_cursor_in_buffer()
1285
1294
1286 return intercepted
1295 return intercepted
1287
1296
1288 def _event_filter_page_keypress(self, event):
1297 def _event_filter_page_keypress(self, event):
1289 """ Filter key events for the paging widget to create console-like
1298 """ Filter key events for the paging widget to create console-like
1290 interface.
1299 interface.
1291 """
1300 """
1292 key = event.key()
1301 key = event.key()
1293 ctrl_down = self._control_key_down(event.modifiers())
1302 ctrl_down = self._control_key_down(event.modifiers())
1294 alt_down = event.modifiers() & QtCore.Qt.AltModifier
1303 alt_down = event.modifiers() & QtCore.Qt.AltModifier
1295
1304
1296 if ctrl_down:
1305 if ctrl_down:
1297 if key == QtCore.Qt.Key_O:
1306 if key == QtCore.Qt.Key_O:
1298 self._control.setFocus()
1307 self._control.setFocus()
1299 intercept = True
1308 intercept = True
1300
1309
1301 elif alt_down:
1310 elif alt_down:
1302 if key == QtCore.Qt.Key_Greater:
1311 if key == QtCore.Qt.Key_Greater:
1303 self._page_control.moveCursor(QtGui.QTextCursor.End)
1312 self._page_control.moveCursor(QtGui.QTextCursor.End)
1304 intercepted = True
1313 intercepted = True
1305
1314
1306 elif key == QtCore.Qt.Key_Less:
1315 elif key == QtCore.Qt.Key_Less:
1307 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1316 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1308 intercepted = True
1317 intercepted = True
1309
1318
1310 elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
1319 elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
1311 if self._splitter:
1320 if self._splitter:
1312 self._page_control.hide()
1321 self._page_control.hide()
1313 self._control.setFocus()
1322 self._control.setFocus()
1314 else:
1323 else:
1315 self.layout().setCurrentWidget(self._control)
1324 self.layout().setCurrentWidget(self._control)
1316 return True
1325 return True
1317
1326
1318 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return,
1327 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return,
1319 QtCore.Qt.Key_Tab):
1328 QtCore.Qt.Key_Tab):
1320 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1329 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1321 QtCore.Qt.Key_PageDown,
1330 QtCore.Qt.Key_PageDown,
1322 QtCore.Qt.NoModifier)
1331 QtCore.Qt.NoModifier)
1323 QtGui.qApp.sendEvent(self._page_control, new_event)
1332 QtGui.qApp.sendEvent(self._page_control, new_event)
1324 return True
1333 return True
1325
1334
1326 elif key == QtCore.Qt.Key_Backspace:
1335 elif key == QtCore.Qt.Key_Backspace:
1327 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1336 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1328 QtCore.Qt.Key_PageUp,
1337 QtCore.Qt.Key_PageUp,
1329 QtCore.Qt.NoModifier)
1338 QtCore.Qt.NoModifier)
1330 QtGui.qApp.sendEvent(self._page_control, new_event)
1339 QtGui.qApp.sendEvent(self._page_control, new_event)
1331 return True
1340 return True
1332
1341
1333 return False
1342 return False
1334
1343
1335 def _format_as_columns(self, items, separator=' '):
1344 def _format_as_columns(self, items, separator=' '):
1336 """ Transform a list of strings into a single string with columns.
1345 """ Transform a list of strings into a single string with columns.
1337
1346
1338 Parameters
1347 Parameters
1339 ----------
1348 ----------
1340 items : sequence of strings
1349 items : sequence of strings
1341 The strings to process.
1350 The strings to process.
1342
1351
1343 separator : str, optional [default is two spaces]
1352 separator : str, optional [default is two spaces]
1344 The string that separates columns.
1353 The string that separates columns.
1345
1354
1346 Returns
1355 Returns
1347 -------
1356 -------
1348 The formatted string.
1357 The formatted string.
1349 """
1358 """
1350 # Calculate the number of characters available.
1359 # Calculate the number of characters available.
1351 width = self._control.viewport().width()
1360 width = self._control.viewport().width()
1352 char_width = QtGui.QFontMetrics(self.font).width(' ')
1361 char_width = QtGui.QFontMetrics(self.font).width(' ')
1353 displaywidth = max(10, (width / char_width) - 1)
1362 displaywidth = max(10, (width / char_width) - 1)
1354
1363
1355 return columnize(items, separator, displaywidth)
1364 return columnize(items, separator, displaywidth)
1356
1365
1357 def _get_block_plain_text(self, block):
1366 def _get_block_plain_text(self, block):
1358 """ Given a QTextBlock, return its unformatted text.
1367 """ Given a QTextBlock, return its unformatted text.
1359 """
1368 """
1360 cursor = QtGui.QTextCursor(block)
1369 cursor = QtGui.QTextCursor(block)
1361 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1370 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1362 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
1371 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
1363 QtGui.QTextCursor.KeepAnchor)
1372 QtGui.QTextCursor.KeepAnchor)
1364 return cursor.selection().toPlainText()
1373 return cursor.selection().toPlainText()
1365
1374
1366 def _get_cursor(self):
1375 def _get_cursor(self):
1367 """ Convenience method that returns a cursor for the current position.
1376 """ Convenience method that returns a cursor for the current position.
1368 """
1377 """
1369 return self._control.textCursor()
1378 return self._control.textCursor()
1370
1379
1371 def _get_end_cursor(self):
1380 def _get_end_cursor(self):
1372 """ Convenience method that returns a cursor for the last character.
1381 """ Convenience method that returns a cursor for the last character.
1373 """
1382 """
1374 cursor = self._control.textCursor()
1383 cursor = self._control.textCursor()
1375 cursor.movePosition(QtGui.QTextCursor.End)
1384 cursor.movePosition(QtGui.QTextCursor.End)
1376 return cursor
1385 return cursor
1377
1386
1378 def _get_input_buffer_cursor_column(self):
1387 def _get_input_buffer_cursor_column(self):
1379 """ Returns the column of the cursor in the input buffer, excluding the
1388 """ Returns the column of the cursor in the input buffer, excluding the
1380 contribution by the prompt, or -1 if there is no such column.
1389 contribution by the prompt, or -1 if there is no such column.
1381 """
1390 """
1382 prompt = self._get_input_buffer_cursor_prompt()
1391 prompt = self._get_input_buffer_cursor_prompt()
1383 if prompt is None:
1392 if prompt is None:
1384 return -1
1393 return -1
1385 else:
1394 else:
1386 cursor = self._control.textCursor()
1395 cursor = self._control.textCursor()
1387 return cursor.columnNumber() - len(prompt)
1396 return cursor.columnNumber() - len(prompt)
1388
1397
1389 def _get_input_buffer_cursor_line(self):
1398 def _get_input_buffer_cursor_line(self):
1390 """ Returns the text of the line of the input buffer that contains the
1399 """ Returns the text of the line of the input buffer that contains the
1391 cursor, or None if there is no such line.
1400 cursor, or None if there is no such line.
1392 """
1401 """
1393 prompt = self._get_input_buffer_cursor_prompt()
1402 prompt = self._get_input_buffer_cursor_prompt()
1394 if prompt is None:
1403 if prompt is None:
1395 return None
1404 return None
1396 else:
1405 else:
1397 cursor = self._control.textCursor()
1406 cursor = self._control.textCursor()
1398 text = self._get_block_plain_text(cursor.block())
1407 text = self._get_block_plain_text(cursor.block())
1399 return text[len(prompt):]
1408 return text[len(prompt):]
1400
1409
1401 def _get_input_buffer_cursor_prompt(self):
1410 def _get_input_buffer_cursor_prompt(self):
1402 """ Returns the (plain text) prompt for line of the input buffer that
1411 """ Returns the (plain text) prompt for line of the input buffer that
1403 contains the cursor, or None if there is no such line.
1412 contains the cursor, or None if there is no such line.
1404 """
1413 """
1405 if self._executing:
1414 if self._executing:
1406 return None
1415 return None
1407 cursor = self._control.textCursor()
1416 cursor = self._control.textCursor()
1408 if cursor.position() >= self._prompt_pos:
1417 if cursor.position() >= self._prompt_pos:
1409 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
1418 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
1410 return self._prompt
1419 return self._prompt
1411 else:
1420 else:
1412 return self._continuation_prompt
1421 return self._continuation_prompt
1413 else:
1422 else:
1414 return None
1423 return None
1415
1424
1416 def _get_prompt_cursor(self):
1425 def _get_prompt_cursor(self):
1417 """ Convenience method that returns a cursor for the prompt position.
1426 """ Convenience method that returns a cursor for the prompt position.
1418 """
1427 """
1419 cursor = self._control.textCursor()
1428 cursor = self._control.textCursor()
1420 cursor.setPosition(self._prompt_pos)
1429 cursor.setPosition(self._prompt_pos)
1421 return cursor
1430 return cursor
1422
1431
1423 def _get_selection_cursor(self, start, end):
1432 def _get_selection_cursor(self, start, end):
1424 """ Convenience method that returns a cursor with text selected between
1433 """ Convenience method that returns a cursor with text selected between
1425 the positions 'start' and 'end'.
1434 the positions 'start' and 'end'.
1426 """
1435 """
1427 cursor = self._control.textCursor()
1436 cursor = self._control.textCursor()
1428 cursor.setPosition(start)
1437 cursor.setPosition(start)
1429 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
1438 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
1430 return cursor
1439 return cursor
1431
1440
1432 def _get_word_start_cursor(self, position):
1441 def _get_word_start_cursor(self, position):
1433 """ Find the start of the word to the left the given position. If a
1442 """ Find the start of the word to the left the given position. If a
1434 sequence of non-word characters precedes the first word, skip over
1443 sequence of non-word characters precedes the first word, skip over
1435 them. (This emulates the behavior of bash, emacs, etc.)
1444 them. (This emulates the behavior of bash, emacs, etc.)
1436 """
1445 """
1437 document = self._control.document()
1446 document = self._control.document()
1438 position -= 1
1447 position -= 1
1439 while position >= self._prompt_pos and \
1448 while position >= self._prompt_pos and \
1440 not is_letter_or_number(document.characterAt(position)):
1449 not is_letter_or_number(document.characterAt(position)):
1441 position -= 1
1450 position -= 1
1442 while position >= self._prompt_pos and \
1451 while position >= self._prompt_pos and \
1443 is_letter_or_number(document.characterAt(position)):
1452 is_letter_or_number(document.characterAt(position)):
1444 position -= 1
1453 position -= 1
1445 cursor = self._control.textCursor()
1454 cursor = self._control.textCursor()
1446 cursor.setPosition(position + 1)
1455 cursor.setPosition(position + 1)
1447 return cursor
1456 return cursor
1448
1457
1449 def _get_word_end_cursor(self, position):
1458 def _get_word_end_cursor(self, position):
1450 """ Find the end of the word to the right the given position. If a
1459 """ Find the end of the word to the right the given position. If a
1451 sequence of non-word characters precedes the first word, skip over
1460 sequence of non-word characters precedes the first word, skip over
1452 them. (This emulates the behavior of bash, emacs, etc.)
1461 them. (This emulates the behavior of bash, emacs, etc.)
1453 """
1462 """
1454 document = self._control.document()
1463 document = self._control.document()
1455 end = self._get_end_cursor().position()
1464 end = self._get_end_cursor().position()
1456 while position < end and \
1465 while position < end and \
1457 not is_letter_or_number(document.characterAt(position)):
1466 not is_letter_or_number(document.characterAt(position)):
1458 position += 1
1467 position += 1
1459 while position < end and \
1468 while position < end and \
1460 is_letter_or_number(document.characterAt(position)):
1469 is_letter_or_number(document.characterAt(position)):
1461 position += 1
1470 position += 1
1462 cursor = self._control.textCursor()
1471 cursor = self._control.textCursor()
1463 cursor.setPosition(position)
1472 cursor.setPosition(position)
1464 return cursor
1473 return cursor
1465
1474
1466 def _insert_continuation_prompt(self, cursor):
1475 def _insert_continuation_prompt(self, cursor):
1467 """ Inserts new continuation prompt using the specified cursor.
1476 """ Inserts new continuation prompt using the specified cursor.
1468 """
1477 """
1469 if self._continuation_prompt_html is None:
1478 if self._continuation_prompt_html is None:
1470 self._insert_plain_text(cursor, self._continuation_prompt)
1479 self._insert_plain_text(cursor, self._continuation_prompt)
1471 else:
1480 else:
1472 self._continuation_prompt = self._insert_html_fetching_plain_text(
1481 self._continuation_prompt = self._insert_html_fetching_plain_text(
1473 cursor, self._continuation_prompt_html)
1482 cursor, self._continuation_prompt_html)
1474
1483
1475 def _insert_html(self, cursor, html):
1484 def _insert_html(self, cursor, html):
1476 """ Inserts HTML using the specified cursor in such a way that future
1485 """ Inserts HTML using the specified cursor in such a way that future
1477 formatting is unaffected.
1486 formatting is unaffected.
1478 """
1487 """
1479 cursor.beginEditBlock()
1488 cursor.beginEditBlock()
1480 cursor.insertHtml(html)
1489 cursor.insertHtml(html)
1481
1490
1482 # After inserting HTML, the text document "remembers" it's in "html
1491 # After inserting HTML, the text document "remembers" it's in "html
1483 # mode", which means that subsequent calls adding plain text will result
1492 # mode", which means that subsequent calls adding plain text will result
1484 # in unwanted formatting, lost tab characters, etc. The following code
1493 # in unwanted formatting, lost tab characters, etc. The following code
1485 # hacks around this behavior, which I consider to be a bug in Qt, by
1494 # hacks around this behavior, which I consider to be a bug in Qt, by
1486 # (crudely) resetting the document's style state.
1495 # (crudely) resetting the document's style state.
1487 cursor.movePosition(QtGui.QTextCursor.Left,
1496 cursor.movePosition(QtGui.QTextCursor.Left,
1488 QtGui.QTextCursor.KeepAnchor)
1497 QtGui.QTextCursor.KeepAnchor)
1489 if cursor.selection().toPlainText() == ' ':
1498 if cursor.selection().toPlainText() == ' ':
1490 cursor.removeSelectedText()
1499 cursor.removeSelectedText()
1491 else:
1500 else:
1492 cursor.movePosition(QtGui.QTextCursor.Right)
1501 cursor.movePosition(QtGui.QTextCursor.Right)
1493 cursor.insertText(' ', QtGui.QTextCharFormat())
1502 cursor.insertText(' ', QtGui.QTextCharFormat())
1494 cursor.endEditBlock()
1503 cursor.endEditBlock()
1495
1504
1496 def _insert_html_fetching_plain_text(self, cursor, html):
1505 def _insert_html_fetching_plain_text(self, cursor, html):
1497 """ Inserts HTML using the specified cursor, then returns its plain text
1506 """ Inserts HTML using the specified cursor, then returns its plain text
1498 version.
1507 version.
1499 """
1508 """
1500 cursor.beginEditBlock()
1509 cursor.beginEditBlock()
1501 cursor.removeSelectedText()
1510 cursor.removeSelectedText()
1502
1511
1503 start = cursor.position()
1512 start = cursor.position()
1504 self._insert_html(cursor, html)
1513 self._insert_html(cursor, html)
1505 end = cursor.position()
1514 end = cursor.position()
1506 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
1515 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
1507 text = cursor.selection().toPlainText()
1516 text = cursor.selection().toPlainText()
1508
1517
1509 cursor.setPosition(end)
1518 cursor.setPosition(end)
1510 cursor.endEditBlock()
1519 cursor.endEditBlock()
1511 return text
1520 return text
1512
1521
1513 def _insert_plain_text(self, cursor, text):
1522 def _insert_plain_text(self, cursor, text):
1514 """ Inserts plain text using the specified cursor, processing ANSI codes
1523 """ Inserts plain text using the specified cursor, processing ANSI codes
1515 if enabled.
1524 if enabled.
1516 """
1525 """
1517 cursor.beginEditBlock()
1526 cursor.beginEditBlock()
1518 if self.ansi_codes:
1527 if self.ansi_codes:
1519 for substring in self._ansi_processor.split_string(text):
1528 for substring in self._ansi_processor.split_string(text):
1520 for act in self._ansi_processor.actions:
1529 for act in self._ansi_processor.actions:
1521
1530
1522 # Unlike real terminal emulators, we don't distinguish
1531 # Unlike real terminal emulators, we don't distinguish
1523 # between the screen and the scrollback buffer. A screen
1532 # between the screen and the scrollback buffer. A screen
1524 # erase request clears everything.
1533 # erase request clears everything.
1525 if act.action == 'erase' and act.area == 'screen':
1534 if act.action == 'erase' and act.area == 'screen':
1526 cursor.select(QtGui.QTextCursor.Document)
1535 cursor.select(QtGui.QTextCursor.Document)
1527 cursor.removeSelectedText()
1536 cursor.removeSelectedText()
1528
1537
1529 # Simulate a form feed by scrolling just past the last line.
1538 # Simulate a form feed by scrolling just past the last line.
1530 elif act.action == 'scroll' and act.unit == 'page':
1539 elif act.action == 'scroll' and act.unit == 'page':
1531 cursor.insertText('\n')
1540 cursor.insertText('\n')
1532 cursor.endEditBlock()
1541 cursor.endEditBlock()
1533 self._set_top_cursor(cursor)
1542 self._set_top_cursor(cursor)
1534 cursor.joinPreviousEditBlock()
1543 cursor.joinPreviousEditBlock()
1535 cursor.deletePreviousChar()
1544 cursor.deletePreviousChar()
1536
1545
1537 elif act.action == 'carriage-return':
1546 elif act.action == 'carriage-return':
1538 cursor.movePosition(
1547 cursor.movePosition(
1539 cursor.StartOfLine, cursor.KeepAnchor)
1548 cursor.StartOfLine, cursor.KeepAnchor)
1540
1549
1541 elif act.action == 'beep':
1550 elif act.action == 'beep':
1542 QtGui.qApp.beep()
1551 QtGui.qApp.beep()
1543
1552
1544 format = self._ansi_processor.get_format()
1553 format = self._ansi_processor.get_format()
1545 cursor.insertText(substring, format)
1554 cursor.insertText(substring, format)
1546 else:
1555 else:
1547 cursor.insertText(text)
1556 cursor.insertText(text)
1548 cursor.endEditBlock()
1557 cursor.endEditBlock()
1549
1558
1550 def _insert_plain_text_into_buffer(self, cursor, text):
1559 def _insert_plain_text_into_buffer(self, cursor, text):
1551 """ Inserts text into the input buffer using the specified cursor (which
1560 """ Inserts text into the input buffer using the specified cursor (which
1552 must be in the input buffer), ensuring that continuation prompts are
1561 must be in the input buffer), ensuring that continuation prompts are
1553 inserted as necessary.
1562 inserted as necessary.
1554 """
1563 """
1555 lines = text.splitlines(True)
1564 lines = text.splitlines(True)
1556 if lines:
1565 if lines:
1557 cursor.beginEditBlock()
1566 cursor.beginEditBlock()
1558 cursor.insertText(lines[0])
1567 cursor.insertText(lines[0])
1559 for line in lines[1:]:
1568 for line in lines[1:]:
1560 if self._continuation_prompt_html is None:
1569 if self._continuation_prompt_html is None:
1561 cursor.insertText(self._continuation_prompt)
1570 cursor.insertText(self._continuation_prompt)
1562 else:
1571 else:
1563 self._continuation_prompt = \
1572 self._continuation_prompt = \
1564 self._insert_html_fetching_plain_text(
1573 self._insert_html_fetching_plain_text(
1565 cursor, self._continuation_prompt_html)
1574 cursor, self._continuation_prompt_html)
1566 cursor.insertText(line)
1575 cursor.insertText(line)
1567 cursor.endEditBlock()
1576 cursor.endEditBlock()
1568
1577
1569 def _in_buffer(self, position=None):
1578 def _in_buffer(self, position=None):
1570 """ Returns whether the current cursor (or, if specified, a position) is
1579 """ Returns whether the current cursor (or, if specified, a position) is
1571 inside the editing region.
1580 inside the editing region.
1572 """
1581 """
1573 cursor = self._control.textCursor()
1582 cursor = self._control.textCursor()
1574 if position is None:
1583 if position is None:
1575 position = cursor.position()
1584 position = cursor.position()
1576 else:
1585 else:
1577 cursor.setPosition(position)
1586 cursor.setPosition(position)
1578 line = cursor.blockNumber()
1587 line = cursor.blockNumber()
1579 prompt_line = self._get_prompt_cursor().blockNumber()
1588 prompt_line = self._get_prompt_cursor().blockNumber()
1580 if line == prompt_line:
1589 if line == prompt_line:
1581 return position >= self._prompt_pos
1590 return position >= self._prompt_pos
1582 elif line > prompt_line:
1591 elif line > prompt_line:
1583 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1592 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1584 prompt_pos = cursor.position() + len(self._continuation_prompt)
1593 prompt_pos = cursor.position() + len(self._continuation_prompt)
1585 return position >= prompt_pos
1594 return position >= prompt_pos
1586 return False
1595 return False
1587
1596
1588 def _keep_cursor_in_buffer(self):
1597 def _keep_cursor_in_buffer(self):
1589 """ Ensures that the cursor is inside the editing region. Returns
1598 """ Ensures that the cursor is inside the editing region. Returns
1590 whether the cursor was moved.
1599 whether the cursor was moved.
1591 """
1600 """
1592 moved = not self._in_buffer()
1601 moved = not self._in_buffer()
1593 if moved:
1602 if moved:
1594 cursor = self._control.textCursor()
1603 cursor = self._control.textCursor()
1595 cursor.movePosition(QtGui.QTextCursor.End)
1604 cursor.movePosition(QtGui.QTextCursor.End)
1596 self._control.setTextCursor(cursor)
1605 self._control.setTextCursor(cursor)
1597 return moved
1606 return moved
1598
1607
1599 def _keyboard_quit(self):
1608 def _keyboard_quit(self):
1600 """ Cancels the current editing task ala Ctrl-G in Emacs.
1609 """ Cancels the current editing task ala Ctrl-G in Emacs.
1601 """
1610 """
1602 if self._text_completing_pos:
1611 if self._text_completing_pos:
1603 self._cancel_text_completion()
1612 self._cancel_text_completion()
1604 else:
1613 else:
1605 self.input_buffer = ''
1614 self.input_buffer = ''
1606
1615
1607 def _page(self, text, html=False):
1616 def _page(self, text, html=False):
1608 """ Displays text using the pager if it exceeds the height of the
1617 """ Displays text using the pager if it exceeds the height of the
1609 viewport.
1618 viewport.
1610
1619
1611 Parameters:
1620 Parameters:
1612 -----------
1621 -----------
1613 html : bool, optional (default False)
1622 html : bool, optional (default False)
1614 If set, the text will be interpreted as HTML instead of plain text.
1623 If set, the text will be interpreted as HTML instead of plain text.
1615 """
1624 """
1616 line_height = QtGui.QFontMetrics(self.font).height()
1625 line_height = QtGui.QFontMetrics(self.font).height()
1617 minlines = self._control.viewport().height() / line_height
1626 minlines = self._control.viewport().height() / line_height
1618 if self.paging != 'none' and \
1627 if self.paging != 'none' and \
1619 re.match("(?:[^\n]*\n){%i}" % minlines, text):
1628 re.match("(?:[^\n]*\n){%i}" % minlines, text):
1620 if self.paging == 'custom':
1629 if self.paging == 'custom':
1621 self.custom_page_requested.emit(text)
1630 self.custom_page_requested.emit(text)
1622 else:
1631 else:
1623 self._page_control.clear()
1632 self._page_control.clear()
1624 cursor = self._page_control.textCursor()
1633 cursor = self._page_control.textCursor()
1625 if html:
1634 if html:
1626 self._insert_html(cursor, text)
1635 self._insert_html(cursor, text)
1627 else:
1636 else:
1628 self._insert_plain_text(cursor, text)
1637 self._insert_plain_text(cursor, text)
1629 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1638 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1630
1639
1631 self._page_control.viewport().resize(self._control.size())
1640 self._page_control.viewport().resize(self._control.size())
1632 if self._splitter:
1641 if self._splitter:
1633 self._page_control.show()
1642 self._page_control.show()
1634 self._page_control.setFocus()
1643 self._page_control.setFocus()
1635 else:
1644 else:
1636 self.layout().setCurrentWidget(self._page_control)
1645 self.layout().setCurrentWidget(self._page_control)
1637 elif html:
1646 elif html:
1638 self._append_plain_html(text)
1647 self._append_plain_html(text)
1639 else:
1648 else:
1640 self._append_plain_text(text)
1649 self._append_plain_text(text)
1641
1650
1642 def _prompt_finished(self):
1651 def _prompt_finished(self):
1643 """ Called immediately after a prompt is finished, i.e. when some input
1652 """ Called immediately after a prompt is finished, i.e. when some input
1644 will be processed and a new prompt displayed.
1653 will be processed and a new prompt displayed.
1645 """
1654 """
1646 self._control.setReadOnly(True)
1655 self._control.setReadOnly(True)
1647 self._prompt_finished_hook()
1656 self._prompt_finished_hook()
1648
1657
1649 def _prompt_started(self):
1658 def _prompt_started(self):
1650 """ Called immediately after a new prompt is displayed.
1659 """ Called immediately after a new prompt is displayed.
1651 """
1660 """
1652 # Temporarily disable the maximum block count to permit undo/redo and
1661 # Temporarily disable the maximum block count to permit undo/redo and
1653 # to ensure that the prompt position does not change due to truncation.
1662 # to ensure that the prompt position does not change due to truncation.
1654 self._control.document().setMaximumBlockCount(0)
1663 self._control.document().setMaximumBlockCount(0)
1655 self._control.setUndoRedoEnabled(True)
1664 self._control.setUndoRedoEnabled(True)
1656
1665
1657 # Work around bug in QPlainTextEdit: input method is not re-enabled
1666 # Work around bug in QPlainTextEdit: input method is not re-enabled
1658 # when read-only is disabled.
1667 # when read-only is disabled.
1659 self._control.setReadOnly(False)
1668 self._control.setReadOnly(False)
1660 self._control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
1669 self._control.setAttribute(QtCore.Qt.WA_InputMethodEnabled, True)
1661
1670
1662 if not self._reading:
1671 if not self._reading:
1663 self._executing = False
1672 self._executing = False
1664 self._prompt_started_hook()
1673 self._prompt_started_hook()
1665
1674
1666 # If the input buffer has changed while executing, load it.
1675 # If the input buffer has changed while executing, load it.
1667 if self._input_buffer_pending:
1676 if self._input_buffer_pending:
1668 self.input_buffer = self._input_buffer_pending
1677 self.input_buffer = self._input_buffer_pending
1669 self._input_buffer_pending = ''
1678 self._input_buffer_pending = ''
1670
1679
1671 self._control.moveCursor(QtGui.QTextCursor.End)
1680 self._control.moveCursor(QtGui.QTextCursor.End)
1672
1681
1673 def _readline(self, prompt='', callback=None):
1682 def _readline(self, prompt='', callback=None):
1674 """ Reads one line of input from the user.
1683 """ Reads one line of input from the user.
1675
1684
1676 Parameters
1685 Parameters
1677 ----------
1686 ----------
1678 prompt : str, optional
1687 prompt : str, optional
1679 The prompt to print before reading the line.
1688 The prompt to print before reading the line.
1680
1689
1681 callback : callable, optional
1690 callback : callable, optional
1682 A callback to execute with the read line. If not specified, input is
1691 A callback to execute with the read line. If not specified, input is
1683 read *synchronously* and this method does not return until it has
1692 read *synchronously* and this method does not return until it has
1684 been read.
1693 been read.
1685
1694
1686 Returns
1695 Returns
1687 -------
1696 -------
1688 If a callback is specified, returns nothing. Otherwise, returns the
1697 If a callback is specified, returns nothing. Otherwise, returns the
1689 input string with the trailing newline stripped.
1698 input string with the trailing newline stripped.
1690 """
1699 """
1691 if self._reading:
1700 if self._reading:
1692 raise RuntimeError('Cannot read a line. Widget is already reading.')
1701 raise RuntimeError('Cannot read a line. Widget is already reading.')
1693
1702
1694 if not callback and not self.isVisible():
1703 if not callback and not self.isVisible():
1695 # If the user cannot see the widget, this function cannot return.
1704 # If the user cannot see the widget, this function cannot return.
1696 raise RuntimeError('Cannot synchronously read a line if the widget '
1705 raise RuntimeError('Cannot synchronously read a line if the widget '
1697 'is not visible!')
1706 'is not visible!')
1698
1707
1699 self._reading = True
1708 self._reading = True
1700 self._show_prompt(prompt, newline=False)
1709 self._show_prompt(prompt, newline=False)
1701
1710
1702 if callback is None:
1711 if callback is None:
1703 self._reading_callback = None
1712 self._reading_callback = None
1704 while self._reading:
1713 while self._reading:
1705 QtCore.QCoreApplication.processEvents()
1714 QtCore.QCoreApplication.processEvents()
1706 return self._get_input_buffer(force=True).rstrip('\n')
1715 return self._get_input_buffer(force=True).rstrip('\n')
1707
1716
1708 else:
1717 else:
1709 self._reading_callback = lambda: \
1718 self._reading_callback = lambda: \
1710 callback(self._get_input_buffer(force=True).rstrip('\n'))
1719 callback(self._get_input_buffer(force=True).rstrip('\n'))
1711
1720
1712 def _set_continuation_prompt(self, prompt, html=False):
1721 def _set_continuation_prompt(self, prompt, html=False):
1713 """ Sets the continuation prompt.
1722 """ Sets the continuation prompt.
1714
1723
1715 Parameters
1724 Parameters
1716 ----------
1725 ----------
1717 prompt : str
1726 prompt : str
1718 The prompt to show when more input is needed.
1727 The prompt to show when more input is needed.
1719
1728
1720 html : bool, optional (default False)
1729 html : bool, optional (default False)
1721 If set, the prompt will be inserted as formatted HTML. Otherwise,
1730 If set, the prompt will be inserted as formatted HTML. Otherwise,
1722 the prompt will be treated as plain text, though ANSI color codes
1731 the prompt will be treated as plain text, though ANSI color codes
1723 will be handled.
1732 will be handled.
1724 """
1733 """
1725 if html:
1734 if html:
1726 self._continuation_prompt_html = prompt
1735 self._continuation_prompt_html = prompt
1727 else:
1736 else:
1728 self._continuation_prompt = prompt
1737 self._continuation_prompt = prompt
1729 self._continuation_prompt_html = None
1738 self._continuation_prompt_html = None
1730
1739
1731 def _set_cursor(self, cursor):
1740 def _set_cursor(self, cursor):
1732 """ Convenience method to set the current cursor.
1741 """ Convenience method to set the current cursor.
1733 """
1742 """
1734 self._control.setTextCursor(cursor)
1743 self._control.setTextCursor(cursor)
1735
1744
1736 def _set_top_cursor(self, cursor):
1745 def _set_top_cursor(self, cursor):
1737 """ Scrolls the viewport so that the specified cursor is at the top.
1746 """ Scrolls the viewport so that the specified cursor is at the top.
1738 """
1747 """
1739 scrollbar = self._control.verticalScrollBar()
1748 scrollbar = self._control.verticalScrollBar()
1740 scrollbar.setValue(scrollbar.maximum())
1749 scrollbar.setValue(scrollbar.maximum())
1741 original_cursor = self._control.textCursor()
1750 original_cursor = self._control.textCursor()
1742 self._control.setTextCursor(cursor)
1751 self._control.setTextCursor(cursor)
1743 self._control.ensureCursorVisible()
1752 self._control.ensureCursorVisible()
1744 self._control.setTextCursor(original_cursor)
1753 self._control.setTextCursor(original_cursor)
1745
1754
1746 def _show_prompt(self, prompt=None, html=False, newline=True):
1755 def _show_prompt(self, prompt=None, html=False, newline=True):
1747 """ Writes a new prompt at the end of the buffer.
1756 """ Writes a new prompt at the end of the buffer.
1748
1757
1749 Parameters
1758 Parameters
1750 ----------
1759 ----------
1751 prompt : str, optional
1760 prompt : str, optional
1752 The prompt to show. If not specified, the previous prompt is used.
1761 The prompt to show. If not specified, the previous prompt is used.
1753
1762
1754 html : bool, optional (default False)
1763 html : bool, optional (default False)
1755 Only relevant when a prompt is specified. If set, the prompt will
1764 Only relevant when a prompt is specified. If set, the prompt will
1756 be inserted as formatted HTML. Otherwise, the prompt will be treated
1765 be inserted as formatted HTML. Otherwise, the prompt will be treated
1757 as plain text, though ANSI color codes will be handled.
1766 as plain text, though ANSI color codes will be handled.
1758
1767
1759 newline : bool, optional (default True)
1768 newline : bool, optional (default True)
1760 If set, a new line will be written before showing the prompt if
1769 If set, a new line will be written before showing the prompt if
1761 there is not already a newline at the end of the buffer.
1770 there is not already a newline at the end of the buffer.
1762 """
1771 """
1763 # Save the current end position to support _append*(before_prompt=True).
1772 # Save the current end position to support _append*(before_prompt=True).
1764 cursor = self._get_end_cursor()
1773 cursor = self._get_end_cursor()
1765 self._append_before_prompt_pos = cursor.position()
1774 self._append_before_prompt_pos = cursor.position()
1766
1775
1767 # Insert a preliminary newline, if necessary.
1776 # Insert a preliminary newline, if necessary.
1768 if newline and cursor.position() > 0:
1777 if newline and cursor.position() > 0:
1769 cursor.movePosition(QtGui.QTextCursor.Left,
1778 cursor.movePosition(QtGui.QTextCursor.Left,
1770 QtGui.QTextCursor.KeepAnchor)
1779 QtGui.QTextCursor.KeepAnchor)
1771 if cursor.selection().toPlainText() != '\n':
1780 if cursor.selection().toPlainText() != '\n':
1772 self._append_plain_text('\n')
1781 self._append_plain_text('\n')
1773
1782
1774 # Write the prompt.
1783 # Write the prompt.
1775 self._append_plain_text(self._prompt_sep)
1784 self._append_plain_text(self._prompt_sep)
1776 if prompt is None:
1785 if prompt is None:
1777 if self._prompt_html is None:
1786 if self._prompt_html is None:
1778 self._append_plain_text(self._prompt)
1787 self._append_plain_text(self._prompt)
1779 else:
1788 else:
1780 self._append_html(self._prompt_html)
1789 self._append_html(self._prompt_html)
1781 else:
1790 else:
1782 if html:
1791 if html:
1783 self._prompt = self._append_html_fetching_plain_text(prompt)
1792 self._prompt = self._append_html_fetching_plain_text(prompt)
1784 self._prompt_html = prompt
1793 self._prompt_html = prompt
1785 else:
1794 else:
1786 self._append_plain_text(prompt)
1795 self._append_plain_text(prompt)
1787 self._prompt = prompt
1796 self._prompt = prompt
1788 self._prompt_html = None
1797 self._prompt_html = None
1789
1798
1790 self._prompt_pos = self._get_end_cursor().position()
1799 self._prompt_pos = self._get_end_cursor().position()
1791 self._prompt_started()
1800 self._prompt_started()
1792
1801
1793 #------ Signal handlers ----------------------------------------------------
1802 #------ Signal handlers ----------------------------------------------------
1794
1803
1795 def _adjust_scrollbars(self):
1804 def _adjust_scrollbars(self):
1796 """ Expands the vertical scrollbar beyond the range set by Qt.
1805 """ Expands the vertical scrollbar beyond the range set by Qt.
1797 """
1806 """
1798 # This code is adapted from _q_adjustScrollbars in qplaintextedit.cpp
1807 # This code is adapted from _q_adjustScrollbars in qplaintextedit.cpp
1799 # and qtextedit.cpp.
1808 # and qtextedit.cpp.
1800 document = self._control.document()
1809 document = self._control.document()
1801 scrollbar = self._control.verticalScrollBar()
1810 scrollbar = self._control.verticalScrollBar()
1802 viewport_height = self._control.viewport().height()
1811 viewport_height = self._control.viewport().height()
1803 if isinstance(self._control, QtGui.QPlainTextEdit):
1812 if isinstance(self._control, QtGui.QPlainTextEdit):
1804 maximum = max(0, document.lineCount() - 1)
1813 maximum = max(0, document.lineCount() - 1)
1805 step = viewport_height / self._control.fontMetrics().lineSpacing()
1814 step = viewport_height / self._control.fontMetrics().lineSpacing()
1806 else:
1815 else:
1807 # QTextEdit does not do line-based layout and blocks will not in
1816 # QTextEdit does not do line-based layout and blocks will not in
1808 # general have the same height. Therefore it does not make sense to
1817 # general have the same height. Therefore it does not make sense to
1809 # attempt to scroll in line height increments.
1818 # attempt to scroll in line height increments.
1810 maximum = document.size().height()
1819 maximum = document.size().height()
1811 step = viewport_height
1820 step = viewport_height
1812 diff = maximum - scrollbar.maximum()
1821 diff = maximum - scrollbar.maximum()
1813 scrollbar.setRange(0, maximum)
1822 scrollbar.setRange(0, maximum)
1814 scrollbar.setPageStep(step)
1823 scrollbar.setPageStep(step)
1815
1824
1816 # Compensate for undesirable scrolling that occurs automatically due to
1825 # Compensate for undesirable scrolling that occurs automatically due to
1817 # maximumBlockCount() text truncation.
1826 # maximumBlockCount() text truncation.
1818 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1827 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1819 scrollbar.setValue(scrollbar.value() + diff)
1828 scrollbar.setValue(scrollbar.value() + diff)
1820
1829
1821 def _cursor_position_changed(self):
1830 def _cursor_position_changed(self):
1822 """ Clears the temporary buffer based on the cursor position.
1831 """ Clears the temporary buffer based on the cursor position.
1823 """
1832 """
1824 if self._text_completing_pos:
1833 if self._text_completing_pos:
1825 document = self._control.document()
1834 document = self._control.document()
1826 if self._text_completing_pos < document.characterCount():
1835 if self._text_completing_pos < document.characterCount():
1827 cursor = self._control.textCursor()
1836 cursor = self._control.textCursor()
1828 pos = cursor.position()
1837 pos = cursor.position()
1829 text_cursor = self._control.textCursor()
1838 text_cursor = self._control.textCursor()
1830 text_cursor.setPosition(self._text_completing_pos)
1839 text_cursor.setPosition(self._text_completing_pos)
1831 if pos < self._text_completing_pos or \
1840 if pos < self._text_completing_pos or \
1832 cursor.blockNumber() > text_cursor.blockNumber():
1841 cursor.blockNumber() > text_cursor.blockNumber():
1833 self._clear_temporary_buffer()
1842 self._clear_temporary_buffer()
1834 self._text_completing_pos = 0
1843 self._text_completing_pos = 0
1835 else:
1844 else:
1836 self._clear_temporary_buffer()
1845 self._clear_temporary_buffer()
1837 self._text_completing_pos = 0
1846 self._text_completing_pos = 0
1838
1847
1839 def _custom_context_menu_requested(self, pos):
1848 def _custom_context_menu_requested(self, pos):
1840 """ Shows a context menu at the given QPoint (in widget coordinates).
1849 """ Shows a context menu at the given QPoint (in widget coordinates).
1841 """
1850 """
1842 menu = self._context_menu_make(pos)
1851 menu = self._context_menu_make(pos)
1843 menu.exec_(self._control.mapToGlobal(pos))
1852 menu.exec_(self._control.mapToGlobal(pos))
@@ -1,908 +1,910 b''
1 """The Qt MainWindow for the QtConsole
1 """The Qt MainWindow for the QtConsole
2
2
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
3 This is a tabbed pseudo-terminal of IPython sessions, with a menu bar for
4 common actions.
4 common actions.
5
5
6 Authors:
6 Authors:
7
7
8 * Evan Patterson
8 * Evan Patterson
9 * Min RK
9 * Min RK
10 * Erik Tollerud
10 * Erik Tollerud
11 * Fernando Perez
11 * Fernando Perez
12 * Bussonnier Matthias
12 * Bussonnier Matthias
13 * Thomas Kluyver
13 * Thomas Kluyver
14
14
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 # stdlib imports
21 # stdlib imports
22 import sys
22 import sys
23 import re
23 import re
24 import webbrowser
24 import webbrowser
25 from threading import Thread
25 from threading import Thread
26
26
27 # System library imports
27 # System library imports
28 from IPython.external.qt import QtGui,QtCore
28 from IPython.external.qt import QtGui,QtCore
29
29
30 def background(f):
30 def background(f):
31 """call a function in a simple thread, to prevent blocking"""
31 """call a function in a simple thread, to prevent blocking"""
32 t = Thread(target=f)
32 t = Thread(target=f)
33 t.start()
33 t.start()
34 return t
34 return t
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Classes
37 # Classes
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 class MainWindow(QtGui.QMainWindow):
40 class MainWindow(QtGui.QMainWindow):
41
41
42 #---------------------------------------------------------------------------
42 #---------------------------------------------------------------------------
43 # 'object' interface
43 # 'object' interface
44 #---------------------------------------------------------------------------
44 #---------------------------------------------------------------------------
45
45
46 def __init__(self, app,
46 def __init__(self, app,
47 confirm_exit=True,
47 confirm_exit=True,
48 new_frontend_factory=None, slave_frontend_factory=None,
48 new_frontend_factory=None, slave_frontend_factory=None,
49 ):
49 ):
50 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
50 """ Create a tabbed MainWindow for managing IPython FrontendWidgets
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54
54
55 app : reference to QApplication parent
55 app : reference to QApplication parent
56 confirm_exit : bool, optional
56 confirm_exit : bool, optional
57 Whether we should prompt on close of tabs
57 Whether we should prompt on close of tabs
58 new_frontend_factory : callable
58 new_frontend_factory : callable
59 A callable that returns a new IPythonWidget instance, attached to
59 A callable that returns a new IPythonWidget instance, attached to
60 its own running kernel.
60 its own running kernel.
61 slave_frontend_factory : callable
61 slave_frontend_factory : callable
62 A callable that takes an existing IPythonWidget, and returns a new
62 A callable that takes an existing IPythonWidget, and returns a new
63 IPythonWidget instance, attached to the same kernel.
63 IPythonWidget instance, attached to the same kernel.
64 """
64 """
65
65
66 super(MainWindow, self).__init__()
66 super(MainWindow, self).__init__()
67 self._kernel_counter = 0
67 self._kernel_counter = 0
68 self._app = app
68 self._app = app
69 self.confirm_exit = confirm_exit
69 self.confirm_exit = confirm_exit
70 self.new_frontend_factory = new_frontend_factory
70 self.new_frontend_factory = new_frontend_factory
71 self.slave_frontend_factory = slave_frontend_factory
71 self.slave_frontend_factory = slave_frontend_factory
72
72
73 self.tab_widget = QtGui.QTabWidget(self)
73 self.tab_widget = QtGui.QTabWidget(self)
74 self.tab_widget.setDocumentMode(True)
74 self.tab_widget.setDocumentMode(True)
75 self.tab_widget.setTabsClosable(True)
75 self.tab_widget.setTabsClosable(True)
76 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
76 self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
77
77
78 self.setCentralWidget(self.tab_widget)
78 self.setCentralWidget(self.tab_widget)
79 # hide tab bar at first, since we have no tabs:
79 # hide tab bar at first, since we have no tabs:
80 self.tab_widget.tabBar().setVisible(False)
80 self.tab_widget.tabBar().setVisible(False)
81 # prevent focus in tab bar
81 # prevent focus in tab bar
82 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
82 self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
83
83
84 def update_tab_bar_visibility(self):
84 def update_tab_bar_visibility(self):
85 """ update visibility of the tabBar depending of the number of tab
85 """ update visibility of the tabBar depending of the number of tab
86
86
87 0 or 1 tab, tabBar hidden
87 0 or 1 tab, tabBar hidden
88 2+ tabs, tabBar visible
88 2+ tabs, tabBar visible
89
89
90 send a self.close if number of tab ==0
90 send a self.close if number of tab ==0
91
91
92 need to be called explicitely, or be connected to tabInserted/tabRemoved
92 need to be called explicitely, or be connected to tabInserted/tabRemoved
93 """
93 """
94 if self.tab_widget.count() <= 1:
94 if self.tab_widget.count() <= 1:
95 self.tab_widget.tabBar().setVisible(False)
95 self.tab_widget.tabBar().setVisible(False)
96 else:
96 else:
97 self.tab_widget.tabBar().setVisible(True)
97 self.tab_widget.tabBar().setVisible(True)
98 if self.tab_widget.count()==0 :
98 if self.tab_widget.count()==0 :
99 self.close()
99 self.close()
100
100
101 @property
101 @property
102 def next_kernel_id(self):
102 def next_kernel_id(self):
103 """constantly increasing counter for kernel IDs"""
103 """constantly increasing counter for kernel IDs"""
104 c = self._kernel_counter
104 c = self._kernel_counter
105 self._kernel_counter += 1
105 self._kernel_counter += 1
106 return c
106 return c
107
107
108 @property
108 @property
109 def active_frontend(self):
109 def active_frontend(self):
110 return self.tab_widget.currentWidget()
110 return self.tab_widget.currentWidget()
111
111
112 def create_tab_with_new_frontend(self):
112 def create_tab_with_new_frontend(self):
113 """create a new frontend and attach it to a new tab"""
113 """create a new frontend and attach it to a new tab"""
114 widget = self.new_frontend_factory()
114 widget = self.new_frontend_factory()
115 self.add_tab_with_frontend(widget)
115 self.add_tab_with_frontend(widget)
116
116
117 def create_tab_with_current_kernel(self):
117 def create_tab_with_current_kernel(self):
118 """create a new frontend attached to the same kernel as the current tab"""
118 """create a new frontend attached to the same kernel as the current tab"""
119 current_widget = self.tab_widget.currentWidget()
119 current_widget = self.tab_widget.currentWidget()
120 current_widget_index = self.tab_widget.indexOf(current_widget)
120 current_widget_index = self.tab_widget.indexOf(current_widget)
121 current_widget_name = self.tab_widget.tabText(current_widget_index)
121 current_widget_name = self.tab_widget.tabText(current_widget_index)
122 widget = self.slave_frontend_factory(current_widget)
122 widget = self.slave_frontend_factory(current_widget)
123 if 'slave' in current_widget_name:
123 if 'slave' in current_widget_name:
124 # don't keep stacking slaves
124 # don't keep stacking slaves
125 name = current_widget_name
125 name = current_widget_name
126 else:
126 else:
127 name = '(%s) slave' % current_widget_name
127 name = '(%s) slave' % current_widget_name
128 self.add_tab_with_frontend(widget,name=name)
128 self.add_tab_with_frontend(widget,name=name)
129
129
130 def close_tab(self,current_tab):
130 def close_tab(self,current_tab):
131 """ Called when you need to try to close a tab.
131 """ Called when you need to try to close a tab.
132
132
133 It takes the number of the tab to be closed as argument, or a referece
133 It takes the number of the tab to be closed as argument, or a referece
134 to the wiget insite this tab
134 to the wiget insite this tab
135 """
135 """
136
136
137 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
137 # let's be sure "tab" and "closing widget are respectivey the index of the tab to close
138 # and a reference to the trontend to close
138 # and a reference to the trontend to close
139 if type(current_tab) is not int :
139 if type(current_tab) is not int :
140 current_tab = self.tab_widget.indexOf(current_tab)
140 current_tab = self.tab_widget.indexOf(current_tab)
141 closing_widget=self.tab_widget.widget(current_tab)
141 closing_widget=self.tab_widget.widget(current_tab)
142
142
143
143
144 # when trying to be closed, widget might re-send a request to be closed again, but will
144 # when trying to be closed, widget might re-send a request to be closed again, but will
145 # be deleted when event will be processed. So need to check that widget still exist and
145 # be deleted when event will be processed. So need to check that widget still exist and
146 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
146 # skip if not. One example of this is when 'exit' is send in a slave tab. 'exit' will be
147 # re-send by this fonction on the master widget, which ask all slaves widget to exit
147 # re-send by this fonction on the master widget, which ask all slaves widget to exit
148 if closing_widget==None:
148 if closing_widget==None:
149 return
149 return
150
150
151 #get a list of all slave widgets on the same kernel.
151 #get a list of all slave widgets on the same kernel.
152 slave_tabs = self.find_slave_widgets(closing_widget)
152 slave_tabs = self.find_slave_widgets(closing_widget)
153
153
154 keepkernel = None #Use the prompt by default
154 keepkernel = None #Use the prompt by default
155 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
155 if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
156 keepkernel = closing_widget._keep_kernel_on_exit
156 keepkernel = closing_widget._keep_kernel_on_exit
157 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
157 # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
158 # we set local slave tabs._hidden to True to avoid prompting for kernel
158 # we set local slave tabs._hidden to True to avoid prompting for kernel
159 # restart when they get the signal. and then "forward" the 'exit'
159 # restart when they get the signal. and then "forward" the 'exit'
160 # to the main window
160 # to the main window
161 if keepkernel is not None:
161 if keepkernel is not None:
162 for tab in slave_tabs:
162 for tab in slave_tabs:
163 tab._hidden = True
163 tab._hidden = True
164 if closing_widget in slave_tabs:
164 if closing_widget in slave_tabs:
165 try :
165 try :
166 self.find_master_tab(closing_widget).execute('exit')
166 self.find_master_tab(closing_widget).execute('exit')
167 except AttributeError:
167 except AttributeError:
168 self.log.info("Master already closed or not local, closing only current tab")
168 self.log.info("Master already closed or not local, closing only current tab")
169 self.tab_widget.removeTab(current_tab)
169 self.tab_widget.removeTab(current_tab)
170 self.update_tab_bar_visibility()
170 self.update_tab_bar_visibility()
171 return
171 return
172
172
173 kernel_manager = closing_widget.kernel_manager
173 kernel_manager = closing_widget.kernel_manager
174
174
175 if keepkernel is None and not closing_widget._confirm_exit:
175 if keepkernel is None and not closing_widget._confirm_exit:
176 # don't prompt, just terminate the kernel if we own it
176 # don't prompt, just terminate the kernel if we own it
177 # or leave it alone if we don't
177 # or leave it alone if we don't
178 keepkernel = closing_widget._existing
178 keepkernel = closing_widget._existing
179 if keepkernel is None: #show prompt
179 if keepkernel is None: #show prompt
180 if kernel_manager and kernel_manager.channels_running:
180 if kernel_manager and kernel_manager.channels_running:
181 title = self.window().windowTitle()
181 title = self.window().windowTitle()
182 cancel = QtGui.QMessageBox.Cancel
182 cancel = QtGui.QMessageBox.Cancel
183 okay = QtGui.QMessageBox.Ok
183 okay = QtGui.QMessageBox.Ok
184 if closing_widget._may_close:
184 if closing_widget._may_close:
185 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
185 msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
186 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
186 info = "Would you like to quit the Kernel and close all attached Consoles as well?"
187 justthis = QtGui.QPushButton("&No, just this Tab", self)
187 justthis = QtGui.QPushButton("&No, just this Tab", self)
188 justthis.setShortcut('N')
188 justthis.setShortcut('N')
189 closeall = QtGui.QPushButton("&Yes, close all", self)
189 closeall = QtGui.QPushButton("&Yes, close all", self)
190 closeall.setShortcut('Y')
190 closeall.setShortcut('Y')
191 # allow ctrl-d ctrl-d exit, like in terminal
192 closeall.setShortcut('Ctrl+D')
191 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
193 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
192 title, msg)
194 title, msg)
193 box.setInformativeText(info)
195 box.setInformativeText(info)
194 box.addButton(cancel)
196 box.addButton(cancel)
195 box.addButton(justthis, QtGui.QMessageBox.NoRole)
197 box.addButton(justthis, QtGui.QMessageBox.NoRole)
196 box.addButton(closeall, QtGui.QMessageBox.YesRole)
198 box.addButton(closeall, QtGui.QMessageBox.YesRole)
197 box.setDefaultButton(closeall)
199 box.setDefaultButton(closeall)
198 box.setEscapeButton(cancel)
200 box.setEscapeButton(cancel)
199 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
201 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
200 box.setIconPixmap(pixmap)
202 box.setIconPixmap(pixmap)
201 reply = box.exec_()
203 reply = box.exec_()
202 if reply == 1: # close All
204 if reply == 1: # close All
203 for slave in slave_tabs:
205 for slave in slave_tabs:
204 background(slave.kernel_manager.stop_channels)
206 background(slave.kernel_manager.stop_channels)
205 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
207 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
206 closing_widget.execute("exit")
208 closing_widget.execute("exit")
207 self.tab_widget.removeTab(current_tab)
209 self.tab_widget.removeTab(current_tab)
208 background(kernel_manager.stop_channels)
210 background(kernel_manager.stop_channels)
209 elif reply == 0: # close Console
211 elif reply == 0: # close Console
210 if not closing_widget._existing:
212 if not closing_widget._existing:
211 # Have kernel: don't quit, just close the tab
213 # Have kernel: don't quit, just close the tab
212 closing_widget.execute("exit True")
214 closing_widget.execute("exit True")
213 self.tab_widget.removeTab(current_tab)
215 self.tab_widget.removeTab(current_tab)
214 background(kernel_manager.stop_channels)
216 background(kernel_manager.stop_channels)
215 else:
217 else:
216 reply = QtGui.QMessageBox.question(self, title,
218 reply = QtGui.QMessageBox.question(self, title,
217 "Are you sure you want to close this Console?"+
219 "Are you sure you want to close this Console?"+
218 "\nThe Kernel and other Consoles will remain active.",
220 "\nThe Kernel and other Consoles will remain active.",
219 okay|cancel,
221 okay|cancel,
220 defaultButton=okay
222 defaultButton=okay
221 )
223 )
222 if reply == okay:
224 if reply == okay:
223 self.tab_widget.removeTab(current_tab)
225 self.tab_widget.removeTab(current_tab)
224 elif keepkernel: #close console but leave kernel running (no prompt)
226 elif keepkernel: #close console but leave kernel running (no prompt)
225 self.tab_widget.removeTab(current_tab)
227 self.tab_widget.removeTab(current_tab)
226 background(kernel_manager.stop_channels)
228 background(kernel_manager.stop_channels)
227 else: #close console and kernel (no prompt)
229 else: #close console and kernel (no prompt)
228 self.tab_widget.removeTab(current_tab)
230 self.tab_widget.removeTab(current_tab)
229 if kernel_manager and kernel_manager.channels_running:
231 if kernel_manager and kernel_manager.channels_running:
230 for slave in slave_tabs:
232 for slave in slave_tabs:
231 background(slave.kernel_manager.stop_channels)
233 background(slave.kernel_manager.stop_channels)
232 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
234 self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
233 kernel_manager.shutdown_kernel()
235 kernel_manager.shutdown_kernel()
234 background(kernel_manager.stop_channels)
236 background(kernel_manager.stop_channels)
235
237
236 self.update_tab_bar_visibility()
238 self.update_tab_bar_visibility()
237
239
238 def add_tab_with_frontend(self,frontend,name=None):
240 def add_tab_with_frontend(self,frontend,name=None):
239 """ insert a tab with a given frontend in the tab bar, and give it a name
241 """ insert a tab with a given frontend in the tab bar, and give it a name
240
242
241 """
243 """
242 if not name:
244 if not name:
243 name = 'kernel %i' % self.next_kernel_id
245 name = 'kernel %i' % self.next_kernel_id
244 self.tab_widget.addTab(frontend,name)
246 self.tab_widget.addTab(frontend,name)
245 self.update_tab_bar_visibility()
247 self.update_tab_bar_visibility()
246 self.make_frontend_visible(frontend)
248 self.make_frontend_visible(frontend)
247 frontend.exit_requested.connect(self.close_tab)
249 frontend.exit_requested.connect(self.close_tab)
248
250
249 def next_tab(self):
251 def next_tab(self):
250 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
252 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
251
253
252 def prev_tab(self):
254 def prev_tab(self):
253 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
255 self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
254
256
255 def make_frontend_visible(self,frontend):
257 def make_frontend_visible(self,frontend):
256 widget_index=self.tab_widget.indexOf(frontend)
258 widget_index=self.tab_widget.indexOf(frontend)
257 if widget_index > 0 :
259 if widget_index > 0 :
258 self.tab_widget.setCurrentIndex(widget_index)
260 self.tab_widget.setCurrentIndex(widget_index)
259
261
260 def find_master_tab(self,tab,as_list=False):
262 def find_master_tab(self,tab,as_list=False):
261 """
263 """
262 Try to return the frontend that own the kernel attached to the given widget/tab.
264 Try to return the frontend that own the kernel attached to the given widget/tab.
263
265
264 Only find frontend owed by the current application. Selection
266 Only find frontend owed by the current application. Selection
265 based on port of the kernel, might be inacurate if several kernel
267 based on port of the kernel, might be inacurate if several kernel
266 on different ip use same port number.
268 on different ip use same port number.
267
269
268 This fonction does the conversion tabNumber/widget if needed.
270 This fonction does the conversion tabNumber/widget if needed.
269 Might return None if no master widget (non local kernel)
271 Might return None if no master widget (non local kernel)
270 Will crash IPython if more than 1 masterWidget
272 Will crash IPython if more than 1 masterWidget
271
273
272 When asList set to True, always return a list of widget(s) owning
274 When asList set to True, always return a list of widget(s) owning
273 the kernel. The list might be empty or containing several Widget.
275 the kernel. The list might be empty or containing several Widget.
274 """
276 """
275
277
276 #convert from/to int/richIpythonWidget if needed
278 #convert from/to int/richIpythonWidget if needed
277 if isinstance(tab, int):
279 if isinstance(tab, int):
278 tab = self.tab_widget.widget(tab)
280 tab = self.tab_widget.widget(tab)
279 km=tab.kernel_manager
281 km=tab.kernel_manager
280
282
281 #build list of all widgets
283 #build list of all widgets
282 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
284 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
283
285
284 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
286 # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
285 # And should have a _may_close attribute
287 # And should have a _may_close attribute
286 filtered_widget_list = [ widget for widget in widget_list if
288 filtered_widget_list = [ widget for widget in widget_list if
287 widget.kernel_manager.connection_file == km.connection_file and
289 widget.kernel_manager.connection_file == km.connection_file and
288 hasattr(widget,'_may_close') ]
290 hasattr(widget,'_may_close') ]
289 # the master widget is the one that may close the kernel
291 # the master widget is the one that may close the kernel
290 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
292 master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
291 if as_list:
293 if as_list:
292 return master_widget
294 return master_widget
293 assert(len(master_widget)<=1 )
295 assert(len(master_widget)<=1 )
294 if len(master_widget)==0:
296 if len(master_widget)==0:
295 return None
297 return None
296
298
297 return master_widget[0]
299 return master_widget[0]
298
300
299 def find_slave_widgets(self,tab):
301 def find_slave_widgets(self,tab):
300 """return all the frontends that do not own the kernel attached to the given widget/tab.
302 """return all the frontends that do not own the kernel attached to the given widget/tab.
301
303
302 Only find frontends owned by the current application. Selection
304 Only find frontends owned by the current application. Selection
303 based on connection file of the kernel.
305 based on connection file of the kernel.
304
306
305 This function does the conversion tabNumber/widget if needed.
307 This function does the conversion tabNumber/widget if needed.
306 """
308 """
307 #convert from/to int/richIpythonWidget if needed
309 #convert from/to int/richIpythonWidget if needed
308 if isinstance(tab, int):
310 if isinstance(tab, int):
309 tab = self.tab_widget.widget(tab)
311 tab = self.tab_widget.widget(tab)
310 km=tab.kernel_manager
312 km=tab.kernel_manager
311
313
312 #build list of all widgets
314 #build list of all widgets
313 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
315 widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
314
316
315 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
317 # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
316 filtered_widget_list = ( widget for widget in widget_list if
318 filtered_widget_list = ( widget for widget in widget_list if
317 widget.kernel_manager.connection_file == km.connection_file)
319 widget.kernel_manager.connection_file == km.connection_file)
318 # Get a list of all widget owning the same kernel and removed it from
320 # Get a list of all widget owning the same kernel and removed it from
319 # the previous cadidate. (better using sets ?)
321 # the previous cadidate. (better using sets ?)
320 master_widget_list = self.find_master_tab(tab, as_list=True)
322 master_widget_list = self.find_master_tab(tab, as_list=True)
321 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
323 slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
322
324
323 return slave_list
325 return slave_list
324
326
325 # Populate the menu bar with common actions and shortcuts
327 # Populate the menu bar with common actions and shortcuts
326 def add_menu_action(self, menu, action, defer_shortcut=False):
328 def add_menu_action(self, menu, action, defer_shortcut=False):
327 """Add action to menu as well as self
329 """Add action to menu as well as self
328
330
329 So that when the menu bar is invisible, its actions are still available.
331 So that when the menu bar is invisible, its actions are still available.
330
332
331 If defer_shortcut is True, set the shortcut context to widget-only,
333 If defer_shortcut is True, set the shortcut context to widget-only,
332 where it will avoid conflict with shortcuts already bound to the
334 where it will avoid conflict with shortcuts already bound to the
333 widgets themselves.
335 widgets themselves.
334 """
336 """
335 menu.addAction(action)
337 menu.addAction(action)
336 self.addAction(action)
338 self.addAction(action)
337
339
338 if defer_shortcut:
340 if defer_shortcut:
339 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
341 action.setShortcutContext(QtCore.Qt.WidgetShortcut)
340
342
341 def init_menu_bar(self):
343 def init_menu_bar(self):
342 #create menu in the order they should appear in the menu bar
344 #create menu in the order they should appear in the menu bar
343 self.init_file_menu()
345 self.init_file_menu()
344 self.init_edit_menu()
346 self.init_edit_menu()
345 self.init_view_menu()
347 self.init_view_menu()
346 self.init_kernel_menu()
348 self.init_kernel_menu()
347 self.init_magic_menu()
349 self.init_magic_menu()
348 self.init_window_menu()
350 self.init_window_menu()
349 self.init_help_menu()
351 self.init_help_menu()
350
352
351 def init_file_menu(self):
353 def init_file_menu(self):
352 self.file_menu = self.menuBar().addMenu("&File")
354 self.file_menu = self.menuBar().addMenu("&File")
353
355
354 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
356 self.new_kernel_tab_act = QtGui.QAction("New Tab with &New kernel",
355 self,
357 self,
356 shortcut="Ctrl+T",
358 shortcut="Ctrl+T",
357 triggered=self.create_tab_with_new_frontend)
359 triggered=self.create_tab_with_new_frontend)
358 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
360 self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
359
361
360 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
362 self.slave_kernel_tab_act = QtGui.QAction("New Tab with Sa&me kernel",
361 self,
363 self,
362 shortcut="Ctrl+Shift+T",
364 shortcut="Ctrl+Shift+T",
363 triggered=self.create_tab_with_current_kernel)
365 triggered=self.create_tab_with_current_kernel)
364 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
366 self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
365
367
366 self.file_menu.addSeparator()
368 self.file_menu.addSeparator()
367
369
368 self.close_action=QtGui.QAction("&Close Tab",
370 self.close_action=QtGui.QAction("&Close Tab",
369 self,
371 self,
370 shortcut=QtGui.QKeySequence.Close,
372 shortcut=QtGui.QKeySequence.Close,
371 triggered=self.close_active_frontend
373 triggered=self.close_active_frontend
372 )
374 )
373 self.add_menu_action(self.file_menu, self.close_action)
375 self.add_menu_action(self.file_menu, self.close_action)
374
376
375 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
377 self.export_action=QtGui.QAction("&Save to HTML/XHTML",
376 self,
378 self,
377 shortcut=QtGui.QKeySequence.Save,
379 shortcut=QtGui.QKeySequence.Save,
378 triggered=self.export_action_active_frontend
380 triggered=self.export_action_active_frontend
379 )
381 )
380 self.add_menu_action(self.file_menu, self.export_action, True)
382 self.add_menu_action(self.file_menu, self.export_action, True)
381
383
382 self.file_menu.addSeparator()
384 self.file_menu.addSeparator()
383
385
384 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
386 printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
385 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
387 if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
386 # Only override the default if there is a collision.
388 # Only override the default if there is a collision.
387 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
389 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
388 printkey = "Ctrl+Shift+P"
390 printkey = "Ctrl+Shift+P"
389 self.print_action = QtGui.QAction("&Print",
391 self.print_action = QtGui.QAction("&Print",
390 self,
392 self,
391 shortcut=printkey,
393 shortcut=printkey,
392 triggered=self.print_action_active_frontend)
394 triggered=self.print_action_active_frontend)
393 self.add_menu_action(self.file_menu, self.print_action, True)
395 self.add_menu_action(self.file_menu, self.print_action, True)
394
396
395 if sys.platform != 'darwin':
397 if sys.platform != 'darwin':
396 # OSX always has Quit in the Application menu, only add it
398 # OSX always has Quit in the Application menu, only add it
397 # to the File menu elsewhere.
399 # to the File menu elsewhere.
398
400
399 self.file_menu.addSeparator()
401 self.file_menu.addSeparator()
400
402
401 self.quit_action = QtGui.QAction("&Quit",
403 self.quit_action = QtGui.QAction("&Quit",
402 self,
404 self,
403 shortcut=QtGui.QKeySequence.Quit,
405 shortcut=QtGui.QKeySequence.Quit,
404 triggered=self.close,
406 triggered=self.close,
405 )
407 )
406 self.add_menu_action(self.file_menu, self.quit_action)
408 self.add_menu_action(self.file_menu, self.quit_action)
407
409
408
410
409 def init_edit_menu(self):
411 def init_edit_menu(self):
410 self.edit_menu = self.menuBar().addMenu("&Edit")
412 self.edit_menu = self.menuBar().addMenu("&Edit")
411
413
412 self.undo_action = QtGui.QAction("&Undo",
414 self.undo_action = QtGui.QAction("&Undo",
413 self,
415 self,
414 shortcut=QtGui.QKeySequence.Undo,
416 shortcut=QtGui.QKeySequence.Undo,
415 statusTip="Undo last action if possible",
417 statusTip="Undo last action if possible",
416 triggered=self.undo_active_frontend
418 triggered=self.undo_active_frontend
417 )
419 )
418 self.add_menu_action(self.edit_menu, self.undo_action)
420 self.add_menu_action(self.edit_menu, self.undo_action)
419
421
420 self.redo_action = QtGui.QAction("&Redo",
422 self.redo_action = QtGui.QAction("&Redo",
421 self,
423 self,
422 shortcut=QtGui.QKeySequence.Redo,
424 shortcut=QtGui.QKeySequence.Redo,
423 statusTip="Redo last action if possible",
425 statusTip="Redo last action if possible",
424 triggered=self.redo_active_frontend)
426 triggered=self.redo_active_frontend)
425 self.add_menu_action(self.edit_menu, self.redo_action)
427 self.add_menu_action(self.edit_menu, self.redo_action)
426
428
427 self.edit_menu.addSeparator()
429 self.edit_menu.addSeparator()
428
430
429 self.cut_action = QtGui.QAction("&Cut",
431 self.cut_action = QtGui.QAction("&Cut",
430 self,
432 self,
431 shortcut=QtGui.QKeySequence.Cut,
433 shortcut=QtGui.QKeySequence.Cut,
432 triggered=self.cut_active_frontend
434 triggered=self.cut_active_frontend
433 )
435 )
434 self.add_menu_action(self.edit_menu, self.cut_action, True)
436 self.add_menu_action(self.edit_menu, self.cut_action, True)
435
437
436 self.copy_action = QtGui.QAction("&Copy",
438 self.copy_action = QtGui.QAction("&Copy",
437 self,
439 self,
438 shortcut=QtGui.QKeySequence.Copy,
440 shortcut=QtGui.QKeySequence.Copy,
439 triggered=self.copy_active_frontend
441 triggered=self.copy_active_frontend
440 )
442 )
441 self.add_menu_action(self.edit_menu, self.copy_action, True)
443 self.add_menu_action(self.edit_menu, self.copy_action, True)
442
444
443 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
445 self.copy_raw_action = QtGui.QAction("Copy (&Raw Text)",
444 self,
446 self,
445 shortcut="Ctrl+Shift+C",
447 shortcut="Ctrl+Shift+C",
446 triggered=self.copy_raw_active_frontend
448 triggered=self.copy_raw_active_frontend
447 )
449 )
448 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
450 self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
449
451
450 self.paste_action = QtGui.QAction("&Paste",
452 self.paste_action = QtGui.QAction("&Paste",
451 self,
453 self,
452 shortcut=QtGui.QKeySequence.Paste,
454 shortcut=QtGui.QKeySequence.Paste,
453 triggered=self.paste_active_frontend
455 triggered=self.paste_active_frontend
454 )
456 )
455 self.add_menu_action(self.edit_menu, self.paste_action, True)
457 self.add_menu_action(self.edit_menu, self.paste_action, True)
456
458
457 self.edit_menu.addSeparator()
459 self.edit_menu.addSeparator()
458
460
459 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
461 selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
460 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
462 if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
461 # Only override the default if there is a collision.
463 # Only override the default if there is a collision.
462 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
464 # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
463 selectall = "Ctrl+Shift+A"
465 selectall = "Ctrl+Shift+A"
464 self.select_all_action = QtGui.QAction("Select &All",
466 self.select_all_action = QtGui.QAction("Select &All",
465 self,
467 self,
466 shortcut=selectall,
468 shortcut=selectall,
467 triggered=self.select_all_active_frontend
469 triggered=self.select_all_active_frontend
468 )
470 )
469 self.add_menu_action(self.edit_menu, self.select_all_action, True)
471 self.add_menu_action(self.edit_menu, self.select_all_action, True)
470
472
471
473
472 def init_view_menu(self):
474 def init_view_menu(self):
473 self.view_menu = self.menuBar().addMenu("&View")
475 self.view_menu = self.menuBar().addMenu("&View")
474
476
475 if sys.platform != 'darwin':
477 if sys.platform != 'darwin':
476 # disable on OSX, where there is always a menu bar
478 # disable on OSX, where there is always a menu bar
477 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
479 self.toggle_menu_bar_act = QtGui.QAction("Toggle &Menu Bar",
478 self,
480 self,
479 shortcut="Ctrl+Shift+M",
481 shortcut="Ctrl+Shift+M",
480 statusTip="Toggle visibility of menubar",
482 statusTip="Toggle visibility of menubar",
481 triggered=self.toggle_menu_bar)
483 triggered=self.toggle_menu_bar)
482 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
484 self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
483
485
484 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
486 fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
485 self.full_screen_act = QtGui.QAction("&Full Screen",
487 self.full_screen_act = QtGui.QAction("&Full Screen",
486 self,
488 self,
487 shortcut=fs_key,
489 shortcut=fs_key,
488 statusTip="Toggle between Fullscreen and Normal Size",
490 statusTip="Toggle between Fullscreen and Normal Size",
489 triggered=self.toggleFullScreen)
491 triggered=self.toggleFullScreen)
490 self.add_menu_action(self.view_menu, self.full_screen_act)
492 self.add_menu_action(self.view_menu, self.full_screen_act)
491
493
492 self.view_menu.addSeparator()
494 self.view_menu.addSeparator()
493
495
494 self.increase_font_size = QtGui.QAction("Zoom &In",
496 self.increase_font_size = QtGui.QAction("Zoom &In",
495 self,
497 self,
496 shortcut=QtGui.QKeySequence.ZoomIn,
498 shortcut=QtGui.QKeySequence.ZoomIn,
497 triggered=self.increase_font_size_active_frontend
499 triggered=self.increase_font_size_active_frontend
498 )
500 )
499 self.add_menu_action(self.view_menu, self.increase_font_size, True)
501 self.add_menu_action(self.view_menu, self.increase_font_size, True)
500
502
501 self.decrease_font_size = QtGui.QAction("Zoom &Out",
503 self.decrease_font_size = QtGui.QAction("Zoom &Out",
502 self,
504 self,
503 shortcut=QtGui.QKeySequence.ZoomOut,
505 shortcut=QtGui.QKeySequence.ZoomOut,
504 triggered=self.decrease_font_size_active_frontend
506 triggered=self.decrease_font_size_active_frontend
505 )
507 )
506 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
508 self.add_menu_action(self.view_menu, self.decrease_font_size, True)
507
509
508 self.reset_font_size = QtGui.QAction("Zoom &Reset",
510 self.reset_font_size = QtGui.QAction("Zoom &Reset",
509 self,
511 self,
510 shortcut="Ctrl+0",
512 shortcut="Ctrl+0",
511 triggered=self.reset_font_size_active_frontend
513 triggered=self.reset_font_size_active_frontend
512 )
514 )
513 self.add_menu_action(self.view_menu, self.reset_font_size, True)
515 self.add_menu_action(self.view_menu, self.reset_font_size, True)
514
516
515 self.view_menu.addSeparator()
517 self.view_menu.addSeparator()
516
518
517 self.clear_action = QtGui.QAction("&Clear Screen",
519 self.clear_action = QtGui.QAction("&Clear Screen",
518 self,
520 self,
519 shortcut='Ctrl+L',
521 shortcut='Ctrl+L',
520 statusTip="Clear the console",
522 statusTip="Clear the console",
521 triggered=self.clear_magic_active_frontend)
523 triggered=self.clear_magic_active_frontend)
522 self.add_menu_action(self.view_menu, self.clear_action)
524 self.add_menu_action(self.view_menu, self.clear_action)
523
525
524 def init_kernel_menu(self):
526 def init_kernel_menu(self):
525 self.kernel_menu = self.menuBar().addMenu("&Kernel")
527 self.kernel_menu = self.menuBar().addMenu("&Kernel")
526 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
528 # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
527 # keep the signal shortcuts to ctrl, rather than
529 # keep the signal shortcuts to ctrl, rather than
528 # platform-default like we do elsewhere.
530 # platform-default like we do elsewhere.
529
531
530 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
532 ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
531
533
532 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
534 self.interrupt_kernel_action = QtGui.QAction("Interrupt current Kernel",
533 self,
535 self,
534 triggered=self.interrupt_kernel_active_frontend,
536 triggered=self.interrupt_kernel_active_frontend,
535 shortcut=ctrl+"+C",
537 shortcut=ctrl+"+C",
536 )
538 )
537 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
539 self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
538
540
539 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
541 self.restart_kernel_action = QtGui.QAction("Restart current Kernel",
540 self,
542 self,
541 triggered=self.restart_kernel_active_frontend,
543 triggered=self.restart_kernel_active_frontend,
542 shortcut=ctrl+"+.",
544 shortcut=ctrl+"+.",
543 )
545 )
544 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
546 self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
545
547
546 self.kernel_menu.addSeparator()
548 self.kernel_menu.addSeparator()
547
549
548 def _make_dynamic_magic(self,magic):
550 def _make_dynamic_magic(self,magic):
549 """Return a function `fun` that will execute `magic` on active frontend.
551 """Return a function `fun` that will execute `magic` on active frontend.
550
552
551 Parameters
553 Parameters
552 ----------
554 ----------
553 magic : string
555 magic : string
554 string that will be executed as is when the returned function is called
556 string that will be executed as is when the returned function is called
555
557
556 Returns
558 Returns
557 -------
559 -------
558 fun : function
560 fun : function
559 function with no parameters, when called will execute `magic` on the
561 function with no parameters, when called will execute `magic` on the
560 current active frontend at call time
562 current active frontend at call time
561
563
562 See Also
564 See Also
563 --------
565 --------
564 populate_all_magic_menu : generate the "All Magics..." menu
566 populate_all_magic_menu : generate the "All Magics..." menu
565
567
566 Notes
568 Notes
567 -----
569 -----
568 `fun` execute `magic` an active frontend at the moment it is triggerd,
570 `fun` execute `magic` an active frontend at the moment it is triggerd,
569 not the active frontend at the moment it has been created.
571 not the active frontend at the moment it has been created.
570
572
571 This function is mostly used to create the "All Magics..." Menu at run time.
573 This function is mostly used to create the "All Magics..." Menu at run time.
572 """
574 """
573 # need to level nested function to be sure to past magic
575 # need to level nested function to be sure to past magic
574 # on active frontend **at run time**.
576 # on active frontend **at run time**.
575 def inner_dynamic_magic():
577 def inner_dynamic_magic():
576 self.active_frontend.execute(magic)
578 self.active_frontend.execute(magic)
577 inner_dynamic_magic.__name__ = "dynamics_magic_s"
579 inner_dynamic_magic.__name__ = "dynamics_magic_s"
578 return inner_dynamic_magic
580 return inner_dynamic_magic
579
581
580 def populate_all_magic_menu(self, listofmagic=None):
582 def populate_all_magic_menu(self, listofmagic=None):
581 """Clean "All Magics..." menu and repopulate it with `listofmagic`
583 """Clean "All Magics..." menu and repopulate it with `listofmagic`
582
584
583 Parameters
585 Parameters
584 ----------
586 ----------
585 listofmagic : string,
587 listofmagic : string,
586 repr() of a list of strings, send back by the kernel
588 repr() of a list of strings, send back by the kernel
587
589
588 Notes
590 Notes
589 -----
591 -----
590 `listofmagic`is a repr() of list because it is fed with the result of
592 `listofmagic`is a repr() of list because it is fed with the result of
591 a 'user_expression'
593 a 'user_expression'
592 """
594 """
593 alm_magic_menu = self.all_magic_menu
595 alm_magic_menu = self.all_magic_menu
594 alm_magic_menu.clear()
596 alm_magic_menu.clear()
595
597
596 # list of protected magic that don't like to be called without argument
598 # list of protected magic that don't like to be called without argument
597 # append '?' to the end to print the docstring when called from the menu
599 # append '?' to the end to print the docstring when called from the menu
598 protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
600 protected_magic = set(["more","less","load_ext","pycat","loadpy","save"])
599 magics=re.findall('\w+', listofmagic)
601 magics=re.findall('\w+', listofmagic)
600 for magic in magics:
602 for magic in magics:
601 if magic in protected_magic:
603 if magic in protected_magic:
602 pmagic = '%s%s%s'%('%',magic,'?')
604 pmagic = '%s%s%s'%('%',magic,'?')
603 else:
605 else:
604 pmagic = '%s%s'%('%',magic)
606 pmagic = '%s%s'%('%',magic)
605 xaction = QtGui.QAction(pmagic,
607 xaction = QtGui.QAction(pmagic,
606 self,
608 self,
607 triggered=self._make_dynamic_magic(pmagic)
609 triggered=self._make_dynamic_magic(pmagic)
608 )
610 )
609 alm_magic_menu.addAction(xaction)
611 alm_magic_menu.addAction(xaction)
610
612
611 def update_all_magic_menu(self):
613 def update_all_magic_menu(self):
612 """ Update the list on magic in the "All Magics..." Menu
614 """ Update the list on magic in the "All Magics..." Menu
613
615
614 Request the kernel with the list of availlable magic and populate the
616 Request the kernel with the list of availlable magic and populate the
615 menu with the list received back
617 menu with the list received back
616
618
617 """
619 """
618 # first define a callback which will get the list of all magic and put it in the menu.
620 # first define a callback which will get the list of all magic and put it in the menu.
619 self.active_frontend._silent_exec_callback('get_ipython().lsmagic()', self.populate_all_magic_menu)
621 self.active_frontend._silent_exec_callback('get_ipython().lsmagic()', self.populate_all_magic_menu)
620
622
621 def init_magic_menu(self):
623 def init_magic_menu(self):
622 self.magic_menu = self.menuBar().addMenu("&Magic")
624 self.magic_menu = self.menuBar().addMenu("&Magic")
623 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
625 self.all_magic_menu = self.magic_menu.addMenu("&All Magics")
624
626
625 # This action should usually not appear as it will be cleared when menu
627 # This action should usually not appear as it will be cleared when menu
626 # is updated at first kernel response. Though, it is necessary when
628 # is updated at first kernel response. Though, it is necessary when
627 # connecting through X-forwarding, as in this case, the menu is not
629 # connecting through X-forwarding, as in this case, the menu is not
628 # auto updated, SO DO NOT DELETE.
630 # auto updated, SO DO NOT DELETE.
629 self.pop = QtGui.QAction("&Update All Magic Menu ",
631 self.pop = QtGui.QAction("&Update All Magic Menu ",
630 self, triggered=self.update_all_magic_menu)
632 self, triggered=self.update_all_magic_menu)
631 self.add_menu_action(self.all_magic_menu, self.pop)
633 self.add_menu_action(self.all_magic_menu, self.pop)
632 # we need to populate the 'Magic Menu' once the kernel has answer at
634 # we need to populate the 'Magic Menu' once the kernel has answer at
633 # least once let's do it immedialy, but it's assured to works
635 # least once let's do it immedialy, but it's assured to works
634 self.pop.trigger()
636 self.pop.trigger()
635
637
636 self.reset_action = QtGui.QAction("&Reset",
638 self.reset_action = QtGui.QAction("&Reset",
637 self,
639 self,
638 statusTip="Clear all varible from workspace",
640 statusTip="Clear all varible from workspace",
639 triggered=self.reset_magic_active_frontend)
641 triggered=self.reset_magic_active_frontend)
640 self.add_menu_action(self.magic_menu, self.reset_action)
642 self.add_menu_action(self.magic_menu, self.reset_action)
641
643
642 self.history_action = QtGui.QAction("&History",
644 self.history_action = QtGui.QAction("&History",
643 self,
645 self,
644 statusTip="show command history",
646 statusTip="show command history",
645 triggered=self.history_magic_active_frontend)
647 triggered=self.history_magic_active_frontend)
646 self.add_menu_action(self.magic_menu, self.history_action)
648 self.add_menu_action(self.magic_menu, self.history_action)
647
649
648 self.save_action = QtGui.QAction("E&xport History ",
650 self.save_action = QtGui.QAction("E&xport History ",
649 self,
651 self,
650 statusTip="Export History as Python File",
652 statusTip="Export History as Python File",
651 triggered=self.save_magic_active_frontend)
653 triggered=self.save_magic_active_frontend)
652 self.add_menu_action(self.magic_menu, self.save_action)
654 self.add_menu_action(self.magic_menu, self.save_action)
653
655
654 self.who_action = QtGui.QAction("&Who",
656 self.who_action = QtGui.QAction("&Who",
655 self,
657 self,
656 statusTip="List interactive variable",
658 statusTip="List interactive variable",
657 triggered=self.who_magic_active_frontend)
659 triggered=self.who_magic_active_frontend)
658 self.add_menu_action(self.magic_menu, self.who_action)
660 self.add_menu_action(self.magic_menu, self.who_action)
659
661
660 self.who_ls_action = QtGui.QAction("Wh&o ls",
662 self.who_ls_action = QtGui.QAction("Wh&o ls",
661 self,
663 self,
662 statusTip="Return a list of interactive variable",
664 statusTip="Return a list of interactive variable",
663 triggered=self.who_ls_magic_active_frontend)
665 triggered=self.who_ls_magic_active_frontend)
664 self.add_menu_action(self.magic_menu, self.who_ls_action)
666 self.add_menu_action(self.magic_menu, self.who_ls_action)
665
667
666 self.whos_action = QtGui.QAction("Who&s",
668 self.whos_action = QtGui.QAction("Who&s",
667 self,
669 self,
668 statusTip="List interactive variable with detail",
670 statusTip="List interactive variable with detail",
669 triggered=self.whos_magic_active_frontend)
671 triggered=self.whos_magic_active_frontend)
670 self.add_menu_action(self.magic_menu, self.whos_action)
672 self.add_menu_action(self.magic_menu, self.whos_action)
671
673
672 def init_window_menu(self):
674 def init_window_menu(self):
673 self.window_menu = self.menuBar().addMenu("&Window")
675 self.window_menu = self.menuBar().addMenu("&Window")
674 if sys.platform == 'darwin':
676 if sys.platform == 'darwin':
675 # add min/maximize actions to OSX, which lacks default bindings.
677 # add min/maximize actions to OSX, which lacks default bindings.
676 self.minimizeAct = QtGui.QAction("Mini&mize",
678 self.minimizeAct = QtGui.QAction("Mini&mize",
677 self,
679 self,
678 shortcut="Ctrl+m",
680 shortcut="Ctrl+m",
679 statusTip="Minimize the window/Restore Normal Size",
681 statusTip="Minimize the window/Restore Normal Size",
680 triggered=self.toggleMinimized)
682 triggered=self.toggleMinimized)
681 # maximize is called 'Zoom' on OSX for some reason
683 # maximize is called 'Zoom' on OSX for some reason
682 self.maximizeAct = QtGui.QAction("&Zoom",
684 self.maximizeAct = QtGui.QAction("&Zoom",
683 self,
685 self,
684 shortcut="Ctrl+Shift+M",
686 shortcut="Ctrl+Shift+M",
685 statusTip="Maximize the window/Restore Normal Size",
687 statusTip="Maximize the window/Restore Normal Size",
686 triggered=self.toggleMaximized)
688 triggered=self.toggleMaximized)
687
689
688 self.add_menu_action(self.window_menu, self.minimizeAct)
690 self.add_menu_action(self.window_menu, self.minimizeAct)
689 self.add_menu_action(self.window_menu, self.maximizeAct)
691 self.add_menu_action(self.window_menu, self.maximizeAct)
690 self.window_menu.addSeparator()
692 self.window_menu.addSeparator()
691
693
692 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
694 prev_key = "Ctrl+Shift+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
693 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
695 self.prev_tab_act = QtGui.QAction("Pre&vious Tab",
694 self,
696 self,
695 shortcut=prev_key,
697 shortcut=prev_key,
696 statusTip="Select previous tab",
698 statusTip="Select previous tab",
697 triggered=self.prev_tab)
699 triggered=self.prev_tab)
698 self.add_menu_action(self.window_menu, self.prev_tab_act)
700 self.add_menu_action(self.window_menu, self.prev_tab_act)
699
701
700 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
702 next_key = "Ctrl+Shift+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
701 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
703 self.next_tab_act = QtGui.QAction("Ne&xt Tab",
702 self,
704 self,
703 shortcut=next_key,
705 shortcut=next_key,
704 statusTip="Select next tab",
706 statusTip="Select next tab",
705 triggered=self.next_tab)
707 triggered=self.next_tab)
706 self.add_menu_action(self.window_menu, self.next_tab_act)
708 self.add_menu_action(self.window_menu, self.next_tab_act)
707
709
708 def init_help_menu(self):
710 def init_help_menu(self):
709 # please keep the Help menu in Mac Os even if empty. It will
711 # please keep the Help menu in Mac Os even if empty. It will
710 # automatically contain a search field to search inside menus and
712 # automatically contain a search field to search inside menus and
711 # please keep it spelled in English, as long as Qt Doesn't support
713 # please keep it spelled in English, as long as Qt Doesn't support
712 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
714 # a QAction.MenuRole like HelpMenuRole otherwise it will loose
713 # this search field fonctionality
715 # this search field fonctionality
714
716
715 self.help_menu = self.menuBar().addMenu("&Help")
717 self.help_menu = self.menuBar().addMenu("&Help")
716
718
717
719
718 # Help Menu
720 # Help Menu
719
721
720 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
722 self.intro_active_frontend_action = QtGui.QAction("&Intro to IPython",
721 self,
723 self,
722 triggered=self.intro_active_frontend
724 triggered=self.intro_active_frontend
723 )
725 )
724 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
726 self.add_menu_action(self.help_menu, self.intro_active_frontend_action)
725
727
726 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
728 self.quickref_active_frontend_action = QtGui.QAction("IPython &Cheat Sheet",
727 self,
729 self,
728 triggered=self.quickref_active_frontend
730 triggered=self.quickref_active_frontend
729 )
731 )
730 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
732 self.add_menu_action(self.help_menu, self.quickref_active_frontend_action)
731
733
732 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
734 self.guiref_active_frontend_action = QtGui.QAction("&Qt Console",
733 self,
735 self,
734 triggered=self.guiref_active_frontend
736 triggered=self.guiref_active_frontend
735 )
737 )
736 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
738 self.add_menu_action(self.help_menu, self.guiref_active_frontend_action)
737
739
738 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
740 self.onlineHelpAct = QtGui.QAction("Open Online &Help",
739 self,
741 self,
740 triggered=self._open_online_help)
742 triggered=self._open_online_help)
741 self.add_menu_action(self.help_menu, self.onlineHelpAct)
743 self.add_menu_action(self.help_menu, self.onlineHelpAct)
742
744
743 # minimize/maximize/fullscreen actions:
745 # minimize/maximize/fullscreen actions:
744
746
745 def toggle_menu_bar(self):
747 def toggle_menu_bar(self):
746 menu_bar = self.menuBar()
748 menu_bar = self.menuBar()
747 if menu_bar.isVisible():
749 if menu_bar.isVisible():
748 menu_bar.setVisible(False)
750 menu_bar.setVisible(False)
749 else:
751 else:
750 menu_bar.setVisible(True)
752 menu_bar.setVisible(True)
751
753
752 def toggleMinimized(self):
754 def toggleMinimized(self):
753 if not self.isMinimized():
755 if not self.isMinimized():
754 self.showMinimized()
756 self.showMinimized()
755 else:
757 else:
756 self.showNormal()
758 self.showNormal()
757
759
758 def _open_online_help(self):
760 def _open_online_help(self):
759 filename="http://ipython.org/ipython-doc/stable/index.html"
761 filename="http://ipython.org/ipython-doc/stable/index.html"
760 webbrowser.open(filename, new=1, autoraise=True)
762 webbrowser.open(filename, new=1, autoraise=True)
761
763
762 def toggleMaximized(self):
764 def toggleMaximized(self):
763 if not self.isMaximized():
765 if not self.isMaximized():
764 self.showMaximized()
766 self.showMaximized()
765 else:
767 else:
766 self.showNormal()
768 self.showNormal()
767
769
768 # Min/Max imizing while in full screen give a bug
770 # Min/Max imizing while in full screen give a bug
769 # when going out of full screen, at least on OSX
771 # when going out of full screen, at least on OSX
770 def toggleFullScreen(self):
772 def toggleFullScreen(self):
771 if not self.isFullScreen():
773 if not self.isFullScreen():
772 self.showFullScreen()
774 self.showFullScreen()
773 if sys.platform == 'darwin':
775 if sys.platform == 'darwin':
774 self.maximizeAct.setEnabled(False)
776 self.maximizeAct.setEnabled(False)
775 self.minimizeAct.setEnabled(False)
777 self.minimizeAct.setEnabled(False)
776 else:
778 else:
777 self.showNormal()
779 self.showNormal()
778 if sys.platform == 'darwin':
780 if sys.platform == 'darwin':
779 self.maximizeAct.setEnabled(True)
781 self.maximizeAct.setEnabled(True)
780 self.minimizeAct.setEnabled(True)
782 self.minimizeAct.setEnabled(True)
781
783
782 def close_active_frontend(self):
784 def close_active_frontend(self):
783 self.close_tab(self.active_frontend)
785 self.close_tab(self.active_frontend)
784
786
785 def restart_kernel_active_frontend(self):
787 def restart_kernel_active_frontend(self):
786 self.active_frontend.request_restart_kernel()
788 self.active_frontend.request_restart_kernel()
787
789
788 def interrupt_kernel_active_frontend(self):
790 def interrupt_kernel_active_frontend(self):
789 self.active_frontend.request_interrupt_kernel()
791 self.active_frontend.request_interrupt_kernel()
790
792
791 def cut_active_frontend(self):
793 def cut_active_frontend(self):
792 widget = self.active_frontend
794 widget = self.active_frontend
793 if widget.can_cut():
795 if widget.can_cut():
794 widget.cut()
796 widget.cut()
795
797
796 def copy_active_frontend(self):
798 def copy_active_frontend(self):
797 widget = self.active_frontend
799 widget = self.active_frontend
798 widget.copy()
800 widget.copy()
799
801
800 def copy_raw_active_frontend(self):
802 def copy_raw_active_frontend(self):
801 self.active_frontend._copy_raw_action.trigger()
803 self.active_frontend._copy_raw_action.trigger()
802
804
803 def paste_active_frontend(self):
805 def paste_active_frontend(self):
804 widget = self.active_frontend
806 widget = self.active_frontend
805 if widget.can_paste():
807 if widget.can_paste():
806 widget.paste()
808 widget.paste()
807
809
808 def undo_active_frontend(self):
810 def undo_active_frontend(self):
809 self.active_frontend.undo()
811 self.active_frontend.undo()
810
812
811 def redo_active_frontend(self):
813 def redo_active_frontend(self):
812 self.active_frontend.redo()
814 self.active_frontend.redo()
813
815
814 def reset_magic_active_frontend(self):
816 def reset_magic_active_frontend(self):
815 self.active_frontend.execute("%reset")
817 self.active_frontend.execute("%reset")
816
818
817 def history_magic_active_frontend(self):
819 def history_magic_active_frontend(self):
818 self.active_frontend.execute("%history")
820 self.active_frontend.execute("%history")
819
821
820 def save_magic_active_frontend(self):
822 def save_magic_active_frontend(self):
821 self.active_frontend.save_magic()
823 self.active_frontend.save_magic()
822
824
823 def clear_magic_active_frontend(self):
825 def clear_magic_active_frontend(self):
824 self.active_frontend.execute("%clear")
826 self.active_frontend.execute("%clear")
825
827
826 def who_magic_active_frontend(self):
828 def who_magic_active_frontend(self):
827 self.active_frontend.execute("%who")
829 self.active_frontend.execute("%who")
828
830
829 def who_ls_magic_active_frontend(self):
831 def who_ls_magic_active_frontend(self):
830 self.active_frontend.execute("%who_ls")
832 self.active_frontend.execute("%who_ls")
831
833
832 def whos_magic_active_frontend(self):
834 def whos_magic_active_frontend(self):
833 self.active_frontend.execute("%whos")
835 self.active_frontend.execute("%whos")
834
836
835 def print_action_active_frontend(self):
837 def print_action_active_frontend(self):
836 self.active_frontend.print_action.trigger()
838 self.active_frontend.print_action.trigger()
837
839
838 def export_action_active_frontend(self):
840 def export_action_active_frontend(self):
839 self.active_frontend.export_action.trigger()
841 self.active_frontend.export_action.trigger()
840
842
841 def select_all_active_frontend(self):
843 def select_all_active_frontend(self):
842 self.active_frontend.select_all_action.trigger()
844 self.active_frontend.select_all_action.trigger()
843
845
844 def increase_font_size_active_frontend(self):
846 def increase_font_size_active_frontend(self):
845 self.active_frontend.increase_font_size.trigger()
847 self.active_frontend.increase_font_size.trigger()
846
848
847 def decrease_font_size_active_frontend(self):
849 def decrease_font_size_active_frontend(self):
848 self.active_frontend.decrease_font_size.trigger()
850 self.active_frontend.decrease_font_size.trigger()
849
851
850 def reset_font_size_active_frontend(self):
852 def reset_font_size_active_frontend(self):
851 self.active_frontend.reset_font_size.trigger()
853 self.active_frontend.reset_font_size.trigger()
852
854
853 def guiref_active_frontend(self):
855 def guiref_active_frontend(self):
854 self.active_frontend.execute("%guiref")
856 self.active_frontend.execute("%guiref")
855
857
856 def intro_active_frontend(self):
858 def intro_active_frontend(self):
857 self.active_frontend.execute("?")
859 self.active_frontend.execute("?")
858
860
859 def quickref_active_frontend(self):
861 def quickref_active_frontend(self):
860 self.active_frontend.execute("%quickref")
862 self.active_frontend.execute("%quickref")
861 #---------------------------------------------------------------------------
863 #---------------------------------------------------------------------------
862 # QWidget interface
864 # QWidget interface
863 #---------------------------------------------------------------------------
865 #---------------------------------------------------------------------------
864
866
865 def closeEvent(self, event):
867 def closeEvent(self, event):
866 """ Forward the close event to every tabs contained by the windows
868 """ Forward the close event to every tabs contained by the windows
867 """
869 """
868 if self.tab_widget.count() == 0:
870 if self.tab_widget.count() == 0:
869 # no tabs, just close
871 # no tabs, just close
870 event.accept()
872 event.accept()
871 return
873 return
872 # Do Not loop on the widget count as it change while closing
874 # Do Not loop on the widget count as it change while closing
873 title = self.window().windowTitle()
875 title = self.window().windowTitle()
874 cancel = QtGui.QMessageBox.Cancel
876 cancel = QtGui.QMessageBox.Cancel
875 okay = QtGui.QMessageBox.Ok
877 okay = QtGui.QMessageBox.Ok
876
878
877 if self.confirm_exit:
879 if self.confirm_exit:
878 if self.tab_widget.count() > 1:
880 if self.tab_widget.count() > 1:
879 msg = "Close all tabs, stop all kernels, and Quit?"
881 msg = "Close all tabs, stop all kernels, and Quit?"
880 else:
882 else:
881 msg = "Close console, stop kernel, and Quit?"
883 msg = "Close console, stop kernel, and Quit?"
882 info = "Kernels not started here (e.g. notebooks) will be left alone."
884 info = "Kernels not started here (e.g. notebooks) will be left alone."
883 closeall = QtGui.QPushButton("&Quit", self)
885 closeall = QtGui.QPushButton("&Quit", self)
884 closeall.setShortcut('Q')
886 closeall.setShortcut('Q')
885 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
887 box = QtGui.QMessageBox(QtGui.QMessageBox.Question,
886 title, msg)
888 title, msg)
887 box.setInformativeText(info)
889 box.setInformativeText(info)
888 box.addButton(cancel)
890 box.addButton(cancel)
889 box.addButton(closeall, QtGui.QMessageBox.YesRole)
891 box.addButton(closeall, QtGui.QMessageBox.YesRole)
890 box.setDefaultButton(closeall)
892 box.setDefaultButton(closeall)
891 box.setEscapeButton(cancel)
893 box.setEscapeButton(cancel)
892 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
894 pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
893 box.setIconPixmap(pixmap)
895 box.setIconPixmap(pixmap)
894 reply = box.exec_()
896 reply = box.exec_()
895 else:
897 else:
896 reply = okay
898 reply = okay
897
899
898 if reply == cancel:
900 if reply == cancel:
899 event.ignore()
901 event.ignore()
900 return
902 return
901 if reply == okay:
903 if reply == okay:
902 while self.tab_widget.count() >= 1:
904 while self.tab_widget.count() >= 1:
903 # prevent further confirmations:
905 # prevent further confirmations:
904 widget = self.active_frontend
906 widget = self.active_frontend
905 widget._confirm_exit = False
907 widget._confirm_exit = False
906 self.close_tab(widget)
908 self.close_tab(widget)
907 event.accept()
909 event.accept()
908
910
General Comments 0
You need to be logged in to leave comments. Login now