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