##// END OF EJS Templates
strip trailing \n for history lines
Satrajit Ghosh -
Show More
@@ -1,515 +1,515 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 fnmatch
16 import fnmatch
17 import json
17 import json
18 import os
18 import os
19 import sys
19 import sys
20
20
21 # Our own packages
21 # Our own packages
22 import IPython.utils.io
22 import IPython.utils.io
23
23
24 from IPython.utils.pickleshare import PickleShareDB
24 from IPython.utils.pickleshare import PickleShareDB
25 from IPython.utils.io import ask_yes_no
25 from IPython.utils.io import ask_yes_no
26 from IPython.utils.warn import warn
26 from IPython.utils.warn import warn
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes and functions
29 # Classes and functions
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class HistoryManager(object):
32 class HistoryManager(object):
33 """A class to organize all history-related functionality in one place.
33 """A class to organize all history-related functionality in one place.
34 """
34 """
35 # Public interface
35 # Public interface
36
36
37 # An instance of the IPython shell we are attached to
37 # An instance of the IPython shell we are attached to
38 shell = None
38 shell = None
39 # A list to hold processed history
39 # A list to hold processed history
40 input_hist_parsed = None
40 input_hist_parsed = None
41 # A list to hold raw history (as typed by user)
41 # A list to hold raw history (as typed by user)
42 input_hist_raw = None
42 input_hist_raw = None
43 # A list of directories visited during session
43 # A list of directories visited during session
44 dir_hist = None
44 dir_hist = None
45 # A dict of output history, keyed with ints from the shell's execution count
45 # A dict of output history, keyed with ints from the shell's execution count
46 output_hist = None
46 output_hist = None
47 # String with path to the history file
47 # String with path to the history file
48 hist_file = None
48 hist_file = None
49 # PickleShareDB instance holding the raw data for the shadow history
49 # PickleShareDB instance holding the raw data for the shadow history
50 shadow_db = None
50 shadow_db = None
51 # ShadowHist instance with the actual shadow history
51 # ShadowHist instance with the actual shadow history
52 shadow_hist = None
52 shadow_hist = None
53
53
54 # Private interface
54 # Private interface
55 # Variables used to store the three last inputs from the user. On each new
55 # Variables used to store the three last inputs from the user. On each new
56 # history update, we populate the user's namespace with these, shifted as
56 # history update, we populate the user's namespace with these, shifted as
57 # necessary.
57 # necessary.
58 _i00, _i, _ii, _iii = '','','',''
58 _i00, _i, _ii, _iii = '','','',''
59
59
60 def __init__(self, shell):
60 def __init__(self, shell):
61 """Create a new history manager associated with a shell instance.
61 """Create a new history manager associated with a shell instance.
62 """
62 """
63 # We need a pointer back to the shell for various tasks.
63 # We need a pointer back to the shell for various tasks.
64 self.shell = shell
64 self.shell = shell
65
65
66 # List of input with multi-line handling.
66 # List of input with multi-line handling.
67 self.input_hist_parsed = []
67 self.input_hist_parsed = []
68 # This one will hold the 'raw' input history, without any
68 # This one will hold the 'raw' input history, without any
69 # pre-processing. This will allow users to retrieve the input just as
69 # pre-processing. This will allow users to retrieve the input just as
70 # it was exactly typed in by the user, with %hist -r.
70 # it was exactly typed in by the user, with %hist -r.
71 self.input_hist_raw = []
71 self.input_hist_raw = []
72
72
73 # list of visited directories
73 # list of visited directories
74 try:
74 try:
75 self.dir_hist = [os.getcwd()]
75 self.dir_hist = [os.getcwd()]
76 except OSError:
76 except OSError:
77 self.dir_hist = []
77 self.dir_hist = []
78
78
79 # dict of output history
79 # dict of output history
80 self.output_hist = {}
80 self.output_hist = {}
81
81
82 # Now the history file
82 # Now the history file
83 if shell.profile:
83 if shell.profile:
84 histfname = 'history-%s' % shell.profile
84 histfname = 'history-%s' % shell.profile
85 else:
85 else:
86 histfname = 'history'
86 histfname = 'history'
87 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.json')
87 self.hist_file = os.path.join(shell.ipython_dir, histfname + '.json')
88
88
89 # Objects related to shadow history management
89 # Objects related to shadow history management
90 self._init_shadow_hist()
90 self._init_shadow_hist()
91
91
92 self._i00, self._i, self._ii, self._iii = '','','',''
92 self._i00, self._i, self._ii, self._iii = '','','',''
93
93
94 # Object is fully initialized, we can now call methods on it.
94 # Object is fully initialized, we can now call methods on it.
95
95
96 # Fill the history zero entry, user counter starts at 1
96 # Fill the history zero entry, user counter starts at 1
97 self.store_inputs('\n', '\n')
97 self.store_inputs('\n', '\n')
98
98
99 def _init_shadow_hist(self):
99 def _init_shadow_hist(self):
100 try:
100 try:
101 self.shadow_db = PickleShareDB(os.path.join(
101 self.shadow_db = PickleShareDB(os.path.join(
102 self.shell.ipython_dir, 'db'))
102 self.shell.ipython_dir, 'db'))
103 except UnicodeDecodeError:
103 except UnicodeDecodeError:
104 print("Your ipython_dir can't be decoded to unicode!")
104 print("Your ipython_dir can't be decoded to unicode!")
105 print("Please set HOME environment variable to something that")
105 print("Please set HOME environment variable to something that")
106 print(r"only has ASCII characters, e.g. c:\home")
106 print(r"only has ASCII characters, e.g. c:\home")
107 print("Now it is", self.ipython_dir)
107 print("Now it is", self.ipython_dir)
108 sys.exit()
108 sys.exit()
109 self.shadow_hist = ShadowHist(self.shadow_db, self.shell)
109 self.shadow_hist = ShadowHist(self.shadow_db, self.shell)
110
110
111 def populate_readline_history(self):
111 def populate_readline_history(self):
112 """Populate the readline history from the raw history.
112 """Populate the readline history from the raw history.
113
113
114 We only store one copy of the raw history, which is persisted to a json
114 We only store one copy of the raw history, which is persisted to a json
115 file on disk. The readline history is repopulated from the contents of
115 file on disk. The readline history is repopulated from the contents of
116 this file."""
116 this file."""
117
117
118 try:
118 try:
119 self.shell.readline.clear_history()
119 self.shell.readline.clear_history()
120 except AttributeError:
120 except AttributeError:
121 pass
121 pass
122 else:
122 else:
123 for h in self.input_hist_raw:
123 for h in self.input_hist_raw:
124 if not h.isspace():
124 if not h.isspace():
125 for line in h.splitlines():
125 for line in h.splitlines():
126 self.shell.readline.add_history(line)
126 self.shell.readline.add_history(line)
127
127
128 def save_history(self):
128 def save_history(self):
129 """Save input history to a file (via readline library)."""
129 """Save input history to a file (via readline library)."""
130 hist = dict(raw=self.input_hist_raw, #[-self.shell.history_length:],
130 hist = dict(raw=self.input_hist_raw, #[-self.shell.history_length:],
131 parsed=self.input_hist_parsed) #[-self.shell.history_length:])
131 parsed=self.input_hist_parsed) #[-self.shell.history_length:])
132 with open(self.hist_file,'wt') as hfile:
132 with open(self.hist_file,'wt') as hfile:
133 json.dump(hist, hfile,
133 json.dump(hist, hfile,
134 sort_keys=True, indent=4)
134 sort_keys=True, indent=4)
135
135
136 def reload_history(self):
136 def reload_history(self):
137 """Reload the input history from disk file."""
137 """Reload the input history from disk file."""
138
138
139 with open(self.hist_file,'rt') as hfile:
139 with open(self.hist_file,'rt') as hfile:
140 hist = json.load(hfile)
140 hist = json.load(hfile)
141 self.input_hist_parsed = hist['parsed']
141 self.input_hist_parsed = hist['parsed']
142 self.input_hist_raw = hist['raw']
142 self.input_hist_raw = hist['raw']
143 if self.shell.has_readline:
143 if self.shell.has_readline:
144 self.populate_readline_history()
144 self.populate_readline_history()
145
145
146 def get_history(self, index=None, raw=False, output=True):
146 def get_history(self, index=None, raw=False, output=True):
147 """Get the history list.
147 """Get the history list.
148
148
149 Get the input and output history.
149 Get the input and output history.
150
150
151 Parameters
151 Parameters
152 ----------
152 ----------
153 index : n or (n1, n2) or None
153 index : n or (n1, n2) or None
154 If n, then the last entries. If a tuple, then all in
154 If n, then the last entries. If a tuple, then all in
155 range(n1, n2). If None, then all entries. Raises IndexError if
155 range(n1, n2). If None, then all entries. Raises IndexError if
156 the format of index is incorrect.
156 the format of index is incorrect.
157 raw : bool
157 raw : bool
158 If True, return the raw input.
158 If True, return the raw input.
159 output : bool
159 output : bool
160 If True, then return the output as well.
160 If True, then return the output as well.
161
161
162 Returns
162 Returns
163 -------
163 -------
164 If output is True, then return a dict of tuples, keyed by the prompt
164 If output is True, then return a dict of tuples, keyed by the prompt
165 numbers and with values of (input, output). If output is False, then
165 numbers and with values of (input, output). If output is False, then
166 a dict, keyed by the prompt number with the values of input. Raises
166 a dict, keyed by the prompt number with the values of input. Raises
167 IndexError if no history is found.
167 IndexError if no history is found.
168 """
168 """
169 if raw:
169 if raw:
170 input_hist = self.input_hist_raw
170 input_hist = self.input_hist_raw
171 else:
171 else:
172 input_hist = self.input_hist_parsed
172 input_hist = self.input_hist_parsed
173 if output:
173 if output:
174 output_hist = self.output_hist
174 output_hist = self.output_hist
175 n = len(input_hist)
175 n = len(input_hist)
176 if index is None:
176 if index is None:
177 start=0; stop=n
177 start=0; stop=n
178 elif isinstance(index, int):
178 elif isinstance(index, int):
179 start=n-index; stop=n
179 start=n-index; stop=n
180 elif isinstance(index, tuple) and len(index) == 2:
180 elif isinstance(index, tuple) and len(index) == 2:
181 start=index[0]; stop=index[1]
181 start=index[0]; stop=index[1]
182 else:
182 else:
183 raise IndexError('Not a valid index for the input history: %r'
183 raise IndexError('Not a valid index for the input history: %r'
184 % index)
184 % index)
185 hist = {}
185 hist = {}
186 for i in range(start, stop):
186 for i in range(start, stop):
187 if output:
187 if output:
188 hist[i] = (input_hist[i], output_hist.get(i))
188 hist[i] = (input_hist[i], output_hist.get(i))
189 else:
189 else:
190 hist[i] = input_hist[i]
190 hist[i] = input_hist[i]
191 if not hist:
191 if not hist:
192 raise IndexError('No history for range of indices: %r' % index)
192 raise IndexError('No history for range of indices: %r' % index)
193 return hist
193 return hist
194
194
195 def store_inputs(self, source, source_raw=None):
195 def store_inputs(self, source, source_raw=None):
196 """Store source and raw input in history and create input cache
196 """Store source and raw input in history and create input cache
197 variables _i*.
197 variables _i*.
198
198
199 Parameters
199 Parameters
200 ----------
200 ----------
201 source : str
201 source : str
202 Python input.
202 Python input.
203
203
204 source_raw : str, optional
204 source_raw : str, optional
205 If given, this is the raw input without any IPython transformations
205 If given, this is the raw input without any IPython transformations
206 applied to it. If not given, ``source`` is used.
206 applied to it. If not given, ``source`` is used.
207 """
207 """
208 if source_raw is None:
208 if source_raw is None:
209 source_raw = source
209 source_raw = source
210 # do not store quit/exit commands
210 # do not store quit/exit commands
211 if source_raw in ['Quit', 'quit', 'Exit', 'exit', '%Quit', '%quit', '%Exit', '%exit']:
211 if source_raw in ['Quit', 'quit', 'Exit', 'exit', '%Quit', '%quit', '%Exit', '%exit']:
212 return
212 return
213 self.input_hist_parsed.append(source)
213 self.input_hist_parsed.append(source.rstrip())
214 self.input_hist_raw.append(source_raw)
214 self.input_hist_raw.append(source_raw.rstrip())
215 self.shadow_hist.add(source)
215 self.shadow_hist.add(source)
216
216
217 # update the auto _i variables
217 # update the auto _i variables
218 self._iii = self._ii
218 self._iii = self._ii
219 self._ii = self._i
219 self._ii = self._i
220 self._i = self._i00
220 self._i = self._i00
221 self._i00 = source_raw
221 self._i00 = source_raw
222
222
223 # hackish access to user namespace to create _i1,_i2... dynamically
223 # hackish access to user namespace to create _i1,_i2... dynamically
224 new_i = '_i%s' % self.shell.execution_count
224 new_i = '_i%s' % self.shell.execution_count
225 to_main = {'_i': self._i,
225 to_main = {'_i': self._i,
226 '_ii': self._ii,
226 '_ii': self._ii,
227 '_iii': self._iii,
227 '_iii': self._iii,
228 new_i : self._i00 }
228 new_i : self._i00 }
229 self.shell.user_ns.update(to_main)
229 self.shell.user_ns.update(to_main)
230
230
231 def sync_inputs(self):
231 def sync_inputs(self):
232 """Ensure raw and translated histories have same length."""
232 """Ensure raw and translated histories have same length."""
233 if len(self.input_hist_parsed) != len (self.input_hist_raw):
233 if len(self.input_hist_parsed) != len (self.input_hist_raw):
234 self.input_hist_raw[:] = self.input_hist_parsed
234 self.input_hist_raw[:] = self.input_hist_parsed
235
235
236 def reset(self):
236 def reset(self):
237 """Clear all histories managed by this object."""
237 """Clear all histories managed by this object."""
238 self.input_hist_parsed[:] = []
238 self.input_hist_parsed[:] = []
239 self.input_hist_raw[:] = []
239 self.input_hist_raw[:] = []
240 self.output_hist.clear()
240 self.output_hist.clear()
241 # The directory history can't be completely empty
241 # The directory history can't be completely empty
242 self.dir_hist[:] = [os.getcwd()]
242 self.dir_hist[:] = [os.getcwd()]
243
243
244
244
245 def magic_history(self, parameter_s = ''):
245 def magic_history(self, parameter_s = ''):
246 """Print input history (_i<n> variables), with most recent last.
246 """Print input history (_i<n> variables), with most recent last.
247
247
248 %history -> print at most 40 inputs (some may be multi-line)\\
248 %history -> print at most 40 inputs (some may be multi-line)\\
249 %history n -> print at most n inputs\\
249 %history n -> print at most n inputs\\
250 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
250 %history n1 n2 -> print inputs between n1 and n2 (n2 not included)\\
251
251
252 By default, input history is printed without line numbers so it can be
252 By default, input history is printed without line numbers so it can be
253 directly pasted into an editor.
253 directly pasted into an editor.
254
254
255 With -n, each input's number <n> is shown, and is accessible as the
255 With -n, each input's number <n> is shown, and is accessible as the
256 automatically generated variable _i<n> as well as In[<n>]. Multi-line
256 automatically generated variable _i<n> as well as In[<n>]. Multi-line
257 statements are printed starting at a new line for easy copy/paste.
257 statements are printed starting at a new line for easy copy/paste.
258
258
259 Options:
259 Options:
260
260
261 -n: print line numbers for each input.
261 -n: print line numbers for each input.
262 This feature is only available if numbered prompts are in use.
262 This feature is only available if numbered prompts are in use.
263
263
264 -o: also print outputs for each input.
264 -o: also print outputs for each input.
265
265
266 -p: print classic '>>>' python prompts before each input. This is useful
266 -p: print classic '>>>' python prompts before each input. This is useful
267 for making documentation, and in conjunction with -o, for producing
267 for making documentation, and in conjunction with -o, for producing
268 doctest-ready output.
268 doctest-ready output.
269
269
270 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
270 -r: (default) print the 'raw' history, i.e. the actual commands you typed.
271
271
272 -t: print the 'translated' history, as IPython understands it. IPython
272 -t: print the 'translated' history, as IPython understands it. IPython
273 filters your input and converts it all into valid Python source before
273 filters your input and converts it all into valid Python source before
274 executing it (things like magics or aliases are turned into function
274 executing it (things like magics or aliases are turned into function
275 calls, for example). With this option, you'll see the native history
275 calls, for example). With this option, you'll see the native history
276 instead of the user-entered version: '%cd /' will be seen as
276 instead of the user-entered version: '%cd /' will be seen as
277 'get_ipython().magic("%cd /")' instead of '%cd /'.
277 'get_ipython().magic("%cd /")' instead of '%cd /'.
278
278
279 -g: treat the arg as a pattern to grep for in (full) history.
279 -g: treat the arg as a pattern to grep for in (full) history.
280 This includes the "shadow history" (almost all commands ever written).
280 This includes the "shadow history" (almost all commands ever written).
281 Use '%hist -g' to show full shadow history (may be very long).
281 Use '%hist -g' to show full shadow history (may be very long).
282 In shadow history, every index nuwber starts with 0.
282 In shadow history, every index nuwber starts with 0.
283
283
284 -f FILENAME: instead of printing the output to the screen, redirect it to
284 -f FILENAME: instead of printing the output to the screen, redirect it to
285 the given file. The file is always overwritten, though IPython asks for
285 the given file. The file is always overwritten, though IPython asks for
286 confirmation first if it already exists.
286 confirmation first if it already exists.
287 """
287 """
288
288
289 if not self.shell.displayhook.do_full_cache:
289 if not self.shell.displayhook.do_full_cache:
290 print('This feature is only available if numbered prompts are in use.')
290 print('This feature is only available if numbered prompts are in use.')
291 return
291 return
292 opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list')
292 opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list')
293
293
294 # Check if output to specific file was requested.
294 # Check if output to specific file was requested.
295 try:
295 try:
296 outfname = opts['f']
296 outfname = opts['f']
297 except KeyError:
297 except KeyError:
298 outfile = IPython.utils.io.Term.cout # default
298 outfile = IPython.utils.io.Term.cout # default
299 # We don't want to close stdout at the end!
299 # We don't want to close stdout at the end!
300 close_at_end = False
300 close_at_end = False
301 else:
301 else:
302 if os.path.exists(outfname):
302 if os.path.exists(outfname):
303 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
303 if not ask_yes_no("File %r exists. Overwrite?" % outfname):
304 print('Aborting.')
304 print('Aborting.')
305 return
305 return
306
306
307 outfile = open(outfname,'w')
307 outfile = open(outfname,'w')
308 close_at_end = True
308 close_at_end = True
309
309
310 if 't' in opts:
310 if 't' in opts:
311 input_hist = self.shell.history_manager.input_hist_parsed
311 input_hist = self.shell.history_manager.input_hist_parsed
312 elif 'r' in opts:
312 elif 'r' in opts:
313 input_hist = self.shell.history_manager.input_hist_raw
313 input_hist = self.shell.history_manager.input_hist_raw
314 else:
314 else:
315 # Raw history is the default
315 # Raw history is the default
316 input_hist = self.shell.history_manager.input_hist_raw
316 input_hist = self.shell.history_manager.input_hist_raw
317
317
318 default_length = 40
318 default_length = 40
319 pattern = None
319 pattern = None
320 if 'g' in opts:
320 if 'g' in opts:
321 init = 1
321 init = 1
322 final = len(input_hist)
322 final = len(input_hist)
323 parts = parameter_s.split(None, 1)
323 parts = parameter_s.split(None, 1)
324 if len(parts) == 1:
324 if len(parts) == 1:
325 parts += '*'
325 parts += '*'
326 head, pattern = parts
326 head, pattern = parts
327 pattern = "*" + pattern + "*"
327 pattern = "*" + pattern + "*"
328 elif len(args) == 0:
328 elif len(args) == 0:
329 final = len(input_hist)-1
329 final = len(input_hist)-1
330 init = max(1,final-default_length)
330 init = max(1,final-default_length)
331 elif len(args) == 1:
331 elif len(args) == 1:
332 final = len(input_hist)
332 final = len(input_hist)
333 init = max(1, final-int(args[0]))
333 init = max(1, final-int(args[0]))
334 elif len(args) == 2:
334 elif len(args) == 2:
335 init, final = map(int, args)
335 init, final = map(int, args)
336 else:
336 else:
337 warn('%hist takes 0, 1 or 2 arguments separated by spaces.')
337 warn('%hist takes 0, 1 or 2 arguments separated by spaces.')
338 print(self.magic_hist.__doc__, file=IPython.utils.io.Term.cout)
338 print(self.magic_hist.__doc__, file=IPython.utils.io.Term.cout)
339 return
339 return
340
340
341 width = len(str(final))
341 width = len(str(final))
342 line_sep = ['','\n']
342 line_sep = ['','\n']
343 print_nums = 'n' in opts
343 print_nums = 'n' in opts
344 print_outputs = 'o' in opts
344 print_outputs = 'o' in opts
345 pyprompts = 'p' in opts
345 pyprompts = 'p' in opts
346
346
347 found = False
347 found = False
348 if pattern is not None:
348 if pattern is not None:
349 sh = self.shell.history_manager.shadowhist.all()
349 sh = self.shell.history_manager.shadowhist.all()
350 for idx, s in sh:
350 for idx, s in sh:
351 if fnmatch.fnmatch(s, pattern):
351 if fnmatch.fnmatch(s, pattern):
352 print("0%d: %s" %(idx, s.expandtabs(4)), file=outfile)
352 print("0%d: %s" %(idx, s.expandtabs(4)), file=outfile)
353 found = True
353 found = True
354
354
355 if found:
355 if found:
356 print("===", file=outfile)
356 print("===", file=outfile)
357 print("shadow history ends, fetch by %rep <number> (must start with 0)",
357 print("shadow history ends, fetch by %rep <number> (must start with 0)",
358 file=outfile)
358 file=outfile)
359 print("=== start of normal history ===", file=outfile)
359 print("=== start of normal history ===", file=outfile)
360
360
361 for in_num in range(init, final):
361 for in_num in range(init, final):
362 # Print user history with tabs expanded to 4 spaces. The GUI clients
362 # Print user history with tabs expanded to 4 spaces. The GUI clients
363 # use hard tabs for easier usability in auto-indented code, but we want
363 # use hard tabs for easier usability in auto-indented code, but we want
364 # to produce PEP-8 compliant history for safe pasting into an editor.
364 # to produce PEP-8 compliant history for safe pasting into an editor.
365 inline = input_hist[in_num].expandtabs(4)
365 inline = input_hist[in_num].expandtabs(4)
366
366
367 if pattern is not None and not fnmatch.fnmatch(inline, pattern):
367 if pattern is not None and not fnmatch.fnmatch(inline, pattern):
368 continue
368 continue
369
369
370 multiline = int(inline.count('\n') > 1)
370 multiline = int(inline.count('\n') > 1)
371 if print_nums:
371 if print_nums:
372 print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]),
372 print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]),
373 file=outfile)
373 file=outfile)
374 if pyprompts:
374 if pyprompts:
375 print('>>>', file=outfile)
375 print('>>>', file=outfile)
376 if multiline:
376 if multiline:
377 lines = inline.splitlines()
377 lines = inline.splitlines()
378 print('\n... '.join(lines), file=outfile)
378 print('\n... '.join(lines), file=outfile)
379 print('... ', file=outfile)
379 print('... ', file=outfile)
380 else:
380 else:
381 print(inline, end='', file=outfile)
381 print(inline, end='', file=outfile)
382 else:
382 else:
383 print(inline, end='', file=outfile)
383 print(inline, end='', file=outfile)
384 if print_outputs:
384 if print_outputs:
385 output = self.shell.history_manager.output_hist.get(in_num)
385 output = self.shell.history_manager.output_hist.get(in_num)
386 if output is not None:
386 if output is not None:
387 print(repr(output), file=outfile)
387 print(repr(output), file=outfile)
388
388
389 if close_at_end:
389 if close_at_end:
390 outfile.close()
390 outfile.close()
391
391
392
392
393 def magic_hist(self, parameter_s=''):
393 def magic_hist(self, parameter_s=''):
394 """Alternate name for %history."""
394 """Alternate name for %history."""
395 return self.magic_history(parameter_s)
395 return self.magic_history(parameter_s)
396
396
397
397
398 def rep_f(self, arg):
398 def rep_f(self, arg):
399 r""" Repeat a command, or get command to input line for editing
399 r""" Repeat a command, or get command to input line for editing
400
400
401 - %rep (no arguments):
401 - %rep (no arguments):
402
402
403 Place a string version of last computation result (stored in the special '_'
403 Place a string version of last computation result (stored in the special '_'
404 variable) to the next input prompt. Allows you to create elaborate command
404 variable) to the next input prompt. Allows you to create elaborate command
405 lines without using copy-paste::
405 lines without using copy-paste::
406
406
407 $ l = ["hei", "vaan"]
407 $ l = ["hei", "vaan"]
408 $ "".join(l)
408 $ "".join(l)
409 ==> heivaan
409 ==> heivaan
410 $ %rep
410 $ %rep
411 $ heivaan_ <== cursor blinking
411 $ heivaan_ <== cursor blinking
412
412
413 %rep 45
413 %rep 45
414
414
415 Place history line 45 to next input prompt. Use %hist to find out the
415 Place history line 45 to next input prompt. Use %hist to find out the
416 number.
416 number.
417
417
418 %rep 1-4 6-7 3
418 %rep 1-4 6-7 3
419
419
420 Repeat the specified lines immediately. Input slice syntax is the same as
420 Repeat the specified lines immediately. Input slice syntax is the same as
421 in %macro and %save.
421 in %macro and %save.
422
422
423 %rep foo
423 %rep foo
424
424
425 Place the most recent line that has the substring "foo" to next input.
425 Place the most recent line that has the substring "foo" to next input.
426 (e.g. 'svn ci -m foobar').
426 (e.g. 'svn ci -m foobar').
427 """
427 """
428
428
429 opts,args = self.parse_options(arg,'',mode='list')
429 opts,args = self.parse_options(arg,'',mode='list')
430 if not args:
430 if not args:
431 self.set_next_input(str(self.shell.user_ns["_"]))
431 self.set_next_input(str(self.shell.user_ns["_"]))
432 return
432 return
433
433
434 if len(args) == 1 and not '-' in args[0]:
434 if len(args) == 1 and not '-' in args[0]:
435 arg = args[0]
435 arg = args[0]
436 if len(arg) > 1 and arg.startswith('0'):
436 if len(arg) > 1 and arg.startswith('0'):
437 # get from shadow hist
437 # get from shadow hist
438 num = int(arg[1:])
438 num = int(arg[1:])
439 line = self.shell.shadowhist.get(num)
439 line = self.shell.shadowhist.get(num)
440 self.set_next_input(str(line))
440 self.set_next_input(str(line))
441 return
441 return
442 try:
442 try:
443 num = int(args[0])
443 num = int(args[0])
444 self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip())
444 self.set_next_input(str(self.shell.input_hist_raw[num]).rstrip())
445 return
445 return
446 except ValueError:
446 except ValueError:
447 pass
447 pass
448
448
449 for h in reversed(self.shell.input_hist_raw):
449 for h in reversed(self.shell.input_hist_raw):
450 if 'rep' in h:
450 if 'rep' in h:
451 continue
451 continue
452 if fnmatch.fnmatch(h,'*' + arg + '*'):
452 if fnmatch.fnmatch(h,'*' + arg + '*'):
453 self.set_next_input(str(h).rstrip())
453 self.set_next_input(str(h).rstrip())
454 return
454 return
455
455
456 try:
456 try:
457 lines = self.extract_input_slices(args, True)
457 lines = self.extract_input_slices(args, True)
458 print("lines", lines)
458 print("lines", lines)
459 self.run_cell(lines)
459 self.run_cell(lines)
460 except ValueError:
460 except ValueError:
461 print("Not found in recent history:", args)
461 print("Not found in recent history:", args)
462
462
463
463
464 _sentinel = object()
464 _sentinel = object()
465
465
466 class ShadowHist(object):
466 class ShadowHist(object):
467 def __init__(self, db, shell):
467 def __init__(self, db, shell):
468 # cmd => idx mapping
468 # cmd => idx mapping
469 self.curidx = 0
469 self.curidx = 0
470 self.db = db
470 self.db = db
471 self.disabled = False
471 self.disabled = False
472 self.shell = shell
472 self.shell = shell
473
473
474 def inc_idx(self):
474 def inc_idx(self):
475 idx = self.db.get('shadowhist_idx', 1)
475 idx = self.db.get('shadowhist_idx', 1)
476 self.db['shadowhist_idx'] = idx + 1
476 self.db['shadowhist_idx'] = idx + 1
477 return idx
477 return idx
478
478
479 def add(self, ent):
479 def add(self, ent):
480 if self.disabled:
480 if self.disabled:
481 return
481 return
482 try:
482 try:
483 old = self.db.hget('shadowhist', ent, _sentinel)
483 old = self.db.hget('shadowhist', ent, _sentinel)
484 if old is not _sentinel:
484 if old is not _sentinel:
485 return
485 return
486 newidx = self.inc_idx()
486 newidx = self.inc_idx()
487 #print("new", newidx) # dbg
487 #print("new", newidx) # dbg
488 self.db.hset('shadowhist',ent, newidx)
488 self.db.hset('shadowhist',ent, newidx)
489 except:
489 except:
490 self.shell.showtraceback()
490 self.shell.showtraceback()
491 print("WARNING: disabling shadow history")
491 print("WARNING: disabling shadow history")
492 self.disabled = True
492 self.disabled = True
493
493
494 def all(self):
494 def all(self):
495 d = self.db.hdict('shadowhist')
495 d = self.db.hdict('shadowhist')
496 items = [(i,s) for (s,i) in d.iteritems()]
496 items = [(i,s) for (s,i) in d.iteritems()]
497 items.sort()
497 items.sort()
498 return items
498 return items
499
499
500 def get(self, idx):
500 def get(self, idx):
501 all = self.all()
501 all = self.all()
502
502
503 for k, v in all:
503 for k, v in all:
504 if k == idx:
504 if k == idx:
505 return v
505 return v
506
506
507
507
508 def init_ipython(ip):
508 def init_ipython(ip):
509 ip.define_magic("rep",rep_f)
509 ip.define_magic("rep",rep_f)
510 ip.define_magic("hist",magic_hist)
510 ip.define_magic("hist",magic_hist)
511 ip.define_magic("history",magic_history)
511 ip.define_magic("history",magic_history)
512
512
513 # XXX - ipy_completers are in quarantine, need to be updated to new apis
513 # XXX - ipy_completers are in quarantine, need to be updated to new apis
514 #import ipy_completers
514 #import ipy_completers
515 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
515 #ipy_completers.quick_completer('%hist' ,'-g -t -r -n')
@@ -1,467 +1,467 b''
1 """ A FrontendWidget that emulates the interface of the console IPython and
1 """ A FrontendWidget that emulates the interface of the console IPython and
2 supports the additional functionality provided by the IPython kernel.
2 supports the additional functionality provided by the IPython kernel.
3
3
4 TODO: Add support for retrieving the system default editor. Requires code
4 TODO: Add support for retrieving the system default editor. Requires code
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
5 paths for Windows (use the registry), Mac OS (use LaunchServices), and
6 Linux (use the xdg system).
6 Linux (use the xdg system).
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 # Standard library imports
13 # Standard library imports
14 from collections import namedtuple
14 from collections import namedtuple
15 import re
15 import re
16 from subprocess import Popen
16 from subprocess import Popen
17 from textwrap import dedent
17 from textwrap import dedent
18
18
19 # System library imports
19 # System library imports
20 from PyQt4 import QtCore, QtGui
20 from PyQt4 import QtCore, QtGui
21
21
22 # Local imports
22 # Local imports
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
23 from IPython.core.inputsplitter import IPythonInputSplitter, \
24 transform_ipy_prompt
24 transform_ipy_prompt
25 from IPython.core.usage import default_gui_banner
25 from IPython.core.usage import default_gui_banner
26 from IPython.utils.traitlets import Bool, Str
26 from IPython.utils.traitlets import Bool, Str
27 from frontend_widget import FrontendWidget
27 from frontend_widget import FrontendWidget
28 from styles import (default_light_style_sheet, default_light_syntax_style,
28 from styles import (default_light_style_sheet, default_light_syntax_style,
29 default_dark_style_sheet, default_dark_syntax_style,
29 default_dark_style_sheet, default_dark_syntax_style,
30 default_bw_style_sheet, default_bw_syntax_style)
30 default_bw_style_sheet, default_bw_syntax_style)
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Constants
33 # Constants
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36 # Default strings to build and display input and output prompts (and separators
36 # Default strings to build and display input and output prompts (and separators
37 # in between)
37 # in between)
38 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
38 default_in_prompt = 'In [<span class="in-prompt-number">%i</span>]: '
39 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
39 default_out_prompt = 'Out[<span class="out-prompt-number">%i</span>]: '
40 default_input_sep = '\n'
40 default_input_sep = '\n'
41 default_output_sep = ''
41 default_output_sep = ''
42 default_output_sep2 = ''
42 default_output_sep2 = ''
43
43
44 # Base path for most payload sources.
44 # Base path for most payload sources.
45 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
45 zmq_shell_source = 'IPython.zmq.zmqshell.ZMQInteractiveShell'
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # IPythonWidget class
48 # IPythonWidget class
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51 class IPythonWidget(FrontendWidget):
51 class IPythonWidget(FrontendWidget):
52 """ A FrontendWidget for an IPython kernel.
52 """ A FrontendWidget for an IPython kernel.
53 """
53 """
54
54
55 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
55 # If set, the 'custom_edit_requested(str, int)' signal will be emitted when
56 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
56 # an editor is needed for a file. This overrides 'editor' and 'editor_line'
57 # settings.
57 # settings.
58 custom_edit = Bool(False)
58 custom_edit = Bool(False)
59 custom_edit_requested = QtCore.pyqtSignal(object, object)
59 custom_edit_requested = QtCore.pyqtSignal(object, object)
60
60
61 # A command for invoking a system text editor. If the string contains a
61 # A command for invoking a system text editor. If the string contains a
62 # {filename} format specifier, it will be used. Otherwise, the filename will
62 # {filename} format specifier, it will be used. Otherwise, the filename will
63 # be appended to the end the command.
63 # be appended to the end the command.
64 editor = Str('default', config=True)
64 editor = Str('default', config=True)
65
65
66 # The editor command to use when a specific line number is requested. The
66 # The editor command to use when a specific line number is requested. The
67 # string should contain two format specifiers: {line} and {filename}. If
67 # string should contain two format specifiers: {line} and {filename}. If
68 # this parameter is not specified, the line number option to the %edit magic
68 # this parameter is not specified, the line number option to the %edit magic
69 # will be ignored.
69 # will be ignored.
70 editor_line = Str(config=True)
70 editor_line = Str(config=True)
71
71
72 # A CSS stylesheet. The stylesheet can contain classes for:
72 # A CSS stylesheet. The stylesheet can contain classes for:
73 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
73 # 1. Qt: QPlainTextEdit, QFrame, QWidget, etc
74 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
74 # 2. Pygments: .c, .k, .o, etc (see PygmentsHighlighter)
75 # 3. IPython: .error, .in-prompt, .out-prompt, etc
75 # 3. IPython: .error, .in-prompt, .out-prompt, etc
76 style_sheet = Str(config=True)
76 style_sheet = Str(config=True)
77
77
78 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
78 # If not empty, use this Pygments style for syntax highlighting. Otherwise,
79 # the style sheet is queried for Pygments style information.
79 # the style sheet is queried for Pygments style information.
80 syntax_style = Str(config=True)
80 syntax_style = Str(config=True)
81
81
82 # Prompts.
82 # Prompts.
83 in_prompt = Str(default_in_prompt, config=True)
83 in_prompt = Str(default_in_prompt, config=True)
84 out_prompt = Str(default_out_prompt, config=True)
84 out_prompt = Str(default_out_prompt, config=True)
85 input_sep = Str(default_input_sep, config=True)
85 input_sep = Str(default_input_sep, config=True)
86 output_sep = Str(default_output_sep, config=True)
86 output_sep = Str(default_output_sep, config=True)
87 output_sep2 = Str(default_output_sep2, config=True)
87 output_sep2 = Str(default_output_sep2, config=True)
88
88
89 # FrontendWidget protected class variables.
89 # FrontendWidget protected class variables.
90 _input_splitter_class = IPythonInputSplitter
90 _input_splitter_class = IPythonInputSplitter
91
91
92 # IPythonWidget protected class variables.
92 # IPythonWidget protected class variables.
93 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
93 _PromptBlock = namedtuple('_PromptBlock', ['block', 'length', 'number'])
94 _payload_source_edit = zmq_shell_source + '.edit_magic'
94 _payload_source_edit = zmq_shell_source + '.edit_magic'
95 _payload_source_exit = zmq_shell_source + '.ask_exit'
95 _payload_source_exit = zmq_shell_source + '.ask_exit'
96 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
96 _payload_source_loadpy = zmq_shell_source + '.magic_loadpy'
97 _payload_source_page = 'IPython.zmq.page.page'
97 _payload_source_page = 'IPython.zmq.page.page'
98
98
99 #---------------------------------------------------------------------------
99 #---------------------------------------------------------------------------
100 # 'object' interface
100 # 'object' interface
101 #---------------------------------------------------------------------------
101 #---------------------------------------------------------------------------
102
102
103 def __init__(self, *args, **kw):
103 def __init__(self, *args, **kw):
104 super(IPythonWidget, self).__init__(*args, **kw)
104 super(IPythonWidget, self).__init__(*args, **kw)
105
105
106 # IPythonWidget protected variables.
106 # IPythonWidget protected variables.
107 self._code_to_load = None
107 self._code_to_load = None
108 self._payload_handlers = {
108 self._payload_handlers = {
109 self._payload_source_edit : self._handle_payload_edit,
109 self._payload_source_edit : self._handle_payload_edit,
110 self._payload_source_exit : self._handle_payload_exit,
110 self._payload_source_exit : self._handle_payload_exit,
111 self._payload_source_page : self._handle_payload_page,
111 self._payload_source_page : self._handle_payload_page,
112 self._payload_source_loadpy : self._handle_payload_loadpy }
112 self._payload_source_loadpy : self._handle_payload_loadpy }
113 self._previous_prompt_obj = None
113 self._previous_prompt_obj = None
114 self._keep_kernel_on_exit = None
114 self._keep_kernel_on_exit = None
115
115
116 # Initialize widget styling.
116 # Initialize widget styling.
117 if self.style_sheet:
117 if self.style_sheet:
118 self._style_sheet_changed()
118 self._style_sheet_changed()
119 self._syntax_style_changed()
119 self._syntax_style_changed()
120 else:
120 else:
121 self.set_default_style()
121 self.set_default_style()
122
122
123 #---------------------------------------------------------------------------
123 #---------------------------------------------------------------------------
124 # 'BaseFrontendMixin' abstract interface
124 # 'BaseFrontendMixin' abstract interface
125 #---------------------------------------------------------------------------
125 #---------------------------------------------------------------------------
126
126
127 def _handle_complete_reply(self, rep):
127 def _handle_complete_reply(self, rep):
128 """ Reimplemented to support IPython's improved completion machinery.
128 """ Reimplemented to support IPython's improved completion machinery.
129 """
129 """
130 cursor = self._get_cursor()
130 cursor = self._get_cursor()
131 info = self._request_info.get('complete')
131 info = self._request_info.get('complete')
132 if info and info.id == rep['parent_header']['msg_id'] and \
132 if info and info.id == rep['parent_header']['msg_id'] and \
133 info.pos == cursor.position():
133 info.pos == cursor.position():
134 matches = rep['content']['matches']
134 matches = rep['content']['matches']
135 text = rep['content']['matched_text']
135 text = rep['content']['matched_text']
136 offset = len(text)
136 offset = len(text)
137
137
138 # Clean up matches with period and path separators if the matched
138 # Clean up matches with period and path separators if the matched
139 # text has not been transformed. This is done by truncating all
139 # text has not been transformed. This is done by truncating all
140 # but the last component and then suitably decreasing the offset
140 # but the last component and then suitably decreasing the offset
141 # between the current cursor position and the start of completion.
141 # between the current cursor position and the start of completion.
142 if len(matches) > 1 and matches[0][:offset] == text:
142 if len(matches) > 1 and matches[0][:offset] == text:
143 parts = re.split(r'[./\\]', text)
143 parts = re.split(r'[./\\]', text)
144 sep_count = len(parts) - 1
144 sep_count = len(parts) - 1
145 if sep_count:
145 if sep_count:
146 chop_length = sum(map(len, parts[:sep_count])) + sep_count
146 chop_length = sum(map(len, parts[:sep_count])) + sep_count
147 matches = [ match[chop_length:] for match in matches ]
147 matches = [ match[chop_length:] for match in matches ]
148 offset -= chop_length
148 offset -= chop_length
149
149
150 # Move the cursor to the start of the match and complete.
150 # Move the cursor to the start of the match and complete.
151 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
151 cursor.movePosition(QtGui.QTextCursor.Left, n=offset)
152 self._complete_with_items(cursor, matches)
152 self._complete_with_items(cursor, matches)
153
153
154 def _handle_execute_reply(self, msg):
154 def _handle_execute_reply(self, msg):
155 """ Reimplemented to support prompt requests.
155 """ Reimplemented to support prompt requests.
156 """
156 """
157 info = self._request_info.get('execute')
157 info = self._request_info.get('execute')
158 if info and info.id == msg['parent_header']['msg_id']:
158 if info and info.id == msg['parent_header']['msg_id']:
159 if info.kind == 'prompt':
159 if info.kind == 'prompt':
160 number = msg['content']['execution_count'] + 1
160 number = msg['content']['execution_count'] + 1
161 self._show_interpreter_prompt(number)
161 self._show_interpreter_prompt(number)
162 else:
162 else:
163 super(IPythonWidget, self)._handle_execute_reply(msg)
163 super(IPythonWidget, self)._handle_execute_reply(msg)
164
164
165 def _handle_history_reply(self, msg):
165 def _handle_history_reply(self, msg):
166 """ Implemented to handle history replies, which are only supported by
166 """ Implemented to handle history replies, which are only supported by
167 the IPython kernel.
167 the IPython kernel.
168 """
168 """
169 history_dict = msg['content']['history']
169 history_dict = msg['content']['history']
170 input_history_dict = {}
170 input_history_dict = {}
171 for key,val in history_dict.items():
171 for key,val in history_dict.items():
172 input_history_dict[int(key)] = val
172 input_history_dict[int(key)] = val
173 items = [ val for _, val in sorted(input_history_dict.items()) ]
173 items = [ val.rstrip() for _, val in sorted(input_history_dict.items()) ]
174 self._set_history(items)
174 self._set_history(items)
175
175
176 def _handle_pyout(self, msg):
176 def _handle_pyout(self, msg):
177 """ Reimplemented for IPython-style "display hook".
177 """ Reimplemented for IPython-style "display hook".
178 """
178 """
179 if not self._hidden and self._is_from_this_session(msg):
179 if not self._hidden and self._is_from_this_session(msg):
180 content = msg['content']
180 content = msg['content']
181 prompt_number = content['execution_count']
181 prompt_number = content['execution_count']
182 self._append_plain_text(self.output_sep)
182 self._append_plain_text(self.output_sep)
183 self._append_html(self._make_out_prompt(prompt_number))
183 self._append_html(self._make_out_prompt(prompt_number))
184 self._append_plain_text(content['data']+self.output_sep2)
184 self._append_plain_text(content['data']+self.output_sep2)
185
185
186 def _started_channels(self):
186 def _started_channels(self):
187 """ Reimplemented to make a history request.
187 """ Reimplemented to make a history request.
188 """
188 """
189 super(IPythonWidget, self)._started_channels()
189 super(IPythonWidget, self)._started_channels()
190 self.kernel_manager.xreq_channel.history(raw=True, output=False)
190 self.kernel_manager.xreq_channel.history(raw=True, output=False)
191
191
192 #---------------------------------------------------------------------------
192 #---------------------------------------------------------------------------
193 # 'ConsoleWidget' public interface
193 # 'ConsoleWidget' public interface
194 #---------------------------------------------------------------------------
194 #---------------------------------------------------------------------------
195
195
196 def copy(self):
196 def copy(self):
197 """ Copy the currently selected text to the clipboard, removing prompts
197 """ Copy the currently selected text to the clipboard, removing prompts
198 if possible.
198 if possible.
199 """
199 """
200 text = unicode(self._control.textCursor().selection().toPlainText())
200 text = unicode(self._control.textCursor().selection().toPlainText())
201 if text:
201 if text:
202 lines = map(transform_ipy_prompt, text.splitlines())
202 lines = map(transform_ipy_prompt, text.splitlines())
203 text = '\n'.join(lines)
203 text = '\n'.join(lines)
204 QtGui.QApplication.clipboard().setText(text)
204 QtGui.QApplication.clipboard().setText(text)
205
205
206 #---------------------------------------------------------------------------
206 #---------------------------------------------------------------------------
207 # 'FrontendWidget' public interface
207 # 'FrontendWidget' public interface
208 #---------------------------------------------------------------------------
208 #---------------------------------------------------------------------------
209
209
210 def execute_file(self, path, hidden=False):
210 def execute_file(self, path, hidden=False):
211 """ Reimplemented to use the 'run' magic.
211 """ Reimplemented to use the 'run' magic.
212 """
212 """
213 self.execute('%%run %s' % path, hidden=hidden)
213 self.execute('%%run %s' % path, hidden=hidden)
214
214
215 #---------------------------------------------------------------------------
215 #---------------------------------------------------------------------------
216 # 'FrontendWidget' protected interface
216 # 'FrontendWidget' protected interface
217 #---------------------------------------------------------------------------
217 #---------------------------------------------------------------------------
218
218
219 def _complete(self):
219 def _complete(self):
220 """ Reimplemented to support IPython's improved completion machinery.
220 """ Reimplemented to support IPython's improved completion machinery.
221 """
221 """
222 # We let the kernel split the input line, so we *always* send an empty
222 # We let the kernel split the input line, so we *always* send an empty
223 # text field. Readline-based frontends do get a real text field which
223 # text field. Readline-based frontends do get a real text field which
224 # they can use.
224 # they can use.
225 text = ''
225 text = ''
226
226
227 # Send the completion request to the kernel
227 # Send the completion request to the kernel
228 msg_id = self.kernel_manager.xreq_channel.complete(
228 msg_id = self.kernel_manager.xreq_channel.complete(
229 text, # text
229 text, # text
230 self._get_input_buffer_cursor_line(), # line
230 self._get_input_buffer_cursor_line(), # line
231 self._get_input_buffer_cursor_column(), # cursor_pos
231 self._get_input_buffer_cursor_column(), # cursor_pos
232 self.input_buffer) # block
232 self.input_buffer) # block
233 pos = self._get_cursor().position()
233 pos = self._get_cursor().position()
234 info = self._CompletionRequest(msg_id, pos)
234 info = self._CompletionRequest(msg_id, pos)
235 self._request_info['complete'] = info
235 self._request_info['complete'] = info
236
236
237 def _get_banner(self):
237 def _get_banner(self):
238 """ Reimplemented to return IPython's default banner.
238 """ Reimplemented to return IPython's default banner.
239 """
239 """
240 return default_gui_banner
240 return default_gui_banner
241
241
242 def _process_execute_error(self, msg):
242 def _process_execute_error(self, msg):
243 """ Reimplemented for IPython-style traceback formatting.
243 """ Reimplemented for IPython-style traceback formatting.
244 """
244 """
245 content = msg['content']
245 content = msg['content']
246 traceback = '\n'.join(content['traceback']) + '\n'
246 traceback = '\n'.join(content['traceback']) + '\n'
247 if False:
247 if False:
248 # FIXME: For now, tracebacks come as plain text, so we can't use
248 # FIXME: For now, tracebacks come as plain text, so we can't use
249 # the html renderer yet. Once we refactor ultratb to produce
249 # the html renderer yet. Once we refactor ultratb to produce
250 # properly styled tracebacks, this branch should be the default
250 # properly styled tracebacks, this branch should be the default
251 traceback = traceback.replace(' ', '&nbsp;')
251 traceback = traceback.replace(' ', '&nbsp;')
252 traceback = traceback.replace('\n', '<br/>')
252 traceback = traceback.replace('\n', '<br/>')
253
253
254 ename = content['ename']
254 ename = content['ename']
255 ename_styled = '<span class="error">%s</span>' % ename
255 ename_styled = '<span class="error">%s</span>' % ename
256 traceback = traceback.replace(ename, ename_styled)
256 traceback = traceback.replace(ename, ename_styled)
257
257
258 self._append_html(traceback)
258 self._append_html(traceback)
259 else:
259 else:
260 # This is the fallback for now, using plain text with ansi escapes
260 # This is the fallback for now, using plain text with ansi escapes
261 self._append_plain_text(traceback)
261 self._append_plain_text(traceback)
262
262
263 def _process_execute_payload(self, item):
263 def _process_execute_payload(self, item):
264 """ Reimplemented to dispatch payloads to handler methods.
264 """ Reimplemented to dispatch payloads to handler methods.
265 """
265 """
266 handler = self._payload_handlers.get(item['source'])
266 handler = self._payload_handlers.get(item['source'])
267 if handler is None:
267 if handler is None:
268 # We have no handler for this type of payload, simply ignore it
268 # We have no handler for this type of payload, simply ignore it
269 return False
269 return False
270 else:
270 else:
271 handler(item)
271 handler(item)
272 return True
272 return True
273
273
274 def _show_interpreter_prompt(self, number=None):
274 def _show_interpreter_prompt(self, number=None):
275 """ Reimplemented for IPython-style prompts.
275 """ Reimplemented for IPython-style prompts.
276 """
276 """
277 # If a number was not specified, make a prompt number request.
277 # If a number was not specified, make a prompt number request.
278 if number is None:
278 if number is None:
279 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
279 msg_id = self.kernel_manager.xreq_channel.execute('', silent=True)
280 info = self._ExecutionRequest(msg_id, 'prompt')
280 info = self._ExecutionRequest(msg_id, 'prompt')
281 self._request_info['execute'] = info
281 self._request_info['execute'] = info
282 return
282 return
283
283
284 # Show a new prompt and save information about it so that it can be
284 # Show a new prompt and save information about it so that it can be
285 # updated later if the prompt number turns out to be wrong.
285 # updated later if the prompt number turns out to be wrong.
286 self._prompt_sep = self.input_sep
286 self._prompt_sep = self.input_sep
287 self._show_prompt(self._make_in_prompt(number), html=True)
287 self._show_prompt(self._make_in_prompt(number), html=True)
288 block = self._control.document().lastBlock()
288 block = self._control.document().lastBlock()
289 length = len(self._prompt)
289 length = len(self._prompt)
290 self._previous_prompt_obj = self._PromptBlock(block, length, number)
290 self._previous_prompt_obj = self._PromptBlock(block, length, number)
291
291
292 # Update continuation prompt to reflect (possibly) new prompt length.
292 # Update continuation prompt to reflect (possibly) new prompt length.
293 self._set_continuation_prompt(
293 self._set_continuation_prompt(
294 self._make_continuation_prompt(self._prompt), html=True)
294 self._make_continuation_prompt(self._prompt), html=True)
295
295
296 # Load code from the %loadpy magic, if necessary.
296 # Load code from the %loadpy magic, if necessary.
297 if self._code_to_load is not None:
297 if self._code_to_load is not None:
298 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
298 self.input_buffer = dedent(unicode(self._code_to_load).rstrip())
299 self._code_to_load = None
299 self._code_to_load = None
300
300
301 def _show_interpreter_prompt_for_reply(self, msg):
301 def _show_interpreter_prompt_for_reply(self, msg):
302 """ Reimplemented for IPython-style prompts.
302 """ Reimplemented for IPython-style prompts.
303 """
303 """
304 # Update the old prompt number if necessary.
304 # Update the old prompt number if necessary.
305 content = msg['content']
305 content = msg['content']
306 previous_prompt_number = content['execution_count']
306 previous_prompt_number = content['execution_count']
307 if self._previous_prompt_obj and \
307 if self._previous_prompt_obj and \
308 self._previous_prompt_obj.number != previous_prompt_number:
308 self._previous_prompt_obj.number != previous_prompt_number:
309 block = self._previous_prompt_obj.block
309 block = self._previous_prompt_obj.block
310
310
311 # Make sure the prompt block has not been erased.
311 # Make sure the prompt block has not been erased.
312 if block.isValid() and not block.text().isEmpty():
312 if block.isValid() and not block.text().isEmpty():
313
313
314 # Remove the old prompt and insert a new prompt.
314 # Remove the old prompt and insert a new prompt.
315 cursor = QtGui.QTextCursor(block)
315 cursor = QtGui.QTextCursor(block)
316 cursor.movePosition(QtGui.QTextCursor.Right,
316 cursor.movePosition(QtGui.QTextCursor.Right,
317 QtGui.QTextCursor.KeepAnchor,
317 QtGui.QTextCursor.KeepAnchor,
318 self._previous_prompt_obj.length)
318 self._previous_prompt_obj.length)
319 prompt = self._make_in_prompt(previous_prompt_number)
319 prompt = self._make_in_prompt(previous_prompt_number)
320 self._prompt = self._insert_html_fetching_plain_text(
320 self._prompt = self._insert_html_fetching_plain_text(
321 cursor, prompt)
321 cursor, prompt)
322
322
323 # When the HTML is inserted, Qt blows away the syntax
323 # When the HTML is inserted, Qt blows away the syntax
324 # highlighting for the line, so we need to rehighlight it.
324 # highlighting for the line, so we need to rehighlight it.
325 self._highlighter.rehighlightBlock(cursor.block())
325 self._highlighter.rehighlightBlock(cursor.block())
326
326
327 self._previous_prompt_obj = None
327 self._previous_prompt_obj = None
328
328
329 # Show a new prompt with the kernel's estimated prompt number.
329 # Show a new prompt with the kernel's estimated prompt number.
330 self._show_interpreter_prompt(previous_prompt_number + 1)
330 self._show_interpreter_prompt(previous_prompt_number + 1)
331
331
332 #---------------------------------------------------------------------------
332 #---------------------------------------------------------------------------
333 # 'IPythonWidget' interface
333 # 'IPythonWidget' interface
334 #---------------------------------------------------------------------------
334 #---------------------------------------------------------------------------
335
335
336 def set_default_style(self, colors='lightbg'):
336 def set_default_style(self, colors='lightbg'):
337 """ Sets the widget style to the class defaults.
337 """ Sets the widget style to the class defaults.
338
338
339 Parameters:
339 Parameters:
340 -----------
340 -----------
341 colors : str, optional (default lightbg)
341 colors : str, optional (default lightbg)
342 Whether to use the default IPython light background or dark
342 Whether to use the default IPython light background or dark
343 background or B&W style.
343 background or B&W style.
344 """
344 """
345 colors = colors.lower()
345 colors = colors.lower()
346 if colors=='lightbg':
346 if colors=='lightbg':
347 self.style_sheet = default_light_style_sheet
347 self.style_sheet = default_light_style_sheet
348 self.syntax_style = default_light_syntax_style
348 self.syntax_style = default_light_syntax_style
349 elif colors=='linux':
349 elif colors=='linux':
350 self.style_sheet = default_dark_style_sheet
350 self.style_sheet = default_dark_style_sheet
351 self.syntax_style = default_dark_syntax_style
351 self.syntax_style = default_dark_syntax_style
352 elif colors=='nocolor':
352 elif colors=='nocolor':
353 self.style_sheet = default_bw_style_sheet
353 self.style_sheet = default_bw_style_sheet
354 self.syntax_style = default_bw_syntax_style
354 self.syntax_style = default_bw_syntax_style
355 else:
355 else:
356 raise KeyError("No such color scheme: %s"%colors)
356 raise KeyError("No such color scheme: %s"%colors)
357
357
358 #---------------------------------------------------------------------------
358 #---------------------------------------------------------------------------
359 # 'IPythonWidget' protected interface
359 # 'IPythonWidget' protected interface
360 #---------------------------------------------------------------------------
360 #---------------------------------------------------------------------------
361
361
362 def _edit(self, filename, line=None):
362 def _edit(self, filename, line=None):
363 """ Opens a Python script for editing.
363 """ Opens a Python script for editing.
364
364
365 Parameters:
365 Parameters:
366 -----------
366 -----------
367 filename : str
367 filename : str
368 A path to a local system file.
368 A path to a local system file.
369
369
370 line : int, optional
370 line : int, optional
371 A line of interest in the file.
371 A line of interest in the file.
372 """
372 """
373 if self.custom_edit:
373 if self.custom_edit:
374 self.custom_edit_requested.emit(filename, line)
374 self.custom_edit_requested.emit(filename, line)
375 elif self.editor == 'default':
375 elif self.editor == 'default':
376 self._append_plain_text('No default editor available.\n')
376 self._append_plain_text('No default editor available.\n')
377 else:
377 else:
378 try:
378 try:
379 filename = '"%s"' % filename
379 filename = '"%s"' % filename
380 if line and self.editor_line:
380 if line and self.editor_line:
381 command = self.editor_line.format(filename=filename,
381 command = self.editor_line.format(filename=filename,
382 line=line)
382 line=line)
383 else:
383 else:
384 try:
384 try:
385 command = self.editor.format()
385 command = self.editor.format()
386 except KeyError:
386 except KeyError:
387 command = self.editor.format(filename=filename)
387 command = self.editor.format(filename=filename)
388 else:
388 else:
389 command += ' ' + filename
389 command += ' ' + filename
390 except KeyError:
390 except KeyError:
391 self._append_plain_text('Invalid editor command.\n')
391 self._append_plain_text('Invalid editor command.\n')
392 else:
392 else:
393 try:
393 try:
394 Popen(command, shell=True)
394 Popen(command, shell=True)
395 except OSError:
395 except OSError:
396 msg = 'Opening editor with command "%s" failed.\n'
396 msg = 'Opening editor with command "%s" failed.\n'
397 self._append_plain_text(msg % command)
397 self._append_plain_text(msg % command)
398
398
399 def _make_in_prompt(self, number):
399 def _make_in_prompt(self, number):
400 """ Given a prompt number, returns an HTML In prompt.
400 """ Given a prompt number, returns an HTML In prompt.
401 """
401 """
402 body = self.in_prompt % number
402 body = self.in_prompt % number
403 return '<span class="in-prompt">%s</span>' % body
403 return '<span class="in-prompt">%s</span>' % body
404
404
405 def _make_continuation_prompt(self, prompt):
405 def _make_continuation_prompt(self, prompt):
406 """ Given a plain text version of an In prompt, returns an HTML
406 """ Given a plain text version of an In prompt, returns an HTML
407 continuation prompt.
407 continuation prompt.
408 """
408 """
409 end_chars = '...: '
409 end_chars = '...: '
410 space_count = len(prompt.lstrip('\n')) - len(end_chars)
410 space_count = len(prompt.lstrip('\n')) - len(end_chars)
411 body = '&nbsp;' * space_count + end_chars
411 body = '&nbsp;' * space_count + end_chars
412 return '<span class="in-prompt">%s</span>' % body
412 return '<span class="in-prompt">%s</span>' % body
413
413
414 def _make_out_prompt(self, number):
414 def _make_out_prompt(self, number):
415 """ Given a prompt number, returns an HTML Out prompt.
415 """ Given a prompt number, returns an HTML Out prompt.
416 """
416 """
417 body = self.out_prompt % number
417 body = self.out_prompt % number
418 return '<span class="out-prompt">%s</span>' % body
418 return '<span class="out-prompt">%s</span>' % body
419
419
420 #------ Payload handlers --------------------------------------------------
420 #------ Payload handlers --------------------------------------------------
421
421
422 # Payload handlers with a generic interface: each takes the opaque payload
422 # Payload handlers with a generic interface: each takes the opaque payload
423 # dict, unpacks it and calls the underlying functions with the necessary
423 # dict, unpacks it and calls the underlying functions with the necessary
424 # arguments.
424 # arguments.
425
425
426 def _handle_payload_edit(self, item):
426 def _handle_payload_edit(self, item):
427 self._edit(item['filename'], item['line_number'])
427 self._edit(item['filename'], item['line_number'])
428
428
429 def _handle_payload_exit(self, item):
429 def _handle_payload_exit(self, item):
430 self._keep_kernel_on_exit = item['keepkernel']
430 self._keep_kernel_on_exit = item['keepkernel']
431 self.exit_requested.emit()
431 self.exit_requested.emit()
432
432
433 def _handle_payload_loadpy(self, item):
433 def _handle_payload_loadpy(self, item):
434 # Simple save the text of the .py file for later. The text is written
434 # Simple save the text of the .py file for later. The text is written
435 # to the buffer when _prompt_started_hook is called.
435 # to the buffer when _prompt_started_hook is called.
436 self._code_to_load = item['text']
436 self._code_to_load = item['text']
437
437
438 def _handle_payload_page(self, item):
438 def _handle_payload_page(self, item):
439 # Since the plain text widget supports only a very small subset of HTML
439 # Since the plain text widget supports only a very small subset of HTML
440 # and we have no control over the HTML source, we only page HTML
440 # and we have no control over the HTML source, we only page HTML
441 # payloads in the rich text widget.
441 # payloads in the rich text widget.
442 if item['html'] and self.kind == 'rich':
442 if item['html'] and self.kind == 'rich':
443 self._page(item['html'], html=True)
443 self._page(item['html'], html=True)
444 else:
444 else:
445 self._page(item['text'], html=False)
445 self._page(item['text'], html=False)
446
446
447 #------ Trait change handlers ---------------------------------------------
447 #------ Trait change handlers ---------------------------------------------
448
448
449 def _style_sheet_changed(self):
449 def _style_sheet_changed(self):
450 """ Set the style sheets of the underlying widgets.
450 """ Set the style sheets of the underlying widgets.
451 """
451 """
452 self.setStyleSheet(self.style_sheet)
452 self.setStyleSheet(self.style_sheet)
453 self._control.document().setDefaultStyleSheet(self.style_sheet)
453 self._control.document().setDefaultStyleSheet(self.style_sheet)
454 if self._page_control:
454 if self._page_control:
455 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
455 self._page_control.document().setDefaultStyleSheet(self.style_sheet)
456
456
457 bg_color = self._control.palette().background().color()
457 bg_color = self._control.palette().background().color()
458 self._ansi_processor.set_background_color(bg_color)
458 self._ansi_processor.set_background_color(bg_color)
459
459
460 def _syntax_style_changed(self):
460 def _syntax_style_changed(self):
461 """ Set the style for the syntax highlighter.
461 """ Set the style for the syntax highlighter.
462 """
462 """
463 if self.syntax_style:
463 if self.syntax_style:
464 self._highlighter.set_style(self.syntax_style)
464 self._highlighter.set_style(self.syntax_style)
465 else:
465 else:
466 self._highlighter.set_style_sheet(self.style_sheet)
466 self._highlighter.set_style_sheet(self.style_sheet)
467
467
General Comments 0
You need to be logged in to leave comments. Login now