Show More
@@ -1,14 +1,163 b'' | |||||
1 | # -*- coding: utf-8 -*- |
|
|||
2 |
|
|
1 | """ History related magics and functionality """ | |
|
2 | #----------------------------------------------------------------------------- | |||
|
3 | # Copyright (C) 2010 The IPython Development Team. | |||
|
4 | # | |||
|
5 | # Distributed under the terms of the BSD License. | |||
|
6 | # | |||
|
7 | # The full license is in the file COPYING.txt, distributed with this software. | |||
|
8 | #----------------------------------------------------------------------------- | |||
|
9 | ||||
|
10 | #----------------------------------------------------------------------------- | |||
|
11 | # Imports | |||
|
12 | #----------------------------------------------------------------------------- | |||
|
13 | from __future__ import print_function | |||
3 |
|
14 | |||
4 | # Stdlib imports |
|
15 | # Stdlib imports | |
5 | import fnmatch |
|
16 | import fnmatch | |
6 | import os |
|
17 | import os | |
|
18 | import sys | |||
7 |
|
19 | |||
|
20 | # Our own packages | |||
8 | import IPython.utils.io |
|
21 | import IPython.utils.io | |
|
22 | ||||
|
23 | from IPython.core import ipapi | |||
|
24 | from IPython.core.inputlist import InputList | |||
|
25 | from IPython.utils.pickleshare import PickleShareDB | |||
9 | from IPython.utils.io import ask_yes_no |
|
26 | from IPython.utils.io import ask_yes_no | |
10 | from IPython.utils.warn import warn |
|
27 | from IPython.utils.warn import warn | |
11 | from IPython.core import ipapi |
|
28 | ||
|
29 | #----------------------------------------------------------------------------- | |||
|
30 | # Classes and functions | |||
|
31 | #----------------------------------------------------------------------------- | |||
|
32 | ||||
|
33 | class HistoryManager(object): | |||
|
34 | """A class to organize all history-related functionality in one place. | |||
|
35 | """ | |||
|
36 | def __init__(self, shell): | |||
|
37 | """Create a new history manager associated with a shell instance. | |||
|
38 | """ | |||
|
39 | self.shell = shell | |||
|
40 | ||||
|
41 | # List of input with multi-line handling. | |||
|
42 | self.input_hist = InputList() | |||
|
43 | # This one will hold the 'raw' input history, without any | |||
|
44 | # pre-processing. This will allow users to retrieve the input just as | |||
|
45 | # it was exactly typed in by the user, with %hist -r. | |||
|
46 | self.input_hist_raw = InputList() | |||
|
47 | ||||
|
48 | # list of visited directories | |||
|
49 | try: | |||
|
50 | self.dir_hist = [os.getcwd()] | |||
|
51 | except OSError: | |||
|
52 | self.dir_hist = [] | |||
|
53 | ||||
|
54 | # dict of output history | |||
|
55 | self.output_hist = {} | |||
|
56 | ||||
|
57 | # Now the history file | |||
|
58 | if shell.profile: | |||
|
59 | histfname = 'history-%s' % shell.profile | |||
|
60 | else: | |||
|
61 | histfname = 'history' | |||
|
62 | self.hist_file = os.path.join(shell.ipython_dir, histfname) | |||
|
63 | ||||
|
64 | # Fill the history zero entry, user counter starts at 1 | |||
|
65 | self.input_hist.append('\n') | |||
|
66 | self.input_hist_raw.append('\n') | |||
|
67 | ||||
|
68 | # Objects related to shadow history management | |||
|
69 | self._init_shadow_hist() | |||
|
70 | ||||
|
71 | # For backwards compatibility, we must put these back in the shell | |||
|
72 | # object, until we've removed all direct uses of the history objects in | |||
|
73 | # the shell itself. | |||
|
74 | shell.input_hist = self.input_hist | |||
|
75 | shell.input_hist_raw = self.input_hist_raw | |||
|
76 | shell.output_hist = self.output_hist | |||
|
77 | shell.dir_hist = self.dir_hist | |||
|
78 | shell.histfile = self.hist_file | |||
|
79 | shell.shadowhist = self.shadow_hist | |||
|
80 | shell.db = self.shadow_db | |||
|
81 | ||||
|
82 | def _init_shadow_hist(self): | |||
|
83 | try: | |||
|
84 | self.shadow_db = PickleShareDB(os.path.join( | |||
|
85 | self.shell.ipython_dir, 'db')) | |||
|
86 | except UnicodeDecodeError: | |||
|
87 | print("Your ipython_dir can't be decoded to unicode!") | |||
|
88 | print("Please set HOME environment variable to something that") | |||
|
89 | print(r"only has ASCII characters, e.g. c:\home") | |||
|
90 | print("Now it is", self.ipython_dir) | |||
|
91 | sys.exit() | |||
|
92 | self.shadow_hist = ShadowHist(self.shadow_db) | |||
|
93 | ||||
|
94 | def save_hist(self): | |||
|
95 | """Save input history to a file (via readline library).""" | |||
|
96 | ||||
|
97 | try: | |||
|
98 | self.shell.readline.write_history_file(self.hist_file) | |||
|
99 | except: | |||
|
100 | print('Unable to save IPython command history to file: ' + | |||
|
101 | `self.hist_file`) | |||
|
102 | ||||
|
103 | def reload_hist(self): | |||
|
104 | """Reload the input history from disk file.""" | |||
|
105 | ||||
|
106 | try: | |||
|
107 | self.shell.readline.clear_history() | |||
|
108 | self.shell.readline.read_history_file(self.hist_file) | |||
|
109 | except AttributeError: | |||
|
110 | pass | |||
|
111 | ||||
|
112 | def get_history(self, index=None, raw=False, output=True): | |||
|
113 | """Get the history list. | |||
|
114 | ||||
|
115 | Get the input and output history. | |||
|
116 | ||||
|
117 | Parameters | |||
|
118 | ---------- | |||
|
119 | index : n or (n1, n2) or None | |||
|
120 | If n, then the last entries. If a tuple, then all in | |||
|
121 | range(n1, n2). If None, then all entries. Raises IndexError if | |||
|
122 | the format of index is incorrect. | |||
|
123 | raw : bool | |||
|
124 | If True, return the raw input. | |||
|
125 | output : bool | |||
|
126 | If True, then return the output as well. | |||
|
127 | ||||
|
128 | Returns | |||
|
129 | ------- | |||
|
130 | If output is True, then return a dict of tuples, keyed by the prompt | |||
|
131 | numbers and with values of (input, output). If output is False, then | |||
|
132 | a dict, keyed by the prompt number with the values of input. Raises | |||
|
133 | IndexError if no history is found. | |||
|
134 | """ | |||
|
135 | if raw: | |||
|
136 | input_hist = self.input_hist_raw | |||
|
137 | else: | |||
|
138 | input_hist = self.input_hist | |||
|
139 | if output: | |||
|
140 | output_hist = self.output_hist | |||
|
141 | n = len(input_hist) | |||
|
142 | if index is None: | |||
|
143 | start=0; stop=n | |||
|
144 | elif isinstance(index, int): | |||
|
145 | start=n-index; stop=n | |||
|
146 | elif isinstance(index, tuple) and len(index) == 2: | |||
|
147 | start=index[0]; stop=index[1] | |||
|
148 | else: | |||
|
149 | raise IndexError('Not a valid index for the input history: %r' | |||
|
150 | % index) | |||
|
151 | hist = {} | |||
|
152 | for i in range(start, stop): | |||
|
153 | if output: | |||
|
154 | hist[i] = (input_hist[i], output_hist.get(i)) | |||
|
155 | else: | |||
|
156 | hist[i] = input_hist[i] | |||
|
157 | if len(hist)==0: | |||
|
158 | raise IndexError('No history for range of indices: %r' % index) | |||
|
159 | return hist | |||
|
160 | ||||
12 |
|
161 | |||
13 | def magic_history(self, parameter_s = ''): |
|
162 | def magic_history(self, parameter_s = ''): | |
14 | """Print input history (_i<n> variables), with most recent last. |
|
163 | """Print input history (_i<n> variables), with most recent last. | |
@@ -55,7 +204,7 b" def magic_history(self, parameter_s = ''):" | |||||
55 | """ |
|
204 | """ | |
56 |
|
205 | |||
57 | if not self.shell.displayhook.do_full_cache: |
|
206 | if not self.shell.displayhook.do_full_cache: | |
58 |
print |
|
207 | print('This feature is only available if numbered prompts are in use.') | |
59 | return |
|
208 | return | |
60 | opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list') |
|
209 | opts,args = self.parse_options(parameter_s,'gnoptsrf:',mode='list') | |
61 |
|
210 | |||
@@ -69,7 +218,7 b" def magic_history(self, parameter_s = ''):" | |||||
69 | else: |
|
218 | else: | |
70 | if os.path.exists(outfname): |
|
219 | if os.path.exists(outfname): | |
71 | if not ask_yes_no("File %r exists. Overwrite?" % outfname): |
|
220 | if not ask_yes_no("File %r exists. Overwrite?" % outfname): | |
72 |
print |
|
221 | print('Aborting.') | |
73 | return |
|
222 | return | |
74 |
|
223 | |||
75 | outfile = open(outfname,'w') |
|
224 | outfile = open(outfname,'w') | |
@@ -103,7 +252,7 b" def magic_history(self, parameter_s = ''):" | |||||
103 | init, final = map(int, args) |
|
252 | init, final = map(int, args) | |
104 | else: |
|
253 | else: | |
105 | warn('%hist takes 0, 1 or 2 arguments separated by spaces.') |
|
254 | warn('%hist takes 0, 1 or 2 arguments separated by spaces.') | |
106 | print >> IPython.utils.io.Term.cout, self.magic_hist.__doc__ |
|
255 | print(self.magic_hist.__doc__, file=IPython.utils.io.Term.cout) | |
107 | return |
|
256 | return | |
108 |
|
257 | |||
109 | width = len(str(final)) |
|
258 | width = len(str(final)) | |
@@ -117,14 +266,14 b" def magic_history(self, parameter_s = ''):" | |||||
117 | sh = self.shell.shadowhist.all() |
|
266 | sh = self.shell.shadowhist.all() | |
118 | for idx, s in sh: |
|
267 | for idx, s in sh: | |
119 | if fnmatch.fnmatch(s, pattern): |
|
268 | if fnmatch.fnmatch(s, pattern): | |
120 |
print |
|
269 | print("0%d: %s" %(idx, s.expandtabs(4)), file=outfile) | |
121 | found = True |
|
270 | found = True | |
122 |
|
271 | |||
123 | if found: |
|
272 | if found: | |
124 |
print |
|
273 | print("===", file=outfile) | |
125 | print >> outfile, \ |
|
274 | print("shadow history ends, fetch by %rep <number> (must start with 0)", | |
126 | "shadow history ends, fetch by %rep <number> (must start with 0)" |
|
275 | file=outfile) | |
127 |
print |
|
276 | print("=== start of normal history ===", file=outfile) | |
128 |
|
277 | |||
129 | for in_num in range(init, final): |
|
278 | for in_num in range(init, final): | |
130 | # Print user history with tabs expanded to 4 spaces. The GUI clients |
|
279 | # Print user history with tabs expanded to 4 spaces. The GUI clients | |
@@ -137,22 +286,22 b" def magic_history(self, parameter_s = ''):" | |||||
137 |
|
286 | |||
138 | multiline = int(inline.count('\n') > 1) |
|
287 | multiline = int(inline.count('\n') > 1) | |
139 | if print_nums: |
|
288 | if print_nums: | |
140 | print >> outfile, \ |
|
289 | print('%s:%s' % (str(in_num).ljust(width), line_sep[multiline]), | |
141 | '%s:%s' % (str(in_num).ljust(width), line_sep[multiline]), |
|
290 | file=outfile) | |
142 | if pyprompts: |
|
291 | if pyprompts: | |
143 |
print |
|
292 | print('>>>', file=outfile) | |
144 | if multiline: |
|
293 | if multiline: | |
145 | lines = inline.splitlines() |
|
294 | lines = inline.splitlines() | |
146 |
print |
|
295 | print('\n... '.join(lines), file=outfile) | |
147 |
print |
|
296 | print('... ', file=outfile) | |
148 | else: |
|
297 | else: | |
149 |
print |
|
298 | print(inline, end='', file=outfile) | |
150 | else: |
|
299 | else: | |
151 |
print |
|
300 | print(inline,end='', file=outfile) | |
152 | if print_outputs: |
|
301 | if print_outputs: | |
153 | output = self.shell.output_hist.get(in_num) |
|
302 | output = self.shell.output_hist.get(in_num) | |
154 | if output is not None: |
|
303 | if output is not None: | |
155 |
print |
|
304 | print(repr(output), file=outfile) | |
156 |
|
305 | |||
157 | if close_at_end: |
|
306 | if close_at_end: | |
158 | outfile.close() |
|
307 | outfile.close() | |
@@ -223,10 +372,10 b' def rep_f(self, arg):' | |||||
223 |
|
372 | |||
224 | try: |
|
373 | try: | |
225 | lines = self.extract_input_slices(args, True) |
|
374 | lines = self.extract_input_slices(args, True) | |
226 |
print |
|
375 | print("lines", lines) | |
227 | self.runlines(lines) |
|
376 | self.runlines(lines) | |
228 | except ValueError: |
|
377 | except ValueError: | |
229 |
print |
|
378 | print("Not found in recent history:", args) | |
230 |
|
379 | |||
231 |
|
380 | |||
232 | _sentinel = object() |
|
381 | _sentinel = object() | |
@@ -251,11 +400,11 b' class ShadowHist(object):' | |||||
251 | if old is not _sentinel: |
|
400 | if old is not _sentinel: | |
252 | return |
|
401 | return | |
253 | newidx = self.inc_idx() |
|
402 | newidx = self.inc_idx() | |
254 |
#print |
|
403 | #print("new", newidx) # dbg | |
255 | self.db.hset('shadowhist',ent, newidx) |
|
404 | self.db.hset('shadowhist',ent, newidx) | |
256 | except: |
|
405 | except: | |
257 | ipapi.get().showtraceback() |
|
406 | ipapi.get().showtraceback() | |
258 |
print |
|
407 | print("WARNING: disabling shadow history") | |
259 | self.disabled = True |
|
408 | self.disabled = True | |
260 |
|
409 | |||
261 | def all(self): |
|
410 | def all(self): | |
@@ -268,7 +417,6 b' class ShadowHist(object):' | |||||
268 | all = self.all() |
|
417 | all = self.all() | |
269 |
|
418 | |||
270 | for k, v in all: |
|
419 | for k, v in all: | |
271 | #print k,v |
|
|||
272 | if k == idx: |
|
420 | if k == idx: | |
273 | return v |
|
421 | return v | |
274 |
|
422 |
@@ -45,6 +45,7 b' from IPython.core.displayhook import DisplayHook' | |||||
45 | from IPython.core.error import TryNext, UsageError |
|
45 | from IPython.core.error import TryNext, UsageError | |
46 | from IPython.core.extensions import ExtensionManager |
|
46 | from IPython.core.extensions import ExtensionManager | |
47 | from IPython.core.fakemodule import FakeModule, init_fakemod_dict |
|
47 | from IPython.core.fakemodule import FakeModule, init_fakemod_dict | |
|
48 | from IPython.core.history import HistoryManager | |||
48 | from IPython.core.inputlist import InputList |
|
49 | from IPython.core.inputlist import InputList | |
49 | from IPython.core.inputsplitter import IPythonInputSplitter |
|
50 | from IPython.core.inputsplitter import IPythonInputSplitter | |
50 | from IPython.core.logger import Logger |
|
51 | from IPython.core.logger import Logger | |
@@ -216,7 +217,8 b' class InteractiveShell(Configurable, Magic):' | |||||
216 | extension_manager = Instance('IPython.core.extensions.ExtensionManager') |
|
217 | extension_manager = Instance('IPython.core.extensions.ExtensionManager') | |
217 | plugin_manager = Instance('IPython.core.plugin.PluginManager') |
|
218 | plugin_manager = Instance('IPython.core.plugin.PluginManager') | |
218 | payload_manager = Instance('IPython.core.payload.PayloadManager') |
|
219 | payload_manager = Instance('IPython.core.payload.PayloadManager') | |
219 |
|
220 | history_manager = Instance('IPython.core.history.HistoryManager') | ||
|
221 | ||||
220 | # Private interface |
|
222 | # Private interface | |
221 | _post_execute = set() |
|
223 | _post_execute = set() | |
222 |
|
224 | |||
@@ -261,7 +263,6 b' class InteractiveShell(Configurable, Magic):' | |||||
261 | self.init_builtins() |
|
263 | self.init_builtins() | |
262 |
|
264 | |||
263 | # pre_config_initialization |
|
265 | # pre_config_initialization | |
264 | self.init_shadow_hist() |
|
|||
265 |
|
266 | |||
266 | # The next section should contain everything that was in ipmaker. |
|
267 | # The next section should contain everything that was in ipmaker. | |
267 | self.init_logstart() |
|
268 | self.init_logstart() | |
@@ -1211,61 +1212,15 b' class InteractiveShell(Configurable, Magic):' | |||||
1211 | #------------------------------------------------------------------------- |
|
1212 | #------------------------------------------------------------------------- | |
1212 |
|
1213 | |||
1213 | def init_history(self): |
|
1214 | def init_history(self): | |
1214 | # List of input with multi-line handling. |
|
1215 | self.history_manager = HistoryManager(shell=self) | |
1215 | self.input_hist = InputList() |
|
|||
1216 | # This one will hold the 'raw' input history, without any |
|
|||
1217 | # pre-processing. This will allow users to retrieve the input just as |
|
|||
1218 | # it was exactly typed in by the user, with %hist -r. |
|
|||
1219 | self.input_hist_raw = InputList() |
|
|||
1220 |
|
||||
1221 | # list of visited directories |
|
|||
1222 | try: |
|
|||
1223 | self.dir_hist = [os.getcwd()] |
|
|||
1224 | except OSError: |
|
|||
1225 | self.dir_hist = [] |
|
|||
1226 |
|
||||
1227 | # dict of output history |
|
|||
1228 | self.output_hist = {} |
|
|||
1229 |
|
||||
1230 | # Now the history file |
|
|||
1231 | if self.profile: |
|
|||
1232 | histfname = 'history-%s' % self.profile |
|
|||
1233 | else: |
|
|||
1234 | histfname = 'history' |
|
|||
1235 | self.histfile = os.path.join(self.ipython_dir, histfname) |
|
|||
1236 |
|
||||
1237 | # Fill the history zero entry, user counter starts at 1 |
|
|||
1238 | self.input_hist.append('\n') |
|
|||
1239 | self.input_hist_raw.append('\n') |
|
|||
1240 |
|
||||
1241 | def init_shadow_hist(self): |
|
|||
1242 | try: |
|
|||
1243 | self.db = pickleshare.PickleShareDB(self.ipython_dir + "/db") |
|
|||
1244 | except exceptions.UnicodeDecodeError: |
|
|||
1245 | print "Your ipython_dir can't be decoded to unicode!" |
|
|||
1246 | print "Please set HOME environment variable to something that" |
|
|||
1247 | print r"only has ASCII characters, e.g. c:\home" |
|
|||
1248 | print "Now it is", self.ipython_dir |
|
|||
1249 | sys.exit() |
|
|||
1250 | self.shadowhist = ipcorehist.ShadowHist(self.db) |
|
|||
1251 |
|
1216 | |||
1252 | def savehist(self): |
|
1217 | def savehist(self): | |
1253 | """Save input history to a file (via readline library).""" |
|
1218 | """Save input history to a file (via readline library).""" | |
1254 |
|
1219 | self.history_manager.save_hist() | ||
1255 |
|
|
1220 | ||
1256 | self.readline.write_history_file(self.histfile) |
|
|||
1257 | except: |
|
|||
1258 | print 'Unable to save IPython command history to file: ' + \ |
|
|||
1259 | `self.histfile` |
|
|||
1260 |
|
||||
1261 | def reloadhist(self): |
|
1221 | def reloadhist(self): | |
1262 | """Reload the input history from disk file.""" |
|
1222 | """Reload the input history from disk file.""" | |
1263 |
|
1223 | self.history_manager.reload_hist() | ||
1264 | try: |
|
|||
1265 | self.readline.clear_history() |
|
|||
1266 | self.readline.read_history_file(self.shell.histfile) |
|
|||
1267 | except AttributeError: |
|
|||
1268 | pass |
|
|||
1269 |
|
1224 | |||
1270 | def history_saving_wrapper(self, func): |
|
1225 | def history_saving_wrapper(self, func): | |
1271 | """ Wrap func for readline history saving |
|
1226 | """ Wrap func for readline history saving | |
@@ -1286,55 +1241,6 b' class InteractiveShell(Configurable, Magic):' | |||||
1286 | readline.read_history_file(self.histfile) |
|
1241 | readline.read_history_file(self.histfile) | |
1287 | return wrapper |
|
1242 | return wrapper | |
1288 |
|
1243 | |||
1289 | def get_history(self, index=None, raw=False, output=True): |
|
|||
1290 | """Get the history list. |
|
|||
1291 |
|
||||
1292 | Get the input and output history. |
|
|||
1293 |
|
||||
1294 | Parameters |
|
|||
1295 | ---------- |
|
|||
1296 | index : n or (n1, n2) or None |
|
|||
1297 | If n, then the last entries. If a tuple, then all in |
|
|||
1298 | range(n1, n2). If None, then all entries. Raises IndexError if |
|
|||
1299 | the format of index is incorrect. |
|
|||
1300 | raw : bool |
|
|||
1301 | If True, return the raw input. |
|
|||
1302 | output : bool |
|
|||
1303 | If True, then return the output as well. |
|
|||
1304 |
|
||||
1305 | Returns |
|
|||
1306 | ------- |
|
|||
1307 | If output is True, then return a dict of tuples, keyed by the prompt |
|
|||
1308 | numbers and with values of (input, output). If output is False, then |
|
|||
1309 | a dict, keyed by the prompt number with the values of input. Raises |
|
|||
1310 | IndexError if no history is found. |
|
|||
1311 | """ |
|
|||
1312 | if raw: |
|
|||
1313 | input_hist = self.input_hist_raw |
|
|||
1314 | else: |
|
|||
1315 | input_hist = self.input_hist |
|
|||
1316 | if output: |
|
|||
1317 | output_hist = self.user_ns['Out'] |
|
|||
1318 | n = len(input_hist) |
|
|||
1319 | if index is None: |
|
|||
1320 | start=0; stop=n |
|
|||
1321 | elif isinstance(index, int): |
|
|||
1322 | start=n-index; stop=n |
|
|||
1323 | elif isinstance(index, tuple) and len(index) == 2: |
|
|||
1324 | start=index[0]; stop=index[1] |
|
|||
1325 | else: |
|
|||
1326 | raise IndexError('Not a valid index for the input history: %r' |
|
|||
1327 | % index) |
|
|||
1328 | hist = {} |
|
|||
1329 | for i in range(start, stop): |
|
|||
1330 | if output: |
|
|||
1331 | hist[i] = (input_hist[i], output_hist.get(i)) |
|
|||
1332 | else: |
|
|||
1333 | hist[i] = input_hist[i] |
|
|||
1334 | if len(hist)==0: |
|
|||
1335 | raise IndexError('No history for range of indices: %r' % index) |
|
|||
1336 | return hist |
|
|||
1337 |
|
||||
1338 | #------------------------------------------------------------------------- |
|
1244 | #------------------------------------------------------------------------- | |
1339 | # Things related to exception handling and tracebacks (not debugging) |
|
1245 | # Things related to exception handling and tracebacks (not debugging) | |
1340 | #------------------------------------------------------------------------- |
|
1246 | #------------------------------------------------------------------------- | |
@@ -2200,6 +2106,20 b' class InteractiveShell(Configurable, Magic):' | |||||
2200 | self.input_hist_raw.append(cell) |
|
2106 | self.input_hist_raw.append(cell) | |
2201 | self.input_hist.append(ipy_cell) |
|
2107 | self.input_hist.append(ipy_cell) | |
2202 |
|
2108 | |||
|
2109 | ||||
|
2110 | # dbg code!!! | |||
|
2111 | def myapp(self, val): # dbg | |||
|
2112 | import traceback as tb | |||
|
2113 | stack = ''.join(tb.format_stack()) | |||
|
2114 | print 'Value:', val | |||
|
2115 | print 'Stack:\n', stack | |||
|
2116 | list.append(self, val) | |||
|
2117 | ||||
|
2118 | import new | |||
|
2119 | self.input_hist.append = new.instancemethod(myapp, self.input_hist, | |||
|
2120 | list) | |||
|
2121 | # End dbg | |||
|
2122 | ||||
2203 | # All user code execution must happen with our context managers active |
|
2123 | # All user code execution must happen with our context managers active | |
2204 | with nested(self.builtin_trap, self.display_trap): |
|
2124 | with nested(self.builtin_trap, self.display_trap): | |
2205 | # Single-block input should behave like an interactive prompt |
|
2125 | # Single-block input should behave like an interactive prompt |
General Comments 0
You need to be logged in to leave comments.
Login now