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