##// END OF EJS Templates
Fix various small bugs.
Thomas Kluyver -
Show More
@@ -1,592 +1,598 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 os
17 17 import re
18 18 import sqlite3
19 19
20 20 # Our own packages
21 21 from IPython.config.configurable import Configurable
22 22 import IPython.utils.io
23 23
24 24 from IPython.testing import decorators as testdec
25 25 from IPython.utils.io import ask_yes_no
26 26 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
27 27 from IPython.utils.warn import warn
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Classes and functions
31 31 #-----------------------------------------------------------------------------
32 32
33 33 class HistoryManager(Configurable):
34 34 """A class to organize all history-related functionality in one place.
35 35 """
36 36 # Public interface
37 37
38 38 # An instance of the IPython shell we are attached to
39 39 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
40 40 # Lists to hold processed and raw history. These start with a blank entry
41 41 # so that we can index them starting from 1
42 42 input_hist_parsed = List([""])
43 43 input_hist_raw = List([""])
44 44 # A list of directories visited during session
45 45 dir_hist = List()
46 46 # A dict of output history, keyed with ints from the shell's execution count
47 47 output_hist = Dict()
48 48 # String holding the path to the history file
49 49 hist_file = Unicode()
50 50 # The SQLite database
51 51 db = Instance(sqlite3.Connection)
52 52 # The number of the current session in the history database
53 53 session_number = Int()
54 54 # Should we log output to the database? (default no)
55 55 db_log_output = Bool(False, config=True)
56 56 # Write to database every x commands (higher values save disk access & power)
57 57 # Values of 1 or less effectively disable caching.
58 58 db_cache_size = Int(0, config=True)
59 59 # The input and output caches
60 60 db_input_cache = List()
61 61 db_output_cache = List()
62 62
63 63 # Private interface
64 64 # Variables used to store the three last inputs from the user. On each new
65 65 # history update, we populate the user's namespace with these, shifted as
66 66 # necessary.
67 67 _i00, _i, _ii, _iii = '','','',''
68 68
69 69 # A set with all forms of the exit command, so that we don't store them in
70 70 # the history (it's annoying to rewind the first entry and land on an exit
71 71 # call).
72 72 _exit_commands = None
73 73
74 74 def __init__(self, shell, config=None):
75 75 """Create a new history manager associated with a shell instance.
76 76 """
77 77 # We need a pointer back to the shell for various tasks.
78 78 super(HistoryManager, self).__init__(shell=shell, config=config)
79 79
80 80 # list of visited directories
81 81 try:
82 82 self.dir_hist = [os.getcwd()]
83 83 except OSError:
84 84 self.dir_hist = []
85 85
86 86 # Now the history file
87 87 if shell.profile:
88 88 histfname = 'history-%s' % shell.profile
89 89 else:
90 90 histfname = 'history'
91 91 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
92 92 self.init_db()
93 93
94 94 self._i00, self._i, self._ii, self._iii = '','','',''
95 95
96 96 self._exit_commands = set(['Quit', 'quit', 'Exit', 'exit', '%Quit',
97 97 '%quit', '%Exit', '%exit'])
98 98
99 99 def init_db(self):
100 100 """Connect to the database and get new session number."""
101 101 self.db = sqlite3.connect(self.hist_file)
102 102 self.db.execute("""CREATE TABLE IF NOT EXISTS history
103 103 (session integer, line integer, source text, source_raw text,
104 104 PRIMARY KEY (session, line))""")
105 105 # Output history is optional, but ensure the table's there so it can be
106 106 # enabled later.
107 107 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
108 108 (session integer, line integer, output text,
109 109 PRIMARY KEY (session, line))""")
110 110 cur = self.db.execute("""SELECT name FROM sqlite_master WHERE
111 111 type='table' AND name='singletons'""")
112 112 if not cur.fetchone():
113 113 self.db.execute("""CREATE TABLE singletons
114 114 (name text PRIMARY KEY, value)""")
115 115 self.db.execute("""INSERT INTO singletons VALUES
116 116 ('session_number', 1)""")
117 117 self.db.commit()
118 118 cur = self.db.execute("""SELECT value FROM singletons WHERE
119 119 name='session_number'""")
120 120 self.session_number = cur.fetchone()[0]
121 121
122 122 #Increment by one for next session.
123 123 self.db.execute("""UPDATE singletons SET value=? WHERE
124 124 name='session_number'""", (self.session_number+1,))
125 125 self.db.commit()
126 126
127 127 def get_hist_tail(self, n=10, raw=True, output=False):
128 128 """Get the last n lines from the history database."""
129 129 toget = 'source_raw' if raw else 'source'
130 130 sqlfrom = "history"
131 131 if output:
132 132 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
133 133 toget = "history.%s, output_history.output" % toget
134 134 cur = self.db.execute("SELECT session, line, " + toget +\
135 135 " FROM "+sqlfrom+" ORDER BY session DESC, line DESC LIMIT ?", (n,))
136 136 hist = reversed(cur.fetchall())
137 137 if output:
138 138 return ((ses, lin, (inp, out)) for ses, lin, inp, out in hist)
139 139 return hist
140 140
141 141 def get_hist_search(self, pattern="*", raw=True):
142 142 """Search the database using unix glob-style matching (wildcards * and
143 143 ?, escape using \).
144 144
145 145 Returns
146 146 -------
147 147 An iterator over tuples: (session, line_number, command)
148 148 """
149 toget = "source_raw" if raw else source
149 toget = "source_raw" if raw else "source"
150 150 return self.db.execute("SELECT session, line, " +toget+ \
151 "FROM history WHERE" +toget+ "GLOB ?", (pattern,))
151 " FROM history WHERE " +toget+ " GLOB ?", (pattern,))
152 152
153 153 def _get_hist_session(self, start=1, stop=None, raw=True, output=False):
154 154 """Get input and output history from the current session. Called by
155 155 get_history, and takes similar parameters."""
156 156 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
157 157
158 158 n = len(input_hist)
159 159 if start < 0:
160 160 start += n
161 161 if not stop:
162 162 stop = n
163 163 elif stop < 0:
164 164 stop += n
165 165
166 166 for i in range(start, stop):
167 167 if output:
168 168 line = (input_hist[i], repr(self.output_hist.get(i)))
169 169 else:
170 170 line = input_hist[i]
171 171 yield (0, i, line)
172 172
173 173 def get_history(self, session=0, start=1, stop=None, raw=True,output=False):
174 174 """Retrieve input by session.
175 175
176 176 Parameters
177 177 ----------
178 178 session : int
179 179 Session number to retrieve. The current session is 0, and negative
180 180 numbers count back from current session, so -1 is previous session.
181 181 start : int
182 182 First line to retrieve.
183 183 stop : int
184 184 End of line range (excluded from output itself). If None, retrieve
185 185 to the end of the session.
186 186 raw : bool
187 187 If True, return untranslated input
188 188 output : bool
189 189 If True, attempt to include output. This will be 'real' Python
190 190 objects for the current session, or text reprs from previous
191 191 sessions if db_log_output was enabled at the time. Where no output
192 192 is found, None is used.
193 193
194 194 Returns
195 195 -------
196 196 An iterator over the desired lines. Each line is a 3-tuple, either
197 197 (session, line, input) if output is False, or
198 198 (session, line, (input, output)) if output is True.
199 199 """
200 200 if session == 0 or session==self.session_number: # Current session
201 201 return self._get_hist_session(start, stop, raw, output)
202 202 if session < 0:
203 203 session += self.session_number
204 204
205 205 # Assemble the SQL query:
206 206 sqlfrom = "history"
207 207 toget = 'source_raw' if raw else 'source'
208 208 if output:
209 209 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
210 210 toget = "history.%s, output_history.output" % toget
211 211 if stop:
212 212 lineclause = "line >= ? AND line < ?"
213 213 params = (session, start, stop)
214 214 else:
215 215 lineclause = "line>=?"
216 216 params = (session, start)
217 217
218 218 cur = self.db.execute("""SELECT session, line, %s FROM %s WHERE
219 219 session==? AND %s""" %(toget, sqlfrom, lineclause), params)
220 220 if output: # Regroup into 3-tuples
221 221 return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
222 222 return cur
223 223
224 224 def get_hist_from_rangestr(self, rangestr, raw=True, output=False):
225 225 """Get lines of history from a string of ranges, as used by magic
226 226 commands %hist, %save, %macro, etc."""
227 227 for sess, s, e in extract_hist_ranges(rangestr):
228 228 for line in self.get_history(sess, s, e, raw=raw, output=output):
229 229 yield line
230 230
231 231 def store_inputs(self, line_num, source, source_raw=None):
232 232 """Store source and raw input in history and create input cache
233 233 variables _i*.
234 234
235 235 Parameters
236 236 ----------
237 237 line_num : int
238 238 The prompt number of this input.
239 239
240 240 source : str
241 241 Python input.
242 242
243 243 source_raw : str, optional
244 244 If given, this is the raw input without any IPython transformations
245 245 applied to it. If not given, ``source`` is used.
246 246 """
247 247 if source_raw is None:
248 248 source_raw = source
249 249
250 250 # do not store exit/quit commands
251 251 if source_raw.strip() in self._exit_commands:
252 252 return
253 253
254 254 self.input_hist_parsed.append(source.rstrip())
255 255 self.input_hist_raw.append(source_raw.rstrip())
256 256
257 257 self.db_input_cache.append((self.session_number, line_num,
258 258 source, source_raw))
259 259 # Trigger to flush cache and write to DB.
260 260 if len(self.db_input_cache) >= self.db_cache_size:
261 261 self.writeout_cache()
262 262
263 263 # update the auto _i variables
264 264 self._iii = self._ii
265 265 self._ii = self._i
266 266 self._i = self._i00
267 267 self._i00 = source_raw
268 268
269 269 # hackish access to user namespace to create _i1,_i2... dynamically
270 270 new_i = '_i%s' % line_num
271 271 to_main = {'_i': self._i,
272 272 '_ii': self._ii,
273 273 '_iii': self._iii,
274 274 new_i : self._i00 }
275 275 self.shell.user_ns.update(to_main)
276 276
277 277 def store_output(self, line_num, output):
278 278 if not self.db_log_output:
279 279 return
280 280 db_row = (self.session_number, line_num, output)
281 281 if self.db_cache_size > 1:
282 282 self.db_output_cache.append(db_row)
283 283 else:
284 284 with self.db:
285 285 self.db.execute("INSERT INTO output_history VALUES (?,?,?)", db_row)
286 286
287 287 def writeout_cache(self):
288 288 #print(self.db_input_cache)
289 289 with self.db:
290 290 self.db.executemany("INSERT INTO history VALUES (?, ?, ?, ?)",
291 291 self.db_input_cache)
292 292 self.db.executemany("INSERT INTO output_history VALUES (?, ?, ?)",
293 293 self.db_output_cache)
294 294 self.db_input_cache = []
295 295 self.db_output_cache = []
296 296
297 297 def sync_inputs(self):
298 298 """Ensure raw and translated histories have same length."""
299 299 lr = len(self.input_hist_raw)
300 300 lp = len(self.input_hist_parsed)
301 301 if lp < lr:
302 302 self.input_hist_raw[:lr-lp] = []
303 303 elif lr < lp:
304 304 self.input_hist_parsed[:lp-lr] = []
305 305
306 306 def reset(self, new_session=True):
307 307 """Clear the current session's history, and (optionally) start a new
308 308 session."""
309 309 self.input_hist_parsed[:] = [""]
310 310 self.input_hist_raw[:] = [""]
311 311 self.output_hist.clear()
312 312 # The directory history can't be completely empty
313 313 self.dir_hist[:] = [os.getcwd()]
314 314
315 315 if new_session:
316 316 self.writeout_cache()
317 317 self.init_db() # Get new session number
318 318
319 319 # To match, e.g. ~5/8-~2/3
320 320 range_re = re.compile(r"""
321 321 ((?P<startsess>~?\d+)/)?
322 322 (?P<start>\d+) # Only the start line num is compulsory
323 323 ((?P<sep>[\-:])
324 324 ((?P<endsess>~?\d+)/)?
325 325 (?P<end>\d+))?
326 326 """, re.VERBOSE)
327 327
328 328 def extract_hist_ranges(ranges_str):
329 329 """Turn a string of history ranges into 3-tuples of (session, start, stop).
330 330
331 331 Examples
332 332 --------
333 333 list(extract_input_ranges("~8/5-~7/4 2"))
334 334 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
335 335 """
336 336 for range_str in ranges_str.split():
337 337 rmatch = range_re.match(range_str)
338 338 start = int(rmatch.group("start"))
339 339 end = rmatch.group("end")
340 340 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
341 341 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
342 342 end += 1
343 343 startsess = rmatch.group("startsess") or "0"
344 344 endsess = rmatch.group("endsess") or startsess
345 345 startsess = int(startsess.replace("~","-"))
346 346 endsess = int(endsess.replace("~","-"))
347 347 assert endsess >= startsess
348 348
349 349 if endsess == startsess:
350 350 yield (startsess, start, end)
351 351 continue
352 352 # Multiple sessions in one range:
353 353 yield (startsess, start, None)
354 354 for sess in range(startsess+1, endsess):
355 355 yield (sess, 1, None)
356 356 yield (endsess, 1, end)
357 357
358 358 def _format_lineno(session, line):
359 359 """Helper function to format line numbers properly."""
360 360 if session == 0:
361 361 return str(line)
362 362 return "%s#%s" % (session, line)
363 363
364 364 @testdec.skip_doctest
365 365 def magic_history(self, parameter_s = ''):
366 366 """Print input history (_i<n> variables), with most recent last.
367 367
368 368 %history -> print at most 40 inputs (some may be multi-line)\\
369 369 %history n -> print at most n inputs\\
370 370 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
371 371
372 372 By default, input history is printed without line numbers so it can be
373 373 directly pasted into an editor.
374 374
375 375 With -n, each input's number <n> is shown, and is accessible as the
376 376 automatically generated variable _i<n> as well as In[<n>]. Multi-line
377 377 statements are printed starting at a new line for easy copy/paste.
378 378
379 379 Options:
380 380
381 381 -n: print line numbers for each input.
382 382 This feature is only available if numbered prompts are in use.
383 383
384 384 -o: also print outputs for each input.
385 385
386 386 -p: print classic '>>>' python prompts before each input. This is useful
387 387 for making documentation, and in conjunction with -o, for producing
388 388 doctest-ready output.
389 389
390 390 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
391 391
392 392 -t: print the 'translated' history, as IPython understands it. IPython
393 393 filters your input and converts it all into valid Python source before
394 394 executing it (things like magics or aliases are turned into function
395 395 calls, for example). With this option, you'll see the native history
396 396 instead of the user-entered version: '%cd /' will be seen as
397 397 'get_ipython().magic("%cd /")' instead of '%cd /'.
398 398
399 399 -g: treat the arg as a pattern to grep for in (full) history.
400 400 This includes the saved history (almost all commands ever written).
401 401 Use '%hist -g' to show full saved history (may be very long).
402 402
403 403 -l: get the last n lines from all sessions. Specify n as a single arg, or
404 404 the default is the last 10 lines.
405 405
406 406 -f FILENAME: instead of printing the output to the screen, redirect it to
407 407 the given file. The file is always overwritten, though IPython asks for
408 408 confirmation first if it already exists.
409 409
410 410 Examples
411 411 --------
412 412 ::
413 413
414 414 In [6]: %hist -n 4 6
415 415 4:a = 12
416 416 5:print a**2
417 417
418 418 """
419 419
420 420 if not self.shell.displayhook.do_full_cache:
421 421 print('This feature is only available if numbered prompts are in use.')
422 422 return
423 423 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
424 424
425 425 # For brevity
426 426 history_manager = self.shell.history_manager
427
428 def _format_lineno(session, line):
429 """Helper function to format line numbers properly."""
430 if session in (0, history_manager.session_number):
431 return str(line)
432 return "%s/%s" % (session, line)
427 433
428 434 # Check if output to specific file was requested.
429 435 try:
430 436 outfname = opts['f']
431 437 except KeyError:
432 438 outfile = IPython.utils.io.Term.cout # default
433 439 # We don't want to close stdout at the end!
434 440 close_at_end = False
435 441 else:
436 442 if os.path.exists(outfname):
437 443 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
438 444 print('Aborting.')
439 445 return
440 446
441 447 outfile = open(outfname,'w')
442 448 close_at_end = True
443 449
444 450 print_nums = 'n' in opts
445 451 get_output = 'o' in opts
446 452 pyprompts = 'p' in opts
447 453 # Raw history is the default
448 454 raw = not('t' in opts)
449 455
450 456 default_length = 40
451 457 pattern = None
452 458
453 459 # Glob search:
454 460 if 'g' in opts:
455 461 pattern = "*" + args + "*" if args else "*"
456 462
457 463 # Display:
458 464 matches_current_session = []
459 465 for session, line, s in history_manager.get_hist_search(pattern, raw):
460 466 if session == history_manager.session_number:
461 matches_current_session.append(line, s)
467 matches_current_session.append((line, s))
462 468 continue
463 print("%d#%d: %s" %(session, line, s.expandtabs(4)), file=outfile)
469 print("%d/%d: %s" %(session, line, s.expandtabs(4)), file=outfile)
464 470 if matches_current_session:
465 471 print("=== Current session: ===", file=outfile)
466 472 for line, s in matches_current_session:
467 473 print("%d: %s" %(line, s.expandtabs(4)), file=outfile)
468 474 return
469 475
470 476 if 'l' in opts: # Get 'tail'
471 477 try:
472 478 n = int(args)
473 479 except ValueError, IndexError:
474 480 n = 10
475 481 hist = history_manager.get_hist_tail(n, raw=raw, output=get_output)
476 482 else:
477 483 if args: # Get history by ranges
478 484 hist = history_manager.get_hist_from_rangestr(args, raw, get_output)
479 485 else: # Just get history for the current session
480 486 hist = history_manager.get_history(raw=raw, output=get_output)
481 487 # Pull hist into a list, so we can get the widest number in it.
482 488 hist = list(hist)
483 489 if not hist:
484 490 return
485 491
486 492 width = max(len(_format_lineno(s, l)) for s, l, _ in hist)
487 493
488 494 for session, lineno, inline in hist:
489 495 # Print user history with tabs expanded to 4 spaces. The GUI clients
490 496 # use hard tabs for easier usability in auto-indented code, but we want
491 497 # to produce PEP-8 compliant history for safe pasting into an editor.
492 498 if get_output:
493 499 inline, output = inline
494 500 inline = inline.expandtabs(4).rstrip()
495 501
496 502 if pattern is not None and not fnmatch.fnmatch(inline, pattern):
497 503 continue
498 504
499 505 multiline = "\n" in inline
500 506 line_sep = '\n' if multiline else ''
501 507 if print_nums:
502 print('%s:%s' % (_format_lineno(session, lineno).ljust(width),
508 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
503 509 line_sep), file=outfile, end='')
504 510 if pyprompts:
505 511 print(">>> ", end="", file=outfile)
506 512 if multiline:
507 513 inline = "\n... ".join(inline.splitlines()) + "\n..."
508 514 print(inline, file=outfile)
509 515 if get_output and output:
510 516 print(output, file=outfile)
511 517
512 518 if close_at_end:
513 519 outfile.close()
514 520
515 521 # %hist is an alternative name
516 522 magic_hist = magic_history
517 523
518 524
519 525 def rep_f(self, arg):
520 526 r""" Repeat a command, or get command to input line for editing
521 527
522 528 - %rep (no arguments):
523 529
524 530 Place a string version of last computation result (stored in the special '_'
525 531 variable) to the next input prompt. Allows you to create elaborate command
526 532 lines without using copy-paste::
527 533
528 534 $ l = ["hei", "vaan"]
529 535 $ "".join(l)
530 536 ==> heivaan
531 537 $ %rep
532 538 $ heivaan_ <== cursor blinking
533 539
534 540 %rep 45
535 541
536 542 Place history line 45 to next input prompt. Use %hist to find out the
537 543 number.
538 544
539 545 %rep 1-4 6-7 3
540 546
541 547 Repeat the specified lines immediately. Input slice syntax is the same as
542 548 in %macro and %save.
543 549
544 550 %rep foo
545 551
546 552 Place the most recent line that has the substring "foo" to next input.
547 553 (e.g. 'svn ci -m foobar').
548 554 """
549 555
550 556 opts,args = self.parse_options(arg,'',mode='list')
551 557 if not args:
552 558 self.set_next_input(str(self.shell.user_ns["_"]))
553 559 return
554 560
555 561 if len(args) == 1 and not '-' in args[0]:
556 562 arg = args[0]
557 563 if len(arg) > 1 and arg.startswith('0'):
558 564 # get from shadow hist
559 565 num = int(arg[1:])
560 566 line = self.shell.shadowhist.get(num)
561 567 self.set_next_input(str(line))
562 568 return
563 569 try:
564 570 num = int(args[0])
565 571 self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip())
566 572 return
567 573 except ValueError:
568 574 pass
569 575
570 576 for h in reversed(self.shell.input_hist_raw):
571 577 if 'rep' in h:
572 578 continue
573 579 if fnmatch.fnmatch(h,'*' + arg + '*'):
574 580 self.set_next_input(str(h).rstrip())
575 581 return
576 582
577 583 try:
578 584 lines = self.extract_input_slices(args, True)
579 585 print("lines", lines)
580 586 self.run_cell(lines)
581 587 except ValueError:
582 588 print("Not found in recent history:", args)
583 589
584 590
585 591 def init_ipython(ip):
586 592 ip.define_magic("rep",rep_f)
587 593 ip.define_magic("hist",magic_hist)
588 594 ip.define_magic("history",magic_history)
589 595
590 596 # XXX - ipy_completers are in quarantine, need to be updated to new apis
591 597 #import ipy_completers
592 598 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now