history.py
283 lines
| 9.0 KiB
| text/x-python
|
PythonLexer
vivainio
|
r851 | # -*- coding: utf-8 -*- | ||
""" History related magics and functionality """ | ||||
fperez
|
r960 | # Stdlib imports | ||
vivainio
|
r851 | import fnmatch | ||
fperez
|
r960 | import os | ||
Brian Granger
|
r2775 | import IPython.utils.io | ||
from IPython.utils.io import ask_yes_no | ||||
Brian Granger
|
r2498 | from IPython.utils.warn import warn | ||
Brian Granger
|
r2027 | from IPython.core import ipapi | ||
vivainio
|
r851 | |||
def magic_history(self, parameter_s = ''): | ||||
"""Print input history (_i<n> variables), with most recent last. | ||||
%history -> print at most 40 inputs (some may be multi-line)\\ | ||||
%history n -> print at most n inputs\\ | ||||
%history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\ | ||||
Fernando Perez
|
r2441 | By default, input history is printed without line numbers so it can be | ||
directly pasted into an editor. | ||||
With -n, each input's number <n> is shown, and is accessible as the | ||||
automatically generated variable _i<n> as well as In[<n>]. Multi-line | ||||
statements are printed starting at a new line for easy copy/paste. | ||||
vivainio
|
r851 | |||
Fernando Perez
|
r2441 | Options: | ||
vivainio
|
r851 | |||
Fernando Perez
|
r2441 | -n: print line numbers for each input. | ||
vivainio
|
r851 | This feature is only available if numbered prompts are in use. | ||
Fernando Perez
|
r2441 | -o: also print outputs for each input. | ||
-p: print classic '>>>' python prompts before each input. This is useful | ||||
for making documentation, and in conjunction with -o, for producing | ||||
doctest-ready output. | ||||
Fernando Perez
|
r2899 | -r: (default) print the 'raw' history, i.e. the actual commands you typed. | ||
vivainio
|
r851 | |||
Fernando Perez
|
r2899 | -t: print the 'translated' history, as IPython understands it. IPython | ||
filters your input and converts it all into valid Python source before | ||||
executing it (things like magics or aliases are turned into function | ||||
calls, for example). With this option, you'll see the native history | ||||
instead of the user-entered version: '%cd /' will be seen as | ||||
'get_ipython().magic("%cd /")' instead of '%cd /'. | ||||
vivainio
|
r851 | |||
-g: treat the arg as a pattern to grep for in (full) history. | ||||
This includes the "shadow history" (almost all commands ever written). | ||||
Use '%hist -g' to show full shadow history (may be very long). | ||||
In shadow history, every index nuwber starts with 0. | ||||
fperez
|
r961 | |||
-f FILENAME: instead of printing the output to the screen, redirect it to | ||||
the given file. The file is always overwritten, though IPython asks for | ||||
confirmation first if it already exists. | ||||
vivainio
|
r851 | """ | ||
Fernando Perez
|
r2993 | if not self.shell.displayhook.do_full_cache: | ||
vivainio
|
r851 | print 'This feature is only available if numbered prompts are in use.' | ||
return | ||||
Fernando Perez
|
r2441 | opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list') | ||
fperez
|
r960 | |||
# Check if output to specific file was requested. | ||||
try: | ||||
outfname = opts['f'] | ||||
except KeyError: | ||||
Brian Granger
|
r2775 | outfile = IPython.utils.io.Term.cout # default | ||
fperez
|
r960 | # We don't want to close stdout at the end! | ||
close_at_end = False | ||||
else: | ||||
if os.path.exists(outfname): | ||||
Fernando Perez
|
r1762 | if not ask_yes_no("File %r exists. Overwrite?" % outfname): | ||
fperez
|
r960 | print 'Aborting.' | ||
return | ||||
vivainio
|
r851 | |||
Fernando Perez
|
r1762 | outfile = open(outfname,'w') | ||
close_at_end = True | ||||
if 't' in opts: | ||||
Fernando Perez
|
r2993 | input_hist = self.shell.input_hist | ||
Fernando Perez
|
r1762 | elif 'r' in opts: | ||
Fernando Perez
|
r2993 | input_hist = self.shell.input_hist_raw | ||
vivainio
|
r851 | else: | ||
Fernando Perez
|
r2899 | # Raw history is the default | ||
Fernando Perez
|
r2993 | input_hist = self.shell.input_hist_raw | ||
Fernando Perez
|
r1762 | |||
vivainio
|
r851 | default_length = 40 | ||
pattern = None | ||||
Fernando Perez
|
r1762 | if 'g' in opts: | ||
vivainio
|
r851 | init = 1 | ||
final = len(input_hist) | ||||
Fernando Perez
|
r2474 | parts = parameter_s.split(None, 1) | ||
vivainio
|
r851 | if len(parts) == 1: | ||
parts += '*' | ||||
head, pattern = parts | ||||
pattern = "*" + pattern + "*" | ||||
elif len(args) == 0: | ||||
Fernando Perez
|
r2474 | final = len(input_hist)-1 | ||
vivainio
|
r851 | init = max(1,final-default_length) | ||
elif len(args) == 1: | ||||
final = len(input_hist) | ||||
Fernando Perez
|
r2474 | init = max(1, final-int(args[0])) | ||
vivainio
|
r851 | elif len(args) == 2: | ||
Fernando Perez
|
r2474 | init, final = map(int, args) | ||
vivainio
|
r851 | else: | ||
warn('%hist takes 0, 1 or 2 arguments separated by spaces.') | ||||
Brian Granger
|
r2775 | print >> IPython.utils.io.Term.cout, self.magic_hist.__doc__ | ||
vivainio
|
r851 | return | ||
Fernando Perez
|
r2441 | |||
vivainio
|
r851 | width = len(str(final)) | ||
line_sep = ['','\n'] | ||||
Fernando Perez
|
r2441 | print_nums = 'n' in opts | ||
print_outputs = 'o' in opts | ||||
pyprompts = 'p' in opts | ||||
vivainio
|
r851 | |||
found = False | ||||
if pattern is not None: | ||||
Fernando Perez
|
r2993 | sh = self.shell.shadowhist.all() | ||
vivainio
|
r851 | for idx, s in sh: | ||
if fnmatch.fnmatch(s, pattern): | ||||
Fernando Perez
|
r2970 | print >> outfile, "0%d: %s" %(idx, s.expandtabs(4)) | ||
vivainio
|
r851 | found = True | ||
if found: | ||||
Fernando Perez
|
r2474 | print >> outfile, "===" | ||
print >> outfile, \ | ||||
"shadow history ends, fetch by %rep <number> (must start with 0)" | ||||
print >> outfile, "=== start of normal history ===" | ||||
vivainio
|
r851 | |||
Fernando Perez
|
r2993 | for in_num in range(init, final): | ||
Fernando Perez
|
r2970 | # Print user history with tabs expanded to 4 spaces. The GUI clients | ||
# use hard tabs for easier usability in auto-indented code, but we want | ||||
# to produce PEP-8 compliant history for safe pasting into an editor. | ||||
inline = input_hist[in_num].expandtabs(4) | ||||
Fernando Perez
|
r2993 | |||
vivainio
|
r851 | if pattern is not None and not fnmatch.fnmatch(inline, pattern): | ||
continue | ||||
multiline = int(inline.count('\n') > 1) | ||||
if print_nums: | ||||
fperez
|
r960 | print >> outfile, \ | ||
Fernando Perez
|
r2474 | '%s:%s' % (str(in_num).ljust(width), line_sep[multiline]), | ||
Fernando Perez
|
r2441 | if pyprompts: | ||
print >> outfile, '>>>', | ||||
if multiline: | ||||
lines = inline.splitlines() | ||||
print >> outfile, '\n... '.join(lines) | ||||
print >> outfile, '... ' | ||||
else: | ||||
print >> outfile, inline, | ||||
else: | ||||
print >> outfile, inline, | ||||
Fernando Perez
|
r2474 | if print_outputs: | ||
Fernando Perez
|
r2993 | output = self.shell.output_hist.get(in_num) | ||
Fernando Perez
|
r2474 | if output is not None: | ||
print >> outfile, repr(output) | ||||
fperez
|
r960 | |||
if close_at_end: | ||||
outfile.close() | ||||
vivainio
|
r851 | |||
def magic_hist(self, parameter_s=''): | ||||
"""Alternate name for %history.""" | ||||
return self.magic_history(parameter_s) | ||||
def rep_f(self, arg): | ||||
r""" Repeat a command, or get command to input line for editing | ||||
- %rep (no arguments): | ||||
Place a string version of last computation result (stored in the special '_' | ||||
variable) to the next input prompt. Allows you to create elaborate command | ||||
lines without using copy-paste:: | ||||
$ l = ["hei", "vaan"] | ||||
$ "".join(l) | ||||
==> heivaan | ||||
$ %rep | ||||
$ heivaan_ <== cursor blinking | ||||
%rep 45 | ||||
Place history line 45 to next input prompt. Use %hist to find out the | ||||
number. | ||||
%rep 1-4 6-7 3 | ||||
Repeat the specified lines immediately. Input slice syntax is the same as | ||||
in %macro and %save. | ||||
%rep foo | ||||
Place the most recent line that has the substring "foo" to next input. | ||||
Fernando Perez
|
r1762 | (e.g. 'svn ci -m foobar'). | ||
vivainio
|
r851 | """ | ||
opts,args = self.parse_options(arg,'',mode='list') | ||||
if not args: | ||||
Fernando Perez
|
r2993 | self.set_next_input(str(self.shell.user_ns["_"])) | ||
vivainio
|
r851 | return | ||
if len(args) == 1 and not '-' in args[0]: | ||||
arg = args[0] | ||||
if len(arg) > 1 and arg.startswith('0'): | ||||
# get from shadow hist | ||||
num = int(arg[1:]) | ||||
Fernando Perez
|
r2993 | line = self.shell.shadowhist.get(num) | ||
Brian Granger
|
r2205 | self.set_next_input(str(line)) | ||
vivainio
|
r851 | return | ||
try: | ||||
num = int(args[0]) | ||||
Fernando Perez
|
r2993 | self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip()) | ||
vivainio
|
r851 | return | ||
except ValueError: | ||||
pass | ||||
Fernando Perez
|
r2993 | for h in reversed(self.shell.input_hist_raw): | ||
vivainio
|
r851 | if 'rep' in h: | ||
continue | ||||
if fnmatch.fnmatch(h,'*' + arg + '*'): | ||||
Brian Granger
|
r2205 | self.set_next_input(str(h).rstrip()) | ||
vivainio
|
r851 | return | ||
try: | ||||
lines = self.extract_input_slices(args, True) | ||||
print "lines",lines | ||||
Brian Granger
|
r2205 | self.runlines(lines) | ||
vivainio
|
r851 | except ValueError: | ||
print "Not found in recent history:", args | ||||
_sentinel = object() | ||||
Brian Granger
|
r2205 | class ShadowHist(object): | ||
Fernando Perez
|
r2993 | def __init__(self, db): | ||
vivainio
|
r851 | # cmd => idx mapping | ||
self.curidx = 0 | ||||
self.db = db | ||||
Ville M. Vainio
|
r1686 | self.disabled = False | ||
vivainio
|
r851 | |||
def inc_idx(self): | ||||
idx = self.db.get('shadowhist_idx', 1) | ||||
self.db['shadowhist_idx'] = idx + 1 | ||||
return idx | ||||
def add(self, ent): | ||||
Ville M. Vainio
|
r1686 | if self.disabled: | ||
vivainio
|
r851 | return | ||
Ville M. Vainio
|
r1686 | try: | ||
old = self.db.hget('shadowhist', ent, _sentinel) | ||||
if old is not _sentinel: | ||||
return | ||||
newidx = self.inc_idx() | ||||
#print "new",newidx # dbg | ||||
self.db.hset('shadowhist',ent, newidx) | ||||
except: | ||||
Brian Granger
|
r2205 | ipapi.get().showtraceback() | ||
Ville M. Vainio
|
r1686 | print "WARNING: disabling shadow history" | ||
self.disabled = True | ||||
vivainio
|
r851 | |||
def all(self): | ||||
d = self.db.hdict('shadowhist') | ||||
items = [(i,s) for (s,i) in d.items()] | ||||
items.sort() | ||||
return items | ||||
def get(self, idx): | ||||
all = self.all() | ||||
for k, v in all: | ||||
#print k,v | ||||
if k == idx: | ||||
return v | ||||
def init_ipython(ip): | ||||
Brian Granger
|
r2205 | ip.define_magic("rep",rep_f) | ||
ip.define_magic("hist",magic_hist) | ||||
ip.define_magic("history",magic_history) | ||||
vivainio
|
r851 | |||
Fernando Perez
|
r2421 | # XXX - ipy_completers are in quarantine, need to be updated to new apis | ||
#import ipy_completers | ||||
Fernando Perez
|
r2392 | #ipy_completers.quick_completer('%hist' ,'-g -t -r -n') | ||