##// END OF EJS Templates
Small changes as suggested by Fernando.
Thomas Kluyver -
Show More
@@ -1,587 +1,592 b''
1 1 """ History related magics and functionality """
2 2 #-----------------------------------------------------------------------------
3 3 # Copyright (C) 2010 The IPython Development Team.
4 4 #
5 5 # Distributed under the terms of the BSD License.
6 6 #
7 7 # The full license is in the file COPYING.txt, distributed with this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 from __future__ import print_function
14 14
15 15 # Stdlib imports
16 16 import atexit
17 17 import fnmatch
18 18 import json
19 19 import os
20 20 import sys
21 21 import threading
22 22 import time
23 23
24 24 # Our own packages
25 25 import IPython.utils.io
26 26
27 27 from IPython.testing import decorators as testdec
28 28 from IPython.utils.pickleshare import PickleShareDB
29 29 from IPython.utils.io import ask_yes_no
30 30 from IPython.utils.warn import warn
31 31
32 32 #-----------------------------------------------------------------------------
33 33 # Classes and functions
34 34 #-----------------------------------------------------------------------------
35 35
36 36 class HistoryManager(object):
37 37 """A class to organize all history-related functionality in one place.
38 38 """
39 39 # Public interface
40 40
41 41 # An instance of the IPython shell we are attached to
42 42 shell = None
43 43 # A list to hold processed history
44 44 input_hist_parsed = None
45 45 # A list to hold raw history (as typed by user)
46 46 input_hist_raw = None
47 47 # A list of directories visited during session
48 48 dir_hist = None
49 49 # A dict of output history, keyed with ints from the shell's execution count
50 50 output_hist = None
51 51 # String with path to the history file
52 52 hist_file = None
53 53 # PickleShareDB instance holding the raw data for the shadow history
54 54 shadow_db = None
55 55 # ShadowHist instance with the actual shadow history
56 56 shadow_hist = None
57 57
58 # Offset so the first line of the current session is #1. Can be
59 # updated after loading history from file.
60 session_offset = -1
61
58 62 # Private interface
59 63 # Variables used to store the three last inputs from the user. On each new
60 64 # history update, we populate the user's namespace with these, shifted as
61 65 # necessary.
62 66 _i00, _i, _ii, _iii = '','','',''
63 67
64 68 # A set with all forms of the exit command, so that we don't store them in
65 69 # the history (it's annoying to rewind the first entry and land on an exit
66 70 # call).
67 71 _exit_commands = None
68 72
69 73 def __init__(self, shell, load_history=False):
70 74 """Create a new history manager associated with a shell instance.
71 75
72 If load_history is true, it will load the history from file and set the
73 session offset so that the next line typed can be retrieved as #1.
76 Parameters
77 ----------
78 load_history: bool, optional
79 If True, history will be loaded from file, and the session
80 offset set, so that the next line entered can be retrieved
81 as #1.
74 82 """
75 83 # We need a pointer back to the shell for various tasks.
76 84 self.shell = shell
77 85
78 86 # List of input with multi-line handling.
79 87 self.input_hist_parsed = []
80 88 # This one will hold the 'raw' input history, without any
81 89 # pre-processing. This will allow users to retrieve the input just as
82 90 # it was exactly typed in by the user, with %hist -r.
83 91 self.input_hist_raw = []
84
85 # Offset so the first line of the current session is #1
86 self.session_offset = -1
87 92
88 93 # list of visited directories
89 94 try:
90 95 self.dir_hist = [os.getcwd()]
91 96 except OSError:
92 97 self.dir_hist = []
93 98
94 99 # dict of output history
95 100 self.output_hist = {}
96 101
97 102 # Now the history file
98 103 if shell.profile:
99 104 histfname = 'history-%s' % shell.profile
100 105 else:
101 106 histfname = 'history'
102 107 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.json')
103 108
104 109 # Objects related to shadow history management
105 110 self._init_shadow_hist()
106 111
107 112 self._i00, self._i, self._ii, self._iii = '','','',''
108 113
109 114 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
110 115 '%quit', '%Exit', '%exit'])
111 116
112 117 # Object is fully initialized, we can now call methods on it.
113 118
114 119 if load_history:
115 120 self.reload_history()
116 121 self.session_offset = len(self.input_hist_raw) -1
117 122
118 123 # Create and start the autosaver.
119 124 self.autosave_flag = threading.Event()
120 125 self.autosave_timer = HistorySaveThread(self.autosave_flag, 60)
121 126 self.autosave_timer.start()
122 127 # Register the autosave handler to be triggered as a post execute
123 128 # callback.
124 129 self.shell.register_post_execute(self.autosave_if_due)
125 130
126 131
127 132 def _init_shadow_hist(self):
128 133 try:
129 134 self.shadow_db = PickleShareDB(os.path.join(
130 135 self.shell.ipython_dir, 'db'))
131 136 except UnicodeDecodeError:
132 137 print("Your ipython_dir can't be decoded to unicode!")
133 138 print("Please set HOME environment variable to something that")
134 139 print(r"only has ASCII characters, e.g. c:\home")
135 140 print("Now it is", self.ipython_dir)
136 141 sys.exit()
137 142 self.shadow_hist = ShadowHist(self.shadow_db, self.shell)
138 143
139 144 def populate_readline_history(self):
140 145 """Populate the readline history from the raw history.
141 146
142 147 We only store one copy of the raw history, which is persisted to a json
143 148 file on disk. The readline history is repopulated from the contents of
144 149 this file."""
145 150
146 151 try:
147 152 self.shell.readline.clear_history()
148 153 except AttributeError:
149 154 pass
150 155 else:
151 156 for h in self.input_hist_raw:
152 157 if not h.isspace():
153 158 for line in h.splitlines():
154 159 self.shell.readline.add_history(line)
155 160
156 161 def save_history(self):
157 162 """Save input history to a file (via readline library)."""
158 163 hist = dict(raw=self.input_hist_raw, #[-self.shell.history_length:],
159 164 parsed=self.input_hist_parsed) #[-self.shell.history_length:])
160 165 with open(self.hist_file,'wt') as hfile:
161 166 json.dump(hist, hfile,
162 167 sort_keys=True, indent=4)
163 168
164 169 def autosave_if_due(self):
165 170 """Check if the autosave event is set; if so, save history. We do it
166 171 this way so that the save takes place in the main thread."""
167 172 if self.autosave_flag.is_set():
168 173 self.save_history()
169 174 self.autosave_flag.clear()
170 175
171 176 def reload_history(self):
172 177 """Reload the input history from disk file."""
173 178
174 179 with open(self.hist_file,'rt') as hfile:
175 180 try:
176 181 hist = json.load(hfile)
177 182 except ValueError: # Ignore it if JSON is corrupt.
178 183 return
179 184 self.input_hist_parsed = hist['parsed']
180 185 self.input_hist_raw = hist['raw']
181 186 if self.shell.has_readline:
182 187 self.populate_readline_history()
183 188
184 189 def get_history(self, index=None, raw=False, output=True):
185 190 """Get the history list.
186 191
187 192 Get the input and output history.
188 193
189 194 Parameters
190 195 ----------
191 196 index : n or (n1, n2) or None
192 197 If n, then the last n entries. If a tuple, then all in
193 198 range(n1, n2). If None, then all entries. Raises IndexError if
194 199 the format of index is incorrect.
195 200 raw : bool
196 201 If True, return the raw input.
197 202 output : bool
198 203 If True, then return the output as well.
199 204
200 205 Returns
201 206 -------
202 207 If output is True, then return a dict of tuples, keyed by the prompt
203 208 numbers and with values of (input, output). If output is False, then
204 209 a dict, keyed by the prompt number with the values of input.
205 210 """
206 211 if raw:
207 212 input_hist = self.input_hist_raw
208 213 else:
209 214 input_hist = self.input_hist_parsed
210 215 if output:
211 216 output_hist = self.output_hist
212 217
213 218 n = len(input_hist)
214 219 offset = self.session_offset
215 220 if index is None:
216 221 start=offset+1; stop=n
217 222 elif isinstance(index, int):
218 223 start=n-index; stop=n
219 224 elif len(index) == 2:
220 225 start = index[0] + offset
221 226 stop = index[1] + offset
222 227 else:
223 228 raise IndexError('Not a valid index for the input history: %r'
224 229 % index)
225 230 hist = {}
226 231 for i in range(start, stop):
227 232 if output:
228 233 hist[i-offset] = (input_hist[i], output_hist.get(i-offset))
229 234 else:
230 235 hist[i-offset] = input_hist[i]
231 236 return hist
232 237
233 238 def store_inputs(self, source, source_raw=None):
234 239 """Store source and raw input in history and create input cache
235 240 variables _i*.
236 241
237 242 Parameters
238 243 ----------
239 244 source : str
240 245 Python input.
241 246
242 247 source_raw : str, optional
243 248 If given, this is the raw input without any IPython transformations
244 249 applied to it. If not given, ``source`` is used.
245 250 """
246 251 if source_raw is None:
247 252 source_raw = source
248 253
249 254 # do not store exit/quit commands
250 255 if source_raw.strip() in self._exit_commands:
251 256 return
252 257
253 258 self.input_hist_parsed.append(source.rstrip())
254 259 self.input_hist_raw.append(source_raw.rstrip())
255 260 self.shadow_hist.add(source)
256 261
257 262 # update the auto _i variables
258 263 self._iii = self._ii
259 264 self._ii = self._i
260 265 self._i = self._i00
261 266 self._i00 = source_raw
262 267
263 268 # hackish access to user namespace to create _i1,_i2... dynamically
264 269 new_i = '_i%s' % self.shell.execution_count
265 270 to_main = {'_i': self._i,
266 271 '_ii': self._ii,
267 272 '_iii': self._iii,
268 273 new_i : self._i00 }
269 274 self.shell.user_ns.update(to_main)
270 275
271 276 def sync_inputs(self):
272 277 """Ensure raw and translated histories have same length."""
273 278 if len(self.input_hist_parsed) != len (self.input_hist_raw):
274 279 self.input_hist_raw[:] = self.input_hist_parsed
275 280
276 281 def reset(self):
277 282 """Clear all histories managed by this object."""
278 283 self.input_hist_parsed[:] = []
279 284 self.input_hist_raw[:] = []
280 285 self.output_hist.clear()
281 286 # The directory history can't be completely empty
282 287 self.dir_hist[:] = [os.getcwd()]
283 288
284 289 class HistorySaveThread(threading.Thread):
285 290 """This thread makes IPython save history periodically.
286 291
287 292 Without this class, IPython would only save the history on a clean exit.
288 293 This saves the history periodically (the current default is once per
289 294 minute), so that it is not lost in the event of a crash.
290 295
291 296 The implementation sets an event to indicate that history should be saved.
292 297 The actual save is carried out after executing a user command, to avoid
293 298 thread issues.
294 299 """
295 300 daemon = True
296 301
297 302 def __init__(self, autosave_flag, time_interval=60):
298 303 threading.Thread.__init__(self)
299 304 self.time_interval = time_interval
300 305 self.autosave_flag = autosave_flag
301 306 self.exit_now = threading.Event()
302 307 # Ensure the thread is stopped tidily when exiting normally
303 308 atexit.register(self.stop)
304 309
305 310 def run(self):
306 311 while True:
307 312 self.exit_now.wait(self.time_interval)
308 313 if self.exit_now.is_set():
309 314 break
310 315 self.autosave_flag.set()
311 316
312 317 def stop(self):
313 318 """Safely and quickly stop the autosave timer thread."""
314 319 self.exit_now.set()
315 320 self.join()
316 321
317 322 @testdec.skip_doctest
318 323 def magic_history(self, parameter_s = ''):
319 324 """Print input history (_i<n> variables), with most recent last.
320 325
321 326 %history -> print at most 40 inputs (some may be multi-line)\\
322 327 %history n -> print at most n inputs\\
323 328 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
324 329
325 330 By default, input history is printed without line numbers so it can be
326 331 directly pasted into an editor.
327 332
328 333 With -n, each input's number <n> is shown, and is accessible as the
329 334 automatically generated variable _i<n> as well as In[<n>]. Multi-line
330 335 statements are printed starting at a new line for easy copy/paste.
331 336
332 337 Options:
333 338
334 339 -n: print line numbers for each input.
335 340 This feature is only available if numbered prompts are in use.
336 341
337 342 -o: also print outputs for each input.
338 343
339 344 -p: print classic '>>>' python prompts before each input. This is useful
340 345 for making documentation, and in conjunction with -o, for producing
341 346 doctest-ready output.
342 347
343 348 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
344 349
345 350 -t: print the 'translated' history, as IPython understands it. IPython
346 351 filters your input and converts it all into valid Python source before
347 352 executing it (things like magics or aliases are turned into function
348 353 calls, for example). With this option, you'll see the native history
349 354 instead of the user-entered version: '%cd /' will be seen as
350 355 'get_ipython().magic("%cd /")' instead of '%cd /'.
351 356
352 357 -g: treat the arg as a pattern to grep for in (full) history.
353 358 This includes the "shadow history" (almost all commands ever written).
354 359 Use '%hist -g' to show full shadow history (may be very long).
355 360 In shadow history, every index nuwber starts with 0.
356 361
357 362 -f FILENAME: instead of printing the output to the screen, redirect it to
358 363 the given file. The file is always overwritten, though IPython asks for
359 364 confirmation first if it already exists.
360 365
361 366 Examples
362 367 --------
363 368 ::
364 369
365 370 In [6]: %hist -n 4 6
366 371 4:a = 12
367 372 5:print a**2
368 373
369 374 """
370 375
371 376 if not self.shell.displayhook.do_full_cache:
372 377 print('This feature is only available if numbered prompts are in use.')
373 378 return
374 379 opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list')
375 380
376 381 # For brevity
377 382 history_manager = self.shell.history_manager
378 383
379 384 # Check if output to specific file was requested.
380 385 try:
381 386 outfname = opts['f']
382 387 except KeyError:
383 388 outfile = IPython.utils.io.Term.cout # default
384 389 # We don't want to close stdout at the end!
385 390 close_at_end = False
386 391 else:
387 392 if os.path.exists(outfname):
388 393 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
389 394 print('Aborting.')
390 395 return
391 396
392 397 outfile = open(outfname,'w')
393 398 close_at_end = True
394 399
395 400 print_nums = 'n' in opts
396 401 print_outputs = 'o' in opts
397 402 pyprompts = 'p' in opts
398 403 # Raw history is the default
399 404 raw = not('t' in opts)
400 405
401 406 default_length = 40
402 407 pattern = None
403 408 if 'g' in opts:
404 409 index = None
405 410 parts = parameter_s.split(None, 1)
406 411 if len(parts) == 1:
407 412 parts += '*'
408 413 head, pattern = parts
409 414 pattern = "*" + pattern + "*"
410 415 elif len(args) == 0:
411 416 index = None
412 417 elif len(args) == 1:
413 418 index = int(args[0])
414 419 elif len(args) == 2:
415 420 index = map(int, args)
416 421 else:
417 422 warn('%hist takes 0, 1 or 2 arguments separated by spaces.')
418 423 print(self.magic_hist.__doc__, file=IPython.utils.io.Term.cout)
419 424 return
420 425
421 426 hist = history_manager.get_history(index, raw, print_outputs)
422 427
423 428 width = len(str(max(hist.iterkeys())))
424 429 line_sep = ['','\n']
425 430
426 431 found = False
427 432 if pattern is not None:
428 433 sh = history_manager.shadow_hist.all()
429 434 for idx, s in sh:
430 435 if fnmatch.fnmatch(s, pattern):
431 436 print("0%d: %s" %(idx, s.expandtabs(4)), file=outfile)
432 437 found = True
433 438
434 439 if found:
435 440 print("===", file=outfile)
436 441 print("shadow history ends, fetch by %rep <number> (must start with 0)",
437 442 file=outfile)
438 443 print("=== start of normal history ===", file=outfile)
439 444
440 445 for in_num, inline in sorted(hist.iteritems()):
441 446 # Print user history with tabs expanded to 4 spaces. The GUI clients
442 447 # use hard tabs for easier usability in auto-indented code, but we want
443 448 # to produce PEP-8 compliant history for safe pasting into an editor.
444 449 if print_outputs:
445 450 inline, output = inline
446 451 inline = inline.expandtabs(4).rstrip()
447 452
448 453 if pattern is not None and not fnmatch.fnmatch(inline, pattern):
449 454 continue
450 455
451 456 multiline = "\n" in inline
452 457 if print_nums:
453 458 print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]),
454 459 file=outfile, end='')
455 460 if pyprompts:
456 461 print(">>> ", end="", file=outfile)
457 462 if multiline:
458 463 inline = "\n... ".join(inline.splitlines()) + "\n..."
459 464 print(inline, file=outfile)
460 465 if print_outputs and output:
461 466 print(repr(output), file=outfile)
462 467
463 468 if close_at_end:
464 469 outfile.close()
465 470
466 471 # %hist is an alternative name
467 472 magic_hist = magic_history
468 473
469 474
470 475 def rep_f(self, arg):
471 476 r""" Repeat a command, or get command to input line for editing
472 477
473 478 - %rep (no arguments):
474 479
475 480 Place a string version of last computation result (stored in the special '_'
476 481 variable) to the next input prompt. Allows you to create elaborate command
477 482 lines without using copy-paste::
478 483
479 484 $ l = ["hei", "vaan"]
480 485 $ "".join(l)
481 486 ==> heivaan
482 487 $ %rep
483 488 $ heivaan_ <== cursor blinking
484 489
485 490 %rep 45
486 491
487 492 Place history line 45 to next input prompt. Use %hist to find out the
488 493 number.
489 494
490 495 %rep 1-4 6-7 3
491 496
492 497 Repeat the specified lines immediately. Input slice syntax is the same as
493 498 in %macro and %save.
494 499
495 500 %rep foo
496 501
497 502 Place the most recent line that has the substring "foo" to next input.
498 503 (e.g. 'svn ci -m foobar').
499 504 """
500 505
501 506 opts,args = self.parse_options(arg,'',mode='list')
502 507 if not args:
503 508 self.set_next_input(str(self.shell.user_ns["_"]))
504 509 return
505 510
506 511 if len(args) == 1 and not '-' in args[0]:
507 512 arg = args[0]
508 513 if len(arg) > 1 and arg.startswith('0'):
509 514 # get from shadow hist
510 515 num = int(arg[1:])
511 516 line = self.shell.shadowhist.get(num)
512 517 self.set_next_input(str(line))
513 518 return
514 519 try:
515 520 num = int(args[0])
516 521 self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip())
517 522 return
518 523 except ValueError:
519 524 pass
520 525
521 526 for h in reversed(self.shell.input_hist_raw):
522 527 if 'rep' in h:
523 528 continue
524 529 if fnmatch.fnmatch(h,'*' + arg + '*'):
525 530 self.set_next_input(str(h).rstrip())
526 531 return
527 532
528 533 try:
529 534 lines = self.extract_input_slices(args, True)
530 535 print("lines", lines)
531 536 self.run_cell(lines)
532 537 except ValueError:
533 538 print("Not found in recent history:", args)
534 539
535 540
536 541 _sentinel = object()
537 542
538 543 class ShadowHist(object):
539 544 def __init__(self, db, shell):
540 545 # cmd => idx mapping
541 546 self.curidx = 0
542 547 self.db = db
543 548 self.disabled = False
544 549 self.shell = shell
545 550
546 551 def inc_idx(self):
547 552 idx = self.db.get('shadowhist_idx', 1)
548 553 self.db['shadowhist_idx'] = idx + 1
549 554 return idx
550 555
551 556 def add(self, ent):
552 557 if self.disabled:
553 558 return
554 559 try:
555 560 old = self.db.hget('shadowhist', ent, _sentinel)
556 561 if old is not _sentinel:
557 562 return
558 563 newidx = self.inc_idx()
559 564 #print("new", newidx) # dbg
560 565 self.db.hset('shadowhist',ent, newidx)
561 566 except:
562 567 self.shell.showtraceback()
563 568 print("WARNING: disabling shadow history")
564 569 self.disabled = True
565 570
566 571 def all(self):
567 572 d = self.db.hdict('shadowhist')
568 573 items = [(i,s) for (s,i) in d.iteritems()]
569 574 items.sort()
570 575 return items
571 576
572 577 def get(self, idx):
573 578 all = self.all()
574 579
575 580 for k, v in all:
576 581 if k == idx:
577 582 return v
578 583
579 584
580 585 def init_ipython(ip):
581 586 ip.define_magic("rep",rep_f)
582 587 ip.define_magic("hist",magic_hist)
583 588 ip.define_magic("history",magic_history)
584 589
585 590 # XXX - ipy_completers are in quarantine, need to be updated to new apis
586 591 #import ipy_completers
587 592 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now