diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py index 4f41bcd..c0f38a1 100644 --- a/IPython/core/displayhook.py +++ b/IPython/core/displayhook.py @@ -282,9 +282,8 @@ class DisplayHook(Configurable): """Log the output.""" if self.shell.logger.log_output: self.shell.logger.log_write(format_dict['text/plain'], 'output') - # This is a defaultdict of lists, so we can always append - self.shell.history_manager.output_hist_reprs[self.prompt_count]\ - .append(format_dict['text/plain']) + self.shell.history_manager.output_hist_reprs[self.prompt_count] = \ + format_dict['text/plain'] def finish_displayhook(self): """Finish up all displayhook activities.""" diff --git a/IPython/core/history.py b/IPython/core/history.py index 12c306b..e95b0b1 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -15,14 +15,11 @@ from __future__ import print_function # Stdlib imports import atexit import datetime -import json import os import re import sqlite3 import threading -from collections import defaultdict - # Our own packages from IPython.config.configurable import Configurable import IPython.utils.io @@ -56,11 +53,10 @@ class HistoryManager(Configurable): return [] # A dict of output history, keyed with ints from the shell's - # execution count. If there are several outputs from one command, - # only the last one is stored. + # execution count. output_hist = Dict() - # Contains all outputs, in lists of reprs. - output_hist_reprs = Instance(defaultdict, args=(list,)) + # The text/plain repr of outputs. + output_hist_reprs = Dict() # String holding the path to the history file hist_file = Unicode(config=True) @@ -92,11 +88,10 @@ class HistoryManager(Configurable): _ii = Unicode(u'') _iii = Unicode(u'') - # A set with all forms of the exit command, so that we don't store them in - # the history (it's annoying to rewind the first entry and land on an exit - # call). - _exit_commands = Instance(set, args=(['Quit', 'quit', 'Exit', 'exit', - '%Quit', '%quit', '%Exit', '%exit'],)) + # A regex matching all forms of the exit command, so that we don't store + # them in the history (it's annoying to rewind the first entry and land on + # an exit call). + _exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$") def __init__(self, shell, config=None, **traits): """Create a new history manager associated with a shell instance. @@ -218,9 +213,7 @@ class HistoryManager(Configurable): cur = self.db.execute("SELECT session, line, %s FROM %s " %\ (toget, sqlfrom) + sql, params) if output: # Regroup into 3-tuples, and parse JSON - loads = lambda out: json.loads(out) if out else None - return ((ses, lin, (inp, loads(out))) \ - for ses, lin, inp, out in cur) + return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur) return cur @@ -383,7 +376,7 @@ class HistoryManager(Configurable): source_raw = source_raw.rstrip('\n') # do not store exit/quit commands - if source_raw.strip() in self._exit_commands: + if self._exit_re.match(source_raw.strip()): return self.input_hist_parsed.append(source) @@ -419,9 +412,9 @@ class HistoryManager(Configurable): line_num : int The line number from which to save outputs """ - if (not self.db_log_output) or not self.output_hist_reprs[line_num]: + if (not self.db_log_output) or (line_num not in self.output_hist_reprs): return - output = json.dumps(self.output_hist_reprs[line_num]) + output = self.output_hist_reprs[line_num] with self.db_output_cache_lock: self.db_output_cache.append((line_num, output)) @@ -696,7 +689,7 @@ def magic_history(self, parameter_s = ''): inline = "\n... ".join(inline.splitlines()) + "\n..." print(inline, file=outfile) if get_output and output: - print("\n".join(output), file=outfile) + print(output, file=outfile) if close_at_end: outfile.close() diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 2198484..e8f4aee 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2150,12 +2150,9 @@ class InteractiveShell(Configurable, Magic): self.showsyntaxerror() self.execution_count += 1 return None - - interactivity = 'last' # Last node to be run interactive - if len(cell.splitlines()) == 1: - interactivity = 'all' # Single line; run fully interactive - self.run_ast_nodes(code_ast.body, cell_name, interactivity) + self.run_ast_nodes(code_ast.body, cell_name, + interactivity="last_expr") # Execute any registered post-execution functions. for func, status in self._post_execute.iteritems(): @@ -2175,7 +2172,7 @@ class InteractiveShell(Configurable, Magic): # Each cell is a *single* input, regardless of how many lines it has self.execution_count += 1 - def run_ast_nodes(self, nodelist, cell_name, interactivity='last'): + def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr'): """Run a sequence of AST nodes. The execution mode depends on the interactivity parameter. @@ -2187,13 +2184,21 @@ class InteractiveShell(Configurable, Magic): Will be passed to the compiler as the filename of the cell. Typically the value returned by ip.compile.cache(cell). interactivity : str - 'all', 'last' or 'none', specifying which nodes should be run - interactively (displaying output from expressions). Other values for - this parameter will raise a ValueError. + 'all', 'last', 'last_expr' or 'none', specifying which nodes should be + run interactively (displaying output from expressions). 'last_expr' + will run the last node interactively only if it is an expression (i.e. + expressions in loops or other blocks are not displayed. Other values + for this parameter will raise a ValueError. """ if not nodelist: return + if interactivity == 'last_expr': + if isinstance(nodelist[-1], ast.Expr): + interactivity = "last" + else: + interactivity = "none" + if interactivity == 'none': to_run_exec, to_run_interactive = nodelist, [] elif interactivity == 'last': diff --git a/IPython/core/tests/test_history.py b/IPython/core/tests/test_history.py index 27f43f7..9a74a4e 100644 --- a/IPython/core/tests/test_history.py +++ b/IPython/core/tests/test_history.py @@ -33,7 +33,7 @@ def test_history(): ip.history_manager.db_log_output = True # Doesn't match the input, but we'll just check it's stored. - ip.history_manager.output_hist_reprs[3].append("spam") + ip.history_manager.output_hist_reprs[3] = "spam" ip.history_manager.store_output(3) nt.assert_equal(ip.history_manager.input_hist_raw, [''] + hist) @@ -53,7 +53,7 @@ def test_history(): # Check get_hist_tail gothist = ip.history_manager.get_tail(4, output=True, include_latest=True) - expected = [(1, 3, (hist[-1], ["spam"])), + expected = [(1, 3, (hist[-1], "spam")), (2, 1, (newcmds[0], None)), (2, 2, (newcmds[1], None)), (2, 3, (newcmds[2], None)),] @@ -68,7 +68,7 @@ def test_history(): gothist = ip.history_manager.search("*test*") nt.assert_equal(list(gothist), [(1,2,hist[1])] ) gothist = ip.history_manager.search("b*", output=True) - nt.assert_equal(list(gothist), [(1,3,(hist[2],["spam"]))] ) + nt.assert_equal(list(gothist), [(1,3,(hist[2],"spam"))] ) # Cross testing: check that magic %save can get previous session. testfilename = os.path.realpath(os.path.join(tmpdir, "test.py"))