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