diff --git a/bpython/config.py b/bpython/config.py index f074fb745..1a58bf525 100644 --- a/bpython/config.py +++ b/bpython/config.py @@ -108,6 +108,7 @@ def loadini(struct, configfile): "last_output": "F9", "left": "C-b", "pastebin": "F8", + "redo": "C-g", "reimport": "F6", "reverse_incremental_search": "M-r", "right": "C-f", @@ -193,6 +194,7 @@ def get_key_no_doublebind(command): struct.suspend_key = get_key_no_doublebind("suspend") struct.toggle_file_watch_key = get_key_no_doublebind("toggle_file_watch") struct.undo_key = get_key_no_doublebind("undo") + struct.redo_key = get_key_no_doublebind("redo") struct.reimport_key = get_key_no_doublebind("reimport") struct.reverse_incremental_search_key = get_key_no_doublebind( "reverse_incremental_search" diff --git a/bpython/curtsiesfrontend/repl.py b/bpython/curtsiesfrontend/repl.py index 13c393783..4e8357b43 100644 --- a/bpython/curtsiesfrontend/repl.py +++ b/bpython/curtsiesfrontend/repl.py @@ -775,6 +775,8 @@ def process_key_event(self, e): self.on_tab(back=True) elif e in key_dispatch[self.config.undo_key]: # ctrl-r for undo self.prompt_undo() + elif e in key_dispatch[self.config.redo_key]: # ctrl-g for redo + self.redo() elif e in key_dispatch[self.config.save_key]: # ctrl-s for save greenlet.greenlet(self.write2file).switch() elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin @@ -858,14 +860,17 @@ def readline_kill(self, e): else: self.cut_buffer = cut - def on_enter(self, insert_into_history=True, reset_rl_history=True): + def on_enter(self, new_code=True, reset_rl_history=True): # so the cursor isn't touching a paren TODO: necessary? + if new_code: + self.redo_stack = [] + self._set_cursor_offset(-1, update_completion=False) if reset_rl_history: self.rl_history.reset() self.history.append(self.current_line) - self.push(self.current_line, insert_into_history=insert_into_history) + self.push(self.current_line, insert_into_history=new_code) def on_tab(self, back=False): """Do something on tab key @@ -1019,7 +1024,7 @@ def send_session_to_external_editor(self, filename=None): source = preprocess("\n".join(from_editor), self.interp.compile) lines = source.split("\n") self.history = lines - self.reevaluate(insert_into_history=True) + self.reevaluate(new_code=True) self.current_line = current_line self.cursor_offset = len(self.current_line) self.status_bar.message(_("Session edited and reevaluated")) @@ -1030,7 +1035,7 @@ def clear_modules_and_reevaluate(self): cursor, line = self.cursor_offset, self.current_line for modname in set(sys.modules.keys()) - self.original_modules: del sys.modules[modname] - self.reevaluate(insert_into_history=True) + self.reevaluate(new_code=True) self.cursor_offset, self.current_line = cursor, line self.status_bar.message( _("Reloaded at %s by user.") % (time.strftime("%X"),) @@ -1811,7 +1816,15 @@ def prompt_for_undo(): greenlet.greenlet(prompt_for_undo).switch() - def reevaluate(self, insert_into_history=False): + def redo(self): + if (self.redo_stack): + temp = self.redo_stack.pop() + self.push(temp) + self.history.append(temp) + else: + self.status_bar.message("Nothing to redo.") + + def reevaluate(self, new_code=False): """bpython.Repl.undo calls this""" if self.watcher: self.watcher.reset() @@ -1835,7 +1848,7 @@ def reevaluate(self, insert_into_history=False): sys.stdin = ReevaluateFakeStdin(self.stdin, self) for line in old_logical_lines: self._current_line = line - self.on_enter(insert_into_history=insert_into_history) + self.on_enter(new_code=new_code) while self.fake_refresh_requested: self.fake_refresh_requested = False self.process_event(bpythonevents.RefreshRequestEvent()) diff --git a/bpython/repl.py b/bpython/repl.py index f966b3faa..96cf2b5d0 100644 --- a/bpython/repl.py +++ b/bpython/repl.py @@ -439,7 +439,8 @@ def __init__(self, interp, config): duplicates=config.hist_duplicates, hist_size=config.hist_length ) self.s_hist = [] - self.history = [] + self.history = [] # commands executed since beginning of session + self.redo_stack = [] self.evaluating = False self.matches_iter = MatchesIterator() self.funcprops = None @@ -1009,6 +1010,10 @@ def undo(self, n=1): entries = list(self.rl_history.entries) + #Most recently undone command + last_entries = self.history[-n:] + last_entries.reverse() + self.redo_stack += last_entries self.history = self.history[:-n] self.reevaluate() diff --git a/bpython/sample-config b/bpython/sample-config index 1f7c8dd65..00b6ee4ef 100644 --- a/bpython/sample-config +++ b/bpython/sample-config @@ -67,6 +67,7 @@ # toggle_file_watch = F5 # save = C-s # undo = C-r +# redo = C-g # up_one_line = C-p # down_one_line = C-n # cut_to_buffer = C-k diff --git a/bpython/test/test_curtsies_painting.py b/bpython/test/test_curtsies_painting.py index 2c68b6092..15424c662 100644 --- a/bpython/test/test_curtsies_painting.py +++ b/bpython/test/test_curtsies_painting.py @@ -102,7 +102,7 @@ def test_run_line(self): orig_stdout = sys.stdout sys.stdout = self.repl.stdout [self.repl.add_normal_character(c) for c in "1 + 1"] - self.repl.on_enter(insert_into_history=False) + self.repl.on_enter(new_code=False) screen = fsarray([">>> 1 + 1", "2", "Welcome to"]) self.assert_paint_ignoring_formatting(screen, (1, 1)) finally: @@ -248,7 +248,7 @@ def enter(self, line=None): self.repl._set_cursor_offset(len(line), update_completion=False) self.repl.current_line = line with output_to_repl(self.repl): - self.repl.on_enter(insert_into_history=False) + self.repl.on_enter(new_code=False) self.assertEqual(self.repl.rl_history.entries, [""]) self.send_refreshes() @@ -592,7 +592,7 @@ def test_cursor_stays_at_bottom_of_screen(self): self.repl.width = 50 self.repl.current_line = "__import__('random').__name__" with output_to_repl(self.repl): - self.repl.on_enter(insert_into_history=False) + self.repl.on_enter(new_code=False) screen = [">>> __import__('random').__name__", "'random'"] self.assert_paint_ignoring_formatting(screen)