##// END OF EJS Templates
Merge pull request #9472 from Carreau/stupid-me...
Thomas Kluyver -
r22339:6ad2eb91 merge
parent child Browse files
Show More
@@ -1,159 +1,162 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for managing IPython history.
4 4
5 5 To be invoked as the `ipython history` subcommand.
6 6 """
7 7 from __future__ import print_function
8 8
9 9 import os
10 10 import sqlite3
11 11
12 12 from traitlets.config.application import Application
13 13 from IPython.core.application import BaseIPythonApplication
14 14 from traitlets import Bool, Int, Dict
15 15 from IPython.utils.io import ask_yes_no
16 16
17 17 trim_hist_help = """Trim the IPython history database to the last 1000 entries.
18 18
19 19 This actually copies the last 1000 entries to a new database, and then replaces
20 20 the old file with the new. Use the `--keep=` argument to specify a number
21 21 other than 1000.
22 22 """
23 23
24 24 clear_hist_help = """Clear the IPython history database, deleting all entries.
25 25
26 26 Because this is a destructive operation, IPython will prompt the user if they
27 27 really want to do this. Passing a `-f` flag will force clearing without a
28 28 prompt.
29 29
30 30 This is an handy alias to `ipython history trim --keep=0`
31 31 """
32 32
33 33
34 34 class HistoryTrim(BaseIPythonApplication):
35 35 description = trim_hist_help
36 36
37 backup = Bool(False).tag(config=True,
38 help="Keep the old history file as history.sqlite.<N>")
37 backup = Bool(False,
38 help="Keep the old history file as history.sqlite.<N>"
39 ).tag(config=True)
39 40
40 keep = Int(1000).tag(config=True,
41 help="Number of recent lines to keep in the database.")
41 keep = Int(1000,
42 help="Number of recent lines to keep in the database."
43 ).tag(config=True)
42 44
43 45 flags = Dict(dict(
44 46 backup = ({'HistoryTrim' : {'backup' : True}},
45 47 backup.help
46 48 )
47 49 ))
48 50
49 51 aliases=Dict(dict(
50 52 keep = 'HistoryTrim.keep'
51 53 ))
52 54
53 55 def start(self):
54 56 profile_dir = self.profile_dir.location
55 57 hist_file = os.path.join(profile_dir, 'history.sqlite')
56 58 con = sqlite3.connect(hist_file)
57 59
58 60 # Grab the recent history from the current database.
59 61 inputs = list(con.execute('SELECT session, line, source, source_raw FROM '
60 62 'history ORDER BY session DESC, line DESC LIMIT ?', (self.keep+1,)))
61 63 if len(inputs) <= self.keep:
62 64 print("There are already at most %d entries in the history database." % self.keep)
63 65 print("Not doing anything. Use --keep= argument to keep fewer entries")
64 66 return
65 67
66 68 print("Trimming history to the most recent %d entries." % self.keep)
67 69
68 70 inputs.pop() # Remove the extra element we got to check the length.
69 71 inputs.reverse()
70 72 if inputs:
71 73 first_session = inputs[0][0]
72 74 outputs = list(con.execute('SELECT session, line, output FROM '
73 75 'output_history WHERE session >= ?', (first_session,)))
74 76 sessions = list(con.execute('SELECT session, start, end, num_cmds, remark FROM '
75 77 'sessions WHERE session >= ?', (first_session,)))
76 78 con.close()
77 79
78 80 # Create the new history database.
79 81 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new')
80 82 i = 0
81 83 while os.path.exists(new_hist_file):
82 84 # Make sure we don't interfere with an existing file.
83 85 i += 1
84 86 new_hist_file = os.path.join(profile_dir, 'history.sqlite.new'+str(i))
85 87 new_db = sqlite3.connect(new_hist_file)
86 88 new_db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
87 89 primary key autoincrement, start timestamp,
88 90 end timestamp, num_cmds integer, remark text)""")
89 91 new_db.execute("""CREATE TABLE IF NOT EXISTS history
90 92 (session integer, line integer, source text, source_raw text,
91 93 PRIMARY KEY (session, line))""")
92 94 new_db.execute("""CREATE TABLE IF NOT EXISTS output_history
93 95 (session integer, line integer, output text,
94 96 PRIMARY KEY (session, line))""")
95 97 new_db.commit()
96 98
97 99
98 100 if inputs:
99 101 with new_db:
100 102 # Add the recent history into the new database.
101 103 new_db.executemany('insert into sessions values (?,?,?,?,?)', sessions)
102 104 new_db.executemany('insert into history values (?,?,?,?)', inputs)
103 105 new_db.executemany('insert into output_history values (?,?,?)', outputs)
104 106 new_db.close()
105 107
106 108 if self.backup:
107 109 i = 1
108 110 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
109 111 while os.path.exists(backup_hist_file):
110 112 i += 1
111 113 backup_hist_file = os.path.join(profile_dir, 'history.sqlite.old.%d' % i)
112 114 os.rename(hist_file, backup_hist_file)
113 115 print("Backed up longer history file to", backup_hist_file)
114 116 else:
115 117 os.remove(hist_file)
116 118
117 119 os.rename(new_hist_file, hist_file)
118 120
119 121 class HistoryClear(HistoryTrim):
120 122 description = clear_hist_help
121 keep = Int(0).tag(config=False,
123 keep = Int(0,
122 124 help="Number of recent lines to keep in the database.")
123 125
124 force = Bool(False).tag(config=True,
125 help="Don't prompt user for confirmation")
126 force = Bool(False,
127 help="Don't prompt user for confirmation"
128 ).tag(config=True)
126 129
127 130 flags = Dict(dict(
128 131 force = ({'HistoryClear' : {'force' : True}},
129 132 force.help),
130 133 f = ({'HistoryTrim' : {'force' : True}},
131 134 force.help
132 135 )
133 136 ))
134 137 aliases = Dict()
135 138
136 139 def start(self):
137 140 if self.force or ask_yes_no("Really delete all ipython history? ",
138 141 default="no", interrupt="no"):
139 142 HistoryTrim.start(self)
140 143
141 144 class HistoryApp(Application):
142 145 name = u'ipython-history'
143 146 description = "Manage the IPython history database."
144 147
145 148 subcommands = Dict(dict(
146 149 trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]),
147 150 clear = (HistoryClear, HistoryClear.description.splitlines()[0]),
148 151 ))
149 152
150 153 def start(self):
151 154 if self.subapp is None:
152 155 print("No subcommand specified. Must specify one of: %s" % \
153 156 (self.subcommands.keys()))
154 157 print()
155 158 self.print_description()
156 159 self.print_subcommands()
157 160 self.exit(1)
158 161 else:
159 162 return self.subapp.start()
@@ -1,713 +1,715 b''
1 1 # encoding: utf-8
2 2 """
3 3 Prefiltering components.
4 4
5 5 Prefilters transform user input before it is exec'd by Python. These
6 6 transforms are used to implement additional syntax such as !ls and %magic.
7 7
8 8 Authors:
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Dan Milstein
13 13 * Ville Vainio
14 14 """
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Copyright (C) 2008-2011 The IPython Development Team
18 18 #
19 19 # Distributed under the terms of the BSD License. The full license is in
20 20 # the file COPYING, distributed as part of this software.
21 21 #-----------------------------------------------------------------------------
22 22
23 23 #-----------------------------------------------------------------------------
24 24 # Imports
25 25 #-----------------------------------------------------------------------------
26 26
27 27 from keyword import iskeyword
28 28 import re
29 29
30 30 from IPython.core.autocall import IPyAutocall
31 31 from traitlets.config.configurable import Configurable
32 32 from IPython.core.inputsplitter import (
33 33 ESC_MAGIC,
34 34 ESC_QUOTE,
35 35 ESC_QUOTE2,
36 36 ESC_PAREN,
37 37 )
38 38 from IPython.core.macro import Macro
39 39 from IPython.core.splitinput import LineInfo
40 40
41 41 from traitlets import (
42 42 List, Integer, Unicode, CBool, Bool, Instance, CRegExp
43 43 )
44 44
45 45 #-----------------------------------------------------------------------------
46 46 # Global utilities, errors and constants
47 47 #-----------------------------------------------------------------------------
48 48
49 49
50 50 class PrefilterError(Exception):
51 51 pass
52 52
53 53
54 54 # RegExp to identify potential function names
55 55 re_fun_name = re.compile(r'[a-zA-Z_]([a-zA-Z0-9_.]*) *$')
56 56
57 57 # RegExp to exclude strings with this start from autocalling. In
58 58 # particular, all binary operators should be excluded, so that if foo is
59 59 # callable, foo OP bar doesn't become foo(OP bar), which is invalid. The
60 60 # characters '!=()' don't need to be checked for, as the checkPythonChars
61 61 # routine explicitely does so, to catch direct calls and rebindings of
62 62 # existing names.
63 63
64 64 # Warning: the '-' HAS TO BE AT THE END of the first group, otherwise
65 65 # it affects the rest of the group in square brackets.
66 66 re_exclude_auto = re.compile(r'^[,&^\|\*/\+-]'
67 67 r'|^is |^not |^in |^and |^or ')
68 68
69 69 # try to catch also methods for stuff in lists/tuples/dicts: off
70 70 # (experimental). For this to work, the line_split regexp would need
71 71 # to be modified so it wouldn't break things at '['. That line is
72 72 # nasty enough that I shouldn't change it until I can test it _well_.
73 73 #self.re_fun_name = re.compile (r'[a-zA-Z_]([a-zA-Z0-9_.\[\]]*) ?$')
74 74
75 75
76 76 # Handler Check Utilities
77 77 def is_shadowed(identifier, ip):
78 78 """Is the given identifier defined in one of the namespaces which shadow
79 79 the alias and magic namespaces? Note that an identifier is different
80 80 than ifun, because it can not contain a '.' character."""
81 81 # This is much safer than calling ofind, which can change state
82 82 return (identifier in ip.user_ns \
83 83 or identifier in ip.user_global_ns \
84 84 or identifier in ip.ns_table['builtin']\
85 85 or iskeyword(identifier))
86 86
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Main Prefilter manager
90 90 #-----------------------------------------------------------------------------
91 91
92 92
93 93 class PrefilterManager(Configurable):
94 94 """Main prefilter component.
95 95
96 96 The IPython prefilter is run on all user input before it is run. The
97 97 prefilter consumes lines of input and produces transformed lines of
98 98 input.
99 99
100 100 The iplementation consists of two phases:
101 101
102 102 1. Transformers
103 103 2. Checkers and handlers
104 104
105 105 Over time, we plan on deprecating the checkers and handlers and doing
106 106 everything in the transformers.
107 107
108 108 The transformers are instances of :class:`PrefilterTransformer` and have
109 109 a single method :meth:`transform` that takes a line and returns a
110 110 transformed line. The transformation can be accomplished using any
111 111 tool, but our current ones use regular expressions for speed.
112 112
113 113 After all the transformers have been run, the line is fed to the checkers,
114 114 which are instances of :class:`PrefilterChecker`. The line is passed to
115 115 the :meth:`check` method, which either returns `None` or a
116 116 :class:`PrefilterHandler` instance. If `None` is returned, the other
117 117 checkers are tried. If an :class:`PrefilterHandler` instance is returned,
118 118 the line is passed to the :meth:`handle` method of the returned
119 119 handler and no further checkers are tried.
120 120
121 121 Both transformers and checkers have a `priority` attribute, that determines
122 122 the order in which they are called. Smaller priorities are tried first.
123 123
124 124 Both transformers and checkers also have `enabled` attribute, which is
125 125 a boolean that determines if the instance is used.
126 126
127 127 Users or developers can change the priority or enabled attribute of
128 128 transformers or checkers, but they must call the :meth:`sort_checkers`
129 129 or :meth:`sort_transformers` method after changing the priority.
130 130 """
131 131
132 132 multi_line_specials = CBool(True).tag(config=True)
133 133 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
134 134
135 135 def __init__(self, shell=None, **kwargs):
136 136 super(PrefilterManager, self).__init__(shell=shell, **kwargs)
137 137 self.shell = shell
138 138 self.init_transformers()
139 139 self.init_handlers()
140 140 self.init_checkers()
141 141
142 142 #-------------------------------------------------------------------------
143 143 # API for managing transformers
144 144 #-------------------------------------------------------------------------
145 145
146 146 def init_transformers(self):
147 147 """Create the default transformers."""
148 148 self._transformers = []
149 149 for transformer_cls in _default_transformers:
150 150 transformer_cls(
151 151 shell=self.shell, prefilter_manager=self, parent=self
152 152 )
153 153
154 154 def sort_transformers(self):
155 155 """Sort the transformers by priority.
156 156
157 157 This must be called after the priority of a transformer is changed.
158 158 The :meth:`register_transformer` method calls this automatically.
159 159 """
160 160 self._transformers.sort(key=lambda x: x.priority)
161 161
162 162 @property
163 163 def transformers(self):
164 164 """Return a list of checkers, sorted by priority."""
165 165 return self._transformers
166 166
167 167 def register_transformer(self, transformer):
168 168 """Register a transformer instance."""
169 169 if transformer not in self._transformers:
170 170 self._transformers.append(transformer)
171 171 self.sort_transformers()
172 172
173 173 def unregister_transformer(self, transformer):
174 174 """Unregister a transformer instance."""
175 175 if transformer in self._transformers:
176 176 self._transformers.remove(transformer)
177 177
178 178 #-------------------------------------------------------------------------
179 179 # API for managing checkers
180 180 #-------------------------------------------------------------------------
181 181
182 182 def init_checkers(self):
183 183 """Create the default checkers."""
184 184 self._checkers = []
185 185 for checker in _default_checkers:
186 186 checker(
187 187 shell=self.shell, prefilter_manager=self, parent=self
188 188 )
189 189
190 190 def sort_checkers(self):
191 191 """Sort the checkers by priority.
192 192
193 193 This must be called after the priority of a checker is changed.
194 194 The :meth:`register_checker` method calls this automatically.
195 195 """
196 196 self._checkers.sort(key=lambda x: x.priority)
197 197
198 198 @property
199 199 def checkers(self):
200 200 """Return a list of checkers, sorted by priority."""
201 201 return self._checkers
202 202
203 203 def register_checker(self, checker):
204 204 """Register a checker instance."""
205 205 if checker not in self._checkers:
206 206 self._checkers.append(checker)
207 207 self.sort_checkers()
208 208
209 209 def unregister_checker(self, checker):
210 210 """Unregister a checker instance."""
211 211 if checker in self._checkers:
212 212 self._checkers.remove(checker)
213 213
214 214 #-------------------------------------------------------------------------
215 215 # API for managing handlers
216 216 #-------------------------------------------------------------------------
217 217
218 218 def init_handlers(self):
219 219 """Create the default handlers."""
220 220 self._handlers = {}
221 221 self._esc_handlers = {}
222 222 for handler in _default_handlers:
223 223 handler(
224 224 shell=self.shell, prefilter_manager=self, parent=self
225 225 )
226 226
227 227 @property
228 228 def handlers(self):
229 229 """Return a dict of all the handlers."""
230 230 return self._handlers
231 231
232 232 def register_handler(self, name, handler, esc_strings):
233 233 """Register a handler instance by name with esc_strings."""
234 234 self._handlers[name] = handler
235 235 for esc_str in esc_strings:
236 236 self._esc_handlers[esc_str] = handler
237 237
238 238 def unregister_handler(self, name, handler, esc_strings):
239 239 """Unregister a handler instance by name with esc_strings."""
240 240 try:
241 241 del self._handlers[name]
242 242 except KeyError:
243 243 pass
244 244 for esc_str in esc_strings:
245 245 h = self._esc_handlers.get(esc_str)
246 246 if h is handler:
247 247 del self._esc_handlers[esc_str]
248 248
249 249 def get_handler_by_name(self, name):
250 250 """Get a handler by its name."""
251 251 return self._handlers.get(name)
252 252
253 253 def get_handler_by_esc(self, esc_str):
254 254 """Get a handler by its escape string."""
255 255 return self._esc_handlers.get(esc_str)
256 256
257 257 #-------------------------------------------------------------------------
258 258 # Main prefiltering API
259 259 #-------------------------------------------------------------------------
260 260
261 261 def prefilter_line_info(self, line_info):
262 262 """Prefilter a line that has been converted to a LineInfo object.
263 263
264 264 This implements the checker/handler part of the prefilter pipe.
265 265 """
266 266 # print "prefilter_line_info: ", line_info
267 267 handler = self.find_handler(line_info)
268 268 return handler.handle(line_info)
269 269
270 270 def find_handler(self, line_info):
271 271 """Find a handler for the line_info by trying checkers."""
272 272 for checker in self.checkers:
273 273 if checker.enabled:
274 274 handler = checker.check(line_info)
275 275 if handler:
276 276 return handler
277 277 return self.get_handler_by_name('normal')
278 278
279 279 def transform_line(self, line, continue_prompt):
280 280 """Calls the enabled transformers in order of increasing priority."""
281 281 for transformer in self.transformers:
282 282 if transformer.enabled:
283 283 line = transformer.transform(line, continue_prompt)
284 284 return line
285 285
286 286 def prefilter_line(self, line, continue_prompt=False):
287 287 """Prefilter a single input line as text.
288 288
289 289 This method prefilters a single line of text by calling the
290 290 transformers and then the checkers/handlers.
291 291 """
292 292
293 293 # print "prefilter_line: ", line, continue_prompt
294 294 # All handlers *must* return a value, even if it's blank ('').
295 295
296 296 # save the line away in case we crash, so the post-mortem handler can
297 297 # record it
298 298 self.shell._last_input_line = line
299 299
300 300 if not line:
301 301 # Return immediately on purely empty lines, so that if the user
302 302 # previously typed some whitespace that started a continuation
303 303 # prompt, he can break out of that loop with just an empty line.
304 304 # This is how the default python prompt works.
305 305 return ''
306 306
307 307 # At this point, we invoke our transformers.
308 308 if not continue_prompt or (continue_prompt and self.multi_line_specials):
309 309 line = self.transform_line(line, continue_prompt)
310 310
311 311 # Now we compute line_info for the checkers and handlers
312 312 line_info = LineInfo(line, continue_prompt)
313 313
314 314 # the input history needs to track even empty lines
315 315 stripped = line.strip()
316 316
317 317 normal_handler = self.get_handler_by_name('normal')
318 318 if not stripped:
319 319 return normal_handler.handle(line_info)
320 320
321 321 # special handlers are only allowed for single line statements
322 322 if continue_prompt and not self.multi_line_specials:
323 323 return normal_handler.handle(line_info)
324 324
325 325 prefiltered = self.prefilter_line_info(line_info)
326 326 # print "prefiltered line: %r" % prefiltered
327 327 return prefiltered
328 328
329 329 def prefilter_lines(self, lines, continue_prompt=False):
330 330 """Prefilter multiple input lines of text.
331 331
332 332 This is the main entry point for prefiltering multiple lines of
333 333 input. This simply calls :meth:`prefilter_line` for each line of
334 334 input.
335 335
336 336 This covers cases where there are multiple lines in the user entry,
337 337 which is the case when the user goes back to a multiline history
338 338 entry and presses enter.
339 339 """
340 340 llines = lines.rstrip('\n').split('\n')
341 341 # We can get multiple lines in one shot, where multiline input 'blends'
342 342 # into one line, in cases like recalling from the readline history
343 343 # buffer. We need to make sure that in such cases, we correctly
344 344 # communicate downstream which line is first and which are continuation
345 345 # ones.
346 346 if len(llines) > 1:
347 347 out = '\n'.join([self.prefilter_line(line, lnum>0)
348 348 for lnum, line in enumerate(llines) ])
349 349 else:
350 350 out = self.prefilter_line(llines[0], continue_prompt)
351 351
352 352 return out
353 353
354 354 #-----------------------------------------------------------------------------
355 355 # Prefilter transformers
356 356 #-----------------------------------------------------------------------------
357 357
358 358
359 359 class PrefilterTransformer(Configurable):
360 360 """Transform a line of user input."""
361 361
362 362 priority = Integer(100).tag(config=True)
363 363 # Transformers don't currently use shell or prefilter_manager, but as we
364 364 # move away from checkers and handlers, they will need them.
365 365 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
366 366 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
367 367 enabled = Bool(True).tag(config=True)
368 368
369 369 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
370 370 super(PrefilterTransformer, self).__init__(
371 371 shell=shell, prefilter_manager=prefilter_manager, **kwargs
372 372 )
373 373 self.prefilter_manager.register_transformer(self)
374 374
375 375 def transform(self, line, continue_prompt):
376 376 """Transform a line, returning the new one."""
377 377 return None
378 378
379 379 def __repr__(self):
380 380 return "<%s(priority=%r, enabled=%r)>" % (
381 381 self.__class__.__name__, self.priority, self.enabled)
382 382
383 383
384 384 #-----------------------------------------------------------------------------
385 385 # Prefilter checkers
386 386 #-----------------------------------------------------------------------------
387 387
388 388
389 389 class PrefilterChecker(Configurable):
390 390 """Inspect an input line and return a handler for that line."""
391 391
392 392 priority = Integer(100).tag(config=True)
393 393 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
394 394 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
395 395 enabled = Bool(True).tag(config=True)
396 396
397 397 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
398 398 super(PrefilterChecker, self).__init__(
399 399 shell=shell, prefilter_manager=prefilter_manager, **kwargs
400 400 )
401 401 self.prefilter_manager.register_checker(self)
402 402
403 403 def check(self, line_info):
404 404 """Inspect line_info and return a handler instance or None."""
405 405 return None
406 406
407 407 def __repr__(self):
408 408 return "<%s(priority=%r, enabled=%r)>" % (
409 409 self.__class__.__name__, self.priority, self.enabled)
410 410
411 411
412 412 class EmacsChecker(PrefilterChecker):
413 413
414 414 priority = Integer(100).tag(config=True)
415 415 enabled = Bool(False).tag(config=True)
416 416
417 417 def check(self, line_info):
418 418 "Emacs ipython-mode tags certain input lines."
419 419 if line_info.line.endswith('# PYTHON-MODE'):
420 420 return self.prefilter_manager.get_handler_by_name('emacs')
421 421 else:
422 422 return None
423 423
424 424
425 425 class MacroChecker(PrefilterChecker):
426 426
427 427 priority = Integer(250).tag(config=True)
428 428
429 429 def check(self, line_info):
430 430 obj = self.shell.user_ns.get(line_info.ifun)
431 431 if isinstance(obj, Macro):
432 432 return self.prefilter_manager.get_handler_by_name('macro')
433 433 else:
434 434 return None
435 435
436 436
437 437 class IPyAutocallChecker(PrefilterChecker):
438 438
439 439 priority = Integer(300).tag(config=True)
440 440
441 441 def check(self, line_info):
442 442 "Instances of IPyAutocall in user_ns get autocalled immediately"
443 443 obj = self.shell.user_ns.get(line_info.ifun, None)
444 444 if isinstance(obj, IPyAutocall):
445 445 obj.set_ip(self.shell)
446 446 return self.prefilter_manager.get_handler_by_name('auto')
447 447 else:
448 448 return None
449 449
450 450
451 451 class AssignmentChecker(PrefilterChecker):
452 452
453 453 priority = Integer(600).tag(config=True)
454 454
455 455 def check(self, line_info):
456 456 """Check to see if user is assigning to a var for the first time, in
457 457 which case we want to avoid any sort of automagic / autocall games.
458 458
459 459 This allows users to assign to either alias or magic names true python
460 460 variables (the magic/alias systems always take second seat to true
461 461 python code). E.g. ls='hi', or ls,that=1,2"""
462 462 if line_info.the_rest:
463 463 if line_info.the_rest[0] in '=,':
464 464 return self.prefilter_manager.get_handler_by_name('normal')
465 465 else:
466 466 return None
467 467
468 468
469 469 class AutoMagicChecker(PrefilterChecker):
470 470
471 471 priority = Integer(700).tag(config=True)
472 472
473 473 def check(self, line_info):
474 474 """If the ifun is magic, and automagic is on, run it. Note: normal,
475 475 non-auto magic would already have been triggered via '%' in
476 476 check_esc_chars. This just checks for automagic. Also, before
477 477 triggering the magic handler, make sure that there is nothing in the
478 478 user namespace which could shadow it."""
479 479 if not self.shell.automagic or not self.shell.find_magic(line_info.ifun):
480 480 return None
481 481
482 482 # We have a likely magic method. Make sure we should actually call it.
483 483 if line_info.continue_prompt and not self.prefilter_manager.multi_line_specials:
484 484 return None
485 485
486 486 head = line_info.ifun.split('.',1)[0]
487 487 if is_shadowed(head, self.shell):
488 488 return None
489 489
490 490 return self.prefilter_manager.get_handler_by_name('magic')
491 491
492 492
493 493 class PythonOpsChecker(PrefilterChecker):
494 494
495 495 priority = Integer(900).tag(config=True)
496 496
497 497 def check(self, line_info):
498 498 """If the 'rest' of the line begins with a function call or pretty much
499 499 any python operator, we should simply execute the line (regardless of
500 500 whether or not there's a possible autocall expansion). This avoids
501 501 spurious (and very confusing) geattr() accesses."""
502 502 if line_info.the_rest and line_info.the_rest[0] in '!=()<>,+*/%^&|':
503 503 return self.prefilter_manager.get_handler_by_name('normal')
504 504 else:
505 505 return None
506 506
507 507
508 508 class AutocallChecker(PrefilterChecker):
509 509
510 510 priority = Integer(1000).tag(config=True)
511 511
512 function_name_regexp = CRegExp(re_fun_name).tag(config=True,
513 help="RegExp to identify potential function names.")
514 exclude_regexp = CRegExp(re_exclude_auto).tag(config=True,
515 help="RegExp to exclude strings with this start from autocalling.")
512 function_name_regexp = CRegExp(re_fun_name,
513 help="RegExp to identify potential function names."
514 ).tag(config=True)
515 exclude_regexp = CRegExp(re_exclude_auto,
516 help="RegExp to exclude strings with this start from autocalling."
517 ).tag(config=True)
516 518
517 519 def check(self, line_info):
518 520 "Check if the initial word/function is callable and autocall is on."
519 521 if not self.shell.autocall:
520 522 return None
521 523
522 524 oinfo = line_info.ofind(self.shell) # This can mutate state via getattr
523 525 if not oinfo['found']:
524 526 return None
525 527
526 528 if callable(oinfo['obj']) \
527 529 and (not self.exclude_regexp.match(line_info.the_rest)) \
528 530 and self.function_name_regexp.match(line_info.ifun):
529 531 return self.prefilter_manager.get_handler_by_name('auto')
530 532 else:
531 533 return None
532 534
533 535
534 536 #-----------------------------------------------------------------------------
535 537 # Prefilter handlers
536 538 #-----------------------------------------------------------------------------
537 539
538 540
539 541 class PrefilterHandler(Configurable):
540 542
541 543 handler_name = Unicode('normal')
542 544 esc_strings = List([])
543 545 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
544 546 prefilter_manager = Instance('IPython.core.prefilter.PrefilterManager', allow_none=True)
545 547
546 548 def __init__(self, shell=None, prefilter_manager=None, **kwargs):
547 549 super(PrefilterHandler, self).__init__(
548 550 shell=shell, prefilter_manager=prefilter_manager, **kwargs
549 551 )
550 552 self.prefilter_manager.register_handler(
551 553 self.handler_name,
552 554 self,
553 555 self.esc_strings
554 556 )
555 557
556 558 def handle(self, line_info):
557 559 # print "normal: ", line_info
558 560 """Handle normal input lines. Use as a template for handlers."""
559 561
560 562 # With autoindent on, we need some way to exit the input loop, and I
561 563 # don't want to force the user to have to backspace all the way to
562 564 # clear the line. The rule will be in this case, that either two
563 565 # lines of pure whitespace in a row, or a line of pure whitespace but
564 566 # of a size different to the indent level, will exit the input loop.
565 567 line = line_info.line
566 568 continue_prompt = line_info.continue_prompt
567 569
568 570 if (continue_prompt and
569 571 self.shell.autoindent and
570 572 line.isspace() and
571 573 0 < abs(len(line) - self.shell.indent_current_nsp) <= 2):
572 574 line = ''
573 575
574 576 return line
575 577
576 578 def __str__(self):
577 579 return "<%s(name=%s)>" % (self.__class__.__name__, self.handler_name)
578 580
579 581
580 582 class MacroHandler(PrefilterHandler):
581 583 handler_name = Unicode("macro")
582 584
583 585 def handle(self, line_info):
584 586 obj = self.shell.user_ns.get(line_info.ifun)
585 587 pre_space = line_info.pre_whitespace
586 588 line_sep = "\n" + pre_space
587 589 return pre_space + line_sep.join(obj.value.splitlines())
588 590
589 591
590 592 class MagicHandler(PrefilterHandler):
591 593
592 594 handler_name = Unicode('magic')
593 595 esc_strings = List([ESC_MAGIC])
594 596
595 597 def handle(self, line_info):
596 598 """Execute magic functions."""
597 599 ifun = line_info.ifun
598 600 the_rest = line_info.the_rest
599 601 cmd = '%sget_ipython().magic(%r)' % (line_info.pre_whitespace,
600 602 (ifun + " " + the_rest))
601 603 return cmd
602 604
603 605
604 606 class AutoHandler(PrefilterHandler):
605 607
606 608 handler_name = Unicode('auto')
607 609 esc_strings = List([ESC_PAREN, ESC_QUOTE, ESC_QUOTE2])
608 610
609 611 def handle(self, line_info):
610 612 """Handle lines which can be auto-executed, quoting if requested."""
611 613 line = line_info.line
612 614 ifun = line_info.ifun
613 615 the_rest = line_info.the_rest
614 616 esc = line_info.esc
615 617 continue_prompt = line_info.continue_prompt
616 618 obj = line_info.ofind(self.shell)['obj']
617 619
618 620 # This should only be active for single-line input!
619 621 if continue_prompt:
620 622 return line
621 623
622 624 force_auto = isinstance(obj, IPyAutocall)
623 625
624 626 # User objects sometimes raise exceptions on attribute access other
625 627 # than AttributeError (we've seen it in the past), so it's safest to be
626 628 # ultra-conservative here and catch all.
627 629 try:
628 630 auto_rewrite = obj.rewrite
629 631 except Exception:
630 632 auto_rewrite = True
631 633
632 634 if esc == ESC_QUOTE:
633 635 # Auto-quote splitting on whitespace
634 636 newcmd = '%s("%s")' % (ifun,'", "'.join(the_rest.split()) )
635 637 elif esc == ESC_QUOTE2:
636 638 # Auto-quote whole string
637 639 newcmd = '%s("%s")' % (ifun,the_rest)
638 640 elif esc == ESC_PAREN:
639 641 newcmd = '%s(%s)' % (ifun,",".join(the_rest.split()))
640 642 else:
641 643 # Auto-paren.
642 644 if force_auto:
643 645 # Don't rewrite if it is already a call.
644 646 do_rewrite = not the_rest.startswith('(')
645 647 else:
646 648 if not the_rest:
647 649 # We only apply it to argument-less calls if the autocall
648 650 # parameter is set to 2.
649 651 do_rewrite = (self.shell.autocall >= 2)
650 652 elif the_rest.startswith('[') and hasattr(obj, '__getitem__'):
651 653 # Don't autocall in this case: item access for an object
652 654 # which is BOTH callable and implements __getitem__.
653 655 do_rewrite = False
654 656 else:
655 657 do_rewrite = True
656 658
657 659 # Figure out the rewritten command
658 660 if do_rewrite:
659 661 if the_rest.endswith(';'):
660 662 newcmd = '%s(%s);' % (ifun.rstrip(),the_rest[:-1])
661 663 else:
662 664 newcmd = '%s(%s)' % (ifun.rstrip(), the_rest)
663 665 else:
664 666 normal_handler = self.prefilter_manager.get_handler_by_name('normal')
665 667 return normal_handler.handle(line_info)
666 668
667 669 # Display the rewritten call
668 670 if auto_rewrite:
669 671 self.shell.auto_rewrite_input(newcmd)
670 672
671 673 return newcmd
672 674
673 675
674 676 class EmacsHandler(PrefilterHandler):
675 677
676 678 handler_name = Unicode('emacs')
677 679 esc_strings = List([])
678 680
679 681 def handle(self, line_info):
680 682 """Handle input lines marked by python-mode."""
681 683
682 684 # Currently, nothing is done. Later more functionality can be added
683 685 # here if needed.
684 686
685 687 # The input cache shouldn't be updated
686 688 return line_info.line
687 689
688 690
689 691 #-----------------------------------------------------------------------------
690 692 # Defaults
691 693 #-----------------------------------------------------------------------------
692 694
693 695
694 696 _default_transformers = [
695 697 ]
696 698
697 699 _default_checkers = [
698 700 EmacsChecker,
699 701 MacroChecker,
700 702 IPyAutocallChecker,
701 703 AssignmentChecker,
702 704 AutoMagicChecker,
703 705 PythonOpsChecker,
704 706 AutocallChecker
705 707 ]
706 708
707 709 _default_handlers = [
708 710 PrefilterHandler,
709 711 MacroHandler,
710 712 MagicHandler,
711 713 AutoHandler,
712 714 EmacsHandler
713 715 ]
@@ -1,418 +1,420 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Classes for handling input/output prompts.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez
7 7 * Brian Granger
8 8 * Thomas Kluyver
9 9 """
10 10
11 11 #-----------------------------------------------------------------------------
12 12 # Copyright (C) 2008-2011 The IPython Development Team
13 13 # Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu>
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 24 import re
25 25 import socket
26 26 import sys
27 27 import time
28 28
29 29 from string import Formatter
30 30
31 31 from traitlets.config.configurable import Configurable
32 32 from IPython.core import release
33 33 from IPython.utils import coloransi, py3compat
34 34 from traitlets import Unicode, Instance, Dict, Bool, Int, observe
35 35
36 36 from IPython.utils.PyColorize import LightBGColors, LinuxColors, NoColor
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Color schemes for prompts
40 40 #-----------------------------------------------------------------------------
41 41
42 42 InputColors = coloransi.InputTermColors # just a shorthand
43 43 Colors = coloransi.TermColors # just a shorthand
44 44
45 45 color_lists = dict(normal=Colors(), inp=InputColors(), nocolor=coloransi.NoColors())
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Utilities
49 49 #-----------------------------------------------------------------------------
50 50
51 51 class LazyEvaluate(object):
52 52 """This is used for formatting strings with values that need to be updated
53 53 at that time, such as the current time or working directory."""
54 54 def __init__(self, func, *args, **kwargs):
55 55 self.func = func
56 56 self.args = args
57 57 self.kwargs = kwargs
58 58
59 59 def __call__(self, **kwargs):
60 60 self.kwargs.update(kwargs)
61 61 return self.func(*self.args, **self.kwargs)
62 62
63 63 def __str__(self):
64 64 return str(self())
65 65
66 66 def __unicode__(self):
67 67 return py3compat.unicode_type(self())
68 68
69 69 def __format__(self, format_spec):
70 70 return format(self(), format_spec)
71 71
72 72 def multiple_replace(dict, text):
73 73 """ Replace in 'text' all occurrences of any key in the given
74 74 dictionary by its corresponding value. Returns the new string."""
75 75
76 76 # Function by Xavier Defrang, originally found at:
77 77 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81330
78 78
79 79 # Create a regular expression from the dictionary keys
80 80 regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))
81 81 # For each match, look-up corresponding value in dictionary
82 82 return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
83 83
84 84 #-----------------------------------------------------------------------------
85 85 # Special characters that can be used in prompt templates, mainly bash-like
86 86 #-----------------------------------------------------------------------------
87 87
88 88 # If $HOME isn't defined (Windows), make it an absurd string so that it can
89 89 # never be expanded out into '~'. Basically anything which can never be a
90 90 # reasonable directory name will do, we just want the $HOME -> '~' operation
91 91 # to become a no-op. We pre-compute $HOME here so it's not done on every
92 92 # prompt call.
93 93
94 94 # FIXME:
95 95
96 96 # - This should be turned into a class which does proper namespace management,
97 97 # since the prompt specials need to be evaluated in a certain namespace.
98 98 # Currently it's just globals, which need to be managed manually by code
99 99 # below.
100 100
101 101 # - I also need to split up the color schemes from the prompt specials
102 102 # somehow. I don't have a clean design for that quite yet.
103 103
104 104 HOME = py3compat.str_to_unicode(os.environ.get("HOME","//////:::::ZZZZZ,,,~~~"))
105 105
106 106 # This is needed on FreeBSD, and maybe other systems which symlink /home to
107 107 # /usr/home, but retain the $HOME variable as pointing to /home
108 108 HOME = os.path.realpath(HOME)
109 109
110 110 # We precompute a few more strings here for the prompt_specials, which are
111 111 # fixed once ipython starts. This reduces the runtime overhead of computing
112 112 # prompt strings.
113 113 USER = py3compat.str_to_unicode(os.environ.get("USER",''))
114 114 HOSTNAME = py3compat.str_to_unicode(socket.gethostname())
115 115 HOSTNAME_SHORT = HOSTNAME.split(".")[0]
116 116
117 117 # IronPython doesn't currently have os.getuid() even if
118 118 # os.name == 'posix'; 2/8/2014
119 119 ROOT_SYMBOL = "#" if (os.name=='nt' or sys.platform=='cli' or os.getuid()==0) else "$"
120 120
121 121 prompt_abbreviations = {
122 122 # Prompt/history count
123 123 '%n' : '{color.number}' '{count}' '{color.prompt}',
124 124 r'\#': '{color.number}' '{count}' '{color.prompt}',
125 125 # Just the prompt counter number, WITHOUT any coloring wrappers, so users
126 126 # can get numbers displayed in whatever color they want.
127 127 r'\N': '{count}',
128 128
129 129 # Prompt/history count, with the actual digits replaced by dots or
130 130 # spaces. Used mainly in continuation prompts (prompt_in2).
131 131 r'\D': '{dots}',
132 132 r'\S': '{spaces}',
133 133
134 134 # Current time
135 135 r'\T' : '{time}',
136 136 # Current working directory
137 137 r'\w': '{cwd}',
138 138 # Basename of current working directory.
139 139 # (use os.sep to make this portable across OSes)
140 140 r'\W' : '{cwd_last}',
141 141 # These X<N> are an extension to the normal bash prompts. They return
142 142 # N terms of the path, after replacing $HOME with '~'
143 143 r'\X0': '{cwd_x[0]}',
144 144 r'\X1': '{cwd_x[1]}',
145 145 r'\X2': '{cwd_x[2]}',
146 146 r'\X3': '{cwd_x[3]}',
147 147 r'\X4': '{cwd_x[4]}',
148 148 r'\X5': '{cwd_x[5]}',
149 149 # Y<N> are similar to X<N>, but they show '~' if it's the directory
150 150 # N+1 in the list. Somewhat like %cN in tcsh.
151 151 r'\Y0': '{cwd_y[0]}',
152 152 r'\Y1': '{cwd_y[1]}',
153 153 r'\Y2': '{cwd_y[2]}',
154 154 r'\Y3': '{cwd_y[3]}',
155 155 r'\Y4': '{cwd_y[4]}',
156 156 r'\Y5': '{cwd_y[5]}',
157 157 # Hostname up to first .
158 158 r'\h': HOSTNAME_SHORT,
159 159 # Full hostname
160 160 r'\H': HOSTNAME,
161 161 # Username of current user
162 162 r'\u': USER,
163 163 # Escaped '\'
164 164 '\\\\': '\\',
165 165 # Newline
166 166 r'\n': '\n',
167 167 # Carriage return
168 168 r'\r': '\r',
169 169 # Release version
170 170 r'\v': release.version,
171 171 # Root symbol ($ or #)
172 172 r'\$': ROOT_SYMBOL,
173 173 }
174 174
175 175 #-----------------------------------------------------------------------------
176 176 # More utilities
177 177 #-----------------------------------------------------------------------------
178 178
179 179 def cwd_filt(depth):
180 180 """Return the last depth elements of the current working directory.
181 181
182 182 $HOME is always replaced with '~'.
183 183 If depth==0, the full path is returned."""
184 184
185 185 cwd = py3compat.getcwd().replace(HOME,"~")
186 186 out = os.sep.join(cwd.split(os.sep)[-depth:])
187 187 return out or os.sep
188 188
189 189 def cwd_filt2(depth):
190 190 """Return the last depth elements of the current working directory.
191 191
192 192 $HOME is always replaced with '~'.
193 193 If depth==0, the full path is returned."""
194 194
195 195 full_cwd = py3compat.getcwd()
196 196 cwd = full_cwd.replace(HOME,"~").split(os.sep)
197 197 if '~' in cwd and len(cwd) == depth+1:
198 198 depth += 1
199 199 drivepart = ''
200 200 if sys.platform == 'win32' and len(cwd) > depth:
201 201 drivepart = os.path.splitdrive(full_cwd)[0]
202 202 out = drivepart + '/'.join(cwd[-depth:])
203 203
204 204 return out or os.sep
205 205
206 206 #-----------------------------------------------------------------------------
207 207 # Prompt classes
208 208 #-----------------------------------------------------------------------------
209 209
210 210 lazily_evaluate = {'time': LazyEvaluate(time.strftime, "%H:%M:%S"),
211 211 'cwd': LazyEvaluate(py3compat.getcwd),
212 212 'cwd_last': LazyEvaluate(lambda: py3compat.getcwd().split(os.sep)[-1]),
213 213 'cwd_x': [LazyEvaluate(lambda: py3compat.getcwd().replace(HOME,"~"))] +\
214 214 [LazyEvaluate(cwd_filt, x) for x in range(1,6)],
215 215 'cwd_y': [LazyEvaluate(cwd_filt2, x) for x in range(6)]
216 216 }
217 217
218 218 def _lenlastline(s):
219 219 """Get the length of the last line. More intelligent than
220 220 len(s.splitlines()[-1]).
221 221 """
222 222 if not s or s.endswith(('\n', '\r')):
223 223 return 0
224 224 return len(s.splitlines()[-1])
225 225
226 226
227 227 invisible_chars_re = re.compile('\001[^\001\002]*\002')
228 228 def _invisible_characters(s):
229 229 """
230 230 Get the number of invisible ANSI characters in s. Invisible characters
231 231 must be delimited by \001 and \002.
232 232 """
233 233 return _lenlastline(s) - _lenlastline(invisible_chars_re.sub('', s))
234 234
235 235 class UserNSFormatter(Formatter):
236 236 """A Formatter that falls back on a shell's user_ns and __builtins__ for name resolution"""
237 237 def __init__(self, shell):
238 238 self.shell = shell
239 239
240 240 def get_value(self, key, args, kwargs):
241 241 # try regular formatting first:
242 242 try:
243 243 return Formatter.get_value(self, key, args, kwargs)
244 244 except Exception:
245 245 pass
246 246 # next, look in user_ns and builtins:
247 247 for container in (self.shell.user_ns, __builtins__):
248 248 if key in container:
249 249 return container[key]
250 250 # nothing found, put error message in its place
251 251 return "<ERROR: '%s' not found>" % key
252 252
253 253
254 254 class PromptManager(Configurable):
255 255 """This is the primary interface for producing IPython's prompts."""
256 256 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
257 257
258 258 color_scheme_table = Instance(coloransi.ColorSchemeTable, allow_none=True)
259 259 color_scheme = Unicode('Linux').tag(config=True)
260 260
261 261 @observe('color_scheme')
262 262 def _color_scheme_changed(self, change):
263 263 self.color_scheme_table.set_active_scheme(change['new'])
264 264 for pname in ['in', 'in2', 'out', 'rewrite']:
265 265 # We need to recalculate the number of invisible characters
266 266 self.update_prompt(pname)
267 267
268 268 lazy_evaluate_fields = Dict(help="""
269 269 This maps field names used in the prompt templates to functions which
270 270 will be called when the prompt is rendered. This allows us to include
271 271 things like the current time in the prompts. Functions are only called
272 272 if they are used in the prompt.
273 273 """)
274 274 def _lazy_evaluate_fields_default(self): return lazily_evaluate.copy()
275 275
276 in_template = Unicode('In [\\#]: ').tag(config=True,
277 help="Input prompt. '\\#' will be transformed to the prompt number")
278 in2_template = Unicode(' .\\D.: ').tag(config=True,
279 help="Continuation prompt.")
280 out_template = Unicode('Out[\\#]: ').tag(config=True,
281 help="Output prompt. '\\#' will be transformed to the prompt number")
282
283 justify = Bool(True).tag(config=True, help="""
276 in_template = Unicode('In [\\#]: ',
277 help="Input prompt. '\\#' will be transformed to the prompt number"
278 ).tag(config=True)
279 in2_template = Unicode(' .\\D.: ',
280 help="Continuation prompt.").tag(config=True)
281 out_template = Unicode('Out[\\#]: ',
282 help="Output prompt. '\\#' will be transformed to the prompt number"
283 ).tag(config=True)
284
285 justify = Bool(True, help="""
284 286 If True (default), each prompt will be right-aligned with the
285 287 preceding one.
286 """)
288 """).tag(config=True)
287 289
288 290 # We actually store the expanded templates here:
289 291 templates = Dict()
290 292
291 293 # The number of characters in the last prompt rendered, not including
292 294 # colour characters.
293 295 width = Int()
294 296 txtwidth = Int() # Not including right-justification
295 297
296 298 # The number of characters in each prompt which don't contribute to width
297 299 invisible_chars = Dict()
298 300 def _invisible_chars_default(self):
299 301 return {'in': 0, 'in2': 0, 'out': 0, 'rewrite':0}
300 302
301 303 def __init__(self, shell, **kwargs):
302 304 super(PromptManager, self).__init__(shell=shell, **kwargs)
303 305
304 306 # Prepare colour scheme table
305 307 self.color_scheme_table = coloransi.ColorSchemeTable([NoColor,
306 308 LinuxColors, LightBGColors], self.color_scheme)
307 309
308 310 self._formatter = UserNSFormatter(shell)
309 311 # Prepare templates & numbers of invisible characters
310 312 self.update_prompt('in', self.in_template)
311 313 self.update_prompt('in2', self.in2_template)
312 314 self.update_prompt('out', self.out_template)
313 315 self.update_prompt('rewrite')
314 316 self.on_trait_change(self._update_prompt_trait, ['in_template',
315 317 'in2_template', 'out_template'])
316 318
317 319 def update_prompt(self, name, new_template=None):
318 320 """This is called when a prompt template is updated. It processes
319 321 abbreviations used in the prompt template (like \#) and calculates how
320 322 many invisible characters (ANSI colour escapes) the resulting prompt
321 323 contains.
322 324
323 325 It is also called for each prompt on changing the colour scheme. In both
324 326 cases, traitlets should take care of calling this automatically.
325 327 """
326 328 if new_template is not None:
327 329 self.templates[name] = multiple_replace(prompt_abbreviations, new_template)
328 330 # We count invisible characters (colour escapes) on the last line of the
329 331 # prompt, to calculate the width for lining up subsequent prompts.
330 332 invis_chars = _invisible_characters(self._render(name, color=True))
331 333 self.invisible_chars[name] = invis_chars
332 334
333 335 def _update_prompt_trait(self, traitname, new_template):
334 336 name = traitname[:-9] # Cut off '_template'
335 337 self.update_prompt(name, new_template)
336 338
337 339 def _render(self, name, color=True, **kwargs):
338 340 """Render but don't justify, or update the width or txtwidth attributes.
339 341 """
340 342 if name == 'rewrite':
341 343 return self._render_rewrite(color=color)
342 344
343 345 if color:
344 346 scheme = self.color_scheme_table.active_colors
345 347 if name=='out':
346 348 colors = color_lists['normal']
347 349 colors.number, colors.prompt, colors.normal = \
348 350 scheme.out_number, scheme.out_prompt, scheme.normal
349 351 else:
350 352 colors = color_lists['inp']
351 353 colors.number, colors.prompt, colors.normal = \
352 354 scheme.in_number, scheme.in_prompt, scheme.in_normal
353 355 if name=='in2':
354 356 colors.prompt = scheme.in_prompt2
355 357 else:
356 358 # No color
357 359 colors = color_lists['nocolor']
358 360 colors.number, colors.prompt, colors.normal = '', '', ''
359 361
360 362 count = self.shell.execution_count # Shorthand
361 363 # Build the dictionary to be passed to string formatting
362 364 fmtargs = dict(color=colors, count=count,
363 365 dots="."*len(str(count)), spaces=" "*len(str(count)),
364 366 width=self.width, txtwidth=self.txtwidth)
365 367 fmtargs.update(self.lazy_evaluate_fields)
366 368 fmtargs.update(kwargs)
367 369
368 370 # Prepare the prompt
369 371 prompt = colors.prompt + self.templates[name] + colors.normal
370 372
371 373 # Fill in required fields
372 374 return self._formatter.format(prompt, **fmtargs)
373 375
374 376 def _render_rewrite(self, color=True):
375 377 """Render the ---> rewrite prompt."""
376 378 if color:
377 379 scheme = self.color_scheme_table.active_colors
378 380 # We need a non-input version of these escapes
379 381 color_prompt = scheme.in_prompt.replace("\001","").replace("\002","")
380 382 color_normal = scheme.normal
381 383 else:
382 384 color_prompt, color_normal = '', ''
383 385
384 386 return color_prompt + "-> ".rjust(self.txtwidth, "-") + color_normal
385 387
386 388 def render(self, name, color=True, just=None, **kwargs):
387 389 """
388 390 Render the selected prompt.
389 391
390 392 Parameters
391 393 ----------
392 394 name : str
393 395 Which prompt to render. One of 'in', 'in2', 'out', 'rewrite'
394 396 color : bool
395 397 If True (default), include ANSI escape sequences for a coloured prompt.
396 398 just : bool
397 399 If True, justify the prompt to the width of the last prompt. The
398 400 default is stored in self.justify.
399 401 **kwargs :
400 402 Additional arguments will be passed to the string formatting operation,
401 403 so they can override the values that would otherwise fill in the
402 404 template.
403 405
404 406 Returns
405 407 -------
406 408 A string containing the rendered prompt.
407 409 """
408 410 res = self._render(name, color=color, **kwargs)
409 411
410 412 # Handle justification of prompt
411 413 invis_chars = self.invisible_chars[name] if color else 0
412 414 self.txtwidth = _lenlastline(res) - invis_chars
413 415 just = self.justify if (just is None) else just
414 416 # If the prompt spans more than one line, don't try to justify it:
415 417 if just and name != 'in' and ('\n' not in res) and ('\r' not in res):
416 418 res = res.rjust(self.width + invis_chars)
417 419 self.width = _lenlastline(res) - invis_chars
418 420 return res
@@ -1,463 +1,464 b''
1 1 """IPython terminal interface using prompt_toolkit in place of readline"""
2 2 from __future__ import print_function
3 3
4 4 import os
5 5 import sys
6 6 import signal
7 7 import unicodedata
8 8 from warnings import warn
9 9 from wcwidth import wcwidth
10 10
11 11 from IPython.core.error import TryNext
12 12 from IPython.core.interactiveshell import InteractiveShell
13 13 from IPython.utils.py3compat import PY3, cast_unicode_py2, input
14 14 from IPython.utils.terminal import toggle_set_term_title, set_term_title
15 15 from IPython.utils.process import abbrev_cwd
16 16 from traitlets import Bool, CBool, Unicode, Dict, Integer, observe
17 17
18 18 from prompt_toolkit.completion import Completer, Completion
19 19 from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode
20 20 from prompt_toolkit.filters import HasFocus, HasSelection, Condition, ViInsertMode, EmacsInsertMode
21 21 from prompt_toolkit.history import InMemoryHistory
22 22 from prompt_toolkit.shortcuts import create_prompt_application, create_eventloop, create_prompt_layout
23 23 from prompt_toolkit.interface import CommandLineInterface
24 24 from prompt_toolkit.key_binding.manager import KeyBindingManager
25 25 from prompt_toolkit.keys import Keys
26 26 from prompt_toolkit.layout.lexers import Lexer
27 27 from prompt_toolkit.layout.lexers import PygmentsLexer
28 28 from prompt_toolkit.styles import PygmentsStyle, DynamicStyle
29 29
30 30 from pygments.styles import get_style_by_name, get_all_styles
31 31 from pygments.lexers import Python3Lexer, BashLexer, PythonLexer
32 32 from pygments.token import Token
33 33
34 34 from .pt_inputhooks import get_inputhook_func
35 35 from .interactiveshell import get_default_editor, TerminalMagics
36 36
37 37
38 38 class IPythonPTCompleter(Completer):
39 39 """Adaptor to provide IPython completions to prompt_toolkit"""
40 40 def __init__(self, ipy_completer):
41 41 self.ipy_completer = ipy_completer
42 42
43 43 def get_completions(self, document, complete_event):
44 44 if not document.current_line.strip():
45 45 return
46 46
47 47 used, matches = self.ipy_completer.complete(
48 48 line_buffer=document.current_line,
49 49 cursor_pos=document.cursor_position_col
50 50 )
51 51 start_pos = -len(used)
52 52 for m in matches:
53 53 m = unicodedata.normalize('NFC', m)
54 54
55 55 # When the first character of the completion has a zero length,
56 56 # then it's probably a decomposed unicode character. E.g. caused by
57 57 # the "\dot" completion. Try to compose again with the previous
58 58 # character.
59 59 if wcwidth(m[0]) == 0:
60 60 if document.cursor_position + start_pos > 0:
61 61 char_before = document.text[document.cursor_position + start_pos - 1]
62 62 m = unicodedata.normalize('NFC', char_before + m)
63 63
64 64 # Yield the modified completion instead, if this worked.
65 65 if wcwidth(m[0:1]) == 1:
66 66 yield Completion(m, start_position=start_pos - 1)
67 67 continue
68 68
69 69 # TODO: Use Jedi to determine meta_text
70 70 # (Jedi currently has a bug that results in incorrect information.)
71 71 # meta_text = ''
72 72 # yield Completion(m, start_position=start_pos,
73 73 # display_meta=meta_text)
74 74 yield Completion(m, start_position=start_pos)
75 75
76 76 class IPythonPTLexer(Lexer):
77 77 """
78 78 Wrapper around PythonLexer and BashLexer.
79 79 """
80 80 def __init__(self):
81 81 self.python_lexer = PygmentsLexer(Python3Lexer if PY3 else PythonLexer)
82 82 self.shell_lexer = PygmentsLexer(BashLexer)
83 83
84 84 def lex_document(self, cli, document):
85 85 if document.text.startswith('!'):
86 86 return self.shell_lexer.lex_document(cli, document)
87 87 else:
88 88 return self.python_lexer.lex_document(cli, document)
89 89
90 90
91 91 class TerminalInteractiveShell(InteractiveShell):
92 92 colors_force = True
93 93
94 space_for_menu = Integer(6).tag(config=True, help='Number of line at the bottom of the screen '
95 'to reserve for the completion menu')
94 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
95 'to reserve for the completion menu'
96 ).tag(config=True)
96 97
97 98 def _space_for_menu_changed(self, old, new):
98 99 self._update_layout()
99 100
100 101 pt_cli = None
101 102
102 103 autoedit_syntax = CBool(False).tag(config=True,
103 104 help="auto editing of files with syntax errors.")
104 105
105 106 confirm_exit = CBool(True).tag(config=True,
106 107 help="""
107 108 Set to confirm when you try to exit IPython with an EOF (Control-D
108 109 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
109 110 you can force a direct exit without any confirmation.""",
110 111 )
111 editing_mode = Unicode('emacs').tag(config=True,
112 editing_mode = Unicode('emacs',
112 113 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
113 )
114 ).tag(config=True)
114 115
115 mouse_support = Bool(False).tag(config=True,
116 mouse_support = Bool(False,
116 117 help="Enable mouse support in the prompt"
117 )
118 ).tag(config=True)
118 119
119 highlighting_style = Unicode('default').tag(config=True,
120 highlighting_style = Unicode('default',
120 121 help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles())
121 )
122 ).tag(config=True)
122 123
123 124 def _highlighting_style_changed(self, old, new):
124 125 self._style = self._make_style_from_name(self.highlighting_style)
125 126
126 highlighting_style_overrides = Dict().tag(config=True,
127 highlighting_style_overrides = Dict(
127 128 help="Override highlighting format for specific tokens"
128 )
129 ).tag(config=True)
129 130
130 editor = Unicode(get_default_editor()).tag(config=True,
131 editor = Unicode(get_default_editor(),
131 132 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
132 )
133 ).tag(config=True)
133 134
134 term_title = Bool(True).tag(config=True,
135 term_title = Bool(True,
135 136 help="Automatically set the terminal title"
136 )
137 ).tag(config=True)
137 138
138 display_completions_in_columns = Bool(False).tag(config=True,
139 display_completions_in_columns = Bool(False,
139 140 help="Display a multi column completion menu.",
140 )
141 ).tag(config=True)
141 142
142 143 @observe('term_title')
143 144 def _term_title_changed(self, change):
144 145 self.init_term_title()
145 146
146 147 def init_term_title(self):
147 148 # Enable or disable the terminal title.
148 149 if self.term_title:
149 150 toggle_set_term_title(True)
150 151 set_term_title('IPython: ' + abbrev_cwd())
151 152 else:
152 153 toggle_set_term_title(False)
153 154
154 155 def get_prompt_tokens(self, cli):
155 156 return [
156 157 (Token.Prompt, 'In ['),
157 158 (Token.PromptNum, str(self.execution_count)),
158 159 (Token.Prompt, ']: '),
159 160 ]
160 161
161 162 def get_continuation_tokens(self, cli, width):
162 163 return [
163 164 (Token.Prompt, (' ' * (width - 5)) + '...: '),
164 165 ]
165 166
166 167 def init_prompt_toolkit_cli(self):
167 168 if ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or not sys.stdin.isatty():
168 169 # Fall back to plain non-interactive output for tests.
169 170 # This is very limited, and only accepts a single line.
170 171 def prompt():
171 172 return cast_unicode_py2(input('In [%d]: ' % self.execution_count))
172 173 self.prompt_for_code = prompt
173 174 return
174 175
175 176 kbmanager = KeyBindingManager.for_prompt()
176 177 insert_mode = ViInsertMode() | EmacsInsertMode()
177 178 # Ctrl+J == Enter, seemingly
178 179 @kbmanager.registry.add_binding(Keys.ControlJ,
179 180 filter=(HasFocus(DEFAULT_BUFFER)
180 181 & ~HasSelection()
181 182 & insert_mode
182 183 ))
183 184 def _(event):
184 185 b = event.current_buffer
185 186 d = b.document
186 187 if not (d.on_last_line or d.cursor_position_row >= d.line_count
187 188 - d.empty_line_count_at_the_end()):
188 189 b.newline()
189 190 return
190 191
191 192 status, indent = self.input_splitter.check_complete(d.text)
192 193
193 194 if (status != 'incomplete') and b.accept_action.is_returnable:
194 195 b.accept_action.validate_and_handle(event.cli, b)
195 196 else:
196 197 b.insert_text('\n' + (' ' * (indent or 0)))
197 198
198 199 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER))
199 200 def _reset_buffer(event):
200 201 event.current_buffer.reset()
201 202
202 203 @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER))
203 204 def _reset_search_buffer(event):
204 205 if event.current_buffer.document.text:
205 206 event.current_buffer.reset()
206 207 else:
207 208 event.cli.push_focus(DEFAULT_BUFFER)
208 209
209 210 supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP'))
210 211
211 212 @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend)
212 213 def _suspend_to_bg(event):
213 214 event.cli.suspend_to_background()
214 215
215 216 @Condition
216 217 def cursor_in_leading_ws(cli):
217 218 before = cli.application.buffer.document.current_line_before_cursor
218 219 return (not before) or before.isspace()
219 220
220 221 # Ctrl+I == Tab
221 222 @kbmanager.registry.add_binding(Keys.ControlI,
222 223 filter=(HasFocus(DEFAULT_BUFFER)
223 224 & ~HasSelection()
224 225 & insert_mode
225 226 & cursor_in_leading_ws
226 227 ))
227 228 def _indent_buffer(event):
228 229 event.current_buffer.insert_text(' ' * 4)
229 230
230 231 # Pre-populate history from IPython's history database
231 232 history = InMemoryHistory()
232 233 last_cell = u""
233 234 for __, ___, cell in self.history_manager.get_tail(self.history_load_length,
234 235 include_latest=True):
235 236 # Ignore blank lines and consecutive duplicates
236 237 cell = cell.rstrip()
237 238 if cell and (cell != last_cell):
238 239 history.append(cell)
239 240
240 241 self._style = self._make_style_from_name(self.highlighting_style)
241 242 style = DynamicStyle(lambda: self._style)
242 243
243 244 editing_mode = getattr(EditingMode, self.editing_mode.upper())
244 245
245 246 self._app = create_prompt_application(
246 247 editing_mode=editing_mode,
247 248 key_bindings_registry=kbmanager.registry,
248 249 history=history,
249 250 completer=IPythonPTCompleter(self.Completer),
250 251 enable_history_search=True,
251 252 style=style,
252 253 mouse_support=self.mouse_support,
253 254 **self._layout_options()
254 255 )
255 256 self.pt_cli = CommandLineInterface(self._app,
256 257 eventloop=create_eventloop(self.inputhook))
257 258
258 259 def _make_style_from_name(self, name):
259 260 """
260 261 Small wrapper that make an IPython compatible style from a style name
261 262
262 263 We need that to add style for prompt ... etc.
263 264 """
264 265 style_cls = get_style_by_name(name)
265 266 style_overrides = {
266 267 Token.Prompt: '#009900',
267 268 Token.PromptNum: '#00ff00 bold',
268 269 }
269 270 if name == 'default':
270 271 style_cls = get_style_by_name('default')
271 272 # The default theme needs to be visible on both a dark background
272 273 # and a light background, because we can't tell what the terminal
273 274 # looks like. These tweaks to the default theme help with that.
274 275 style_overrides.update({
275 276 Token.Number: '#007700',
276 277 Token.Operator: 'noinherit',
277 278 Token.String: '#BB6622',
278 279 Token.Name.Function: '#2080D0',
279 280 Token.Name.Class: 'bold #2080D0',
280 281 Token.Name.Namespace: 'bold #2080D0',
281 282 })
282 283 style_overrides.update(self.highlighting_style_overrides)
283 284 style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls,
284 285 style_dict=style_overrides)
285 286
286 287 return style
287 288
288 289 def _layout_options(self):
289 290 """
290 291 Return the current layout option for the current Terminal InteractiveShell
291 292 """
292 293 return {
293 294 'lexer':IPythonPTLexer(),
294 295 'reserve_space_for_menu':self.space_for_menu,
295 296 'get_prompt_tokens':self.get_prompt_tokens,
296 297 'get_continuation_tokens':self.get_continuation_tokens,
297 298 'multiline':True,
298 299 'display_completions_in_columns': self.display_completions_in_columns,
299 300 }
300 301
301 302 def _update_layout(self):
302 303 """
303 304 Ask for a re computation of the application layout, if for example ,
304 305 some configuration options have changed.
305 306 """
306 307 self._app.layout = create_prompt_layout(**self._layout_options())
307 308
308 309 def prompt_for_code(self):
309 310 document = self.pt_cli.run(
310 311 pre_run=self.pre_prompt, reset_current_buffer=True)
311 312 return document.text
312 313
313 314 def init_io(self):
314 315 if sys.platform not in {'win32', 'cli'}:
315 316 return
316 317
317 318 import colorama
318 319 colorama.init()
319 320
320 321 # For some reason we make these wrappers around stdout/stderr.
321 322 # For now, we need to reset them so all output gets coloured.
322 323 # https://github.com/ipython/ipython/issues/8669
323 324 from IPython.utils import io
324 325 io.stdout = io.IOStream(sys.stdout)
325 326 io.stderr = io.IOStream(sys.stderr)
326 327
327 328 def init_magics(self):
328 329 super(TerminalInteractiveShell, self).init_magics()
329 330 self.register_magics(TerminalMagics)
330 331
331 332 def init_alias(self):
332 333 # The parent class defines aliases that can be safely used with any
333 334 # frontend.
334 335 super(TerminalInteractiveShell, self).init_alias()
335 336
336 337 # Now define aliases that only make sense on the terminal, because they
337 338 # need direct access to the console in a way that we can't emulate in
338 339 # GUI or web frontend
339 340 if os.name == 'posix':
340 341 for cmd in ['clear', 'more', 'less', 'man']:
341 342 self.alias_manager.soft_define_alias(cmd, cmd)
342 343
343 344
344 345 def __init__(self, *args, **kwargs):
345 346 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
346 347 self.init_prompt_toolkit_cli()
347 348 self.init_term_title()
348 349 self.keep_running = True
349 350
350 351 def ask_exit(self):
351 352 self.keep_running = False
352 353
353 354 rl_next_input = None
354 355
355 356 def pre_prompt(self):
356 357 if self.rl_next_input:
357 358 self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input)
358 359 self.rl_next_input = None
359 360
360 361 def interact(self):
361 362 while self.keep_running:
362 363 print(self.separate_in, end='')
363 364
364 365 try:
365 366 code = self.prompt_for_code()
366 367 except EOFError:
367 368 if (not self.confirm_exit) \
368 369 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
369 370 self.ask_exit()
370 371
371 372 else:
372 373 if code:
373 374 self.run_cell(code, store_history=True)
374 375 if self.autoedit_syntax and self.SyntaxTB.last_syntax_error:
375 376 self.edit_syntax_error()
376 377
377 378 def mainloop(self):
378 379 # An extra layer of protection in case someone mashing Ctrl-C breaks
379 380 # out of our internal code.
380 381 while True:
381 382 try:
382 383 self.interact()
383 384 break
384 385 except KeyboardInterrupt:
385 386 print("\nKeyboardInterrupt escaped interact()\n")
386 387
387 388 _inputhook = None
388 389 def inputhook(self, context):
389 390 if self._inputhook is not None:
390 391 self._inputhook(context)
391 392
392 393 def enable_gui(self, gui=None):
393 394 if gui:
394 395 self._inputhook = get_inputhook_func(gui)
395 396 else:
396 397 self._inputhook = None
397 398
398 399 # Methods to support auto-editing of SyntaxErrors:
399 400
400 401 def edit_syntax_error(self):
401 402 """The bottom half of the syntax error handler called in the main loop.
402 403
403 404 Loop until syntax error is fixed or user cancels.
404 405 """
405 406
406 407 while self.SyntaxTB.last_syntax_error:
407 408 # copy and clear last_syntax_error
408 409 err = self.SyntaxTB.clear_err_state()
409 410 if not self._should_recompile(err):
410 411 return
411 412 try:
412 413 # may set last_syntax_error again if a SyntaxError is raised
413 414 self.safe_execfile(err.filename, self.user_ns)
414 415 except:
415 416 self.showtraceback()
416 417 else:
417 418 try:
418 419 with open(err.filename) as f:
419 420 # This should be inside a display_trap block and I
420 421 # think it is.
421 422 sys.displayhook(f.read())
422 423 except:
423 424 self.showtraceback()
424 425
425 426 def _should_recompile(self, e):
426 427 """Utility routine for edit_syntax_error"""
427 428
428 429 if e.filename in ('<ipython console>', '<input>', '<string>',
429 430 '<console>', '<BackgroundJob compilation>',
430 431 None):
431 432 return False
432 433 try:
433 434 if (self.autoedit_syntax and
434 435 not self.ask_yes_no(
435 436 'Return to editor to correct syntax error? '
436 437 '[Y/n] ', 'y')):
437 438 return False
438 439 except EOFError:
439 440 return False
440 441
441 442 def int0(x):
442 443 try:
443 444 return int(x)
444 445 except TypeError:
445 446 return 0
446 447
447 448 # always pass integer line and offset values to editor hook
448 449 try:
449 450 self.hooks.fix_error_editor(e.filename,
450 451 int0(e.lineno), int0(e.offset),
451 452 e.msg)
452 453 except TryNext:
453 454 warn('Could not open editor')
454 455 return False
455 456 return True
456 457
457 458 # Run !system commands directly, not through pipes, so terminal programs
458 459 # work correctly.
459 460 system = InteractiveShell.system_raw
460 461
461 462
462 463 if __name__ == '__main__':
463 464 TerminalInteractiveShell.instance().interact()
General Comments 0
You need to be logged in to leave comments. Login now