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