| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -878,7 +878,9 @@ def p_key(self, key): | |||
| 878 | 878 | return '' | |
| 879 | 879 | ||
| 880 | 880 | elif key in key_dispatch[config.undo_key]: # C-r | |
| 881 | - self.undo() | ||
| 881 | + n = self.prompt_undo() | ||
| 882 | + if n > 0: | ||
| 883 | + self.undo(n=n) | ||
| 882 | 884 | return '' | |
| 883 | 885 | ||
| 884 | 886 | elif key in key_dispatch[config.search_key]: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -53,6 +53,7 @@ def loadini(struct, configfile): | |||
| 53 | 53 | 'hist_length': 100, | |
| 54 | 54 | 'hist_duplicates': True, | |
| 55 | 55 | 'paste_time': 0.02, | |
| 56 | + 'single_undo_time': 1.0, | ||
| 56 | 57 | 'syntax': True, | |
| 57 | 58 | 'tab_length': 4, | |
| 58 | 59 | 'pastebin_confirm': True, | |
@@ -141,6 +142,7 @@ def get_key_no_doublebind(command): | |||
| 141 | 142 | struct.syntax = config.getboolean('general', 'syntax') | |
| 142 | 143 | struct.arg_spec = config.getboolean('general', 'arg_spec') | |
| 143 | 144 | struct.paste_time = config.getfloat('general', 'paste_time') | |
| 145 | + struct.single_undo_time = config.getfloat('general', 'single_undo_time') | ||
| 144 | 146 | struct.highlight_show_source = config.getboolean('general', | |
| 145 | 147 | 'highlight_show_source') | |
| 146 | 148 | struct.hist_file = config.get('general', 'hist_file') | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -82,6 +82,7 @@ def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True) | |||
| 82 | 82 | schedule_refresh = input_generator.scheduled_event_trigger(bpythonevents.ScheduledRefreshRequestEvent) | |
| 83 | 83 | request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent) | |
| 84 | 84 | interrupting_refresh = input_generator.threadsafe_event_trigger(lambda: None) | |
| 85 | + request_undo = input_generator.event_trigger(bpythonevents.UndoEvent) | ||
| 85 | 86 | ||
| 86 | 87 | def on_suspend(): | |
| 87 | 88 | window.__exit__(None, None, None) | |
@@ -98,6 +99,7 @@ def after_suspend(): | |||
| 98 | 99 | request_refresh=request_refresh, | |
| 99 | 100 | schedule_refresh=schedule_refresh, | |
| 100 | 101 | request_reload=request_reload, | |
| 102 | + request_undo=request_undo, | ||
| 101 | 103 | get_term_hw=window.get_term_hw, | |
| 102 | 104 | get_cursor_vertical_diff=window.get_cursor_vertical_diff, | |
| 103 | 105 | banner=banner, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -34,3 +34,9 @@ def __repr__(self): | |||
| 34 | 34 | ||
| 35 | 35 | class RunStartupFileEvent(curtsies.events.Event): | |
| 36 | 36 | """Request to run the startup file.""" | |
| 37 | + | ||
| 38 | + | ||
| 39 | + class UndoEvent(curtsies.events.Event): | ||
| 40 | + """Request to undo.""" | ||
| 41 | + def __init__(self, n=1): | ||
| 42 | + self.n = n | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -74,11 +74,14 @@ def process_event(self, e): | |||
| 74 | 74 | elif isinstance(e, events.PasteEvent): | |
| 75 | 75 | for ee in e.events: | |
| 76 | 76 | self.add_normal_character(ee if len(ee) == 1 else ee[-1]) #strip control seq | |
| 77 | + elif e in ['<ESC>'] or isinstance(e, events.SigIntEvent): | ||
| 78 | + self.request_greenlet.switch(False) | ||
| 79 | + self.escape() | ||
| 77 | 80 | elif e in edit_keys: | |
| 78 | 81 | self.cursor_offset_in_line, self._current_line = edit_keys[e](self.cursor_offset_in_line, self._current_line) | |
| 79 | - elif e == "<Ctrl-c>": | ||
| 82 | + elif e == "<Ctrl-c>": #TODO can this be removed? | ||
| 80 | 83 | raise KeyboardInterrupt() | |
| 81 | - elif e == "<Ctrl-d>": | ||
| 84 | + elif e == "<Ctrl-d>": #TODO this isn't a very intuitive behavior | ||
| 82 | 85 | raise SystemExit() | |
| 83 | 86 | elif self.in_prompt and e in ("\n", "\r", "<Ctrl-j>", "Ctrl-m>"): | |
| 84 | 87 | line = self._current_line | |
@@ -90,9 +93,6 @@ def process_event(self, e): | |||
| 90 | 93 | else: | |
| 91 | 94 | self.request_greenlet.switch(False) | |
| 92 | 95 | self.escape() | |
| 93 | - elif e in ['<ESC>']: | ||
| 94 | - self.request_greenlet.switch(False) | ||
| 95 | - self.escape() | ||
| 96 | 96 | else: # add normal character | |
| 97 | 97 | self.add_normal_character(e) | |
| 98 | 98 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,11 +1,13 @@ | |||
| 1 | 1 | import code | |
| 2 | + import time | ||
| 2 | 3 | import traceback | |
| 3 | 4 | import sys | |
| 4 | 5 | from pygments.token import Generic, Token, Keyword, Name, Comment, String | |
| 5 | 6 | from pygments.token import Error, Literal, Number, Operator, Punctuation | |
| 6 | 7 | from pygments.token import Whitespace | |
| 7 | 8 | from pygments.formatter import Formatter | |
| 8 | 9 | from bpython.curtsiesfrontend.parse import parse | |
| 10 | + from bpython.repl import RuntimeTimer | ||
| 9 | 11 | from codeop import CommandCompiler | |
| 10 | 12 | from pygments.lexers import get_lexer_by_name | |
| 11 | 13 | ||
@@ -77,6 +79,7 @@ def __init__(self, locals=None): | |||
| 77 | 79 | # typically changed after being instantiated | |
| 78 | 80 | self.write = lambda stuff: sys.stderr.write(stuff) | |
| 79 | 81 | self.outfile = self | |
| 82 | + self.timer = RuntimeTimer() | ||
| 80 | 83 | ||
| 81 | 84 | def showsyntaxerror(self, filename=None): | |
| 82 | 85 | """Display the syntax error that just occurred. | |
@@ -129,18 +132,18 @@ def showtraceback(self): | |||
| 129 | 132 | tbtext = ''.join(l) | |
| 130 | 133 | lexer = get_lexer_by_name("pytb", stripall=True) | |
| 131 | 134 | ||
| 132 | - self.format(tbtext,lexer) | ||
| 135 | + self.format(tbtext, lexer) | ||
| 133 | 136 | ||
| 134 | 137 | def format(self, tbtext, lexer): | |
| 135 | 138 | traceback_informative_formatter = BPythonFormatter(default_colors) | |
| 136 | 139 | traceback_code_formatter = BPythonFormatter({Token: ('d')}) | |
| 137 | - tokens= list(lexer.get_tokens(tbtext)) | ||
| 140 | + tokens = list(lexer.get_tokens(tbtext)) | ||
| 138 | 141 | ||
| 139 | 142 | no_format_mode = False | |
| 140 | 143 | cur_line = [] | |
| 141 | 144 | for token, text in tokens: | |
| 142 | 145 | if text.endswith('\n'): | |
| 143 | - cur_line.append((token,text)) | ||
| 146 | + cur_line.append((token, text)) | ||
| 144 | 147 | if no_format_mode: | |
| 145 | 148 | traceback_code_formatter.format(cur_line, self.outfile) | |
| 146 | 149 | no_format_mode = False | |
@@ -149,11 +152,17 @@ def format(self, tbtext, lexer): | |||
| 149 | 152 | cur_line = [] | |
| 150 | 153 | elif text == ' ' and cur_line == []: | |
| 151 | 154 | no_format_mode = True | |
| 152 | - cur_line.append((token,text)) | ||
| 155 | + cur_line.append((token, text)) | ||
| 153 | 156 | else: | |
| 154 | - cur_line.append((token,text)) | ||
| 157 | + cur_line.append((token, text)) | ||
| 155 | 158 | assert cur_line == [], cur_line | |
| 156 | 159 | ||
| 160 | + def runsource(self, source, filename="<input>", symbol="single"): | ||
| 161 | + with self.timer: | ||
| 162 | + return code.InteractiveInterpreter.runsource( | ||
| 163 | + self, source, filename=filename, symbol=symbol) | ||
| 164 | + | ||
| 165 | + | ||
| 157 | 166 | ||
| 158 | 167 | def code_finished_will_parse(s, compiler): | |
| 159 | 168 | """Returns a tuple of whether the buffer could be complete and whether it will parse | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -219,6 +219,7 @@ def __init__(self, | |||
| 219 | 219 | request_refresh=lambda: None, | |
| 220 | 220 | schedule_refresh=lambda when=0: None, | |
| 221 | 221 | request_reload=lambda desc: None, | |
| 222 | + request_undo=lambda n=1: None, | ||
| 222 | 223 | get_term_hw=lambda:(50, 10), | |
| 223 | 224 | get_cursor_vertical_diff=lambda: 0, | |
| 224 | 225 | banner=None, | |
@@ -282,6 +283,7 @@ def smarter_request_reload(files_modified=()): | |||
| 282 | 283 | else: | |
| 283 | 284 | pass | |
| 284 | 285 | self.request_reload = smarter_request_reload | |
| 286 | + self.request_undo = request_undo | ||
| 285 | 287 | self.get_term_hw = get_term_hw | |
| 286 | 288 | self.get_cursor_vertical_diff = get_cursor_vertical_diff | |
| 287 | 289 | ||
@@ -469,6 +471,9 @@ def process_control_event(self, e): | |||
| 469 | 471 | except IOError as e: | |
| 470 | 472 | self.status_bar.message(_('Executing PYTHONSTARTUP failed: %s') % (str(e))) | |
| 471 | 473 | ||
| 474 | + elif isinstance(e, bpythonevents.UndoEvent): | ||
| 475 | + self.undo(n=e.n) | ||
| 476 | + | ||
| 472 | 477 | elif self.stdin.has_focus: | |
| 473 | 478 | return self.stdin.process_event(e) | |
| 474 | 479 | ||
@@ -540,7 +545,7 @@ def process_key_event(self, e): | |||
| 540 | 545 | elif e in ("<Shift-TAB>",): | |
| 541 | 546 | self.on_tab(back=True) | |
| 542 | 547 | elif e in key_dispatch[self.config.undo_key]: #ctrl-r for undo | |
| 543 | - self.undo() | ||
| 548 | + self.prompt_undo() | ||
| 544 | 549 | elif e in key_dispatch[self.config.save_key]: # ctrl-s for save | |
| 545 | 550 | greenlet.greenlet(self.write2file).switch() | |
| 546 | 551 | elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin | |
@@ -1295,6 +1300,19 @@ def take_back_buffer_line(self): | |||
| 1295 | 1300 | self.display_buffer.pop() | |
| 1296 | 1301 | self.buffer.pop() | |
| 1297 | 1302 | ||
| 1303 | + def prompt_undo(self): | ||
| 1304 | + if self.buffer: | ||
| 1305 | + return self.take_back_buffer_line() | ||
| 1306 | + | ||
| 1307 | + self.reevaluate() | ||
| 1308 | + | ||
| 1309 | + def prompt_for_undo(): | ||
| 1310 | + n = BpythonRepl.prompt_undo(self) | ||
| 1311 | + if n > 0: | ||
| 1312 | + self.request_undo(n=n) | ||
| 1313 | + | ||
| 1314 | + greenlet.greenlet(prompt_for_undo).switch() | ||
| 1315 | + | ||
| 1298 | 1316 | def reevaluate(self, insert_into_history=False): | |
| 1299 | 1317 | """bpython.Repl.undo calls this""" | |
| 1300 | 1318 | if self.watcher: self.watcher.reset() | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -34,6 +34,7 @@ | |||
| 34 | 34 | import sys | |
| 35 | 35 | import tempfile | |
| 36 | 36 | import textwrap | |
| 37 | + import time | ||
| 37 | 38 | import traceback | |
| 38 | 39 | import unicodedata | |
| 39 | 40 | from itertools import takewhile | |
@@ -53,6 +54,26 @@ | |||
| 53 | 54 | import bpython.autocomplete as autocomplete | |
| 54 | 55 | ||
| 55 | 56 | ||
| 57 | + class RuntimeTimer(object): | ||
| 58 | + def __init__(self): | ||
| 59 | + self.reset_timer() | ||
| 60 | + self.time = time.monotonic if hasattr(time, 'monotonic') else time.time | ||
| 61 | + | ||
| 62 | + def __enter__(self): | ||
| 63 | + self.start = self.time() | ||
| 64 | + | ||
| 65 | + def __exit__(self, ty, val, tb): | ||
| 66 | + self.last_command = self.time() - self.start | ||
| 67 | + self.running_time += self.last_command | ||
| 68 | + return False | ||
| 69 | + | ||
| 70 | + def reset_timer(self): | ||
| 71 | + self.running_time = 0.0 | ||
| 72 | + self.last_command = 0.0 | ||
| 73 | + | ||
| 74 | + def estimate(self): | ||
| 75 | + return self.running_time - self.last_command | ||
| 76 | + | ||
| 56 | 77 | class Interpreter(code.InteractiveInterpreter): | |
| 57 | 78 | ||
| 58 | 79 | def __init__(self, locals=None, encoding=None): | |
@@ -68,16 +89,28 @@ def __init__(self, locals=None, encoding=None): | |||
| 68 | 89 | self.syntaxerror_callback = None | |
| 69 | 90 | # Unfortunately code.InteractiveInterpreter is a classic class, so no super() | |
| 70 | 91 | code.InteractiveInterpreter.__init__(self, locals) | |
| 92 | + self.timer = RuntimeTimer() | ||
| 93 | + | ||
| 94 | + def reset_running_time(self): | ||
| 95 | + self.running_time = 0 | ||
| 96 | + | ||
| 97 | + if py3: | ||
| 71 | 98 | ||
| 72 | - if not py3: | ||
| 99 | + def runsource(self, source, filename="<input>", symbol="single"): | ||
| 100 | + with self.timer: | ||
| 101 | + return code.InteractiveInterpreter.runsource(self, source, | ||
| 102 | + filename, symbol) | ||
| 103 | + | ||
| 104 | + else: | ||
| 73 | 105 | ||
| 74 | 106 | def runsource(self, source, filename='<input>', symbol='single', | |
| 75 | 107 | encode=True): | |
| 76 | - if encode: | ||
| 77 | - source = '# coding: %s\n%s' % (self.encoding, | ||
| 78 | - source.encode(self.encoding)) | ||
| 79 | - return code.InteractiveInterpreter.runsource(self, source, | ||
| 80 | - filename, symbol) | ||
| 108 | + with self.timer: | ||
| 109 | + if encode: | ||
| 110 | + source = '# coding: %s\n%s' % (self.encoding, | ||
| 111 | + source.encode(self.encoding)) | ||
| 112 | + return code.InteractiveInterpreter.runsource(self, source, | ||
| 113 | + filename, symbol) | ||
| 81 | 114 | ||
| 82 | 115 | def showsyntaxerror(self, filename=None): | |
| 83 | 116 | """Override the regular handler, the code's copied and pasted from | |
@@ -792,25 +825,51 @@ def insert_into_history(self, s): | |||
| 792 | 825 | except RuntimeError as e: | |
| 793 | 826 | self.interact.notify(str(e)) | |
| 794 | 827 | ||
| 828 | + def prompt_undo(self): | ||
| 829 | + """Returns how many lines to undo, 0 means don't undo""" | ||
| 830 | + if (self.config.single_undo_time < 0 or | ||
| 831 | + self.interp.timer.estimate() < self.config.single_undo_time): | ||
| 832 | + return 1 | ||
| 833 | + est = self.interp.timer.estimate() | ||
| 834 | + n = self.interact.file_prompt( | ||
| 835 | + _("Undo how many lines? (Undo will take up to ~%.1f seconds) [1]") | ||
| 836 | + % (est,)) | ||
| 837 | + try: | ||
| 838 | + if n == '': | ||
| 839 | + n = '1' | ||
| 840 | + n = int(n) | ||
| 841 | + except ValueError: | ||
| 842 | + self.interact.notify(_('Undo canceled'), .1) | ||
| 843 | + return 0 | ||
| 844 | + else: | ||
| 845 | + if n == 0: | ||
| 846 | + self.interact.notify(_('Undo canceled'), .1) | ||
| 847 | + return 0 | ||
| 848 | + if n == 1: | ||
| 849 | + self.interact.notify(_('Undoing 1 line... (est. %.1f seconds)') | ||
| 850 | + % (est,), .1) | ||
| 851 | + else: | ||
| 852 | + self.interact.notify(_('Undoing %d lines... (est. %.1f seconds)') | ||
| 853 | + % (n, est), .1) | ||
| 854 | + return n | ||
| 855 | + | ||
| 795 | 856 | def undo(self, n=1): | |
| 796 | - """Go back in the undo history n steps and call reeavluate() | ||
| 857 | + """Go back in the undo history n steps and call reevaluate() | ||
| 797 | 858 | Note that in the program this is called "Rewind" because I | |
| 798 | 859 | want it to be clear that this is by no means a true undo | |
| 799 | 860 | implementation, it is merely a convenience bonus.""" | |
| 800 | 861 | if not self.history: | |
| 801 | 862 | return None | |
| 802 | 863 | ||
| 864 | + self.interp.timer.reset_timer() | ||
| 865 | + | ||
| 803 | 866 | if len(self.history) < n: | |
| 804 | 867 | n = len(self.history) | |
| 805 | 868 | ||
| 806 | 869 | entries = list(self.rl_history.entries) | |
| 807 | 870 | ||
| 808 | 871 | self.history = self.history[:-n] | |
| 809 | - if (n == 1 and self.buffer and | ||
| 810 | - hasattr(self, 'take_back_buffer_line')): | ||
| 811 | - self.take_back_buffer_line() | ||
| 812 | - else: | ||
| 813 | - self.reevaluate() | ||
| 872 | + self.reevaluate() | ||
| 814 | 873 | ||
| 815 | 874 | self.rl_history.entries = entries | |
| 816 | 875 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,6 +48,11 @@ | |||
| 48 | 48 | # bpython’s behalf. If unset, bpython uploads pastes to bpaste.net. (default: ) | |
| 49 | 49 | #pastebin_helper = gist.py | |
| 50 | 50 | ||
| 51 | + # How long an undo must be expected to take before prompting for how | ||
| 52 | + # many lines should be undone. Set to -1 to never prompt, or 0 to | ||
| 53 | + # always prompt. | ||
| 54 | + # single_undo_time = 1.0 | ||
| 55 | + | ||
| 51 | 56 | [keyboard] | |
| 52 | 57 | ||
| 53 | 58 | # All key bindings are shown commented out with their default binding | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -120,6 +120,15 @@ following helper program can be used to create `gists | |||
| 120 | 120 | ||
| 121 | 121 | .. versionadded:: 0.12 | |
| 122 | 122 | ||
| 123 | + | ||
| 124 | + single_undo_time | ||
| 125 | + ^^^^^^^^^^^^^^^^ | ||
| 126 | + Time duration an undo must be predicted to take before prompting | ||
| 127 | + to undo multiple lines at once. Use -1 to never prompt, or 0 to always prompt. | ||
| 128 | + (default: 1.0) | ||
| 129 | + | ||
| 130 | + .. versionadded:: 0.14 | ||
| 131 | + | ||
| 123 | 132 | .. _configuration_color_scheme: | |
| 124 | 133 | ||
| 125 | 134 | color_scheme | |
| Back | FazBrowse Home | New Git URL |
0 commit comments