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