##// END OF EJS Templates
Add locks for input and output caches.
Thomas Kluyver -
Show More
@@ -1,783 +1,791 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 datetime
18 18 import json
19 19 import os
20 20 import re
21 21 import sqlite3
22 22 import threading
23 23
24 24 from collections import defaultdict
25 from contextlib import nested
25 26
26 27 # Our own packages
27 28 from IPython.config.configurable import Configurable
28 29 import IPython.utils.io
29 30
30 31 from IPython.testing import decorators as testdec
31 32 from IPython.utils.io import ask_yes_no
32 33 from IPython.utils.traitlets import Bool, Dict, Instance, Int, List, Unicode
33 34 from IPython.utils.warn import warn
34 35
35 36 #-----------------------------------------------------------------------------
36 37 # Classes and functions
37 38 #-----------------------------------------------------------------------------
38 39
39 40 class HistoryManager(Configurable):
40 41 """A class to organize all history-related functionality in one place.
41 42 """
42 43 # Public interface
43 44
44 45 # An instance of the IPython shell we are attached to
45 46 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
46 47 # Lists to hold processed and raw history. These start with a blank entry
47 48 # so that we can index them starting from 1
48 49 input_hist_parsed = List([""])
49 50 input_hist_raw = List([""])
50 51 # A list of directories visited during session
51 52 dir_hist = List()
52 53 def _dir_hist_default(self):
53 54 try:
54 55 return [os.getcwd()]
55 56 except OSError:
56 57 return []
57 58
58 59 # A dict of output history, keyed with ints from the shell's
59 60 # execution count. If there are several outputs from one command,
60 61 # only the last one is stored.
61 62 output_hist = Dict()
62 63 # Contains all outputs, in lists of reprs.
63 64 output_hist_reprs = Instance(defaultdict, args=(list,))
64 65
65 66 # String holding the path to the history file
66 67 hist_file = Unicode(config=True)
67 68
68 69 # The SQLite database
69 70 db = Instance(sqlite3.Connection)
70 71 # The number of the current session in the history database
71 72 session_number = Int()
72 73 # Should we log output to the database? (default no)
73 74 db_log_output = Bool(False, config=True)
74 75 # Write to database every x commands (higher values save disk access & power)
75 76 # Values of 1 or less effectively disable caching.
76 77 db_cache_size = Int(0, config=True)
77 78 # The input and output caches
78 79 db_input_cache = List()
79 80 db_output_cache = List()
80 81
81 82 # History saving in separate thread
82 83 save_thread = Instance('IPython.core.history.HistorySavingThread')
83 84 save_flag = Instance(threading._Event)
84 85
85 86 # Private interface
86 87 # Variables used to store the three last inputs from the user. On each new
87 88 # history update, we populate the user's namespace with these, shifted as
88 89 # necessary.
89 90 _i00 = Unicode(u'')
90 91 _i = Unicode(u'')
91 92 _ii = Unicode(u'')
92 93 _iii = Unicode(u'')
93 94
94 95 # A set with all forms of the exit command, so that we don't store them in
95 96 # the history (it's annoying to rewind the first entry and land on an exit
96 97 # call).
97 98 _exit_commands = Instance(set, args=(['Quit', 'quit', 'Exit', 'exit',
98 99 '%Quit', '%quit', '%Exit', '%exit'],))
99 100
100 101 def __init__(self, shell, config=None, **traits):
101 102 """Create a new history manager associated with a shell instance.
102 103 """
103 104 # We need a pointer back to the shell for various tasks.
104 105 super(HistoryManager, self).__init__(shell=shell, config=config,
105 106 **traits)
106 107
107 108 if self.hist_file == u'':
108 109 # No one has set the hist_file, yet.
109 110 if shell.profile:
110 111 histfname = 'history-%s' % shell.profile
111 112 else:
112 113 histfname = 'history'
113 114 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.sqlite')
114 115
115 116 try:
116 117 self.init_db()
117 118 except sqlite3.DatabaseError:
118 119 if os.path.isfile(self.hist_file):
119 120 # Try to move the file out of the way.
120 121 newpath = os.path.join(self.shell.ipython_dir, "hist-corrupt.sqlite")
121 122 os.rename(self.hist_file, newpath)
122 123 print("ERROR! History file wasn't a valid SQLite database.",
123 124 "It was moved to %s" % newpath, "and a new file created.")
124 125 self.init_db()
125 126 else:
126 127 # The hist_file is probably :memory: or something else.
127 128 raise
128 129
129 130 self.save_flag = threading.Event()
131 self.db_input_cache_lock = threading.Lock()
132 self.db_output_cache_lock = threading.Lock()
130 133 self.save_thread = HistorySavingThread(self)
131 134 self.save_thread.start()
132 135
133 136 self.new_session()
134 137
135 138
136 139 def init_db(self):
137 140 """Connect to the database, and create tables if necessary."""
138 141 self.db = sqlite3.connect(self.hist_file)
139 142 self.db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
140 143 primary key autoincrement, start timestamp,
141 144 end timestamp, num_cmds integer, remark text)""")
142 145 self.db.execute("""CREATE TABLE IF NOT EXISTS history
143 146 (session integer, line integer, source text, source_raw text,
144 147 PRIMARY KEY (session, line))""")
145 148 # Output history is optional, but ensure the table's there so it can be
146 149 # enabled later.
147 150 self.db.execute("""CREATE TABLE IF NOT EXISTS output_history
148 151 (session integer, line integer, output text,
149 152 PRIMARY KEY (session, line))""")
150 153 self.db.commit()
151 154
152 155 def new_session(self):
153 156 """Get a new session number."""
154 157 with self.db:
155 158 cur = self.db.execute("""INSERT INTO sessions VALUES (NULL, ?, NULL,
156 159 NULL, "") """, (datetime.datetime.now(),))
157 160 self.session_number = cur.lastrowid
158 161
159 162 def end_session(self):
160 163 """Close the database session, filling in the end time and line count."""
161 164 self.writeout_cache()
162 165 with self.db:
163 166 self.db.execute("""UPDATE sessions SET end=?, num_cmds=? WHERE
164 167 session==?""", (datetime.datetime.now(),
165 168 len(self.input_hist_parsed)-1, self.session_number))
166 169 self.session_number = 0
167 170
168 171 def name_session(self, name):
169 172 """Give the current session a name in the history database."""
170 173 with self.db:
171 174 self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
172 175 (name, self.session_number))
173 176
174 177 def reset(self, new_session=True):
175 178 """Clear the session history, releasing all object references, and
176 179 optionally open a new session."""
177 180 self.output_hist.clear()
178 181 # The directory history can't be completely empty
179 182 self.dir_hist[:] = [os.getcwd()]
180 183
181 184 if new_session:
182 185 if self.session_number:
183 186 self.end_session()
184 187 self.input_hist_parsed[:] = [""]
185 188 self.input_hist_raw[:] = [""]
186 189 self.new_session()
187 190
188 191 ## -------------------------------
189 192 ## Methods for retrieving history:
190 193 ## -------------------------------
191 194 def _run_sql(self, sql, params, raw=True, output=False):
192 195 """Prepares and runs an SQL query for the history database.
193 196
194 197 Parameters
195 198 ----------
196 199 sql : str
197 200 Any filtering expressions to go after SELECT ... FROM ...
198 201 params : tuple
199 202 Parameters passed to the SQL query (to replace "?")
200 203 raw, output : bool
201 204 See :meth:`get_range`
202 205
203 206 Returns
204 207 -------
205 208 Tuples as :meth:`get_range`
206 209 """
207 210 toget = 'source_raw' if raw else 'source'
208 211 sqlfrom = "history"
209 212 if output:
210 213 sqlfrom = "history LEFT JOIN output_history USING (session, line)"
211 214 toget = "history.%s, output_history.output" % toget
212 215 cur = self.db.execute("SELECT session, line, %s FROM %s " %\
213 216 (toget, sqlfrom) + sql, params)
214 217 if output: # Regroup into 3-tuples, and parse JSON
215 218 loads = lambda out: json.loads(out) if out else None
216 219 return ((ses, lin, (inp, loads(out))) \
217 220 for ses, lin, inp, out in cur)
218 221 return cur
219 222
220 223
221 224 def get_tail(self, n=10, raw=True, output=False, include_latest=False):
222 225 """Get the last n lines from the history database.
223 226
224 227 Parameters
225 228 ----------
226 229 n : int
227 230 The number of lines to get
228 231 raw, output : bool
229 232 See :meth:`get_range`
230 233 include_latest : bool
231 234 If False (default), n+1 lines are fetched, and the latest one
232 235 is discarded. This is intended to be used where the function
233 236 is called by a user command, which it should not return.
234 237
235 238 Returns
236 239 -------
237 240 Tuples as :meth:`get_range`
238 241 """
239 242 self.writeout_cache()
240 243 if not include_latest:
241 244 n += 1
242 245 cur = self._run_sql("ORDER BY session DESC, line DESC LIMIT ?",
243 246 (n,), raw=raw, output=output)
244 247 if not include_latest:
245 248 return reversed(list(cur)[1:])
246 249 return reversed(list(cur))
247 250
248 251 def search(self, pattern="*", raw=True, search_raw=True,
249 252 output=False):
250 253 """Search the database using unix glob-style matching (wildcards
251 254 * and ?).
252 255
253 256 Parameters
254 257 ----------
255 258 pattern : str
256 259 The wildcarded pattern to match when searching
257 260 search_raw : bool
258 261 If True, search the raw input, otherwise, the parsed input
259 262 raw, output : bool
260 263 See :meth:`get_range`
261 264
262 265 Returns
263 266 -------
264 267 Tuples as :meth:`get_range`
265 268 """
266 269 tosearch = "source_raw" if search_raw else "source"
267 270 if output:
268 271 tosearch = "history." + tosearch
269 272 self.writeout_cache()
270 273 return self._run_sql("WHERE %s GLOB ?" % tosearch, (pattern,),
271 274 raw=raw, output=output)
272 275
273 276 def _get_range_session(self, start=1, stop=None, raw=True, output=False):
274 277 """Get input and output history from the current session. Called by
275 278 get_range, and takes similar parameters."""
276 279 input_hist = self.input_hist_raw if raw else self.input_hist_parsed
277 280
278 281 n = len(input_hist)
279 282 if start < 0:
280 283 start += n
281 284 if not stop:
282 285 stop = n
283 286 elif stop < 0:
284 287 stop += n
285 288
286 289 for i in range(start, stop):
287 290 if output:
288 291 line = (input_hist[i], self.output_hist_reprs.get(i))
289 292 else:
290 293 line = input_hist[i]
291 294 yield (0, i, line)
292 295
293 296 def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
294 297 """Retrieve input by session.
295 298
296 299 Parameters
297 300 ----------
298 301 session : int
299 302 Session number to retrieve. The current session is 0, and negative
300 303 numbers count back from current session, so -1 is previous session.
301 304 start : int
302 305 First line to retrieve.
303 306 stop : int
304 307 End of line range (excluded from output itself). If None, retrieve
305 308 to the end of the session.
306 309 raw : bool
307 310 If True, return untranslated input
308 311 output : bool
309 312 If True, attempt to include output. This will be 'real' Python
310 313 objects for the current session, or text reprs from previous
311 314 sessions if db_log_output was enabled at the time. Where no output
312 315 is found, None is used.
313 316
314 317 Returns
315 318 -------
316 319 An iterator over the desired lines. Each line is a 3-tuple, either
317 320 (session, line, input) if output is False, or
318 321 (session, line, (input, output)) if output is True.
319 322 """
320 323 if session == 0 or session==self.session_number: # Current session
321 324 return self._get_range_session(start, stop, raw, output)
322 325 if session < 0:
323 326 session += self.session_number
324 327
325 328 if stop:
326 329 lineclause = "line >= ? AND line < ?"
327 330 params = (session, start, stop)
328 331 else:
329 332 lineclause = "line>=?"
330 333 params = (session, start)
331 334
332 335 return self._run_sql("WHERE session==? AND %s""" % lineclause,
333 336 params, raw=raw, output=output)
334 337
335 338 def get_range_by_str(self, rangestr, raw=True, output=False):
336 339 """Get lines of history from a string of ranges, as used by magic
337 340 commands %hist, %save, %macro, etc.
338 341
339 342 Parameters
340 343 ----------
341 344 rangestr : str
342 345 A string specifying ranges, e.g. "5 ~2/1-4". See
343 346 :func:`magic_history` for full details.
344 347 raw, output : bool
345 348 As :meth:`get_range`
346 349
347 350 Returns
348 351 -------
349 352 Tuples as :meth:`get_range`
350 353 """
351 354 for sess, s, e in extract_hist_ranges(rangestr):
352 355 for line in self.get_range(sess, s, e, raw=raw, output=output):
353 356 yield line
354 357
355 358 ## ----------------------------
356 359 ## Methods for storing history:
357 360 ## ----------------------------
358 361 def store_inputs(self, line_num, source, source_raw=None):
359 362 """Store source and raw input in history and create input cache
360 363 variables _i*.
361 364
362 365 Parameters
363 366 ----------
364 367 line_num : int
365 368 The prompt number of this input.
366 369
367 370 source : str
368 371 Python input.
369 372
370 373 source_raw : str, optional
371 374 If given, this is the raw input without any IPython transformations
372 375 applied to it. If not given, ``source`` is used.
373 376 """
374 377 if source_raw is None:
375 378 source_raw = source
376 379 source = source.rstrip('\n')
377 380 source_raw = source_raw.rstrip('\n')
378 381
379 382 # do not store exit/quit commands
380 383 if source_raw.strip() in self._exit_commands:
381 384 return
382 385
383 386 self.input_hist_parsed.append(source)
384 387 self.input_hist_raw.append(source_raw)
385 388
386 self.db_input_cache.append((line_num, source, source_raw))
387 # Trigger to flush cache and write to DB.
388 if len(self.db_input_cache) >= self.db_cache_size:
389 self.save_flag.set()
389 with self.db_input_cache_lock:
390 self.db_input_cache.append((line_num, source, source_raw))
391 # Trigger to flush cache and write to DB.
392 if len(self.db_input_cache) >= self.db_cache_size:
393 self.save_flag.set()
390 394
391 395 # update the auto _i variables
392 396 self._iii = self._ii
393 397 self._ii = self._i
394 398 self._i = self._i00
395 399 self._i00 = source_raw
396 400
397 401 # hackish access to user namespace to create _i1,_i2... dynamically
398 402 new_i = '_i%s' % line_num
399 403 to_main = {'_i': self._i,
400 404 '_ii': self._ii,
401 405 '_iii': self._iii,
402 406 new_i : self._i00 }
403 407 self.shell.user_ns.update(to_main)
404 408
405 409 def store_output(self, line_num):
406 410 """If database output logging is enabled, this saves all the
407 411 outputs from the indicated prompt number to the database. It's
408 412 called by run_cell after code has been executed.
409 413
410 414 Parameters
411 415 ----------
412 416 line_num : int
413 417 The line number from which to save outputs
414 418 """
415 419 if (not self.db_log_output) or not self.output_hist_reprs[line_num]:
416 420 return
417 421 output = json.dumps(self.output_hist_reprs[line_num])
418 422
419 self.db_output_cache.append((line_num, output))
423 with self.db_output_cache_lock:
424 self.db_output_cache.append((line_num, output))
420 425 if self.db_cache_size <= 1:
421 426 self.save_flag.set()
422 427
423 428 def _writeout_input_cache(self, conn):
424 429 with conn:
425 430 for line in self.db_input_cache:
426 431 conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
427 432 (self.session_number,)+line)
428 433
429 434 def _writeout_output_cache(self, conn):
430 435 with conn:
431 436 for line in self.db_output_cache:
432 437 conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
433 438 (self.session_number,)+line)
434 439
435 440 def writeout_cache(self, conn=None):
436 441 """Write any entries in the cache to the database."""
437 442 if conn is None:
438 443 conn = self.db
439 try:
440 self._writeout_input_cache(conn)
441 except sqlite3.IntegrityError:
442 self.new_session()
443 print("ERROR! Session/line number was not unique in",
444 "database. History logging moved to new session",
445 self.session_number)
446 try: # Try writing to the new session. If this fails, don't recurse
444
445 with self.db_input_cache_lock:
446 try:
447 447 self._writeout_input_cache(conn)
448 448 except sqlite3.IntegrityError:
449 pass
450 finally:
451 self.db_input_cache = []
452
453 try:
454 self._writeout_output_cache(conn)
455 except sqlite3.IntegrityError:
456 print("!! Session/line number for output was not unique",
457 "in database. Output will not be stored.")
458 finally:
459 self.db_output_cache = []
449 self.new_session()
450 print("ERROR! Session/line number was not unique in",
451 "database. History logging moved to new session",
452 self.session_number)
453 try: # Try writing to the new session. If this fails, don't recurse
454 self._writeout_input_cache(conn)
455 except sqlite3.IntegrityError:
456 pass
457 finally:
458 self.db_input_cache = []
459
460 with self.db_output_cache_lock:
461 try:
462 self._writeout_output_cache(conn)
463 except sqlite3.IntegrityError:
464 print("!! Session/line number for output was not unique",
465 "in database. Output will not be stored.")
466 finally:
467 self.db_output_cache = []
460 468
461 469
462 470 class HistorySavingThread(threading.Thread):
463 471 daemon = True
464 472 stop_now = False
465 473 def __init__(self, history_manager):
466 474 super(HistorySavingThread, self).__init__()
467 475 self.history_manager = history_manager
468 476 atexit.register(self.stop)
469 477
470 478 def run(self):
471 479 # We need a separate db connection per thread:
472 480 self.db = sqlite3.connect(self.history_manager.hist_file)
473 481 while True:
474 482 self.history_manager.save_flag.wait()
475 483 self.history_manager.save_flag.clear()
476 484 self.history_manager.writeout_cache(self.db)
477 485 if self.stop_now:
478 486 return
479 487
480 488 def stop(self):
481 489 self.stop_now = True
482 490 self.history_manager.save_flag.set()
483 491
484 492
485 493 # To match, e.g. ~5/8-~2/3
486 494 range_re = re.compile(r"""
487 495 ((?P<startsess>~?\d+)/)?
488 496 (?P<start>\d+) # Only the start line num is compulsory
489 497 ((?P<sep>[\-:])
490 498 ((?P<endsess>~?\d+)/)?
491 499 (?P<end>\d+))?
492 500 $""", re.VERBOSE)
493 501
494 502 def extract_hist_ranges(ranges_str):
495 503 """Turn a string of history ranges into 3-tuples of (session, start, stop).
496 504
497 505 Examples
498 506 --------
499 507 list(extract_input_ranges("~8/5-~7/4 2"))
500 508 [(-8, 5, None), (-7, 1, 4), (0, 2, 3)]
501 509 """
502 510 for range_str in ranges_str.split():
503 511 rmatch = range_re.match(range_str)
504 512 if not rmatch:
505 513 continue
506 514 start = int(rmatch.group("start"))
507 515 end = rmatch.group("end")
508 516 end = int(end) if end else start+1 # If no end specified, get (a, a+1)
509 517 if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
510 518 end += 1
511 519 startsess = rmatch.group("startsess") or "0"
512 520 endsess = rmatch.group("endsess") or startsess
513 521 startsess = int(startsess.replace("~","-"))
514 522 endsess = int(endsess.replace("~","-"))
515 523 assert endsess >= startsess
516 524
517 525 if endsess == startsess:
518 526 yield (startsess, start, end)
519 527 continue
520 528 # Multiple sessions in one range:
521 529 yield (startsess, start, None)
522 530 for sess in range(startsess+1, endsess):
523 531 yield (sess, 1, None)
524 532 yield (endsess, 1, end)
525 533
526 534 def _format_lineno(session, line):
527 535 """Helper function to format line numbers properly."""
528 536 if session == 0:
529 537 return str(line)
530 538 return "%s#%s" % (session, line)
531 539
532 540 @testdec.skip_doctest
533 541 def magic_history(self, parameter_s = ''):
534 542 """Print input history (_i<n> variables), with most recent last.
535 543
536 544 %history -> print at most 40 inputs (some may be multi-line)\\
537 545 %history n -> print at most n inputs\\
538 546 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
539 547
540 548 By default, input history is printed without line numbers so it can be
541 549 directly pasted into an editor. Use -n to show them.
542 550
543 551 Ranges of history can be indicated using the syntax:
544 552 4 : Line 4, current session
545 553 4-6 : Lines 4-6, current session
546 554 243/1-5: Lines 1-5, session 243
547 555 ~2/7 : Line 7, session 2 before current
548 556 ~8/1-~6/5 : From the first line of 8 sessions ago, to the fifth line
549 557 of 6 sessions ago.
550 558 Multiple ranges can be entered, separated by spaces
551 559
552 560 The same syntax is used by %macro, %save, %edit, %rerun
553 561
554 562 Options:
555 563
556 564 -n: print line numbers for each input.
557 565 This feature is only available if numbered prompts are in use.
558 566
559 567 -o: also print outputs for each input.
560 568
561 569 -p: print classic '>>>' python prompts before each input. This is useful
562 570 for making documentation, and in conjunction with -o, for producing
563 571 doctest-ready output.
564 572
565 573 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
566 574
567 575 -t: print the 'translated' history, as IPython understands it. IPython
568 576 filters your input and converts it all into valid Python source before
569 577 executing it (things like magics or aliases are turned into function
570 578 calls, for example). With this option, you'll see the native history
571 579 instead of the user-entered version: '%cd /' will be seen as
572 580 'get_ipython().magic("%cd /")' instead of '%cd /'.
573 581
574 582 -g: treat the arg as a pattern to grep for in (full) history.
575 583 This includes the saved history (almost all commands ever written).
576 584 Use '%hist -g' to show full saved history (may be very long).
577 585
578 586 -l: get the last n lines from all sessions. Specify n as a single arg, or
579 587 the default is the last 10 lines.
580 588
581 589 -f FILENAME: instead of printing the output to the screen, redirect it to
582 590 the given file. The file is always overwritten, though IPython asks for
583 591 confirmation first if it already exists.
584 592
585 593 Examples
586 594 --------
587 595 ::
588 596
589 597 In [6]: %hist -n 4 6
590 598 4:a = 12
591 599 5:print a**2
592 600
593 601 """
594 602
595 603 if not self.shell.displayhook.do_full_cache:
596 604 print('This feature is only available if numbered prompts are in use.')
597 605 return
598 606 opts,args = self.parse_options(parameter_s,'noprtglf:',mode='string')
599 607
600 608 # For brevity
601 609 history_manager = self.shell.history_manager
602 610
603 611 def _format_lineno(session, line):
604 612 """Helper function to format line numbers properly."""
605 613 if session in (0, history_manager.session_number):
606 614 return str(line)
607 615 return "%s/%s" % (session, line)
608 616
609 617 # Check if output to specific file was requested.
610 618 try:
611 619 outfname = opts['f']
612 620 except KeyError:
613 621 outfile = IPython.utils.io.Term.cout # default
614 622 # We don't want to close stdout at the end!
615 623 close_at_end = False
616 624 else:
617 625 if os.path.exists(outfname):
618 626 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
619 627 print('Aborting.')
620 628 return
621 629
622 630 outfile = open(outfname,'w')
623 631 close_at_end = True
624 632
625 633 print_nums = 'n' in opts
626 634 get_output = 'o' in opts
627 635 pyprompts = 'p' in opts
628 636 # Raw history is the default
629 637 raw = not('t' in opts)
630 638
631 639 default_length = 40
632 640 pattern = None
633 641
634 642 if 'g' in opts: # Glob search
635 643 pattern = "*" + args + "*" if args else "*"
636 644 hist = history_manager.search(pattern, raw=raw, output=get_output)
637 645 elif 'l' in opts: # Get 'tail'
638 646 try:
639 647 n = int(args)
640 648 except ValueError, IndexError:
641 649 n = 10
642 650 hist = history_manager.get_tail(n, raw=raw, output=get_output)
643 651 else:
644 652 if args: # Get history by ranges
645 653 hist = history_manager.get_range_by_str(args, raw, get_output)
646 654 else: # Just get history for the current session
647 655 hist = history_manager.get_range(raw=raw, output=get_output)
648 656
649 657 # We could be displaying the entire history, so let's not try to pull it
650 658 # into a list in memory. Anything that needs more space will just misalign.
651 659 width = 4
652 660
653 661 for session, lineno, inline in hist:
654 662 # Print user history with tabs expanded to 4 spaces. The GUI clients
655 663 # use hard tabs for easier usability in auto-indented code, but we want
656 664 # to produce PEP-8 compliant history for safe pasting into an editor.
657 665 if get_output:
658 666 inline, output = inline
659 667 inline = inline.expandtabs(4).rstrip()
660 668
661 669 multiline = "\n" in inline
662 670 line_sep = '\n' if multiline else ' '
663 671 if print_nums:
664 672 print('%s:%s' % (_format_lineno(session, lineno).rjust(width),
665 673 line_sep), file=outfile, end='')
666 674 if pyprompts:
667 675 print(">>> ", end="", file=outfile)
668 676 if multiline:
669 677 inline = "\n... ".join(inline.splitlines()) + "\n..."
670 678 print(inline, file=outfile)
671 679 if get_output and output:
672 680 print("\n".join(output), file=outfile)
673 681
674 682 if close_at_end:
675 683 outfile.close()
676 684
677 685
678 686 def magic_rep(self, arg):
679 687 r""" Repeat a command, or get command to input line for editing
680 688
681 689 - %rep (no arguments):
682 690
683 691 Place a string version of last computation result (stored in the special '_'
684 692 variable) to the next input prompt. Allows you to create elaborate command
685 693 lines without using copy-paste::
686 694
687 695 In[1]: l = ["hei", "vaan"]
688 696 In[2]: "".join(l)
689 697 Out[2]: heivaan
690 698 In[3]: %rep
691 699 In[4]: heivaan_ <== cursor blinking
692 700
693 701 %rep 45
694 702
695 703 Place history line 45 on the next input prompt. Use %hist to find
696 704 out the number.
697 705
698 706 %rep 1-4
699 707
700 708 Combine the specified lines into one cell, and place it on the next
701 709 input prompt. See %history for the slice syntax.
702 710
703 711 %rep foo+bar
704 712
705 713 If foo+bar can be evaluated in the user namespace, the result is
706 714 placed at the next input prompt. Otherwise, the history is searched
707 715 for lines which contain that substring, and the most recent one is
708 716 placed at the next input prompt.
709 717 """
710 718 if not arg: # Last output
711 719 self.set_next_input(str(self.shell.user_ns["_"]))
712 720 return
713 721 # Get history range
714 722 histlines = self.history_manager.get_range_by_str(arg)
715 723 cmd = "\n".join(x[2] for x in histlines)
716 724 if cmd:
717 725 self.set_next_input(cmd.rstrip())
718 726 return
719 727
720 728 try: # Variable in user namespace
721 729 cmd = str(eval(arg, self.shell.user_ns))
722 730 except Exception: # Search for term in history
723 731 histlines = self.history_manager.search("*"+arg+"*")
724 732 for h in reversed([x[2] for x in histlines]):
725 733 if 'rep' in h:
726 734 continue
727 735 self.set_next_input(h.rstrip())
728 736 return
729 737 else:
730 738 self.set_next_input(cmd.rstrip())
731 739 print("Couldn't evaluate or find in history:", arg)
732 740
733 741 def magic_rerun(self, parameter_s=''):
734 742 """Re-run previous input
735 743
736 744 By default, you can specify ranges of input history to be repeated
737 745 (as with %history). With no arguments, it will repeat the last line.
738 746
739 747 Options:
740 748
741 749 -l <n> : Repeat the last n lines of input, not including the
742 750 current command.
743 751
744 752 -g foo : Repeat the most recent line which contains foo
745 753 """
746 754 opts, args = self.parse_options(parameter_s, 'l:g:', mode='string')
747 755 if "l" in opts: # Last n lines
748 756 n = int(opts['l'])
749 757 hist = self.history_manager.get_tail(n)
750 758 elif "g" in opts: # Search
751 759 p = "*"+opts['g']+"*"
752 760 hist = list(self.history_manager.search(p))
753 761 for l in reversed(hist):
754 762 if "rerun" not in l[2]:
755 763 hist = [l] # The last match which isn't a %rerun
756 764 break
757 765 else:
758 766 hist = [] # No matches except %rerun
759 767 elif args: # Specify history ranges
760 768 hist = self.history_manager.get_range_by_str(args)
761 769 else: # Last line
762 770 hist = self.history_manager.get_tail(1)
763 771 hist = [x[2] for x in hist]
764 772 if not hist:
765 773 print("No lines in history match specification")
766 774 return
767 775 histlines = "\n".join(hist)
768 776 print("=== Executing: ===")
769 777 print(histlines)
770 778 print("=== Output: ===")
771 779 self.run_cell("\n".join(hist), store_history=False)
772 780
773 781
774 782 def init_ipython(ip):
775 783 ip.define_magic("rep", magic_rep)
776 784 ip.define_magic("recall", magic_rep)
777 785 ip.define_magic("rerun", magic_rerun)
778 786 ip.define_magic("hist",magic_history) # Alternative name
779 787 ip.define_magic("history",magic_history)
780 788
781 789 # XXX - ipy_completers are in quarantine, need to be updated to new apis
782 790 #import ipy_completers
783 791 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
General Comments 0
You need to be logged in to leave comments. Login now