##// END OF EJS Templates
Add Ctrl-+/- to increase/decrease font size....
Fernando Perez -
Show More
@@ -1,477 +1,478 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-2010 The IPython Development Team
5 # Copyright (C) 2008-2010 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 ipython [options] [files]
16 ipython [options] [files]
17
17
18 IPython: an enhanced interactive Python shell.
18 IPython: an enhanced interactive Python shell.
19
19
20 A Python shell with automatic history (input and output), dynamic object
20 A Python shell with automatic history (input and output), dynamic object
21 introspection, easier configuration, command completion, access to the
21 introspection, easier configuration, command completion, access to the
22 system shell and more. IPython can also be embedded in running programs.
22 system shell and more. IPython can also be embedded in running programs.
23
23
24 If invoked with no options, it executes all the files listed in sequence
24 If invoked with no options, it executes all the files listed in sequence
25 and exits, use -i to enter interactive mode after running the files. Files
25 and exits, use -i to enter interactive mode after running the files. Files
26 ending in .py will be treated as normal Python, but files ending in .ipy
26 ending in .py will be treated as normal Python, but files ending in .ipy
27 can contain special IPython syntax (magic commands, shell expansions, etc.)
27 can contain special IPython syntax (magic commands, shell expansions, etc.)
28
28
29 Please note that some of the configuration options are not available at the
29 Please note that some of the configuration options are not available at the
30 command line, simply because they are not practical here. Look into your
30 command line, simply because they are not practical here. Look into your
31 ipython_config.py configuration file for details on those.
31 ipython_config.py configuration file for details on those.
32
32
33 This file typically installed in the $HOME/.ipython directory. For Windows
33 This file typically installed in the $HOME/.ipython directory. For Windows
34 users, $HOME resolves to C:\\Documents and Settings\\YourUserName in most
34 users, $HOME resolves to C:\\Documents and Settings\\YourUserName in most
35 instances.
35 instances.
36
36
37 In IPython's documentation, we will refer to this directory as IPYTHON_DIR,
37 In IPython's documentation, we will refer to this directory as IPYTHON_DIR,
38 you can change its default location by setting any path you want in this
38 you can change its default location by setting any path you want in this
39 environment variable.
39 environment variable.
40
40
41 For more information, see the manual available in HTML and PDF in your
41 For more information, see the manual available in HTML and PDF in your
42 installation, or online at http://ipython.scipy.org.
42 installation, or online at http://ipython.scipy.org.
43 """
43 """
44
44
45 interactive_usage = """
45 interactive_usage = """
46 IPython -- An enhanced Interactive Python
46 IPython -- An enhanced Interactive Python
47 =========================================
47 =========================================
48
48
49 IPython offers a combination of convenient shell features, special commands
49 IPython offers a combination of convenient shell features, special commands
50 and a history mechanism for both input (command history) and output (results
50 and a history mechanism for both input (command history) and output (results
51 caching, similar to Mathematica). It is intended to be a fully compatible
51 caching, similar to Mathematica). It is intended to be a fully compatible
52 replacement for the standard Python interpreter, while offering vastly
52 replacement for the standard Python interpreter, while offering vastly
53 improved functionality and flexibility.
53 improved functionality and flexibility.
54
54
55 At your system command line, type 'ipython -help' to see the command line
55 At your system command line, type 'ipython -help' to see the command line
56 options available. This document only describes interactive features.
56 options available. This document only describes interactive features.
57
57
58 Warning: IPython relies on the existence of a global variable called __IP which
58 Warning: IPython relies on the existence of a global variable called __IP which
59 controls the shell itself. If you redefine __IP to anything, bizarre behavior
59 controls the shell itself. If you redefine __IP to anything, bizarre behavior
60 will quickly occur.
60 will quickly occur.
61
61
62 MAIN FEATURES
62 MAIN FEATURES
63
63
64 * Access to the standard Python help. As of Python 2.1, a help system is
64 * Access to the standard Python help. As of Python 2.1, a help system is
65 available with access to object docstrings and the Python manuals. Simply
65 available with access to object docstrings and the Python manuals. Simply
66 type 'help' (no quotes) to access it.
66 type 'help' (no quotes) to access it.
67
67
68 * Magic commands: type %magic for information on the magic subsystem.
68 * Magic commands: type %magic for information on the magic subsystem.
69
69
70 * System command aliases, via the %alias command or the ipythonrc config file.
70 * System command aliases, via the %alias command or the ipythonrc config file.
71
71
72 * Dynamic object information:
72 * Dynamic object information:
73
73
74 Typing ?word or word? prints detailed information about an object. If
74 Typing ?word or word? prints detailed information about an object. If
75 certain strings in the object are too long (docstrings, code, etc.) they get
75 certain strings in the object are too long (docstrings, code, etc.) they get
76 snipped in the center for brevity.
76 snipped in the center for brevity.
77
77
78 Typing ??word or word?? gives access to the full information without
78 Typing ??word or word?? gives access to the full information without
79 snipping long strings. Long strings are sent to the screen through the less
79 snipping long strings. Long strings are sent to the screen through the less
80 pager if longer than the screen, printed otherwise.
80 pager if longer than the screen, printed otherwise.
81
81
82 The ?/?? system gives access to the full source code for any object (if
82 The ?/?? system gives access to the full source code for any object (if
83 available), shows function prototypes and other useful information.
83 available), shows function prototypes and other useful information.
84
84
85 If you just want to see an object's docstring, type '%pdoc object' (without
85 If you just want to see an object's docstring, type '%pdoc object' (without
86 quotes, and without % if you have automagic on).
86 quotes, and without % if you have automagic on).
87
87
88 Both %pdoc and ?/?? give you access to documentation even on things which are
88 Both %pdoc and ?/?? give you access to documentation even on things which are
89 not explicitely defined. Try for example typing {}.get? or after import os,
89 not explicitely defined. Try for example typing {}.get? or after import os,
90 type os.path.abspath??. The magic functions %pdef, %source and %file operate
90 type os.path.abspath??. The magic functions %pdef, %source and %file operate
91 similarly.
91 similarly.
92
92
93 * Completion in the local namespace, by typing TAB at the prompt.
93 * Completion in the local namespace, by typing TAB at the prompt.
94
94
95 At any time, hitting tab will complete any available python commands or
95 At any time, hitting tab will complete any available python commands or
96 variable names, and show you a list of the possible completions if there's
96 variable names, and show you a list of the possible completions if there's
97 no unambiguous one. It will also complete filenames in the current directory.
97 no unambiguous one. It will also complete filenames in the current directory.
98
98
99 This feature requires the readline and rlcomplete modules, so it won't work
99 This feature requires the readline and rlcomplete modules, so it won't work
100 if your Python lacks readline support (such as under Windows).
100 if your Python lacks readline support (such as under Windows).
101
101
102 * Search previous command history in two ways (also requires readline):
102 * Search previous command history in two ways (also requires readline):
103
103
104 - Start typing, and then use Ctrl-p (previous,up) and Ctrl-n (next,down) to
104 - Start typing, and then use Ctrl-p (previous,up) and Ctrl-n (next,down) to
105 search through only the history items that match what you've typed so
105 search through only the history items that match what you've typed so
106 far. If you use Ctrl-p/Ctrl-n at a blank prompt, they just behave like
106 far. If you use Ctrl-p/Ctrl-n at a blank prompt, they just behave like
107 normal arrow keys.
107 normal arrow keys.
108
108
109 - Hit Ctrl-r: opens a search prompt. Begin typing and the system searches
109 - Hit Ctrl-r: opens a search prompt. Begin typing and the system searches
110 your history for lines that match what you've typed so far, completing as
110 your history for lines that match what you've typed so far, completing as
111 much as it can.
111 much as it can.
112
112
113 * Persistent command history across sessions (readline required).
113 * Persistent command history across sessions (readline required).
114
114
115 * Logging of input with the ability to save and restore a working session.
115 * Logging of input with the ability to save and restore a working session.
116
116
117 * System escape with !. Typing !ls will run 'ls' in the current directory.
117 * System escape with !. Typing !ls will run 'ls' in the current directory.
118
118
119 * The reload command does a 'deep' reload of a module: changes made to the
119 * The reload command does a 'deep' reload of a module: changes made to the
120 module since you imported will actually be available without having to exit.
120 module since you imported will actually be available without having to exit.
121
121
122 * Verbose and colored exception traceback printouts. See the magic xmode and
122 * Verbose and colored exception traceback printouts. See the magic xmode and
123 xcolor functions for details (just type %magic).
123 xcolor functions for details (just type %magic).
124
124
125 * Input caching system:
125 * Input caching system:
126
126
127 IPython offers numbered prompts (In/Out) with input and output caching. All
127 IPython offers numbered prompts (In/Out) with input and output caching. All
128 input is saved and can be retrieved as variables (besides the usual arrow
128 input is saved and can be retrieved as variables (besides the usual arrow
129 key recall).
129 key recall).
130
130
131 The following GLOBAL variables always exist (so don't overwrite them!):
131 The following GLOBAL variables always exist (so don't overwrite them!):
132 _i: stores previous input.
132 _i: stores previous input.
133 _ii: next previous.
133 _ii: next previous.
134 _iii: next-next previous.
134 _iii: next-next previous.
135 _ih : a list of all input _ih[n] is the input from line n.
135 _ih : a list of all input _ih[n] is the input from line n.
136
136
137 Additionally, global variables named _i<n> are dynamically created (<n>
137 Additionally, global variables named _i<n> are dynamically created (<n>
138 being the prompt counter), such that _i<n> == _ih[<n>]
138 being the prompt counter), such that _i<n> == _ih[<n>]
139
139
140 For example, what you typed at prompt 14 is available as _i14 and _ih[14].
140 For example, what you typed at prompt 14 is available as _i14 and _ih[14].
141
141
142 You can create macros which contain multiple input lines from this history,
142 You can create macros which contain multiple input lines from this history,
143 for later re-execution, with the %macro function.
143 for later re-execution, with the %macro function.
144
144
145 The history function %hist allows you to see any part of your input history
145 The history function %hist allows you to see any part of your input history
146 by printing a range of the _i variables. Note that inputs which contain
146 by printing a range of the _i variables. Note that inputs which contain
147 magic functions (%) appear in the history with a prepended comment. This is
147 magic functions (%) appear in the history with a prepended comment. This is
148 because they aren't really valid Python code, so you can't exec them.
148 because they aren't really valid Python code, so you can't exec them.
149
149
150 * Output caching system:
150 * Output caching system:
151
151
152 For output that is returned from actions, a system similar to the input
152 For output that is returned from actions, a system similar to the input
153 cache exists but using _ instead of _i. Only actions that produce a result
153 cache exists but using _ instead of _i. Only actions that produce a result
154 (NOT assignments, for example) are cached. If you are familiar with
154 (NOT assignments, for example) are cached. If you are familiar with
155 Mathematica, IPython's _ variables behave exactly like Mathematica's %
155 Mathematica, IPython's _ variables behave exactly like Mathematica's %
156 variables.
156 variables.
157
157
158 The following GLOBAL variables always exist (so don't overwrite them!):
158 The following GLOBAL variables always exist (so don't overwrite them!):
159 _ (one underscore): previous output.
159 _ (one underscore): previous output.
160 __ (two underscores): next previous.
160 __ (two underscores): next previous.
161 ___ (three underscores): next-next previous.
161 ___ (three underscores): next-next previous.
162
162
163 Global variables named _<n> are dynamically created (<n> being the prompt
163 Global variables named _<n> are dynamically created (<n> being the prompt
164 counter), such that the result of output <n> is always available as _<n>.
164 counter), such that the result of output <n> is always available as _<n>.
165
165
166 Finally, a global dictionary named _oh exists with entries for all lines
166 Finally, a global dictionary named _oh exists with entries for all lines
167 which generated output.
167 which generated output.
168
168
169 * Directory history:
169 * Directory history:
170
170
171 Your history of visited directories is kept in the global list _dh, and the
171 Your history of visited directories is kept in the global list _dh, and the
172 magic %cd command can be used to go to any entry in that list.
172 magic %cd command can be used to go to any entry in that list.
173
173
174 * Auto-parentheses and auto-quotes (adapted from Nathan Gray's LazyPython)
174 * Auto-parentheses and auto-quotes (adapted from Nathan Gray's LazyPython)
175
175
176 1. Auto-parentheses
176 1. Auto-parentheses
177 Callable objects (i.e. functions, methods, etc) can be invoked like
177 Callable objects (i.e. functions, methods, etc) can be invoked like
178 this (notice the commas between the arguments):
178 this (notice the commas between the arguments):
179 >>> callable_ob arg1, arg2, arg3
179 >>> callable_ob arg1, arg2, arg3
180 and the input will be translated to this:
180 and the input will be translated to this:
181 --> callable_ob(arg1, arg2, arg3)
181 --> callable_ob(arg1, arg2, arg3)
182 You can force auto-parentheses by using '/' as the first character
182 You can force auto-parentheses by using '/' as the first character
183 of a line. For example:
183 of a line. For example:
184 >>> /globals # becomes 'globals()'
184 >>> /globals # becomes 'globals()'
185 Note that the '/' MUST be the first character on the line! This
185 Note that the '/' MUST be the first character on the line! This
186 won't work:
186 won't work:
187 >>> print /globals # syntax error
187 >>> print /globals # syntax error
188
188
189 In most cases the automatic algorithm should work, so you should
189 In most cases the automatic algorithm should work, so you should
190 rarely need to explicitly invoke /. One notable exception is if you
190 rarely need to explicitly invoke /. One notable exception is if you
191 are trying to call a function with a list of tuples as arguments (the
191 are trying to call a function with a list of tuples as arguments (the
192 parenthesis will confuse IPython):
192 parenthesis will confuse IPython):
193 In [1]: zip (1,2,3),(4,5,6) # won't work
193 In [1]: zip (1,2,3),(4,5,6) # won't work
194 but this will work:
194 but this will work:
195 In [2]: /zip (1,2,3),(4,5,6)
195 In [2]: /zip (1,2,3),(4,5,6)
196 ------> zip ((1,2,3),(4,5,6))
196 ------> zip ((1,2,3),(4,5,6))
197 Out[2]= [(1, 4), (2, 5), (3, 6)]
197 Out[2]= [(1, 4), (2, 5), (3, 6)]
198
198
199 IPython tells you that it has altered your command line by
199 IPython tells you that it has altered your command line by
200 displaying the new command line preceded by -->. e.g.:
200 displaying the new command line preceded by -->. e.g.:
201 In [18]: callable list
201 In [18]: callable list
202 -------> callable (list)
202 -------> callable (list)
203
203
204 2. Auto-Quoting
204 2. Auto-Quoting
205 You can force auto-quoting of a function's arguments by using ',' as
205 You can force auto-quoting of a function's arguments by using ',' as
206 the first character of a line. For example:
206 the first character of a line. For example:
207 >>> ,my_function /home/me # becomes my_function("/home/me")
207 >>> ,my_function /home/me # becomes my_function("/home/me")
208
208
209 If you use ';' instead, the whole argument is quoted as a single
209 If you use ';' instead, the whole argument is quoted as a single
210 string (while ',' splits on whitespace):
210 string (while ',' splits on whitespace):
211 >>> ,my_function a b c # becomes my_function("a","b","c")
211 >>> ,my_function a b c # becomes my_function("a","b","c")
212 >>> ;my_function a b c # becomes my_function("a b c")
212 >>> ;my_function a b c # becomes my_function("a b c")
213
213
214 Note that the ',' MUST be the first character on the line! This
214 Note that the ',' MUST be the first character on the line! This
215 won't work:
215 won't work:
216 >>> x = ,my_function /home/me # syntax error
216 >>> x = ,my_function /home/me # syntax error
217 """
217 """
218
218
219 interactive_usage_min = """\
219 interactive_usage_min = """\
220 An enhanced console for Python.
220 An enhanced console for Python.
221 Some of its features are:
221 Some of its features are:
222 - Readline support if the readline library is present.
222 - Readline support if the readline library is present.
223 - Tab completion in the local namespace.
223 - Tab completion in the local namespace.
224 - Logging of input, see command-line options.
224 - Logging of input, see command-line options.
225 - System shell escape via ! , eg !ls.
225 - System shell escape via ! , eg !ls.
226 - Magic commands, starting with a % (like %ls, %pwd, %cd, etc.)
226 - Magic commands, starting with a % (like %ls, %pwd, %cd, etc.)
227 - Keeps track of locally defined variables via %who, %whos.
227 - Keeps track of locally defined variables via %who, %whos.
228 - Show object information with a ? eg ?x or x? (use ?? for more info).
228 - Show object information with a ? eg ?x or x? (use ?? for more info).
229 """
229 """
230
230
231 quick_reference = r"""
231 quick_reference = r"""
232 IPython -- An enhanced Interactive Python - Quick Reference Card
232 IPython -- An enhanced Interactive Python - Quick Reference Card
233 ================================================================
233 ================================================================
234
234
235 obj?, obj?? : Get help, or more help for object (also works as
235 obj?, obj?? : Get help, or more help for object (also works as
236 ?obj, ??obj).
236 ?obj, ??obj).
237 ?foo.*abc* : List names in 'foo' containing 'abc' in them.
237 ?foo.*abc* : List names in 'foo' containing 'abc' in them.
238 %magic : Information about IPython's 'magic' % functions.
238 %magic : Information about IPython's 'magic' % functions.
239
239
240 Magic functions are prefixed by %, and typically take their arguments without
240 Magic functions are prefixed by %, and typically take their arguments without
241 parentheses, quotes or even commas for convenience.
241 parentheses, quotes or even commas for convenience.
242
242
243 Example magic function calls:
243 Example magic function calls:
244
244
245 %alias d ls -F : 'd' is now an alias for 'ls -F'
245 %alias d ls -F : 'd' is now an alias for 'ls -F'
246 alias d ls -F : Works if 'alias' not a python name
246 alias d ls -F : Works if 'alias' not a python name
247 alist = %alias : Get list of aliases to 'alist'
247 alist = %alias : Get list of aliases to 'alist'
248 cd /usr/share : Obvious. cd -<tab> to choose from visited dirs.
248 cd /usr/share : Obvious. cd -<tab> to choose from visited dirs.
249 %cd?? : See help AND source for magic %cd
249 %cd?? : See help AND source for magic %cd
250
250
251 System commands:
251 System commands:
252
252
253 !cp a.txt b/ : System command escape, calls os.system()
253 !cp a.txt b/ : System command escape, calls os.system()
254 cp a.txt b/ : after %rehashx, most system commands work without !
254 cp a.txt b/ : after %rehashx, most system commands work without !
255 cp ${f}.txt $bar : Variable expansion in magics and system commands
255 cp ${f}.txt $bar : Variable expansion in magics and system commands
256 files = !ls /usr : Capture sytem command output
256 files = !ls /usr : Capture sytem command output
257 files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc'
257 files.s, files.l, files.n: "a b c", ['a','b','c'], 'a\nb\nc'
258
258
259 History:
259 History:
260
260
261 _i, _ii, _iii : Previous, next previous, next next previous input
261 _i, _ii, _iii : Previous, next previous, next next previous input
262 _i4, _ih[2:5] : Input history line 4, lines 2-4
262 _i4, _ih[2:5] : Input history line 4, lines 2-4
263 exec _i81 : Execute input history line #81 again
263 exec _i81 : Execute input history line #81 again
264 %rep 81 : Edit input history line #81
264 %rep 81 : Edit input history line #81
265 _, __, ___ : previous, next previous, next next previous output
265 _, __, ___ : previous, next previous, next next previous output
266 _dh : Directory history
266 _dh : Directory history
267 _oh : Output history
267 _oh : Output history
268 %hist : Command history. '%hist -g foo' search history for 'foo'
268 %hist : Command history. '%hist -g foo' search history for 'foo'
269
269
270 Autocall:
270 Autocall:
271
271
272 f 1,2 : f(1,2)
272 f 1,2 : f(1,2)
273 /f 1,2 : f(1,2) (forced autoparen)
273 /f 1,2 : f(1,2) (forced autoparen)
274 ,f 1 2 : f("1","2")
274 ,f 1 2 : f("1","2")
275 ;f 1 2 : f("1 2")
275 ;f 1 2 : f("1 2")
276
276
277 Remember: TAB completion works in many contexts, not just file names
277 Remember: TAB completion works in many contexts, not just file names
278 or python names.
278 or python names.
279
279
280 The following magic functions are currently available:
280 The following magic functions are currently available:
281
281
282 """
282 """
283
283
284 gui_reference = """\
284 gui_reference = """\
285 ===============================
285 ===============================
286 The graphical IPython console
286 The graphical IPython console
287 ===============================
287 ===============================
288
288
289 This console is designed to emulate the look, feel and workflow of a terminal
289 This console is designed to emulate the look, feel and workflow of a terminal
290 environment, while adding a number of enhancements that are simply not possible
290 environment, while adding a number of enhancements that are simply not possible
291 in a real terminal, such as inline syntax highlighting, true multiline editing,
291 in a real terminal, such as inline syntax highlighting, true multiline editing,
292 inline graphics and much more.
292 inline graphics and much more.
293
293
294 This quick reference document contains the basic information you'll need to
294 This quick reference document contains the basic information you'll need to
295 know to make the most efficient use of it. For the various command line
295 know to make the most efficient use of it. For the various command line
296 options available at startup, type ``--help`` at the command line.
296 options available at startup, type ``--help`` at the command line.
297
297
298
298
299 Multiline editing
299 Multiline editing
300 =================
300 =================
301
301
302 The graphical console is capable of true multiline editing, but it also tries
302 The graphical console is capable of true multiline editing, but it also tries
303 to behave intuitively like a terminal when possible. If you are used to
303 to behave intuitively like a terminal when possible. If you are used to
304 IPyhton's old terminal behavior, you should find the transition painless, and
304 IPyhton's old terminal behavior, you should find the transition painless, and
305 once you learn a few basic keybindings it will be a much more efficient
305 once you learn a few basic keybindings it will be a much more efficient
306 environment.
306 environment.
307
307
308 For single expressions or indented blocks, the console behaves almost like the
308 For single expressions or indented blocks, the console behaves almost like the
309 terminal IPython: single expressions are immediately evaluated, and indented
309 terminal IPython: single expressions are immediately evaluated, and indented
310 blocks are evaluated once a single blank line is entered::
310 blocks are evaluated once a single blank line is entered::
311
311
312 In [1]: print "Hello IPython!" # Enter was pressed at the end of the line
312 In [1]: print "Hello IPython!" # Enter was pressed at the end of the line
313 Hello IPython!
313 Hello IPython!
314
314
315 In [2]: for i in range(10):
315 In [2]: for i in range(10):
316 ...: print i,
316 ...: print i,
317 ...:
317 ...:
318 0 1 2 3 4 5 6 7 8 9
318 0 1 2 3 4 5 6 7 8 9
319
319
320 If you want to enter more than one expression in a single input block
320 If you want to enter more than one expression in a single input block
321 (something not possible in the terminal), you can use ``Control-Enter`` at the
321 (something not possible in the terminal), you can use ``Control-Enter`` at the
322 end of your first line instead of ``Enter``. At that point the console goes
322 end of your first line instead of ``Enter``. At that point the console goes
323 into 'cell mode' and even if your inputs are not indented, it will continue
323 into 'cell mode' and even if your inputs are not indented, it will continue
324 accepting arbitrarily many lines until either you enter an extra blank line or
324 accepting arbitrarily many lines until either you enter an extra blank line or
325 you hit ``Shift-Enter`` (the key binding that forces execution). When a
325 you hit ``Shift-Enter`` (the key binding that forces execution). When a
326 multiline cell is entered, IPython analyzes it and executes its code producing
326 multiline cell is entered, IPython analyzes it and executes its code producing
327 an ``Out[n]`` prompt only for the last expression in it, while the rest of the
327 an ``Out[n]`` prompt only for the last expression in it, while the rest of the
328 cell is executed as if it was a script. An example should clarify this::
328 cell is executed as if it was a script. An example should clarify this::
329
329
330 In [3]: x=1 # Hit C-Enter here
330 In [3]: x=1 # Hit C-Enter here
331 ...: y=2 # from now on, regular Enter is sufficient
331 ...: y=2 # from now on, regular Enter is sufficient
332 ...: z=3
332 ...: z=3
333 ...: x**2 # This does *not* produce an Out[] value
333 ...: x**2 # This does *not* produce an Out[] value
334 ...: x+y+z # Only the last expression does
334 ...: x+y+z # Only the last expression does
335 ...:
335 ...:
336 Out[3]: 6
336 Out[3]: 6
337
337
338 The behavior where an extra blank line forces execution is only active if you
338 The behavior where an extra blank line forces execution is only active if you
339 are actually typing at the keyboard each line, and is meant to make it mimic
339 are actually typing at the keyboard each line, and is meant to make it mimic
340 the IPython terminal behavior. If you paste a long chunk of input (for example
340 the IPython terminal behavior. If you paste a long chunk of input (for example
341 a long script copied form an editor or web browser), it can contain arbitrarily
341 a long script copied form an editor or web browser), it can contain arbitrarily
342 many intermediate blank lines and they won't cause any problems. As always,
342 many intermediate blank lines and they won't cause any problems. As always,
343 you can then make it execute by appending a blank line *at the end* or hitting
343 you can then make it execute by appending a blank line *at the end* or hitting
344 ``Shift-Enter`` anywhere within the cell.
344 ``Shift-Enter`` anywhere within the cell.
345
345
346 With the up arrow key, you can retrieve previous blocks of input that contain
346 With the up arrow key, you can retrieve previous blocks of input that contain
347 multiple lines. You can move inside of a multiline cell like you would in any
347 multiple lines. You can move inside of a multiline cell like you would in any
348 text editor. When you want it executed, the simplest thing to do is to hit the
348 text editor. When you want it executed, the simplest thing to do is to hit the
349 force execution key, ``Shift-Enter`` (though you can also navigate to the end
349 force execution key, ``Shift-Enter`` (though you can also navigate to the end
350 and append a blank line by using ``Enter`` twice).
350 and append a blank line by using ``Enter`` twice).
351
351
352 If you've edited a multiline cell and accidentally navigate out of it with the
352 If you've edited a multiline cell and accidentally navigate out of it with the
353 up or down arrow keys, IPython will clear the cell and replace it with the
353 up or down arrow keys, IPython will clear the cell and replace it with the
354 contents of the one above or below that you navigated to. If this was an
354 contents of the one above or below that you navigated to. If this was an
355 accident and you want to retrieve the cell you were editing, use the Undo
355 accident and you want to retrieve the cell you were editing, use the Undo
356 keybinding, ``Control-z``.
356 keybinding, ``Control-z``.
357
357
358
358
359 Key bindings
359 Key bindings
360 ============
360 ============
361
361
362 The IPython console supports most of the basic Emacs line-oriented keybindings,
362 The IPython console supports most of the basic Emacs line-oriented keybindings,
363 in addition to some of its own.
363 in addition to some of its own.
364
364
365 The keybinding prefixes mean:
365 The keybinding prefixes mean:
366
366
367 - ``C``: Control
367 - ``C``: Control
368 - ``S``: Shift
368 - ``S``: Shift
369 - ``M``: Meta (typically the Alt key)
369 - ``M``: Meta (typically the Alt key)
370
370
371 The keybindings themselves are:
371 The keybindings themselves are:
372
372
373 - ``Enter``: insert new line (may cause execution, see above).
373 - ``Enter``: insert new line (may cause execution, see above).
374 - ``C-Enter``: force new line, *never* causes execution.
374 - ``C-Enter``: force new line, *never* causes execution.
375 - ``S-Enter``: *force* execution regardless of where cursor is, no newline added.
375 - ``S-Enter``: *force* execution regardless of where cursor is, no newline added.
376 - ``C-c``: copy highlighted text to clipboard (prompts are automatically stripped).
376 - ``C-c``: copy highlighted text to clipboard (prompts are automatically stripped).
377 - ``C-S-c``: copy highlighted text to clipboard (prompts are not stripped).
377 - ``C-S-c``: copy highlighted text to clipboard (prompts are not stripped).
378 - ``C-v``: paste text from clipboard.
378 - ``C-v``: paste text from clipboard.
379 - ``C-z``: undo (retrieves lost text if you move out of a cell with the arrows).
379 - ``C-z``: undo (retrieves lost text if you move out of a cell with the arrows).
380 - ``C-S-z``: redo.
380 - ``C-S-z``: redo.
381 - ``C-o``: move to 'other' area, between pager and terminal.
381 - ``C-o``: move to 'other' area, between pager and terminal.
382 - ``C-l``: clear terminal.
382 - ``C-l``: clear terminal.
383 - ``C-a``: go to beginning of line.
383 - ``C-a``: go to beginning of line.
384 - ``C-e``: go to end of line.
384 - ``C-e``: go to end of line.
385 - ``C-k``: kill from cursor to the end of the line.
385 - ``C-k``: kill from cursor to the end of the line.
386 - ``C-y``: yank (paste)
386 - ``C-y``: yank (paste)
387 - ``C-p``: previous line (like up arrow)
387 - ``C-p``: previous line (like up arrow)
388 - ``C-n``: next line (like down arrow)
388 - ``C-n``: next line (like down arrow)
389 - ``C-f``: forward (like right arrow)
389 - ``C-f``: forward (like right arrow)
390 - ``C-b``: back (like left arrow)
390 - ``C-b``: back (like left arrow)
391 - ``C-d``: delete next character.
391 - ``C-d``: delete next character.
392 - ``M-<``: move to the beginning of the input region.
392 - ``M-<``: move to the beginning of the input region.
393 - ``M->``: move to the end of the input region.
393 - ``M->``: move to the end of the input region.
394 - ``M-d``: delete next word.
394 - ``M-d``: delete next word.
395 - ``M-Backspace``: delete previous word.
395 - ``M-Backspace``: delete previous word.
396 - ``C-.``: force a kernel restart (a confirmation dialog appears).
396 - ``C-.``: force a kernel restart (a confirmation dialog appears).
397
397 - ``C-+``: increase font size.
398 - ``C--``: decrease font size.
398
399
399 The IPython pager
400 The IPython pager
400 =================
401 =================
401
402
402 IPython will show long blocks of text from many sources using a builtin pager.
403 IPython will show long blocks of text from many sources using a builtin pager.
403 You can control where this pager appears with the ``--paging`` command-line
404 You can control where this pager appears with the ``--paging`` command-line
404 flag:
405 flag:
405
406
406 - default: it is overlaid on top of the main terminal. You must quit the pager
407 - default: it is overlaid on top of the main terminal. You must quit the pager
407 to get back to the terminal (similar to how a pager such as ``less`` or
408 to get back to the terminal (similar to how a pager such as ``less`` or
408 ``more`` works).
409 ``more`` works).
409
410
410 - vertical: the console is made double-tall, and the pager appears on the
411 - vertical: the console is made double-tall, and the pager appears on the
411 bottom area when needed. You can view its contents while using the terminal.
412 bottom area when needed. You can view its contents while using the terminal.
412
413
413 - horizontal: the console is made double-wide, and the pager appears on the
414 - horizontal: the console is made double-wide, and the pager appears on the
414 right area when needed. You can view its contents while using the terminal.
415 right area when needed. You can view its contents while using the terminal.
415
416
416 If you use the vertical or horizontal paging modes, you can navigate between
417 If you use the vertical or horizontal paging modes, you can navigate between
417 terminal and pager as follows:
418 terminal and pager as follows:
418
419
419 - Tab key: goes from pager to terminal (but not the other way around).
420 - Tab key: goes from pager to terminal (but not the other way around).
420 - Control-o: goes from one to another always.
421 - Control-o: goes from one to another always.
421 - Mouse: click on either.
422 - Mouse: click on either.
422
423
423 In all cases, the ``q`` or ``Escape`` keys quit the pager (when used with the
424 In all cases, the ``q`` or ``Escape`` keys quit the pager (when used with the
424 focus on the pager area).
425 focus on the pager area).
425
426
426
427
427 Running subprocesses
428 Running subprocesses
428 ====================
429 ====================
429
430
430 The graphical IPython console uses the ``pexpect`` module to run subprocesses
431 The graphical IPython console uses the ``pexpect`` module to run subprocesses
431 when you type ``!command``. This has a number of advantages (true asynchronous
432 when you type ``!command``. This has a number of advantages (true asynchronous
432 output from subprocesses as well as very robust termination of rogue
433 output from subprocesses as well as very robust termination of rogue
433 subprocesses with ``Control-C``), as well as some limitations. The main
434 subprocesses with ``Control-C``), as well as some limitations. The main
434 limitation is that you can *not* interact back with the subprocess, so anything
435 limitation is that you can *not* interact back with the subprocess, so anything
435 that invokes a pager or expects you to type input into it will block and hang
436 that invokes a pager or expects you to type input into it will block and hang
436 (you can kill it with ``Control-C``).
437 (you can kill it with ``Control-C``).
437
438
438 We have provided as magics ``%less`` to page files (aliased to ``%more``),
439 We have provided as magics ``%less`` to page files (aliased to ``%more``),
439 ``%clear`` to clear the terminal, and ``%man`` on Linux/OSX. These cover the
440 ``%clear`` to clear the terminal, and ``%man`` on Linux/OSX. These cover the
440 most common commands you'd want to call in your subshell and that would cause
441 most common commands you'd want to call in your subshell and that would cause
441 problems if invoked via ``!cmd``, but you need to be aware of this limitation.
442 problems if invoked via ``!cmd``, but you need to be aware of this limitation.
442
443
443
444
444 Inline matplotlib graphics
445 Inline matplotlib graphics
445 ==========================
446 ==========================
446
447
447 The IPython console is capable of displaying matplotlib figures inline, in SVG
448 The IPython console is capable of displaying matplotlib figures inline, in SVG
448 format. If started with the ``--pylab inline`` flag, then all figures are
449 format. If started with the ``--pylab inline`` flag, then all figures are
449 rendered inline automatically. If started with ``--pylab`` or ``--pylab <your
450 rendered inline automatically. If started with ``--pylab`` or ``--pylab <your
450 backend>``, then a GUI backend will be used, but the ``pastefig()`` function is
451 backend>``, then a GUI backend will be used, but the ``pastefig()`` function is
451 added to the global and ``plt`` namespaces. You can paste any figure that is
452 added to the global and ``plt`` namespaces. You can paste any figure that is
452 currently open in a window with this function; type ``pastefig?`` for
453 currently open in a window with this function; type ``pastefig?`` for
453 additional details."""
454 additional details."""
454
455
455 quick_guide = """\
456 quick_guide = """\
456 ? -> Introduction and overview of IPython's features.
457 ? -> Introduction and overview of IPython's features.
457 %quickref -> Quick reference.
458 %quickref -> Quick reference.
458 help -> Python's own help system.
459 help -> Python's own help system.
459 object? -> Details about 'object', use 'object??' for extra details.
460 object? -> Details about 'object', use 'object??' for extra details.
460 """
461 """
461
462
462 gui_note = """\
463 gui_note = """\
463 %guiref -> A brief reference about the graphical user interface.
464 %guiref -> A brief reference about the graphical user interface.
464 """
465 """
465
466
466 default_banner_parts = [
467 default_banner_parts = [
467 'Python %s\n' % (sys.version.split('\n')[0],),
468 'Python %s\n' % (sys.version.split('\n')[0],),
468 'Type "copyright", "credits" or "license" for more information.\n\n',
469 'Type "copyright", "credits" or "license" for more information.\n\n',
469 'IPython %s -- An enhanced Interactive Python.\n' % (release.version,),
470 'IPython %s -- An enhanced Interactive Python.\n' % (release.version,),
470 quick_guide
471 quick_guide
471 ]
472 ]
472
473
473 default_gui_banner_parts = default_banner_parts + [gui_note]
474 default_gui_banner_parts = default_banner_parts + [gui_note]
474
475
475 default_banner = ''.join(default_banner_parts)
476 default_banner = ''.join(default_banner_parts)
476
477
477 default_gui_banner = ''.join(default_gui_banner_parts)
478 default_gui_banner = ''.join(default_gui_banner_parts)
@@ -1,1638 +1,1653 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 from os.path import commonprefix
8 from os.path import commonprefix
9 import re
9 import re
10 import sys
10 import sys
11 from textwrap import dedent
11 from textwrap import dedent
12
12
13 # System library imports
13 # System library imports
14 from PyQt4 import QtCore, QtGui
14 from PyQt4 import QtCore, QtGui
15
15
16 # Local imports
16 # Local imports
17 from IPython.config.configurable import Configurable
17 from IPython.config.configurable import Configurable
18 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
18 from IPython.frontend.qt.util import MetaQObjectHasTraits, get_font
19 from IPython.utils.traitlets import Bool, Enum, Int
19 from IPython.utils.traitlets import Bool, Enum, Int
20 from ansi_code_processor import QtAnsiCodeProcessor
20 from ansi_code_processor import QtAnsiCodeProcessor
21 from completion_widget import CompletionWidget
21 from completion_widget import CompletionWidget
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Classes
24 # Classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 class ConsoleWidget(Configurable, QtGui.QWidget):
27 class ConsoleWidget(Configurable, QtGui.QWidget):
28 """ An abstract base class for console-type widgets. This class has
28 """ An abstract base class for console-type widgets. This class has
29 functionality for:
29 functionality for:
30
30
31 * Maintaining a prompt and editing region
31 * Maintaining a prompt and editing region
32 * Providing the traditional Unix-style console keyboard shortcuts
32 * Providing the traditional Unix-style console keyboard shortcuts
33 * Performing tab completion
33 * Performing tab completion
34 * Paging text
34 * Paging text
35 * Handling ANSI escape codes
35 * Handling ANSI escape codes
36
36
37 ConsoleWidget also provides a number of utility methods that will be
37 ConsoleWidget also provides a number of utility methods that will be
38 convenient to implementors of a console-style widget.
38 convenient to implementors of a console-style widget.
39 """
39 """
40 __metaclass__ = MetaQObjectHasTraits
40 __metaclass__ = MetaQObjectHasTraits
41
41
42 #------ Configuration ------------------------------------------------------
42 #------ Configuration ------------------------------------------------------
43
43
44 # Whether to process ANSI escape codes.
44 # Whether to process ANSI escape codes.
45 ansi_codes = Bool(True, config=True)
45 ansi_codes = Bool(True, config=True)
46
46
47 # The maximum number of lines of text before truncation. Specifying a
47 # The maximum number of lines of text before truncation. Specifying a
48 # non-positive number disables text truncation (not recommended).
48 # non-positive number disables text truncation (not recommended).
49 buffer_size = Int(500, config=True)
49 buffer_size = Int(500, config=True)
50
50
51 # Whether to use a list widget or plain text output for tab completion.
51 # Whether to use a list widget or plain text output for tab completion.
52 gui_completion = Bool(False, config=True)
52 gui_completion = Bool(False, config=True)
53
53
54 # The type of underlying text widget to use. Valid values are 'plain', which
54 # The type of underlying text widget to use. Valid values are 'plain', which
55 # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
55 # specifies a QPlainTextEdit, and 'rich', which specifies a QTextEdit.
56 # NOTE: this value can only be specified during initialization.
56 # NOTE: this value can only be specified during initialization.
57 kind = Enum(['plain', 'rich'], default_value='plain', config=True)
57 kind = Enum(['plain', 'rich'], default_value='plain', config=True)
58
58
59 # The type of paging to use. Valid values are:
59 # The type of paging to use. Valid values are:
60 # 'inside' : The widget pages like a traditional terminal.
60 # 'inside' : The widget pages like a traditional terminal.
61 # 'hsplit' : When paging is requested, the widget is split
61 # 'hsplit' : When paging is requested, the widget is split
62 # horizontally. The top pane contains the console, and the
62 # horizontally. The top pane contains the console, and the
63 # bottom pane contains the paged text.
63 # bottom pane contains the paged text.
64 # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
64 # 'vsplit' : Similar to 'hsplit', except that a vertical splitter used.
65 # 'custom' : No action is taken by the widget beyond emitting a
65 # 'custom' : No action is taken by the widget beyond emitting a
66 # 'custom_page_requested(str)' signal.
66 # 'custom_page_requested(str)' signal.
67 # 'none' : The text is written directly to the console.
67 # 'none' : The text is written directly to the console.
68 # NOTE: this value can only be specified during initialization.
68 # NOTE: this value can only be specified during initialization.
69 paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
69 paging = Enum(['inside', 'hsplit', 'vsplit', 'custom', 'none'],
70 default_value='inside', config=True)
70 default_value='inside', config=True)
71
71
72 # Whether to override ShortcutEvents for the keybindings defined by this
72 # Whether to override ShortcutEvents for the keybindings defined by this
73 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
73 # widget (Ctrl+n, Ctrl+a, etc). Enable this if you want this widget to take
74 # priority (when it has focus) over, e.g., window-level menu shortcuts.
74 # priority (when it has focus) over, e.g., window-level menu shortcuts.
75 override_shortcuts = Bool(False)
75 override_shortcuts = Bool(False)
76
76
77 #------ Signals ------------------------------------------------------------
77 #------ Signals ------------------------------------------------------------
78
78
79 # Signals that indicate ConsoleWidget state.
79 # Signals that indicate ConsoleWidget state.
80 copy_available = QtCore.pyqtSignal(bool)
80 copy_available = QtCore.pyqtSignal(bool)
81 redo_available = QtCore.pyqtSignal(bool)
81 redo_available = QtCore.pyqtSignal(bool)
82 undo_available = QtCore.pyqtSignal(bool)
82 undo_available = QtCore.pyqtSignal(bool)
83
83
84 # Signal emitted when paging is needed and the paging style has been
84 # Signal emitted when paging is needed and the paging style has been
85 # specified as 'custom'.
85 # specified as 'custom'.
86 custom_page_requested = QtCore.pyqtSignal(object)
86 custom_page_requested = QtCore.pyqtSignal(object)
87
87
88 # Signal emitted when the font is changed.
88 # Signal emitted when the font is changed.
89 font_changed = QtCore.pyqtSignal(QtGui.QFont)
89 font_changed = QtCore.pyqtSignal(QtGui.QFont)
90
90
91 #------ Protected class variables ------------------------------------------
91 #------ Protected class variables ------------------------------------------
92
92
93 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
93 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
94 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
94 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
95 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
95 QtCore.Qt.Key_A : QtCore.Qt.Key_Home,
96 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
96 QtCore.Qt.Key_E : QtCore.Qt.Key_End,
97 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
97 QtCore.Qt.Key_P : QtCore.Qt.Key_Up,
98 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
98 QtCore.Qt.Key_N : QtCore.Qt.Key_Down,
99 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
99 QtCore.Qt.Key_D : QtCore.Qt.Key_Delete, }
100
100
101 _shortcuts = set(_ctrl_down_remap.keys() +
101 _shortcuts = set(_ctrl_down_remap.keys() +
102 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
102 [ QtCore.Qt.Key_C, QtCore.Qt.Key_G, QtCore.Qt.Key_O,
103 QtCore.Qt.Key_V ])
103 QtCore.Qt.Key_V ])
104
104
105 #---------------------------------------------------------------------------
105 #---------------------------------------------------------------------------
106 # 'QObject' interface
106 # 'QObject' interface
107 #---------------------------------------------------------------------------
107 #---------------------------------------------------------------------------
108
108
109 def __init__(self, parent=None, **kw):
109 def __init__(self, parent=None, **kw):
110 """ Create a ConsoleWidget.
110 """ Create a ConsoleWidget.
111
111
112 Parameters:
112 Parameters:
113 -----------
113 -----------
114 parent : QWidget, optional [default None]
114 parent : QWidget, optional [default None]
115 The parent for this widget.
115 The parent for this widget.
116 """
116 """
117 QtGui.QWidget.__init__(self, parent)
117 QtGui.QWidget.__init__(self, parent)
118 Configurable.__init__(self, **kw)
118 Configurable.__init__(self, **kw)
119
119
120 # Create the layout and underlying text widget.
120 # Create the layout and underlying text widget.
121 layout = QtGui.QStackedLayout(self)
121 layout = QtGui.QStackedLayout(self)
122 layout.setContentsMargins(0, 0, 0, 0)
122 layout.setContentsMargins(0, 0, 0, 0)
123 self._control = self._create_control()
123 self._control = self._create_control()
124 self._page_control = None
124 self._page_control = None
125 self._splitter = None
125 self._splitter = None
126 if self.paging in ('hsplit', 'vsplit'):
126 if self.paging in ('hsplit', 'vsplit'):
127 self._splitter = QtGui.QSplitter()
127 self._splitter = QtGui.QSplitter()
128 if self.paging == 'hsplit':
128 if self.paging == 'hsplit':
129 self._splitter.setOrientation(QtCore.Qt.Horizontal)
129 self._splitter.setOrientation(QtCore.Qt.Horizontal)
130 else:
130 else:
131 self._splitter.setOrientation(QtCore.Qt.Vertical)
131 self._splitter.setOrientation(QtCore.Qt.Vertical)
132 self._splitter.addWidget(self._control)
132 self._splitter.addWidget(self._control)
133 layout.addWidget(self._splitter)
133 layout.addWidget(self._splitter)
134 else:
134 else:
135 layout.addWidget(self._control)
135 layout.addWidget(self._control)
136
136
137 # Create the paging widget, if necessary.
137 # Create the paging widget, if necessary.
138 if self.paging in ('inside', 'hsplit', 'vsplit'):
138 if self.paging in ('inside', 'hsplit', 'vsplit'):
139 self._page_control = self._create_page_control()
139 self._page_control = self._create_page_control()
140 if self._splitter:
140 if self._splitter:
141 self._page_control.hide()
141 self._page_control.hide()
142 self._splitter.addWidget(self._page_control)
142 self._splitter.addWidget(self._page_control)
143 else:
143 else:
144 layout.addWidget(self._page_control)
144 layout.addWidget(self._page_control)
145
145
146 # Initialize protected variables. Some variables contain useful state
146 # Initialize protected variables. Some variables contain useful state
147 # information for subclasses; they should be considered read-only.
147 # information for subclasses; they should be considered read-only.
148 self._ansi_processor = QtAnsiCodeProcessor()
148 self._ansi_processor = QtAnsiCodeProcessor()
149 self._completion_widget = CompletionWidget(self._control)
149 self._completion_widget = CompletionWidget(self._control)
150 self._continuation_prompt = '> '
150 self._continuation_prompt = '> '
151 self._continuation_prompt_html = None
151 self._continuation_prompt_html = None
152 self._executing = False
152 self._executing = False
153 self._filter_drag = False
153 self._filter_drag = False
154 self._filter_resize = False
154 self._filter_resize = False
155 self._prompt = ''
155 self._prompt = ''
156 self._prompt_html = None
156 self._prompt_html = None
157 self._prompt_pos = 0
157 self._prompt_pos = 0
158 self._prompt_sep = ''
158 self._prompt_sep = ''
159 self._reading = False
159 self._reading = False
160 self._reading_callback = None
160 self._reading_callback = None
161 self._tab_width = 8
161 self._tab_width = 8
162 self._text_completing_pos = 0
162 self._text_completing_pos = 0
163
163
164 # Set a monospaced font.
164 # Set a monospaced font.
165 self.reset_font()
165 self.reset_font()
166
166
167 def eventFilter(self, obj, event):
167 def eventFilter(self, obj, event):
168 """ Reimplemented to ensure a console-like behavior in the underlying
168 """ Reimplemented to ensure a console-like behavior in the underlying
169 text widgets.
169 text widgets.
170 """
170 """
171 etype = event.type()
171 etype = event.type()
172 if etype == QtCore.QEvent.KeyPress:
172 if etype == QtCore.QEvent.KeyPress:
173
173
174 # Re-map keys for all filtered widgets.
174 # Re-map keys for all filtered widgets.
175 key = event.key()
175 key = event.key()
176 if self._control_key_down(event.modifiers()) and \
176 if self._control_key_down(event.modifiers()) and \
177 key in self._ctrl_down_remap:
177 key in self._ctrl_down_remap:
178 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
178 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
179 self._ctrl_down_remap[key],
179 self._ctrl_down_remap[key],
180 QtCore.Qt.NoModifier)
180 QtCore.Qt.NoModifier)
181 QtGui.qApp.sendEvent(obj, new_event)
181 QtGui.qApp.sendEvent(obj, new_event)
182 return True
182 return True
183
183
184 elif obj == self._control:
184 elif obj == self._control:
185 return self._event_filter_console_keypress(event)
185 return self._event_filter_console_keypress(event)
186
186
187 elif obj == self._page_control:
187 elif obj == self._page_control:
188 return self._event_filter_page_keypress(event)
188 return self._event_filter_page_keypress(event)
189
189
190 # Make middle-click paste safe.
190 # Make middle-click paste safe.
191 elif etype == QtCore.QEvent.MouseButtonRelease and \
191 elif etype == QtCore.QEvent.MouseButtonRelease and \
192 event.button() == QtCore.Qt.MidButton and \
192 event.button() == QtCore.Qt.MidButton and \
193 obj == self._control.viewport():
193 obj == self._control.viewport():
194 cursor = self._control.cursorForPosition(event.pos())
194 cursor = self._control.cursorForPosition(event.pos())
195 self._control.setTextCursor(cursor)
195 self._control.setTextCursor(cursor)
196 self.paste(QtGui.QClipboard.Selection)
196 self.paste(QtGui.QClipboard.Selection)
197 return True
197 return True
198
198
199 # Manually adjust the scrollbars *after* a resize event is dispatched.
199 # Manually adjust the scrollbars *after* a resize event is dispatched.
200 elif etype == QtCore.QEvent.Resize and not self._filter_resize:
200 elif etype == QtCore.QEvent.Resize and not self._filter_resize:
201 self._filter_resize = True
201 self._filter_resize = True
202 QtGui.qApp.sendEvent(obj, event)
202 QtGui.qApp.sendEvent(obj, event)
203 self._adjust_scrollbars()
203 self._adjust_scrollbars()
204 self._filter_resize = False
204 self._filter_resize = False
205 return True
205 return True
206
206
207 # Override shortcuts for all filtered widgets.
207 # Override shortcuts for all filtered widgets.
208 elif etype == QtCore.QEvent.ShortcutOverride and \
208 elif etype == QtCore.QEvent.ShortcutOverride and \
209 self.override_shortcuts and \
209 self.override_shortcuts and \
210 self._control_key_down(event.modifiers()) and \
210 self._control_key_down(event.modifiers()) and \
211 event.key() in self._shortcuts:
211 event.key() in self._shortcuts:
212 event.accept()
212 event.accept()
213
213
214 # Ensure that drags are safe. The problem is that the drag starting
214 # Ensure that drags are safe. The problem is that the drag starting
215 # logic, which determines whether the drag is a Copy or Move, is locked
215 # logic, which determines whether the drag is a Copy or Move, is locked
216 # down in QTextControl. If the widget is editable, which it must be if
216 # down in QTextControl. If the widget is editable, which it must be if
217 # we're not executing, the drag will be a Move. The following hack
217 # we're not executing, the drag will be a Move. The following hack
218 # prevents QTextControl from deleting the text by clearing the selection
218 # prevents QTextControl from deleting the text by clearing the selection
219 # when a drag leave event originating from this widget is dispatched.
219 # when a drag leave event originating from this widget is dispatched.
220 # The fact that we have to clear the user's selection is unfortunate,
220 # The fact that we have to clear the user's selection is unfortunate,
221 # but the alternative--trying to prevent Qt from using its hardwired
221 # but the alternative--trying to prevent Qt from using its hardwired
222 # drag logic and writing our own--is worse.
222 # drag logic and writing our own--is worse.
223 elif etype == QtCore.QEvent.DragEnter and \
223 elif etype == QtCore.QEvent.DragEnter and \
224 obj == self._control.viewport() and \
224 obj == self._control.viewport() and \
225 event.source() == self._control.viewport():
225 event.source() == self._control.viewport():
226 self._filter_drag = True
226 self._filter_drag = True
227 elif etype == QtCore.QEvent.DragLeave and \
227 elif etype == QtCore.QEvent.DragLeave and \
228 obj == self._control.viewport() and \
228 obj == self._control.viewport() and \
229 self._filter_drag:
229 self._filter_drag:
230 cursor = self._control.textCursor()
230 cursor = self._control.textCursor()
231 cursor.clearSelection()
231 cursor.clearSelection()
232 self._control.setTextCursor(cursor)
232 self._control.setTextCursor(cursor)
233 self._filter_drag = False
233 self._filter_drag = False
234
234
235 # Ensure that drops are safe.
235 # Ensure that drops are safe.
236 elif etype == QtCore.QEvent.Drop and obj == self._control.viewport():
236 elif etype == QtCore.QEvent.Drop and obj == self._control.viewport():
237 cursor = self._control.cursorForPosition(event.pos())
237 cursor = self._control.cursorForPosition(event.pos())
238 if self._in_buffer(cursor.position()):
238 if self._in_buffer(cursor.position()):
239 text = unicode(event.mimeData().text())
239 text = unicode(event.mimeData().text())
240 self._insert_plain_text_into_buffer(cursor, text)
240 self._insert_plain_text_into_buffer(cursor, text)
241
241
242 # Qt is expecting to get something here--drag and drop occurs in its
242 # Qt is expecting to get something here--drag and drop occurs in its
243 # own event loop. Send a DragLeave event to end it.
243 # own event loop. Send a DragLeave event to end it.
244 QtGui.qApp.sendEvent(obj, QtGui.QDragLeaveEvent())
244 QtGui.qApp.sendEvent(obj, QtGui.QDragLeaveEvent())
245 return True
245 return True
246
246
247 return super(ConsoleWidget, self).eventFilter(obj, event)
247 return super(ConsoleWidget, self).eventFilter(obj, event)
248
248
249 #---------------------------------------------------------------------------
249 #---------------------------------------------------------------------------
250 # 'QWidget' interface
250 # 'QWidget' interface
251 #---------------------------------------------------------------------------
251 #---------------------------------------------------------------------------
252
252
253 def sizeHint(self):
253 def sizeHint(self):
254 """ Reimplemented to suggest a size that is 80 characters wide and
254 """ Reimplemented to suggest a size that is 80 characters wide and
255 25 lines high.
255 25 lines high.
256 """
256 """
257 font_metrics = QtGui.QFontMetrics(self.font)
257 font_metrics = QtGui.QFontMetrics(self.font)
258 margin = (self._control.frameWidth() +
258 margin = (self._control.frameWidth() +
259 self._control.document().documentMargin()) * 2
259 self._control.document().documentMargin()) * 2
260 style = self.style()
260 style = self.style()
261 splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth)
261 splitwidth = style.pixelMetric(QtGui.QStyle.PM_SplitterWidth)
262
262
263 # Note 1: Despite my best efforts to take the various margins into
263 # Note 1: Despite my best efforts to take the various margins into
264 # account, the width is still coming out a bit too small, so we include
264 # account, the width is still coming out a bit too small, so we include
265 # a fudge factor of one character here.
265 # a fudge factor of one character here.
266 # Note 2: QFontMetrics.maxWidth is not used here or anywhere else due
266 # Note 2: QFontMetrics.maxWidth is not used here or anywhere else due
267 # to a Qt bug on certain Mac OS systems where it returns 0.
267 # to a Qt bug on certain Mac OS systems where it returns 0.
268 width = font_metrics.width(' ') * 81 + margin
268 width = font_metrics.width(' ') * 81 + margin
269 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
269 width += style.pixelMetric(QtGui.QStyle.PM_ScrollBarExtent)
270 if self.paging == 'hsplit':
270 if self.paging == 'hsplit':
271 width = width * 2 + splitwidth
271 width = width * 2 + splitwidth
272
272
273 height = font_metrics.height() * 25 + margin
273 height = font_metrics.height() * 25 + margin
274 if self.paging == 'vsplit':
274 if self.paging == 'vsplit':
275 height = height * 2 + splitwidth
275 height = height * 2 + splitwidth
276
276
277 return QtCore.QSize(width, height)
277 return QtCore.QSize(width, height)
278
278
279 #---------------------------------------------------------------------------
279 #---------------------------------------------------------------------------
280 # 'ConsoleWidget' public interface
280 # 'ConsoleWidget' public interface
281 #---------------------------------------------------------------------------
281 #---------------------------------------------------------------------------
282
282
283 def can_copy(self):
283 def can_copy(self):
284 """ Returns whether text can be copied to the clipboard.
284 """ Returns whether text can be copied to the clipboard.
285 """
285 """
286 return self._control.textCursor().hasSelection()
286 return self._control.textCursor().hasSelection()
287
287
288 def can_cut(self):
288 def can_cut(self):
289 """ Returns whether text can be cut to the clipboard.
289 """ Returns whether text can be cut to the clipboard.
290 """
290 """
291 cursor = self._control.textCursor()
291 cursor = self._control.textCursor()
292 return (cursor.hasSelection() and
292 return (cursor.hasSelection() and
293 self._in_buffer(cursor.anchor()) and
293 self._in_buffer(cursor.anchor()) and
294 self._in_buffer(cursor.position()))
294 self._in_buffer(cursor.position()))
295
295
296 def can_paste(self):
296 def can_paste(self):
297 """ Returns whether text can be pasted from the clipboard.
297 """ Returns whether text can be pasted from the clipboard.
298 """
298 """
299 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
299 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
300 return not QtGui.QApplication.clipboard().text().isEmpty()
300 return not QtGui.QApplication.clipboard().text().isEmpty()
301 return False
301 return False
302
302
303 def clear(self, keep_input=True):
303 def clear(self, keep_input=True):
304 """ Clear the console.
304 """ Clear the console.
305
305
306 Parameters:
306 Parameters:
307 -----------
307 -----------
308 keep_input : bool, optional (default True)
308 keep_input : bool, optional (default True)
309 If set, restores the old input buffer if a new prompt is written.
309 If set, restores the old input buffer if a new prompt is written.
310 """
310 """
311 if self._executing:
311 if self._executing:
312 self._control.clear()
312 self._control.clear()
313 else:
313 else:
314 if keep_input:
314 if keep_input:
315 input_buffer = self.input_buffer
315 input_buffer = self.input_buffer
316 self._control.clear()
316 self._control.clear()
317 self._show_prompt()
317 self._show_prompt()
318 if keep_input:
318 if keep_input:
319 self.input_buffer = input_buffer
319 self.input_buffer = input_buffer
320
320
321 def copy(self):
321 def copy(self):
322 """ Copy the currently selected text to the clipboard.
322 """ Copy the currently selected text to the clipboard.
323 """
323 """
324 self._control.copy()
324 self._control.copy()
325
325
326 def cut(self):
326 def cut(self):
327 """ Copy the currently selected text to the clipboard and delete it
327 """ Copy the currently selected text to the clipboard and delete it
328 if it's inside the input buffer.
328 if it's inside the input buffer.
329 """
329 """
330 self.copy()
330 self.copy()
331 if self.can_cut():
331 if self.can_cut():
332 self._control.textCursor().removeSelectedText()
332 self._control.textCursor().removeSelectedText()
333
333
334 def execute(self, source=None, hidden=False, interactive=False):
334 def execute(self, source=None, hidden=False, interactive=False):
335 """ Executes source or the input buffer, possibly prompting for more
335 """ Executes source or the input buffer, possibly prompting for more
336 input.
336 input.
337
337
338 Parameters:
338 Parameters:
339 -----------
339 -----------
340 source : str, optional
340 source : str, optional
341
341
342 The source to execute. If not specified, the input buffer will be
342 The source to execute. If not specified, the input buffer will be
343 used. If specified and 'hidden' is False, the input buffer will be
343 used. If specified and 'hidden' is False, the input buffer will be
344 replaced with the source before execution.
344 replaced with the source before execution.
345
345
346 hidden : bool, optional (default False)
346 hidden : bool, optional (default False)
347
347
348 If set, no output will be shown and the prompt will not be modified.
348 If set, no output will be shown and the prompt will not be modified.
349 In other words, it will be completely invisible to the user that
349 In other words, it will be completely invisible to the user that
350 an execution has occurred.
350 an execution has occurred.
351
351
352 interactive : bool, optional (default False)
352 interactive : bool, optional (default False)
353
353
354 Whether the console is to treat the source as having been manually
354 Whether the console is to treat the source as having been manually
355 entered by the user. The effect of this parameter depends on the
355 entered by the user. The effect of this parameter depends on the
356 subclass implementation.
356 subclass implementation.
357
357
358 Raises:
358 Raises:
359 -------
359 -------
360 RuntimeError
360 RuntimeError
361 If incomplete input is given and 'hidden' is True. In this case,
361 If incomplete input is given and 'hidden' is True. In this case,
362 it is not possible to prompt for more input.
362 it is not possible to prompt for more input.
363
363
364 Returns:
364 Returns:
365 --------
365 --------
366 A boolean indicating whether the source was executed.
366 A boolean indicating whether the source was executed.
367 """
367 """
368 # WARNING: The order in which things happen here is very particular, in
368 # WARNING: The order in which things happen here is very particular, in
369 # large part because our syntax highlighting is fragile. If you change
369 # large part because our syntax highlighting is fragile. If you change
370 # something, test carefully!
370 # something, test carefully!
371
371
372 # Decide what to execute.
372 # Decide what to execute.
373 if source is None:
373 if source is None:
374 source = self.input_buffer
374 source = self.input_buffer
375 if not hidden:
375 if not hidden:
376 # A newline is appended later, but it should be considered part
376 # A newline is appended later, but it should be considered part
377 # of the input buffer.
377 # of the input buffer.
378 source += '\n'
378 source += '\n'
379 elif not hidden:
379 elif not hidden:
380 self.input_buffer = source
380 self.input_buffer = source
381
381
382 # Execute the source or show a continuation prompt if it is incomplete.
382 # Execute the source or show a continuation prompt if it is incomplete.
383 complete = self._is_complete(source, interactive)
383 complete = self._is_complete(source, interactive)
384 if hidden:
384 if hidden:
385 if complete:
385 if complete:
386 self._execute(source, hidden)
386 self._execute(source, hidden)
387 else:
387 else:
388 error = 'Incomplete noninteractive input: "%s"'
388 error = 'Incomplete noninteractive input: "%s"'
389 raise RuntimeError(error % source)
389 raise RuntimeError(error % source)
390 else:
390 else:
391 if complete:
391 if complete:
392 self._append_plain_text('\n')
392 self._append_plain_text('\n')
393 self._executing_input_buffer = self.input_buffer
393 self._executing_input_buffer = self.input_buffer
394 self._executing = True
394 self._executing = True
395 self._prompt_finished()
395 self._prompt_finished()
396
396
397 # The maximum block count is only in effect during execution.
397 # The maximum block count is only in effect during execution.
398 # This ensures that _prompt_pos does not become invalid due to
398 # This ensures that _prompt_pos does not become invalid due to
399 # text truncation.
399 # text truncation.
400 self._control.document().setMaximumBlockCount(self.buffer_size)
400 self._control.document().setMaximumBlockCount(self.buffer_size)
401
401
402 # Setting a positive maximum block count will automatically
402 # Setting a positive maximum block count will automatically
403 # disable the undo/redo history, but just to be safe:
403 # disable the undo/redo history, but just to be safe:
404 self._control.setUndoRedoEnabled(False)
404 self._control.setUndoRedoEnabled(False)
405
405
406 # Perform actual execution.
406 # Perform actual execution.
407 self._execute(source, hidden)
407 self._execute(source, hidden)
408
408
409 else:
409 else:
410 # Do this inside an edit block so continuation prompts are
410 # Do this inside an edit block so continuation prompts are
411 # removed seamlessly via undo/redo.
411 # removed seamlessly via undo/redo.
412 cursor = self._get_end_cursor()
412 cursor = self._get_end_cursor()
413 cursor.beginEditBlock()
413 cursor.beginEditBlock()
414 cursor.insertText('\n')
414 cursor.insertText('\n')
415 self._insert_continuation_prompt(cursor)
415 self._insert_continuation_prompt(cursor)
416 cursor.endEditBlock()
416 cursor.endEditBlock()
417
417
418 # Do not do this inside the edit block. It works as expected
418 # Do not do this inside the edit block. It works as expected
419 # when using a QPlainTextEdit control, but does not have an
419 # when using a QPlainTextEdit control, but does not have an
420 # effect when using a QTextEdit. I believe this is a Qt bug.
420 # effect when using a QTextEdit. I believe this is a Qt bug.
421 self._control.moveCursor(QtGui.QTextCursor.End)
421 self._control.moveCursor(QtGui.QTextCursor.End)
422
422
423 return complete
423 return complete
424
424
425 def _get_input_buffer(self):
425 def _get_input_buffer(self):
426 """ The text that the user has entered entered at the current prompt.
426 """ The text that the user has entered entered at the current prompt.
427 """
427 """
428 # If we're executing, the input buffer may not even exist anymore due to
428 # If we're executing, the input buffer may not even exist anymore due to
429 # the limit imposed by 'buffer_size'. Therefore, we store it.
429 # the limit imposed by 'buffer_size'. Therefore, we store it.
430 if self._executing:
430 if self._executing:
431 return self._executing_input_buffer
431 return self._executing_input_buffer
432
432
433 cursor = self._get_end_cursor()
433 cursor = self._get_end_cursor()
434 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
434 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
435 input_buffer = unicode(cursor.selection().toPlainText())
435 input_buffer = unicode(cursor.selection().toPlainText())
436
436
437 # Strip out continuation prompts.
437 # Strip out continuation prompts.
438 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
438 return input_buffer.replace('\n' + self._continuation_prompt, '\n')
439
439
440 def _set_input_buffer(self, string):
440 def _set_input_buffer(self, string):
441 """ Replaces the text in the input buffer with 'string'.
441 """ Replaces the text in the input buffer with 'string'.
442 """
442 """
443 # For now, it is an error to modify the input buffer during execution.
443 # For now, it is an error to modify the input buffer during execution.
444 if self._executing:
444 if self._executing:
445 raise RuntimeError("Cannot change input buffer during execution.")
445 raise RuntimeError("Cannot change input buffer during execution.")
446
446
447 # Remove old text.
447 # Remove old text.
448 cursor = self._get_end_cursor()
448 cursor = self._get_end_cursor()
449 cursor.beginEditBlock()
449 cursor.beginEditBlock()
450 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
450 cursor.setPosition(self._prompt_pos, QtGui.QTextCursor.KeepAnchor)
451 cursor.removeSelectedText()
451 cursor.removeSelectedText()
452
452
453 # Insert new text with continuation prompts.
453 # Insert new text with continuation prompts.
454 self._insert_plain_text_into_buffer(self._get_prompt_cursor(), string)
454 self._insert_plain_text_into_buffer(self._get_prompt_cursor(), string)
455 cursor.endEditBlock()
455 cursor.endEditBlock()
456 self._control.moveCursor(QtGui.QTextCursor.End)
456 self._control.moveCursor(QtGui.QTextCursor.End)
457
457
458 input_buffer = property(_get_input_buffer, _set_input_buffer)
458 input_buffer = property(_get_input_buffer, _set_input_buffer)
459
459
460 def _get_font(self):
460 def _get_font(self):
461 """ The base font being used by the ConsoleWidget.
461 """ The base font being used by the ConsoleWidget.
462 """
462 """
463 return self._control.document().defaultFont()
463 return self._control.document().defaultFont()
464
464
465 def _set_font(self, font):
465 def _set_font(self, font):
466 """ Sets the base font for the ConsoleWidget to the specified QFont.
466 """ Sets the base font for the ConsoleWidget to the specified QFont.
467 """
467 """
468 font_metrics = QtGui.QFontMetrics(font)
468 font_metrics = QtGui.QFontMetrics(font)
469 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
469 self._control.setTabStopWidth(self.tab_width * font_metrics.width(' '))
470
470
471 self._completion_widget.setFont(font)
471 self._completion_widget.setFont(font)
472 self._control.document().setDefaultFont(font)
472 self._control.document().setDefaultFont(font)
473 if self._page_control:
473 if self._page_control:
474 self._page_control.document().setDefaultFont(font)
474 self._page_control.document().setDefaultFont(font)
475
475
476 self.font_changed.emit(font)
476 self.font_changed.emit(font)
477
477
478 font = property(_get_font, _set_font)
478 font = property(_get_font, _set_font)
479
479
480 def paste(self, mode=QtGui.QClipboard.Clipboard):
480 def paste(self, mode=QtGui.QClipboard.Clipboard):
481 """ Paste the contents of the clipboard into the input region.
481 """ Paste the contents of the clipboard into the input region.
482
482
483 Parameters:
483 Parameters:
484 -----------
484 -----------
485 mode : QClipboard::Mode, optional [default QClipboard::Clipboard]
485 mode : QClipboard::Mode, optional [default QClipboard::Clipboard]
486
486
487 Controls which part of the system clipboard is used. This can be
487 Controls which part of the system clipboard is used. This can be
488 used to access the selection clipboard in X11 and the Find buffer
488 used to access the selection clipboard in X11 and the Find buffer
489 in Mac OS. By default, the regular clipboard is used.
489 in Mac OS. By default, the regular clipboard is used.
490 """
490 """
491 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
491 if self._control.textInteractionFlags() & QtCore.Qt.TextEditable:
492 # Make sure the paste is safe.
492 # Make sure the paste is safe.
493 self._keep_cursor_in_buffer()
493 self._keep_cursor_in_buffer()
494 cursor = self._control.textCursor()
494 cursor = self._control.textCursor()
495
495
496 # Remove any trailing newline, which confuses the GUI and forces the
496 # Remove any trailing newline, which confuses the GUI and forces the
497 # user to backspace.
497 # user to backspace.
498 text = unicode(QtGui.QApplication.clipboard().text(mode)).rstrip()
498 text = unicode(QtGui.QApplication.clipboard().text(mode)).rstrip()
499 self._insert_plain_text_into_buffer(cursor, dedent(text))
499 self._insert_plain_text_into_buffer(cursor, dedent(text))
500
500
501 def print_(self, printer):
501 def print_(self, printer):
502 """ Print the contents of the ConsoleWidget to the specified QPrinter.
502 """ Print the contents of the ConsoleWidget to the specified QPrinter.
503 """
503 """
504 self._control.print_(printer)
504 self._control.print_(printer)
505
505
506 def prompt_to_top(self):
506 def prompt_to_top(self):
507 """ Moves the prompt to the top of the viewport.
507 """ Moves the prompt to the top of the viewport.
508 """
508 """
509 if not self._executing:
509 if not self._executing:
510 prompt_cursor = self._get_prompt_cursor()
510 prompt_cursor = self._get_prompt_cursor()
511 if self._get_cursor().blockNumber() < prompt_cursor.blockNumber():
511 if self._get_cursor().blockNumber() < prompt_cursor.blockNumber():
512 self._set_cursor(prompt_cursor)
512 self._set_cursor(prompt_cursor)
513 self._set_top_cursor(prompt_cursor)
513 self._set_top_cursor(prompt_cursor)
514
514
515 def redo(self):
515 def redo(self):
516 """ Redo the last operation. If there is no operation to redo, nothing
516 """ Redo the last operation. If there is no operation to redo, nothing
517 happens.
517 happens.
518 """
518 """
519 self._control.redo()
519 self._control.redo()
520
520
521 def reset_font(self):
521 def reset_font(self):
522 """ Sets the font to the default fixed-width font for this platform.
522 """ Sets the font to the default fixed-width font for this platform.
523 """
523 """
524 if sys.platform == 'win32':
524 if sys.platform == 'win32':
525 # Consolas ships with Vista/Win7, fallback to Courier if needed
525 # Consolas ships with Vista/Win7, fallback to Courier if needed
526 family, fallback = 'Consolas', 'Courier'
526 family, fallback = 'Consolas', 'Courier'
527 elif sys.platform == 'darwin':
527 elif sys.platform == 'darwin':
528 # OSX always has Monaco, no need for a fallback
528 # OSX always has Monaco, no need for a fallback
529 family, fallback = 'Monaco', None
529 family, fallback = 'Monaco', None
530 else:
530 else:
531 # FIXME: remove Consolas as a default on Linux once our font
531 # FIXME: remove Consolas as a default on Linux once our font
532 # selections are configurable by the user.
532 # selections are configurable by the user.
533 family, fallback = 'Consolas', 'Monospace'
533 family, fallback = 'Consolas', 'Monospace'
534 font = get_font(family, fallback)
534 font = get_font(family, fallback)
535 font.setPointSize(QtGui.qApp.font().pointSize())
535 font.setPointSize(QtGui.qApp.font().pointSize())
536 font.setStyleHint(QtGui.QFont.TypeWriter)
536 font.setStyleHint(QtGui.QFont.TypeWriter)
537 self._set_font(font)
537 self._set_font(font)
538
538
539 def change_font_size(self, delta):
540 """Change the font size by the specified amount (in points).
541 """
542 font = self.font
543 font.setPointSize(font.pointSize() + delta)
544 self._set_font(font)
545
539 def select_all(self):
546 def select_all(self):
540 """ Selects all the text in the buffer.
547 """ Selects all the text in the buffer.
541 """
548 """
542 self._control.selectAll()
549 self._control.selectAll()
543
550
544 def _get_tab_width(self):
551 def _get_tab_width(self):
545 """ The width (in terms of space characters) for tab characters.
552 """ The width (in terms of space characters) for tab characters.
546 """
553 """
547 return self._tab_width
554 return self._tab_width
548
555
549 def _set_tab_width(self, tab_width):
556 def _set_tab_width(self, tab_width):
550 """ Sets the width (in terms of space characters) for tab characters.
557 """ Sets the width (in terms of space characters) for tab characters.
551 """
558 """
552 font_metrics = QtGui.QFontMetrics(self.font)
559 font_metrics = QtGui.QFontMetrics(self.font)
553 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
560 self._control.setTabStopWidth(tab_width * font_metrics.width(' '))
554
561
555 self._tab_width = tab_width
562 self._tab_width = tab_width
556
563
557 tab_width = property(_get_tab_width, _set_tab_width)
564 tab_width = property(_get_tab_width, _set_tab_width)
558
565
559 def undo(self):
566 def undo(self):
560 """ Undo the last operation. If there is no operation to undo, nothing
567 """ Undo the last operation. If there is no operation to undo, nothing
561 happens.
568 happens.
562 """
569 """
563 self._control.undo()
570 self._control.undo()
564
571
565 #---------------------------------------------------------------------------
572 #---------------------------------------------------------------------------
566 # 'ConsoleWidget' abstract interface
573 # 'ConsoleWidget' abstract interface
567 #---------------------------------------------------------------------------
574 #---------------------------------------------------------------------------
568
575
569 def _is_complete(self, source, interactive):
576 def _is_complete(self, source, interactive):
570 """ Returns whether 'source' can be executed. When triggered by an
577 """ Returns whether 'source' can be executed. When triggered by an
571 Enter/Return key press, 'interactive' is True; otherwise, it is
578 Enter/Return key press, 'interactive' is True; otherwise, it is
572 False.
579 False.
573 """
580 """
574 raise NotImplementedError
581 raise NotImplementedError
575
582
576 def _execute(self, source, hidden):
583 def _execute(self, source, hidden):
577 """ Execute 'source'. If 'hidden', do not show any output.
584 """ Execute 'source'. If 'hidden', do not show any output.
578 """
585 """
579 raise NotImplementedError
586 raise NotImplementedError
580
587
581 def _prompt_started_hook(self):
588 def _prompt_started_hook(self):
582 """ Called immediately after a new prompt is displayed.
589 """ Called immediately after a new prompt is displayed.
583 """
590 """
584 pass
591 pass
585
592
586 def _prompt_finished_hook(self):
593 def _prompt_finished_hook(self):
587 """ Called immediately after a prompt is finished, i.e. when some input
594 """ Called immediately after a prompt is finished, i.e. when some input
588 will be processed and a new prompt displayed.
595 will be processed and a new prompt displayed.
589 """
596 """
590 pass
597 pass
591
598
592 def _up_pressed(self):
599 def _up_pressed(self):
593 """ Called when the up key is pressed. Returns whether to continue
600 """ Called when the up key is pressed. Returns whether to continue
594 processing the event.
601 processing the event.
595 """
602 """
596 return True
603 return True
597
604
598 def _down_pressed(self):
605 def _down_pressed(self):
599 """ Called when the down key is pressed. Returns whether to continue
606 """ Called when the down key is pressed. Returns whether to continue
600 processing the event.
607 processing the event.
601 """
608 """
602 return True
609 return True
603
610
604 def _tab_pressed(self):
611 def _tab_pressed(self):
605 """ Called when the tab key is pressed. Returns whether to continue
612 """ Called when the tab key is pressed. Returns whether to continue
606 processing the event.
613 processing the event.
607 """
614 """
608 return False
615 return False
609
616
610 #--------------------------------------------------------------------------
617 #--------------------------------------------------------------------------
611 # 'ConsoleWidget' protected interface
618 # 'ConsoleWidget' protected interface
612 #--------------------------------------------------------------------------
619 #--------------------------------------------------------------------------
613
620
614 def _append_html(self, html):
621 def _append_html(self, html):
615 """ Appends html at the end of the console buffer.
622 """ Appends html at the end of the console buffer.
616 """
623 """
617 cursor = self._get_end_cursor()
624 cursor = self._get_end_cursor()
618 self._insert_html(cursor, html)
625 self._insert_html(cursor, html)
619
626
620 def _append_html_fetching_plain_text(self, html):
627 def _append_html_fetching_plain_text(self, html):
621 """ Appends 'html', then returns the plain text version of it.
628 """ Appends 'html', then returns the plain text version of it.
622 """
629 """
623 cursor = self._get_end_cursor()
630 cursor = self._get_end_cursor()
624 return self._insert_html_fetching_plain_text(cursor, html)
631 return self._insert_html_fetching_plain_text(cursor, html)
625
632
626 def _append_plain_text(self, text):
633 def _append_plain_text(self, text):
627 """ Appends plain text at the end of the console buffer, processing
634 """ Appends plain text at the end of the console buffer, processing
628 ANSI codes if enabled.
635 ANSI codes if enabled.
629 """
636 """
630 cursor = self._get_end_cursor()
637 cursor = self._get_end_cursor()
631 self._insert_plain_text(cursor, text)
638 self._insert_plain_text(cursor, text)
632
639
633 def _append_plain_text_keeping_prompt(self, text):
640 def _append_plain_text_keeping_prompt(self, text):
634 """ Writes 'text' after the current prompt, then restores the old prompt
641 """ Writes 'text' after the current prompt, then restores the old prompt
635 with its old input buffer.
642 with its old input buffer.
636 """
643 """
637 input_buffer = self.input_buffer
644 input_buffer = self.input_buffer
638 self._append_plain_text('\n')
645 self._append_plain_text('\n')
639 self._prompt_finished()
646 self._prompt_finished()
640
647
641 self._append_plain_text(text)
648 self._append_plain_text(text)
642 self._show_prompt()
649 self._show_prompt()
643 self.input_buffer = input_buffer
650 self.input_buffer = input_buffer
644
651
645 def _cancel_text_completion(self):
652 def _cancel_text_completion(self):
646 """ If text completion is progress, cancel it.
653 """ If text completion is progress, cancel it.
647 """
654 """
648 if self._text_completing_pos:
655 if self._text_completing_pos:
649 self._clear_temporary_buffer()
656 self._clear_temporary_buffer()
650 self._text_completing_pos = 0
657 self._text_completing_pos = 0
651
658
652 def _clear_temporary_buffer(self):
659 def _clear_temporary_buffer(self):
653 """ Clears the "temporary text" buffer, i.e. all the text following
660 """ Clears the "temporary text" buffer, i.e. all the text following
654 the prompt region.
661 the prompt region.
655 """
662 """
656 # Select and remove all text below the input buffer.
663 # Select and remove all text below the input buffer.
657 cursor = self._get_prompt_cursor()
664 cursor = self._get_prompt_cursor()
658 prompt = self._continuation_prompt.lstrip()
665 prompt = self._continuation_prompt.lstrip()
659 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
666 while cursor.movePosition(QtGui.QTextCursor.NextBlock):
660 temp_cursor = QtGui.QTextCursor(cursor)
667 temp_cursor = QtGui.QTextCursor(cursor)
661 temp_cursor.select(QtGui.QTextCursor.BlockUnderCursor)
668 temp_cursor.select(QtGui.QTextCursor.BlockUnderCursor)
662 text = unicode(temp_cursor.selection().toPlainText()).lstrip()
669 text = unicode(temp_cursor.selection().toPlainText()).lstrip()
663 if not text.startswith(prompt):
670 if not text.startswith(prompt):
664 break
671 break
665 else:
672 else:
666 # We've reached the end of the input buffer and no text follows.
673 # We've reached the end of the input buffer and no text follows.
667 return
674 return
668 cursor.movePosition(QtGui.QTextCursor.Left) # Grab the newline.
675 cursor.movePosition(QtGui.QTextCursor.Left) # Grab the newline.
669 cursor.movePosition(QtGui.QTextCursor.End,
676 cursor.movePosition(QtGui.QTextCursor.End,
670 QtGui.QTextCursor.KeepAnchor)
677 QtGui.QTextCursor.KeepAnchor)
671 cursor.removeSelectedText()
678 cursor.removeSelectedText()
672
679
673 # After doing this, we have no choice but to clear the undo/redo
680 # After doing this, we have no choice but to clear the undo/redo
674 # history. Otherwise, the text is not "temporary" at all, because it
681 # history. Otherwise, the text is not "temporary" at all, because it
675 # can be recalled with undo/redo. Unfortunately, Qt does not expose
682 # can be recalled with undo/redo. Unfortunately, Qt does not expose
676 # fine-grained control to the undo/redo system.
683 # fine-grained control to the undo/redo system.
677 if self._control.isUndoRedoEnabled():
684 if self._control.isUndoRedoEnabled():
678 self._control.setUndoRedoEnabled(False)
685 self._control.setUndoRedoEnabled(False)
679 self._control.setUndoRedoEnabled(True)
686 self._control.setUndoRedoEnabled(True)
680
687
681 def _complete_with_items(self, cursor, items):
688 def _complete_with_items(self, cursor, items):
682 """ Performs completion with 'items' at the specified cursor location.
689 """ Performs completion with 'items' at the specified cursor location.
683 """
690 """
684 self._cancel_text_completion()
691 self._cancel_text_completion()
685
692
686 if len(items) == 1:
693 if len(items) == 1:
687 cursor.setPosition(self._control.textCursor().position(),
694 cursor.setPosition(self._control.textCursor().position(),
688 QtGui.QTextCursor.KeepAnchor)
695 QtGui.QTextCursor.KeepAnchor)
689 cursor.insertText(items[0])
696 cursor.insertText(items[0])
690
697
691 elif len(items) > 1:
698 elif len(items) > 1:
692 current_pos = self._control.textCursor().position()
699 current_pos = self._control.textCursor().position()
693 prefix = commonprefix(items)
700 prefix = commonprefix(items)
694 if prefix:
701 if prefix:
695 cursor.setPosition(current_pos, QtGui.QTextCursor.KeepAnchor)
702 cursor.setPosition(current_pos, QtGui.QTextCursor.KeepAnchor)
696 cursor.insertText(prefix)
703 cursor.insertText(prefix)
697 current_pos = cursor.position()
704 current_pos = cursor.position()
698
705
699 if self.gui_completion:
706 if self.gui_completion:
700 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
707 cursor.movePosition(QtGui.QTextCursor.Left, n=len(prefix))
701 self._completion_widget.show_items(cursor, items)
708 self._completion_widget.show_items(cursor, items)
702 else:
709 else:
703 cursor.beginEditBlock()
710 cursor.beginEditBlock()
704 self._append_plain_text('\n')
711 self._append_plain_text('\n')
705 self._page(self._format_as_columns(items))
712 self._page(self._format_as_columns(items))
706 cursor.endEditBlock()
713 cursor.endEditBlock()
707
714
708 cursor.setPosition(current_pos)
715 cursor.setPosition(current_pos)
709 self._control.moveCursor(QtGui.QTextCursor.End)
716 self._control.moveCursor(QtGui.QTextCursor.End)
710 self._control.setTextCursor(cursor)
717 self._control.setTextCursor(cursor)
711 self._text_completing_pos = current_pos
718 self._text_completing_pos = current_pos
712
719
713 def _context_menu_make(self, pos):
720 def _context_menu_make(self, pos):
714 """ Creates a context menu for the given QPoint (in widget coordinates).
721 """ Creates a context menu for the given QPoint (in widget coordinates).
715 """
722 """
716 menu = QtGui.QMenu()
723 menu = QtGui.QMenu()
717
724
718 cut_action = menu.addAction('Cut', self.cut)
725 cut_action = menu.addAction('Cut', self.cut)
719 cut_action.setEnabled(self.can_cut())
726 cut_action.setEnabled(self.can_cut())
720 cut_action.setShortcut(QtGui.QKeySequence.Cut)
727 cut_action.setShortcut(QtGui.QKeySequence.Cut)
721
728
722 copy_action = menu.addAction('Copy', self.copy)
729 copy_action = menu.addAction('Copy', self.copy)
723 copy_action.setEnabled(self.can_copy())
730 copy_action.setEnabled(self.can_copy())
724 copy_action.setShortcut(QtGui.QKeySequence.Copy)
731 copy_action.setShortcut(QtGui.QKeySequence.Copy)
725
732
726 paste_action = menu.addAction('Paste', self.paste)
733 paste_action = menu.addAction('Paste', self.paste)
727 paste_action.setEnabled(self.can_paste())
734 paste_action.setEnabled(self.can_paste())
728 paste_action.setShortcut(QtGui.QKeySequence.Paste)
735 paste_action.setShortcut(QtGui.QKeySequence.Paste)
729
736
730 menu.addSeparator()
737 menu.addSeparator()
731 menu.addAction('Select All', self.select_all)
738 menu.addAction('Select All', self.select_all)
732
739
733 return menu
740 return menu
734
741
735 def _control_key_down(self, modifiers, include_command=True):
742 def _control_key_down(self, modifiers, include_command=True):
736 """ Given a KeyboardModifiers flags object, return whether the Control
743 """ Given a KeyboardModifiers flags object, return whether the Control
737 key is down.
744 key is down.
738
745
739 Parameters:
746 Parameters:
740 -----------
747 -----------
741 include_command : bool, optional (default True)
748 include_command : bool, optional (default True)
742 Whether to treat the Command key as a (mutually exclusive) synonym
749 Whether to treat the Command key as a (mutually exclusive) synonym
743 for Control when in Mac OS.
750 for Control when in Mac OS.
744 """
751 """
745 # Note that on Mac OS, ControlModifier corresponds to the Command key
752 # Note that on Mac OS, ControlModifier corresponds to the Command key
746 # while MetaModifier corresponds to the Control key.
753 # while MetaModifier corresponds to the Control key.
747 if sys.platform == 'darwin':
754 if sys.platform == 'darwin':
748 down = include_command and (modifiers & QtCore.Qt.ControlModifier)
755 down = include_command and (modifiers & QtCore.Qt.ControlModifier)
749 return bool(down) ^ bool(modifiers & QtCore.Qt.MetaModifier)
756 return bool(down) ^ bool(modifiers & QtCore.Qt.MetaModifier)
750 else:
757 else:
751 return bool(modifiers & QtCore.Qt.ControlModifier)
758 return bool(modifiers & QtCore.Qt.ControlModifier)
752
759
753 def _create_control(self):
760 def _create_control(self):
754 """ Creates and connects the underlying text widget.
761 """ Creates and connects the underlying text widget.
755 """
762 """
756 # Create the underlying control.
763 # Create the underlying control.
757 if self.kind == 'plain':
764 if self.kind == 'plain':
758 control = QtGui.QPlainTextEdit()
765 control = QtGui.QPlainTextEdit()
759 elif self.kind == 'rich':
766 elif self.kind == 'rich':
760 control = QtGui.QTextEdit()
767 control = QtGui.QTextEdit()
761 control.setAcceptRichText(False)
768 control.setAcceptRichText(False)
762
769
763 # Install event filters. The filter on the viewport is needed for
770 # Install event filters. The filter on the viewport is needed for
764 # mouse events and drag events.
771 # mouse events and drag events.
765 control.installEventFilter(self)
772 control.installEventFilter(self)
766 control.viewport().installEventFilter(self)
773 control.viewport().installEventFilter(self)
767
774
768 # Connect signals.
775 # Connect signals.
769 control.cursorPositionChanged.connect(self._cursor_position_changed)
776 control.cursorPositionChanged.connect(self._cursor_position_changed)
770 control.customContextMenuRequested.connect(
777 control.customContextMenuRequested.connect(
771 self._custom_context_menu_requested)
778 self._custom_context_menu_requested)
772 control.copyAvailable.connect(self.copy_available)
779 control.copyAvailable.connect(self.copy_available)
773 control.redoAvailable.connect(self.redo_available)
780 control.redoAvailable.connect(self.redo_available)
774 control.undoAvailable.connect(self.undo_available)
781 control.undoAvailable.connect(self.undo_available)
775
782
776 # Hijack the document size change signal to prevent Qt from adjusting
783 # Hijack the document size change signal to prevent Qt from adjusting
777 # the viewport's scrollbar. We are relying on an implementation detail
784 # the viewport's scrollbar. We are relying on an implementation detail
778 # of Q(Plain)TextEdit here, which is potentially dangerous, but without
785 # of Q(Plain)TextEdit here, which is potentially dangerous, but without
779 # this functionality we cannot create a nice terminal interface.
786 # this functionality we cannot create a nice terminal interface.
780 layout = control.document().documentLayout()
787 layout = control.document().documentLayout()
781 layout.documentSizeChanged.disconnect()
788 layout.documentSizeChanged.disconnect()
782 layout.documentSizeChanged.connect(self._adjust_scrollbars)
789 layout.documentSizeChanged.connect(self._adjust_scrollbars)
783
790
784 # Configure the control.
791 # Configure the control.
785 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
792 control.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
786 control.setReadOnly(True)
793 control.setReadOnly(True)
787 control.setUndoRedoEnabled(False)
794 control.setUndoRedoEnabled(False)
788 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
795 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
789 return control
796 return control
790
797
791 def _create_page_control(self):
798 def _create_page_control(self):
792 """ Creates and connects the underlying paging widget.
799 """ Creates and connects the underlying paging widget.
793 """
800 """
794 if self.kind == 'plain':
801 if self.kind == 'plain':
795 control = QtGui.QPlainTextEdit()
802 control = QtGui.QPlainTextEdit()
796 elif self.kind == 'rich':
803 elif self.kind == 'rich':
797 control = QtGui.QTextEdit()
804 control = QtGui.QTextEdit()
798 control.installEventFilter(self)
805 control.installEventFilter(self)
799 control.setReadOnly(True)
806 control.setReadOnly(True)
800 control.setUndoRedoEnabled(False)
807 control.setUndoRedoEnabled(False)
801 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
808 control.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
802 return control
809 return control
803
810
804 def _event_filter_console_keypress(self, event):
811 def _event_filter_console_keypress(self, event):
805 """ Filter key events for the underlying text widget to create a
812 """ Filter key events for the underlying text widget to create a
806 console-like interface.
813 console-like interface.
807 """
814 """
808 intercepted = False
815 intercepted = False
809 cursor = self._control.textCursor()
816 cursor = self._control.textCursor()
810 position = cursor.position()
817 position = cursor.position()
811 key = event.key()
818 key = event.key()
812 ctrl_down = self._control_key_down(event.modifiers())
819 ctrl_down = self._control_key_down(event.modifiers())
813 alt_down = event.modifiers() & QtCore.Qt.AltModifier
820 alt_down = event.modifiers() & QtCore.Qt.AltModifier
814 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
821 shift_down = event.modifiers() & QtCore.Qt.ShiftModifier
815
822
816 #------ Special sequences ----------------------------------------------
823 #------ Special sequences ----------------------------------------------
817
824
818 if event.matches(QtGui.QKeySequence.Copy):
825 if event.matches(QtGui.QKeySequence.Copy):
819 self.copy()
826 self.copy()
820 intercepted = True
827 intercepted = True
821
828
822 elif event.matches(QtGui.QKeySequence.Cut):
829 elif event.matches(QtGui.QKeySequence.Cut):
823 self.cut()
830 self.cut()
824 intercepted = True
831 intercepted = True
825
832
826 elif event.matches(QtGui.QKeySequence.Paste):
833 elif event.matches(QtGui.QKeySequence.Paste):
827 self.paste()
834 self.paste()
828 intercepted = True
835 intercepted = True
829
836
830 #------ Special modifier logic -----------------------------------------
837 #------ Special modifier logic -----------------------------------------
831
838
832 elif key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
839 elif key in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
833 intercepted = True
840 intercepted = True
834
841
835 # Special handling when tab completing in text mode.
842 # Special handling when tab completing in text mode.
836 self._cancel_text_completion()
843 self._cancel_text_completion()
837
844
838 if self._in_buffer(position):
845 if self._in_buffer(position):
839 if self._reading:
846 if self._reading:
840 self._append_plain_text('\n')
847 self._append_plain_text('\n')
841 self._reading = False
848 self._reading = False
842 if self._reading_callback:
849 if self._reading_callback:
843 self._reading_callback()
850 self._reading_callback()
844
851
845 # If the input buffer is a single line or there is only
852 # If the input buffer is a single line or there is only
846 # whitespace after the cursor, execute. Otherwise, split the
853 # whitespace after the cursor, execute. Otherwise, split the
847 # line with a continuation prompt.
854 # line with a continuation prompt.
848 elif not self._executing:
855 elif not self._executing:
849 cursor.movePosition(QtGui.QTextCursor.End,
856 cursor.movePosition(QtGui.QTextCursor.End,
850 QtGui.QTextCursor.KeepAnchor)
857 QtGui.QTextCursor.KeepAnchor)
851 at_end = cursor.selectedText().trimmed().isEmpty()
858 at_end = cursor.selectedText().trimmed().isEmpty()
852 single_line = (self._get_end_cursor().blockNumber() ==
859 single_line = (self._get_end_cursor().blockNumber() ==
853 self._get_prompt_cursor().blockNumber())
860 self._get_prompt_cursor().blockNumber())
854 if (at_end or shift_down or single_line) and not ctrl_down:
861 if (at_end or shift_down or single_line) and not ctrl_down:
855 self.execute(interactive = not shift_down)
862 self.execute(interactive = not shift_down)
856 else:
863 else:
857 # Do this inside an edit block for clean undo/redo.
864 # Do this inside an edit block for clean undo/redo.
858 cursor.beginEditBlock()
865 cursor.beginEditBlock()
859 cursor.setPosition(position)
866 cursor.setPosition(position)
860 cursor.insertText('\n')
867 cursor.insertText('\n')
861 self._insert_continuation_prompt(cursor)
868 self._insert_continuation_prompt(cursor)
862 cursor.endEditBlock()
869 cursor.endEditBlock()
863
870
864 # Ensure that the whole input buffer is visible.
871 # Ensure that the whole input buffer is visible.
865 # FIXME: This will not be usable if the input buffer is
872 # FIXME: This will not be usable if the input buffer is
866 # taller than the console widget.
873 # taller than the console widget.
867 self._control.moveCursor(QtGui.QTextCursor.End)
874 self._control.moveCursor(QtGui.QTextCursor.End)
868 self._control.setTextCursor(cursor)
875 self._control.setTextCursor(cursor)
869
876
870 #------ Control/Cmd modifier -------------------------------------------
877 #------ Control/Cmd modifier -------------------------------------------
871
878
872 elif ctrl_down:
879 elif ctrl_down:
873 if key == QtCore.Qt.Key_G:
880 if key == QtCore.Qt.Key_G:
874 self._keyboard_quit()
881 self._keyboard_quit()
875 intercepted = True
882 intercepted = True
876
883
877 elif key == QtCore.Qt.Key_K:
884 elif key == QtCore.Qt.Key_K:
878 if self._in_buffer(position):
885 if self._in_buffer(position):
879 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
886 cursor.movePosition(QtGui.QTextCursor.EndOfLine,
880 QtGui.QTextCursor.KeepAnchor)
887 QtGui.QTextCursor.KeepAnchor)
881 if not cursor.hasSelection():
888 if not cursor.hasSelection():
882 # Line deletion (remove continuation prompt)
889 # Line deletion (remove continuation prompt)
883 cursor.movePosition(QtGui.QTextCursor.NextBlock,
890 cursor.movePosition(QtGui.QTextCursor.NextBlock,
884 QtGui.QTextCursor.KeepAnchor)
891 QtGui.QTextCursor.KeepAnchor)
885 cursor.movePosition(QtGui.QTextCursor.Right,
892 cursor.movePosition(QtGui.QTextCursor.Right,
886 QtGui.QTextCursor.KeepAnchor,
893 QtGui.QTextCursor.KeepAnchor,
887 len(self._continuation_prompt))
894 len(self._continuation_prompt))
888 cursor.removeSelectedText()
895 cursor.removeSelectedText()
889 intercepted = True
896 intercepted = True
890
897
891 elif key == QtCore.Qt.Key_L:
898 elif key == QtCore.Qt.Key_L:
892 self.prompt_to_top()
899 self.prompt_to_top()
893 intercepted = True
900 intercepted = True
894
901
895 elif key == QtCore.Qt.Key_O:
902 elif key == QtCore.Qt.Key_O:
896 if self._page_control and self._page_control.isVisible():
903 if self._page_control and self._page_control.isVisible():
897 self._page_control.setFocus()
904 self._page_control.setFocus()
898 intercept = True
905 intercepted = True
899
906
900 elif key == QtCore.Qt.Key_Y:
907 elif key == QtCore.Qt.Key_Y:
901 self.paste()
908 self.paste()
902 intercepted = True
909 intercepted = True
903
910
904 elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
911 elif key in (QtCore.Qt.Key_Backspace, QtCore.Qt.Key_Delete):
905 intercepted = True
912 intercepted = True
906
913
914 elif key == QtCore.Qt.Key_Plus:
915 self.change_font_size(1)
916 intercepted = True
917
918 elif key == QtCore.Qt.Key_Minus:
919 self.change_font_size(-1)
920 intercepted = True
921
907 #------ Alt modifier ---------------------------------------------------
922 #------ Alt modifier ---------------------------------------------------
908
923
909 elif alt_down:
924 elif alt_down:
910 if key == QtCore.Qt.Key_B:
925 if key == QtCore.Qt.Key_B:
911 self._set_cursor(self._get_word_start_cursor(position))
926 self._set_cursor(self._get_word_start_cursor(position))
912 intercepted = True
927 intercepted = True
913
928
914 elif key == QtCore.Qt.Key_F:
929 elif key == QtCore.Qt.Key_F:
915 self._set_cursor(self._get_word_end_cursor(position))
930 self._set_cursor(self._get_word_end_cursor(position))
916 intercepted = True
931 intercepted = True
917
932
918 elif key == QtCore.Qt.Key_Backspace:
933 elif key == QtCore.Qt.Key_Backspace:
919 cursor = self._get_word_start_cursor(position)
934 cursor = self._get_word_start_cursor(position)
920 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
935 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
921 cursor.removeSelectedText()
936 cursor.removeSelectedText()
922 intercepted = True
937 intercepted = True
923
938
924 elif key == QtCore.Qt.Key_D:
939 elif key == QtCore.Qt.Key_D:
925 cursor = self._get_word_end_cursor(position)
940 cursor = self._get_word_end_cursor(position)
926 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
941 cursor.setPosition(position, QtGui.QTextCursor.KeepAnchor)
927 cursor.removeSelectedText()
942 cursor.removeSelectedText()
928 intercepted = True
943 intercepted = True
929
944
930 elif key == QtCore.Qt.Key_Delete:
945 elif key == QtCore.Qt.Key_Delete:
931 intercepted = True
946 intercepted = True
932
947
933 elif key == QtCore.Qt.Key_Greater:
948 elif key == QtCore.Qt.Key_Greater:
934 self._control.moveCursor(QtGui.QTextCursor.End)
949 self._control.moveCursor(QtGui.QTextCursor.End)
935 intercepted = True
950 intercepted = True
936
951
937 elif key == QtCore.Qt.Key_Less:
952 elif key == QtCore.Qt.Key_Less:
938 self._control.setTextCursor(self._get_prompt_cursor())
953 self._control.setTextCursor(self._get_prompt_cursor())
939 intercepted = True
954 intercepted = True
940
955
941 #------ No modifiers ---------------------------------------------------
956 #------ No modifiers ---------------------------------------------------
942
957
943 else:
958 else:
944 if key == QtCore.Qt.Key_Escape:
959 if key == QtCore.Qt.Key_Escape:
945 self._keyboard_quit()
960 self._keyboard_quit()
946 intercepted = True
961 intercepted = True
947
962
948 elif key == QtCore.Qt.Key_Up:
963 elif key == QtCore.Qt.Key_Up:
949 if self._reading or not self._up_pressed():
964 if self._reading or not self._up_pressed():
950 intercepted = True
965 intercepted = True
951 else:
966 else:
952 prompt_line = self._get_prompt_cursor().blockNumber()
967 prompt_line = self._get_prompt_cursor().blockNumber()
953 intercepted = cursor.blockNumber() <= prompt_line
968 intercepted = cursor.blockNumber() <= prompt_line
954
969
955 elif key == QtCore.Qt.Key_Down:
970 elif key == QtCore.Qt.Key_Down:
956 if self._reading or not self._down_pressed():
971 if self._reading or not self._down_pressed():
957 intercepted = True
972 intercepted = True
958 else:
973 else:
959 end_line = self._get_end_cursor().blockNumber()
974 end_line = self._get_end_cursor().blockNumber()
960 intercepted = cursor.blockNumber() == end_line
975 intercepted = cursor.blockNumber() == end_line
961
976
962 elif key == QtCore.Qt.Key_Tab:
977 elif key == QtCore.Qt.Key_Tab:
963 if not self._reading:
978 if not self._reading:
964 intercepted = not self._tab_pressed()
979 intercepted = not self._tab_pressed()
965
980
966 elif key == QtCore.Qt.Key_Left:
981 elif key == QtCore.Qt.Key_Left:
967
982
968 # Move to the previous line
983 # Move to the previous line
969 line, col = cursor.blockNumber(), cursor.columnNumber()
984 line, col = cursor.blockNumber(), cursor.columnNumber()
970 if line > self._get_prompt_cursor().blockNumber() and \
985 if line > self._get_prompt_cursor().blockNumber() and \
971 col == len(self._continuation_prompt):
986 col == len(self._continuation_prompt):
972 self._control.moveCursor(QtGui.QTextCursor.PreviousBlock)
987 self._control.moveCursor(QtGui.QTextCursor.PreviousBlock)
973 self._control.moveCursor(QtGui.QTextCursor.EndOfBlock)
988 self._control.moveCursor(QtGui.QTextCursor.EndOfBlock)
974 intercepted = True
989 intercepted = True
975
990
976 # Regular left movement
991 # Regular left movement
977 else:
992 else:
978 intercepted = not self._in_buffer(position - 1)
993 intercepted = not self._in_buffer(position - 1)
979
994
980 elif key == QtCore.Qt.Key_Right:
995 elif key == QtCore.Qt.Key_Right:
981 original_block_number = cursor.blockNumber()
996 original_block_number = cursor.blockNumber()
982 cursor.movePosition(QtGui.QTextCursor.Right)
997 cursor.movePosition(QtGui.QTextCursor.Right)
983 if cursor.blockNumber() != original_block_number:
998 if cursor.blockNumber() != original_block_number:
984 cursor.movePosition(QtGui.QTextCursor.Right,
999 cursor.movePosition(QtGui.QTextCursor.Right,
985 n=len(self._continuation_prompt))
1000 n=len(self._continuation_prompt))
986 self._set_cursor(cursor)
1001 self._set_cursor(cursor)
987 intercepted = True
1002 intercepted = True
988
1003
989 elif key == QtCore.Qt.Key_Home:
1004 elif key == QtCore.Qt.Key_Home:
990 start_line = cursor.blockNumber()
1005 start_line = cursor.blockNumber()
991 if start_line == self._get_prompt_cursor().blockNumber():
1006 if start_line == self._get_prompt_cursor().blockNumber():
992 start_pos = self._prompt_pos
1007 start_pos = self._prompt_pos
993 else:
1008 else:
994 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1009 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
995 QtGui.QTextCursor.KeepAnchor)
1010 QtGui.QTextCursor.KeepAnchor)
996 start_pos = cursor.position()
1011 start_pos = cursor.position()
997 start_pos += len(self._continuation_prompt)
1012 start_pos += len(self._continuation_prompt)
998 cursor.setPosition(position)
1013 cursor.setPosition(position)
999 if shift_down and self._in_buffer(position):
1014 if shift_down and self._in_buffer(position):
1000 cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor)
1015 cursor.setPosition(start_pos, QtGui.QTextCursor.KeepAnchor)
1001 else:
1016 else:
1002 cursor.setPosition(start_pos)
1017 cursor.setPosition(start_pos)
1003 self._set_cursor(cursor)
1018 self._set_cursor(cursor)
1004 intercepted = True
1019 intercepted = True
1005
1020
1006 elif key == QtCore.Qt.Key_Backspace:
1021 elif key == QtCore.Qt.Key_Backspace:
1007
1022
1008 # Line deletion (remove continuation prompt)
1023 # Line deletion (remove continuation prompt)
1009 line, col = cursor.blockNumber(), cursor.columnNumber()
1024 line, col = cursor.blockNumber(), cursor.columnNumber()
1010 if not self._reading and \
1025 if not self._reading and \
1011 col == len(self._continuation_prompt) and \
1026 col == len(self._continuation_prompt) and \
1012 line > self._get_prompt_cursor().blockNumber():
1027 line > self._get_prompt_cursor().blockNumber():
1013 cursor.beginEditBlock()
1028 cursor.beginEditBlock()
1014 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1029 cursor.movePosition(QtGui.QTextCursor.StartOfBlock,
1015 QtGui.QTextCursor.KeepAnchor)
1030 QtGui.QTextCursor.KeepAnchor)
1016 cursor.removeSelectedText()
1031 cursor.removeSelectedText()
1017 cursor.deletePreviousChar()
1032 cursor.deletePreviousChar()
1018 cursor.endEditBlock()
1033 cursor.endEditBlock()
1019 intercepted = True
1034 intercepted = True
1020
1035
1021 # Regular backwards deletion
1036 # Regular backwards deletion
1022 else:
1037 else:
1023 anchor = cursor.anchor()
1038 anchor = cursor.anchor()
1024 if anchor == position:
1039 if anchor == position:
1025 intercepted = not self._in_buffer(position - 1)
1040 intercepted = not self._in_buffer(position - 1)
1026 else:
1041 else:
1027 intercepted = not self._in_buffer(min(anchor, position))
1042 intercepted = not self._in_buffer(min(anchor, position))
1028
1043
1029 elif key == QtCore.Qt.Key_Delete:
1044 elif key == QtCore.Qt.Key_Delete:
1030
1045
1031 # Line deletion (remove continuation prompt)
1046 # Line deletion (remove continuation prompt)
1032 if not self._reading and self._in_buffer(position) and \
1047 if not self._reading and self._in_buffer(position) and \
1033 cursor.atBlockEnd() and not cursor.hasSelection():
1048 cursor.atBlockEnd() and not cursor.hasSelection():
1034 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1049 cursor.movePosition(QtGui.QTextCursor.NextBlock,
1035 QtGui.QTextCursor.KeepAnchor)
1050 QtGui.QTextCursor.KeepAnchor)
1036 cursor.movePosition(QtGui.QTextCursor.Right,
1051 cursor.movePosition(QtGui.QTextCursor.Right,
1037 QtGui.QTextCursor.KeepAnchor,
1052 QtGui.QTextCursor.KeepAnchor,
1038 len(self._continuation_prompt))
1053 len(self._continuation_prompt))
1039 cursor.removeSelectedText()
1054 cursor.removeSelectedText()
1040 intercepted = True
1055 intercepted = True
1041
1056
1042 # Regular forwards deletion:
1057 # Regular forwards deletion:
1043 else:
1058 else:
1044 anchor = cursor.anchor()
1059 anchor = cursor.anchor()
1045 intercepted = (not self._in_buffer(anchor) or
1060 intercepted = (not self._in_buffer(anchor) or
1046 not self._in_buffer(position))
1061 not self._in_buffer(position))
1047
1062
1048 # Don't move the cursor if control is down to allow copy-paste using
1063 # Don't move the cursor if control is down to allow copy-paste using
1049 # the keyboard in any part of the buffer.
1064 # the keyboard in any part of the buffer.
1050 if not ctrl_down:
1065 if not ctrl_down:
1051 self._keep_cursor_in_buffer()
1066 self._keep_cursor_in_buffer()
1052
1067
1053 return intercepted
1068 return intercepted
1054
1069
1055 def _event_filter_page_keypress(self, event):
1070 def _event_filter_page_keypress(self, event):
1056 """ Filter key events for the paging widget to create console-like
1071 """ Filter key events for the paging widget to create console-like
1057 interface.
1072 interface.
1058 """
1073 """
1059 key = event.key()
1074 key = event.key()
1060 ctrl_down = self._control_key_down(event.modifiers())
1075 ctrl_down = self._control_key_down(event.modifiers())
1061 alt_down = event.modifiers() & QtCore.Qt.AltModifier
1076 alt_down = event.modifiers() & QtCore.Qt.AltModifier
1062
1077
1063 if ctrl_down:
1078 if ctrl_down:
1064 if key == QtCore.Qt.Key_O:
1079 if key == QtCore.Qt.Key_O:
1065 self._control.setFocus()
1080 self._control.setFocus()
1066 intercept = True
1081 intercept = True
1067
1082
1068 elif alt_down:
1083 elif alt_down:
1069 if key == QtCore.Qt.Key_Greater:
1084 if key == QtCore.Qt.Key_Greater:
1070 self._page_control.moveCursor(QtGui.QTextCursor.End)
1085 self._page_control.moveCursor(QtGui.QTextCursor.End)
1071 intercepted = True
1086 intercepted = True
1072
1087
1073 elif key == QtCore.Qt.Key_Less:
1088 elif key == QtCore.Qt.Key_Less:
1074 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1089 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1075 intercepted = True
1090 intercepted = True
1076
1091
1077 elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
1092 elif key in (QtCore.Qt.Key_Q, QtCore.Qt.Key_Escape):
1078 if self._splitter:
1093 if self._splitter:
1079 self._page_control.hide()
1094 self._page_control.hide()
1080 else:
1095 else:
1081 self.layout().setCurrentWidget(self._control)
1096 self.layout().setCurrentWidget(self._control)
1082 return True
1097 return True
1083
1098
1084 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
1099 elif key in (QtCore.Qt.Key_Enter, QtCore.Qt.Key_Return):
1085 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1100 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1086 QtCore.Qt.Key_PageDown,
1101 QtCore.Qt.Key_PageDown,
1087 QtCore.Qt.NoModifier)
1102 QtCore.Qt.NoModifier)
1088 QtGui.qApp.sendEvent(self._page_control, new_event)
1103 QtGui.qApp.sendEvent(self._page_control, new_event)
1089 return True
1104 return True
1090
1105
1091 elif key == QtCore.Qt.Key_Backspace:
1106 elif key == QtCore.Qt.Key_Backspace:
1092 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1107 new_event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress,
1093 QtCore.Qt.Key_PageUp,
1108 QtCore.Qt.Key_PageUp,
1094 QtCore.Qt.NoModifier)
1109 QtCore.Qt.NoModifier)
1095 QtGui.qApp.sendEvent(self._page_control, new_event)
1110 QtGui.qApp.sendEvent(self._page_control, new_event)
1096 return True
1111 return True
1097
1112
1098 return False
1113 return False
1099
1114
1100 def _format_as_columns(self, items, separator=' '):
1115 def _format_as_columns(self, items, separator=' '):
1101 """ Transform a list of strings into a single string with columns.
1116 """ Transform a list of strings into a single string with columns.
1102
1117
1103 Parameters
1118 Parameters
1104 ----------
1119 ----------
1105 items : sequence of strings
1120 items : sequence of strings
1106 The strings to process.
1121 The strings to process.
1107
1122
1108 separator : str, optional [default is two spaces]
1123 separator : str, optional [default is two spaces]
1109 The string that separates columns.
1124 The string that separates columns.
1110
1125
1111 Returns
1126 Returns
1112 -------
1127 -------
1113 The formatted string.
1128 The formatted string.
1114 """
1129 """
1115 # Note: this code is adapted from columnize 0.3.2.
1130 # Note: this code is adapted from columnize 0.3.2.
1116 # See http://code.google.com/p/pycolumnize/
1131 # See http://code.google.com/p/pycolumnize/
1117
1132
1118 # Calculate the number of characters available.
1133 # Calculate the number of characters available.
1119 width = self._control.viewport().width()
1134 width = self._control.viewport().width()
1120 char_width = QtGui.QFontMetrics(self.font).width(' ')
1135 char_width = QtGui.QFontMetrics(self.font).width(' ')
1121 displaywidth = max(10, (width / char_width) - 1)
1136 displaywidth = max(10, (width / char_width) - 1)
1122
1137
1123 # Some degenerate cases.
1138 # Some degenerate cases.
1124 size = len(items)
1139 size = len(items)
1125 if size == 0:
1140 if size == 0:
1126 return '\n'
1141 return '\n'
1127 elif size == 1:
1142 elif size == 1:
1128 return '%s\n' % items[0]
1143 return '%s\n' % items[0]
1129
1144
1130 # Try every row count from 1 upwards
1145 # Try every row count from 1 upwards
1131 array_index = lambda nrows, row, col: nrows*col + row
1146 array_index = lambda nrows, row, col: nrows*col + row
1132 for nrows in range(1, size):
1147 for nrows in range(1, size):
1133 ncols = (size + nrows - 1) // nrows
1148 ncols = (size + nrows - 1) // nrows
1134 colwidths = []
1149 colwidths = []
1135 totwidth = -len(separator)
1150 totwidth = -len(separator)
1136 for col in range(ncols):
1151 for col in range(ncols):
1137 # Get max column width for this column
1152 # Get max column width for this column
1138 colwidth = 0
1153 colwidth = 0
1139 for row in range(nrows):
1154 for row in range(nrows):
1140 i = array_index(nrows, row, col)
1155 i = array_index(nrows, row, col)
1141 if i >= size: break
1156 if i >= size: break
1142 x = items[i]
1157 x = items[i]
1143 colwidth = max(colwidth, len(x))
1158 colwidth = max(colwidth, len(x))
1144 colwidths.append(colwidth)
1159 colwidths.append(colwidth)
1145 totwidth += colwidth + len(separator)
1160 totwidth += colwidth + len(separator)
1146 if totwidth > displaywidth:
1161 if totwidth > displaywidth:
1147 break
1162 break
1148 if totwidth <= displaywidth:
1163 if totwidth <= displaywidth:
1149 break
1164 break
1150
1165
1151 # The smallest number of rows computed and the max widths for each
1166 # The smallest number of rows computed and the max widths for each
1152 # column has been obtained. Now we just have to format each of the rows.
1167 # column has been obtained. Now we just have to format each of the rows.
1153 string = ''
1168 string = ''
1154 for row in range(nrows):
1169 for row in range(nrows):
1155 texts = []
1170 texts = []
1156 for col in range(ncols):
1171 for col in range(ncols):
1157 i = row + nrows*col
1172 i = row + nrows*col
1158 if i >= size:
1173 if i >= size:
1159 texts.append('')
1174 texts.append('')
1160 else:
1175 else:
1161 texts.append(items[i])
1176 texts.append(items[i])
1162 while texts and not texts[-1]:
1177 while texts and not texts[-1]:
1163 del texts[-1]
1178 del texts[-1]
1164 for col in range(len(texts)):
1179 for col in range(len(texts)):
1165 texts[col] = texts[col].ljust(colwidths[col])
1180 texts[col] = texts[col].ljust(colwidths[col])
1166 string += '%s\n' % separator.join(texts)
1181 string += '%s\n' % separator.join(texts)
1167 return string
1182 return string
1168
1183
1169 def _get_block_plain_text(self, block):
1184 def _get_block_plain_text(self, block):
1170 """ Given a QTextBlock, return its unformatted text.
1185 """ Given a QTextBlock, return its unformatted text.
1171 """
1186 """
1172 cursor = QtGui.QTextCursor(block)
1187 cursor = QtGui.QTextCursor(block)
1173 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1188 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1174 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
1189 cursor.movePosition(QtGui.QTextCursor.EndOfBlock,
1175 QtGui.QTextCursor.KeepAnchor)
1190 QtGui.QTextCursor.KeepAnchor)
1176 return unicode(cursor.selection().toPlainText())
1191 return unicode(cursor.selection().toPlainText())
1177
1192
1178 def _get_cursor(self):
1193 def _get_cursor(self):
1179 """ Convenience method that returns a cursor for the current position.
1194 """ Convenience method that returns a cursor for the current position.
1180 """
1195 """
1181 return self._control.textCursor()
1196 return self._control.textCursor()
1182
1197
1183 def _get_end_cursor(self):
1198 def _get_end_cursor(self):
1184 """ Convenience method that returns a cursor for the last character.
1199 """ Convenience method that returns a cursor for the last character.
1185 """
1200 """
1186 cursor = self._control.textCursor()
1201 cursor = self._control.textCursor()
1187 cursor.movePosition(QtGui.QTextCursor.End)
1202 cursor.movePosition(QtGui.QTextCursor.End)
1188 return cursor
1203 return cursor
1189
1204
1190 def _get_input_buffer_cursor_column(self):
1205 def _get_input_buffer_cursor_column(self):
1191 """ Returns the column of the cursor in the input buffer, excluding the
1206 """ Returns the column of the cursor in the input buffer, excluding the
1192 contribution by the prompt, or -1 if there is no such column.
1207 contribution by the prompt, or -1 if there is no such column.
1193 """
1208 """
1194 prompt = self._get_input_buffer_cursor_prompt()
1209 prompt = self._get_input_buffer_cursor_prompt()
1195 if prompt is None:
1210 if prompt is None:
1196 return -1
1211 return -1
1197 else:
1212 else:
1198 cursor = self._control.textCursor()
1213 cursor = self._control.textCursor()
1199 return cursor.columnNumber() - len(prompt)
1214 return cursor.columnNumber() - len(prompt)
1200
1215
1201 def _get_input_buffer_cursor_line(self):
1216 def _get_input_buffer_cursor_line(self):
1202 """ Returns the text of the line of the input buffer that contains the
1217 """ Returns the text of the line of the input buffer that contains the
1203 cursor, or None if there is no such line.
1218 cursor, or None if there is no such line.
1204 """
1219 """
1205 prompt = self._get_input_buffer_cursor_prompt()
1220 prompt = self._get_input_buffer_cursor_prompt()
1206 if prompt is None:
1221 if prompt is None:
1207 return None
1222 return None
1208 else:
1223 else:
1209 cursor = self._control.textCursor()
1224 cursor = self._control.textCursor()
1210 text = self._get_block_plain_text(cursor.block())
1225 text = self._get_block_plain_text(cursor.block())
1211 return text[len(prompt):]
1226 return text[len(prompt):]
1212
1227
1213 def _get_input_buffer_cursor_prompt(self):
1228 def _get_input_buffer_cursor_prompt(self):
1214 """ Returns the (plain text) prompt for line of the input buffer that
1229 """ Returns the (plain text) prompt for line of the input buffer that
1215 contains the cursor, or None if there is no such line.
1230 contains the cursor, or None if there is no such line.
1216 """
1231 """
1217 if self._executing:
1232 if self._executing:
1218 return None
1233 return None
1219 cursor = self._control.textCursor()
1234 cursor = self._control.textCursor()
1220 if cursor.position() >= self._prompt_pos:
1235 if cursor.position() >= self._prompt_pos:
1221 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
1236 if cursor.blockNumber() == self._get_prompt_cursor().blockNumber():
1222 return self._prompt
1237 return self._prompt
1223 else:
1238 else:
1224 return self._continuation_prompt
1239 return self._continuation_prompt
1225 else:
1240 else:
1226 return None
1241 return None
1227
1242
1228 def _get_prompt_cursor(self):
1243 def _get_prompt_cursor(self):
1229 """ Convenience method that returns a cursor for the prompt position.
1244 """ Convenience method that returns a cursor for the prompt position.
1230 """
1245 """
1231 cursor = self._control.textCursor()
1246 cursor = self._control.textCursor()
1232 cursor.setPosition(self._prompt_pos)
1247 cursor.setPosition(self._prompt_pos)
1233 return cursor
1248 return cursor
1234
1249
1235 def _get_selection_cursor(self, start, end):
1250 def _get_selection_cursor(self, start, end):
1236 """ Convenience method that returns a cursor with text selected between
1251 """ Convenience method that returns a cursor with text selected between
1237 the positions 'start' and 'end'.
1252 the positions 'start' and 'end'.
1238 """
1253 """
1239 cursor = self._control.textCursor()
1254 cursor = self._control.textCursor()
1240 cursor.setPosition(start)
1255 cursor.setPosition(start)
1241 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
1256 cursor.setPosition(end, QtGui.QTextCursor.KeepAnchor)
1242 return cursor
1257 return cursor
1243
1258
1244 def _get_word_start_cursor(self, position):
1259 def _get_word_start_cursor(self, position):
1245 """ Find the start of the word to the left the given position. If a
1260 """ Find the start of the word to the left the given position. If a
1246 sequence of non-word characters precedes the first word, skip over
1261 sequence of non-word characters precedes the first word, skip over
1247 them. (This emulates the behavior of bash, emacs, etc.)
1262 them. (This emulates the behavior of bash, emacs, etc.)
1248 """
1263 """
1249 document = self._control.document()
1264 document = self._control.document()
1250 position -= 1
1265 position -= 1
1251 while position >= self._prompt_pos and \
1266 while position >= self._prompt_pos and \
1252 not document.characterAt(position).isLetterOrNumber():
1267 not document.characterAt(position).isLetterOrNumber():
1253 position -= 1
1268 position -= 1
1254 while position >= self._prompt_pos and \
1269 while position >= self._prompt_pos and \
1255 document.characterAt(position).isLetterOrNumber():
1270 document.characterAt(position).isLetterOrNumber():
1256 position -= 1
1271 position -= 1
1257 cursor = self._control.textCursor()
1272 cursor = self._control.textCursor()
1258 cursor.setPosition(position + 1)
1273 cursor.setPosition(position + 1)
1259 return cursor
1274 return cursor
1260
1275
1261 def _get_word_end_cursor(self, position):
1276 def _get_word_end_cursor(self, position):
1262 """ Find the end of the word to the right the given position. If a
1277 """ Find the end of the word to the right the given position. If a
1263 sequence of non-word characters precedes the first word, skip over
1278 sequence of non-word characters precedes the first word, skip over
1264 them. (This emulates the behavior of bash, emacs, etc.)
1279 them. (This emulates the behavior of bash, emacs, etc.)
1265 """
1280 """
1266 document = self._control.document()
1281 document = self._control.document()
1267 end = self._get_end_cursor().position()
1282 end = self._get_end_cursor().position()
1268 while position < end and \
1283 while position < end and \
1269 not document.characterAt(position).isLetterOrNumber():
1284 not document.characterAt(position).isLetterOrNumber():
1270 position += 1
1285 position += 1
1271 while position < end and \
1286 while position < end and \
1272 document.characterAt(position).isLetterOrNumber():
1287 document.characterAt(position).isLetterOrNumber():
1273 position += 1
1288 position += 1
1274 cursor = self._control.textCursor()
1289 cursor = self._control.textCursor()
1275 cursor.setPosition(position)
1290 cursor.setPosition(position)
1276 return cursor
1291 return cursor
1277
1292
1278 def _insert_continuation_prompt(self, cursor):
1293 def _insert_continuation_prompt(self, cursor):
1279 """ Inserts new continuation prompt using the specified cursor.
1294 """ Inserts new continuation prompt using the specified cursor.
1280 """
1295 """
1281 if self._continuation_prompt_html is None:
1296 if self._continuation_prompt_html is None:
1282 self._insert_plain_text(cursor, self._continuation_prompt)
1297 self._insert_plain_text(cursor, self._continuation_prompt)
1283 else:
1298 else:
1284 self._continuation_prompt = self._insert_html_fetching_plain_text(
1299 self._continuation_prompt = self._insert_html_fetching_plain_text(
1285 cursor, self._continuation_prompt_html)
1300 cursor, self._continuation_prompt_html)
1286
1301
1287 def _insert_html(self, cursor, html):
1302 def _insert_html(self, cursor, html):
1288 """ Inserts HTML using the specified cursor in such a way that future
1303 """ Inserts HTML using the specified cursor in such a way that future
1289 formatting is unaffected.
1304 formatting is unaffected.
1290 """
1305 """
1291 cursor.beginEditBlock()
1306 cursor.beginEditBlock()
1292 cursor.insertHtml(html)
1307 cursor.insertHtml(html)
1293
1308
1294 # After inserting HTML, the text document "remembers" it's in "html
1309 # After inserting HTML, the text document "remembers" it's in "html
1295 # mode", which means that subsequent calls adding plain text will result
1310 # mode", which means that subsequent calls adding plain text will result
1296 # in unwanted formatting, lost tab characters, etc. The following code
1311 # in unwanted formatting, lost tab characters, etc. The following code
1297 # hacks around this behavior, which I consider to be a bug in Qt, by
1312 # hacks around this behavior, which I consider to be a bug in Qt, by
1298 # (crudely) resetting the document's style state.
1313 # (crudely) resetting the document's style state.
1299 cursor.movePosition(QtGui.QTextCursor.Left,
1314 cursor.movePosition(QtGui.QTextCursor.Left,
1300 QtGui.QTextCursor.KeepAnchor)
1315 QtGui.QTextCursor.KeepAnchor)
1301 if cursor.selection().toPlainText() == ' ':
1316 if cursor.selection().toPlainText() == ' ':
1302 cursor.removeSelectedText()
1317 cursor.removeSelectedText()
1303 else:
1318 else:
1304 cursor.movePosition(QtGui.QTextCursor.Right)
1319 cursor.movePosition(QtGui.QTextCursor.Right)
1305 cursor.insertText(' ', QtGui.QTextCharFormat())
1320 cursor.insertText(' ', QtGui.QTextCharFormat())
1306 cursor.endEditBlock()
1321 cursor.endEditBlock()
1307
1322
1308 def _insert_html_fetching_plain_text(self, cursor, html):
1323 def _insert_html_fetching_plain_text(self, cursor, html):
1309 """ Inserts HTML using the specified cursor, then returns its plain text
1324 """ Inserts HTML using the specified cursor, then returns its plain text
1310 version.
1325 version.
1311 """
1326 """
1312 cursor.beginEditBlock()
1327 cursor.beginEditBlock()
1313 cursor.removeSelectedText()
1328 cursor.removeSelectedText()
1314
1329
1315 start = cursor.position()
1330 start = cursor.position()
1316 self._insert_html(cursor, html)
1331 self._insert_html(cursor, html)
1317 end = cursor.position()
1332 end = cursor.position()
1318 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
1333 cursor.setPosition(start, QtGui.QTextCursor.KeepAnchor)
1319 text = unicode(cursor.selection().toPlainText())
1334 text = unicode(cursor.selection().toPlainText())
1320
1335
1321 cursor.setPosition(end)
1336 cursor.setPosition(end)
1322 cursor.endEditBlock()
1337 cursor.endEditBlock()
1323 return text
1338 return text
1324
1339
1325 def _insert_plain_text(self, cursor, text):
1340 def _insert_plain_text(self, cursor, text):
1326 """ Inserts plain text using the specified cursor, processing ANSI codes
1341 """ Inserts plain text using the specified cursor, processing ANSI codes
1327 if enabled.
1342 if enabled.
1328 """
1343 """
1329 cursor.beginEditBlock()
1344 cursor.beginEditBlock()
1330 if self.ansi_codes:
1345 if self.ansi_codes:
1331 for substring in self._ansi_processor.split_string(text):
1346 for substring in self._ansi_processor.split_string(text):
1332 for act in self._ansi_processor.actions:
1347 for act in self._ansi_processor.actions:
1333
1348
1334 # Unlike real terminal emulators, we don't distinguish
1349 # Unlike real terminal emulators, we don't distinguish
1335 # between the screen and the scrollback buffer. A screen
1350 # between the screen and the scrollback buffer. A screen
1336 # erase request clears everything.
1351 # erase request clears everything.
1337 if act.action == 'erase' and act.area == 'screen':
1352 if act.action == 'erase' and act.area == 'screen':
1338 cursor.select(QtGui.QTextCursor.Document)
1353 cursor.select(QtGui.QTextCursor.Document)
1339 cursor.removeSelectedText()
1354 cursor.removeSelectedText()
1340
1355
1341 # Simulate a form feed by scrolling just past the last line.
1356 # Simulate a form feed by scrolling just past the last line.
1342 elif act.action == 'scroll' and act.unit == 'page':
1357 elif act.action == 'scroll' and act.unit == 'page':
1343 cursor.insertText('\n')
1358 cursor.insertText('\n')
1344 cursor.endEditBlock()
1359 cursor.endEditBlock()
1345 self._set_top_cursor(cursor)
1360 self._set_top_cursor(cursor)
1346 cursor.joinPreviousEditBlock()
1361 cursor.joinPreviousEditBlock()
1347 cursor.deletePreviousChar()
1362 cursor.deletePreviousChar()
1348
1363
1349 format = self._ansi_processor.get_format()
1364 format = self._ansi_processor.get_format()
1350 cursor.insertText(substring, format)
1365 cursor.insertText(substring, format)
1351 else:
1366 else:
1352 cursor.insertText(text)
1367 cursor.insertText(text)
1353 cursor.endEditBlock()
1368 cursor.endEditBlock()
1354
1369
1355 def _insert_plain_text_into_buffer(self, cursor, text):
1370 def _insert_plain_text_into_buffer(self, cursor, text):
1356 """ Inserts text into the input buffer using the specified cursor (which
1371 """ Inserts text into the input buffer using the specified cursor (which
1357 must be in the input buffer), ensuring that continuation prompts are
1372 must be in the input buffer), ensuring that continuation prompts are
1358 inserted as necessary.
1373 inserted as necessary.
1359 """
1374 """
1360 lines = unicode(text).splitlines(True)
1375 lines = unicode(text).splitlines(True)
1361 if lines:
1376 if lines:
1362 cursor.beginEditBlock()
1377 cursor.beginEditBlock()
1363 cursor.insertText(lines[0])
1378 cursor.insertText(lines[0])
1364 for line in lines[1:]:
1379 for line in lines[1:]:
1365 if self._continuation_prompt_html is None:
1380 if self._continuation_prompt_html is None:
1366 cursor.insertText(self._continuation_prompt)
1381 cursor.insertText(self._continuation_prompt)
1367 else:
1382 else:
1368 self._continuation_prompt = \
1383 self._continuation_prompt = \
1369 self._insert_html_fetching_plain_text(
1384 self._insert_html_fetching_plain_text(
1370 cursor, self._continuation_prompt_html)
1385 cursor, self._continuation_prompt_html)
1371 cursor.insertText(line)
1386 cursor.insertText(line)
1372 cursor.endEditBlock()
1387 cursor.endEditBlock()
1373
1388
1374 def _in_buffer(self, position=None):
1389 def _in_buffer(self, position=None):
1375 """ Returns whether the current cursor (or, if specified, a position) is
1390 """ Returns whether the current cursor (or, if specified, a position) is
1376 inside the editing region.
1391 inside the editing region.
1377 """
1392 """
1378 cursor = self._control.textCursor()
1393 cursor = self._control.textCursor()
1379 if position is None:
1394 if position is None:
1380 position = cursor.position()
1395 position = cursor.position()
1381 else:
1396 else:
1382 cursor.setPosition(position)
1397 cursor.setPosition(position)
1383 line = cursor.blockNumber()
1398 line = cursor.blockNumber()
1384 prompt_line = self._get_prompt_cursor().blockNumber()
1399 prompt_line = self._get_prompt_cursor().blockNumber()
1385 if line == prompt_line:
1400 if line == prompt_line:
1386 return position >= self._prompt_pos
1401 return position >= self._prompt_pos
1387 elif line > prompt_line:
1402 elif line > prompt_line:
1388 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1403 cursor.movePosition(QtGui.QTextCursor.StartOfBlock)
1389 prompt_pos = cursor.position() + len(self._continuation_prompt)
1404 prompt_pos = cursor.position() + len(self._continuation_prompt)
1390 return position >= prompt_pos
1405 return position >= prompt_pos
1391 return False
1406 return False
1392
1407
1393 def _keep_cursor_in_buffer(self):
1408 def _keep_cursor_in_buffer(self):
1394 """ Ensures that the cursor is inside the editing region. Returns
1409 """ Ensures that the cursor is inside the editing region. Returns
1395 whether the cursor was moved.
1410 whether the cursor was moved.
1396 """
1411 """
1397 moved = not self._in_buffer()
1412 moved = not self._in_buffer()
1398 if moved:
1413 if moved:
1399 cursor = self._control.textCursor()
1414 cursor = self._control.textCursor()
1400 cursor.movePosition(QtGui.QTextCursor.End)
1415 cursor.movePosition(QtGui.QTextCursor.End)
1401 self._control.setTextCursor(cursor)
1416 self._control.setTextCursor(cursor)
1402 return moved
1417 return moved
1403
1418
1404 def _keyboard_quit(self):
1419 def _keyboard_quit(self):
1405 """ Cancels the current editing task ala Ctrl-G in Emacs.
1420 """ Cancels the current editing task ala Ctrl-G in Emacs.
1406 """
1421 """
1407 if self._text_completing_pos:
1422 if self._text_completing_pos:
1408 self._cancel_text_completion()
1423 self._cancel_text_completion()
1409 else:
1424 else:
1410 self.input_buffer = ''
1425 self.input_buffer = ''
1411
1426
1412 def _page(self, text, html=False):
1427 def _page(self, text, html=False):
1413 """ Displays text using the pager if it exceeds the height of the
1428 """ Displays text using the pager if it exceeds the height of the
1414 viewport.
1429 viewport.
1415
1430
1416 Parameters:
1431 Parameters:
1417 -----------
1432 -----------
1418 html : bool, optional (default False)
1433 html : bool, optional (default False)
1419 If set, the text will be interpreted as HTML instead of plain text.
1434 If set, the text will be interpreted as HTML instead of plain text.
1420 """
1435 """
1421 line_height = QtGui.QFontMetrics(self.font).height()
1436 line_height = QtGui.QFontMetrics(self.font).height()
1422 minlines = self._control.viewport().height() / line_height
1437 minlines = self._control.viewport().height() / line_height
1423 if self.paging != 'none' and \
1438 if self.paging != 'none' and \
1424 re.match("(?:[^\n]*\n){%i}" % minlines, text):
1439 re.match("(?:[^\n]*\n){%i}" % minlines, text):
1425 if self.paging == 'custom':
1440 if self.paging == 'custom':
1426 self.custom_page_requested.emit(text)
1441 self.custom_page_requested.emit(text)
1427 else:
1442 else:
1428 self._page_control.clear()
1443 self._page_control.clear()
1429 cursor = self._page_control.textCursor()
1444 cursor = self._page_control.textCursor()
1430 if html:
1445 if html:
1431 self._insert_html(cursor, text)
1446 self._insert_html(cursor, text)
1432 else:
1447 else:
1433 self._insert_plain_text(cursor, text)
1448 self._insert_plain_text(cursor, text)
1434 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1449 self._page_control.moveCursor(QtGui.QTextCursor.Start)
1435
1450
1436 self._page_control.viewport().resize(self._control.size())
1451 self._page_control.viewport().resize(self._control.size())
1437 if self._splitter:
1452 if self._splitter:
1438 self._page_control.show()
1453 self._page_control.show()
1439 self._page_control.setFocus()
1454 self._page_control.setFocus()
1440 else:
1455 else:
1441 self.layout().setCurrentWidget(self._page_control)
1456 self.layout().setCurrentWidget(self._page_control)
1442 elif html:
1457 elif html:
1443 self._append_plain_html(text)
1458 self._append_plain_html(text)
1444 else:
1459 else:
1445 self._append_plain_text(text)
1460 self._append_plain_text(text)
1446
1461
1447 def _prompt_finished(self):
1462 def _prompt_finished(self):
1448 """ Called immediately after a prompt is finished, i.e. when some input
1463 """ Called immediately after a prompt is finished, i.e. when some input
1449 will be processed and a new prompt displayed.
1464 will be processed and a new prompt displayed.
1450 """
1465 """
1451 # Flush all state from the input splitter so the next round of
1466 # Flush all state from the input splitter so the next round of
1452 # reading input starts with a clean buffer.
1467 # reading input starts with a clean buffer.
1453 self._input_splitter.reset()
1468 self._input_splitter.reset()
1454
1469
1455 self._control.setReadOnly(True)
1470 self._control.setReadOnly(True)
1456 self._prompt_finished_hook()
1471 self._prompt_finished_hook()
1457
1472
1458 def _prompt_started(self):
1473 def _prompt_started(self):
1459 """ Called immediately after a new prompt is displayed.
1474 """ Called immediately after a new prompt is displayed.
1460 """
1475 """
1461 # Temporarily disable the maximum block count to permit undo/redo and
1476 # Temporarily disable the maximum block count to permit undo/redo and
1462 # to ensure that the prompt position does not change due to truncation.
1477 # to ensure that the prompt position does not change due to truncation.
1463 self._control.document().setMaximumBlockCount(0)
1478 self._control.document().setMaximumBlockCount(0)
1464 self._control.setUndoRedoEnabled(True)
1479 self._control.setUndoRedoEnabled(True)
1465
1480
1466 self._control.setReadOnly(False)
1481 self._control.setReadOnly(False)
1467 self._control.moveCursor(QtGui.QTextCursor.End)
1482 self._control.moveCursor(QtGui.QTextCursor.End)
1468 self._executing = False
1483 self._executing = False
1469 self._prompt_started_hook()
1484 self._prompt_started_hook()
1470
1485
1471 def _readline(self, prompt='', callback=None):
1486 def _readline(self, prompt='', callback=None):
1472 """ Reads one line of input from the user.
1487 """ Reads one line of input from the user.
1473
1488
1474 Parameters
1489 Parameters
1475 ----------
1490 ----------
1476 prompt : str, optional
1491 prompt : str, optional
1477 The prompt to print before reading the line.
1492 The prompt to print before reading the line.
1478
1493
1479 callback : callable, optional
1494 callback : callable, optional
1480 A callback to execute with the read line. If not specified, input is
1495 A callback to execute with the read line. If not specified, input is
1481 read *synchronously* and this method does not return until it has
1496 read *synchronously* and this method does not return until it has
1482 been read.
1497 been read.
1483
1498
1484 Returns
1499 Returns
1485 -------
1500 -------
1486 If a callback is specified, returns nothing. Otherwise, returns the
1501 If a callback is specified, returns nothing. Otherwise, returns the
1487 input string with the trailing newline stripped.
1502 input string with the trailing newline stripped.
1488 """
1503 """
1489 if self._reading:
1504 if self._reading:
1490 raise RuntimeError('Cannot read a line. Widget is already reading.')
1505 raise RuntimeError('Cannot read a line. Widget is already reading.')
1491
1506
1492 if not callback and not self.isVisible():
1507 if not callback and not self.isVisible():
1493 # If the user cannot see the widget, this function cannot return.
1508 # If the user cannot see the widget, this function cannot return.
1494 raise RuntimeError('Cannot synchronously read a line if the widget '
1509 raise RuntimeError('Cannot synchronously read a line if the widget '
1495 'is not visible!')
1510 'is not visible!')
1496
1511
1497 self._reading = True
1512 self._reading = True
1498 self._show_prompt(prompt, newline=False)
1513 self._show_prompt(prompt, newline=False)
1499
1514
1500 if callback is None:
1515 if callback is None:
1501 self._reading_callback = None
1516 self._reading_callback = None
1502 while self._reading:
1517 while self._reading:
1503 QtCore.QCoreApplication.processEvents()
1518 QtCore.QCoreApplication.processEvents()
1504 return self.input_buffer.rstrip('\n')
1519 return self.input_buffer.rstrip('\n')
1505
1520
1506 else:
1521 else:
1507 self._reading_callback = lambda: \
1522 self._reading_callback = lambda: \
1508 callback(self.input_buffer.rstrip('\n'))
1523 callback(self.input_buffer.rstrip('\n'))
1509
1524
1510 def _set_continuation_prompt(self, prompt, html=False):
1525 def _set_continuation_prompt(self, prompt, html=False):
1511 """ Sets the continuation prompt.
1526 """ Sets the continuation prompt.
1512
1527
1513 Parameters
1528 Parameters
1514 ----------
1529 ----------
1515 prompt : str
1530 prompt : str
1516 The prompt to show when more input is needed.
1531 The prompt to show when more input is needed.
1517
1532
1518 html : bool, optional (default False)
1533 html : bool, optional (default False)
1519 If set, the prompt will be inserted as formatted HTML. Otherwise,
1534 If set, the prompt will be inserted as formatted HTML. Otherwise,
1520 the prompt will be treated as plain text, though ANSI color codes
1535 the prompt will be treated as plain text, though ANSI color codes
1521 will be handled.
1536 will be handled.
1522 """
1537 """
1523 if html:
1538 if html:
1524 self._continuation_prompt_html = prompt
1539 self._continuation_prompt_html = prompt
1525 else:
1540 else:
1526 self._continuation_prompt = prompt
1541 self._continuation_prompt = prompt
1527 self._continuation_prompt_html = None
1542 self._continuation_prompt_html = None
1528
1543
1529 def _set_cursor(self, cursor):
1544 def _set_cursor(self, cursor):
1530 """ Convenience method to set the current cursor.
1545 """ Convenience method to set the current cursor.
1531 """
1546 """
1532 self._control.setTextCursor(cursor)
1547 self._control.setTextCursor(cursor)
1533
1548
1534 def _set_top_cursor(self, cursor):
1549 def _set_top_cursor(self, cursor):
1535 """ Scrolls the viewport so that the specified cursor is at the top.
1550 """ Scrolls the viewport so that the specified cursor is at the top.
1536 """
1551 """
1537 scrollbar = self._control.verticalScrollBar()
1552 scrollbar = self._control.verticalScrollBar()
1538 scrollbar.setValue(scrollbar.maximum())
1553 scrollbar.setValue(scrollbar.maximum())
1539 original_cursor = self._control.textCursor()
1554 original_cursor = self._control.textCursor()
1540 self._control.setTextCursor(cursor)
1555 self._control.setTextCursor(cursor)
1541 self._control.ensureCursorVisible()
1556 self._control.ensureCursorVisible()
1542 self._control.setTextCursor(original_cursor)
1557 self._control.setTextCursor(original_cursor)
1543
1558
1544 def _show_prompt(self, prompt=None, html=False, newline=True):
1559 def _show_prompt(self, prompt=None, html=False, newline=True):
1545 """ Writes a new prompt at the end of the buffer.
1560 """ Writes a new prompt at the end of the buffer.
1546
1561
1547 Parameters
1562 Parameters
1548 ----------
1563 ----------
1549 prompt : str, optional
1564 prompt : str, optional
1550 The prompt to show. If not specified, the previous prompt is used.
1565 The prompt to show. If not specified, the previous prompt is used.
1551
1566
1552 html : bool, optional (default False)
1567 html : bool, optional (default False)
1553 Only relevant when a prompt is specified. If set, the prompt will
1568 Only relevant when a prompt is specified. If set, the prompt will
1554 be inserted as formatted HTML. Otherwise, the prompt will be treated
1569 be inserted as formatted HTML. Otherwise, the prompt will be treated
1555 as plain text, though ANSI color codes will be handled.
1570 as plain text, though ANSI color codes will be handled.
1556
1571
1557 newline : bool, optional (default True)
1572 newline : bool, optional (default True)
1558 If set, a new line will be written before showing the prompt if
1573 If set, a new line will be written before showing the prompt if
1559 there is not already a newline at the end of the buffer.
1574 there is not already a newline at the end of the buffer.
1560 """
1575 """
1561 # Insert a preliminary newline, if necessary.
1576 # Insert a preliminary newline, if necessary.
1562 if newline:
1577 if newline:
1563 cursor = self._get_end_cursor()
1578 cursor = self._get_end_cursor()
1564 if cursor.position() > 0:
1579 if cursor.position() > 0:
1565 cursor.movePosition(QtGui.QTextCursor.Left,
1580 cursor.movePosition(QtGui.QTextCursor.Left,
1566 QtGui.QTextCursor.KeepAnchor)
1581 QtGui.QTextCursor.KeepAnchor)
1567 if unicode(cursor.selection().toPlainText()) != '\n':
1582 if unicode(cursor.selection().toPlainText()) != '\n':
1568 self._append_plain_text('\n')
1583 self._append_plain_text('\n')
1569
1584
1570 # Write the prompt.
1585 # Write the prompt.
1571 self._append_plain_text(self._prompt_sep)
1586 self._append_plain_text(self._prompt_sep)
1572 if prompt is None:
1587 if prompt is None:
1573 if self._prompt_html is None:
1588 if self._prompt_html is None:
1574 self._append_plain_text(self._prompt)
1589 self._append_plain_text(self._prompt)
1575 else:
1590 else:
1576 self._append_html(self._prompt_html)
1591 self._append_html(self._prompt_html)
1577 else:
1592 else:
1578 if html:
1593 if html:
1579 self._prompt = self._append_html_fetching_plain_text(prompt)
1594 self._prompt = self._append_html_fetching_plain_text(prompt)
1580 self._prompt_html = prompt
1595 self._prompt_html = prompt
1581 else:
1596 else:
1582 self._append_plain_text(prompt)
1597 self._append_plain_text(prompt)
1583 self._prompt = prompt
1598 self._prompt = prompt
1584 self._prompt_html = None
1599 self._prompt_html = None
1585
1600
1586 self._prompt_pos = self._get_end_cursor().position()
1601 self._prompt_pos = self._get_end_cursor().position()
1587 self._prompt_started()
1602 self._prompt_started()
1588
1603
1589 #------ Signal handlers ----------------------------------------------------
1604 #------ Signal handlers ----------------------------------------------------
1590
1605
1591 def _adjust_scrollbars(self):
1606 def _adjust_scrollbars(self):
1592 """ Expands the vertical scrollbar beyond the range set by Qt.
1607 """ Expands the vertical scrollbar beyond the range set by Qt.
1593 """
1608 """
1594 # This code is adapted from _q_adjustScrollbars in qplaintextedit.cpp
1609 # This code is adapted from _q_adjustScrollbars in qplaintextedit.cpp
1595 # and qtextedit.cpp.
1610 # and qtextedit.cpp.
1596 document = self._control.document()
1611 document = self._control.document()
1597 scrollbar = self._control.verticalScrollBar()
1612 scrollbar = self._control.verticalScrollBar()
1598 viewport_height = self._control.viewport().height()
1613 viewport_height = self._control.viewport().height()
1599 if isinstance(self._control, QtGui.QPlainTextEdit):
1614 if isinstance(self._control, QtGui.QPlainTextEdit):
1600 maximum = max(0, document.lineCount() - 1)
1615 maximum = max(0, document.lineCount() - 1)
1601 step = viewport_height / self._control.fontMetrics().lineSpacing()
1616 step = viewport_height / self._control.fontMetrics().lineSpacing()
1602 else:
1617 else:
1603 # QTextEdit does not do line-based layout and blocks will not in
1618 # QTextEdit does not do line-based layout and blocks will not in
1604 # general have the same height. Therefore it does not make sense to
1619 # general have the same height. Therefore it does not make sense to
1605 # attempt to scroll in line height increments.
1620 # attempt to scroll in line height increments.
1606 maximum = document.size().height()
1621 maximum = document.size().height()
1607 step = viewport_height
1622 step = viewport_height
1608 diff = maximum - scrollbar.maximum()
1623 diff = maximum - scrollbar.maximum()
1609 scrollbar.setRange(0, maximum)
1624 scrollbar.setRange(0, maximum)
1610 scrollbar.setPageStep(step)
1625 scrollbar.setPageStep(step)
1611 # Compensate for undesirable scrolling that occurs automatically due to
1626 # Compensate for undesirable scrolling that occurs automatically due to
1612 # maximumBlockCount() text truncation.
1627 # maximumBlockCount() text truncation.
1613 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1628 if diff < 0 and document.blockCount() == document.maximumBlockCount():
1614 scrollbar.setValue(scrollbar.value() + diff)
1629 scrollbar.setValue(scrollbar.value() + diff)
1615
1630
1616 def _cursor_position_changed(self):
1631 def _cursor_position_changed(self):
1617 """ Clears the temporary buffer based on the cursor position.
1632 """ Clears the temporary buffer based on the cursor position.
1618 """
1633 """
1619 if self._text_completing_pos:
1634 if self._text_completing_pos:
1620 document = self._control.document()
1635 document = self._control.document()
1621 if self._text_completing_pos < document.characterCount():
1636 if self._text_completing_pos < document.characterCount():
1622 cursor = self._control.textCursor()
1637 cursor = self._control.textCursor()
1623 pos = cursor.position()
1638 pos = cursor.position()
1624 text_cursor = self._control.textCursor()
1639 text_cursor = self._control.textCursor()
1625 text_cursor.setPosition(self._text_completing_pos)
1640 text_cursor.setPosition(self._text_completing_pos)
1626 if pos < self._text_completing_pos or \
1641 if pos < self._text_completing_pos or \
1627 cursor.blockNumber() > text_cursor.blockNumber():
1642 cursor.blockNumber() > text_cursor.blockNumber():
1628 self._clear_temporary_buffer()
1643 self._clear_temporary_buffer()
1629 self._text_completing_pos = 0
1644 self._text_completing_pos = 0
1630 else:
1645 else:
1631 self._clear_temporary_buffer()
1646 self._clear_temporary_buffer()
1632 self._text_completing_pos = 0
1647 self._text_completing_pos = 0
1633
1648
1634 def _custom_context_menu_requested(self, pos):
1649 def _custom_context_menu_requested(self, pos):
1635 """ Shows a context menu at the given QPoint (in widget coordinates).
1650 """ Shows a context menu at the given QPoint (in widget coordinates).
1636 """
1651 """
1637 menu = self._context_menu_make(pos)
1652 menu = self._context_menu_make(pos)
1638 menu.exec_(self._control.mapToGlobal(pos))
1653 menu.exec_(self._control.mapToGlobal(pos))
General Comments 0
You need to be logged in to leave comments. Login now