X Tutup
Skip to content

Commit babd41f

Browse files
Predict how long an undo will take and prompt for how many lines
1 parent fed23ca commit babd41f

File tree

7 files changed

+112
-19
lines changed

7 files changed

+112
-19
lines changed

bpython/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,7 +878,9 @@ def p_key(self, key):
878878
return ''
879879

880880
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)
882884
return ''
883885

884886
elif key in key_dispatch[config.search_key]:

bpython/curtsies.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def mainloop(config, locals_, banner, interp=None, paste=None, interactive=True)
8282
schedule_refresh = input_generator.scheduled_event_trigger(bpythonevents.ScheduledRefreshRequestEvent)
8383
request_reload = input_generator.threadsafe_event_trigger(bpythonevents.ReloadEvent)
8484
interrupting_refresh = input_generator.threadsafe_event_trigger(lambda: None)
85+
request_undo = input_generator.event_trigger(bpythonevents.UndoEvent)
8586

8687
def on_suspend():
8788
window.__exit__(None, None, None)
@@ -98,6 +99,7 @@ def after_suspend():
9899
request_refresh=request_refresh,
99100
schedule_refresh=schedule_refresh,
100101
request_reload=request_reload,
102+
request_undo=request_undo,
101103
get_term_hw=window.get_term_hw,
102104
get_cursor_vertical_diff=window.get_cursor_vertical_diff,
103105
banner=banner,

bpython/curtsiesfrontend/events.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,9 @@ def __repr__(self):
3434

3535
class RunStartupFileEvent(curtsies.events.Event):
3636
"""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

bpython/curtsiesfrontend/interaction.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,14 @@ def process_event(self, e):
7474
elif isinstance(e, events.PasteEvent):
7575
for ee in e.events:
7676
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()
7780
elif e in edit_keys:
7881
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?
8083
raise KeyboardInterrupt()
81-
elif e == "<Ctrl-d>":
84+
elif e == "<Ctrl-d>": #TODO this isn't a very intuitive behavior
8285
raise SystemExit()
8386
elif self.in_prompt and e in ("\n", "\r", "<Ctrl-j>", "Ctrl-m>"):
8487
line = self._current_line
@@ -90,9 +93,6 @@ def process_event(self, e):
9093
else:
9194
self.request_greenlet.switch(False)
9295
self.escape()
93-
elif e in ['<ESC>']:
94-
self.request_greenlet.switch(False)
95-
self.escape()
9696
else: # add normal character
9797
self.add_normal_character(e)
9898

bpython/curtsiesfrontend/interpreter.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import code
2+
import time
23
import traceback
34
import sys
45
from pygments.token import Generic, Token, Keyword, Name, Comment, String
56
from pygments.token import Error, Literal, Number, Operator, Punctuation
67
from pygments.token import Whitespace
78
from pygments.formatter import Formatter
89
from bpython.curtsiesfrontend.parse import parse
10+
from bpython.repl import RuntimeTimer
911
from codeop import CommandCompiler
1012
from pygments.lexers import get_lexer_by_name
1113

@@ -77,6 +79,7 @@ def __init__(self, locals=None):
7779
# typically changed after being instantiated
7880
self.write = lambda stuff: sys.stderr.write(stuff)
7981
self.outfile = self
82+
self.timer = RuntimeTimer()
8083

8184
def showsyntaxerror(self, filename=None):
8285
"""Display the syntax error that just occurred.
@@ -129,18 +132,18 @@ def showtraceback(self):
129132
tbtext = ''.join(l)
130133
lexer = get_lexer_by_name("pytb", stripall=True)
131134

132-
self.format(tbtext,lexer)
135+
self.format(tbtext, lexer)
133136

134137
def format(self, tbtext, lexer):
135138
traceback_informative_formatter = BPythonFormatter(default_colors)
136139
traceback_code_formatter = BPythonFormatter({Token: ('d')})
137-
tokens= list(lexer.get_tokens(tbtext))
140+
tokens = list(lexer.get_tokens(tbtext))
138141

139142
no_format_mode = False
140143
cur_line = []
141144
for token, text in tokens:
142145
if text.endswith('\n'):
143-
cur_line.append((token,text))
146+
cur_line.append((token, text))
144147
if no_format_mode:
145148
traceback_code_formatter.format(cur_line, self.outfile)
146149
no_format_mode = False
@@ -149,11 +152,17 @@ def format(self, tbtext, lexer):
149152
cur_line = []
150153
elif text == ' ' and cur_line == []:
151154
no_format_mode = True
152-
cur_line.append((token,text))
155+
cur_line.append((token, text))
153156
else:
154-
cur_line.append((token,text))
157+
cur_line.append((token, text))
155158
assert cur_line == [], cur_line
156159

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+
157166

158167
def code_finished_will_parse(s, compiler):
159168
"""Returns a tuple of whether the buffer could be complete and whether it will parse

bpython/curtsiesfrontend/repl.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ def __init__(self,
219219
request_refresh=lambda: None,
220220
schedule_refresh=lambda when=0: None,
221221
request_reload=lambda desc: None,
222+
request_undo=lambda n=1: None,
222223
get_term_hw=lambda:(50, 10),
223224
get_cursor_vertical_diff=lambda: 0,
224225
banner=None,
@@ -282,6 +283,7 @@ def smarter_request_reload(files_modified=()):
282283
else:
283284
pass
284285
self.request_reload = smarter_request_reload
286+
self.request_undo = request_undo
285287
self.get_term_hw = get_term_hw
286288
self.get_cursor_vertical_diff = get_cursor_vertical_diff
287289

@@ -469,6 +471,9 @@ def process_control_event(self, e):
469471
except IOError as e:
470472
self.status_bar.message(_('Executing PYTHONSTARTUP failed: %s') % (str(e)))
471473

474+
elif isinstance(e, bpythonevents.UndoEvent):
475+
self.undo(n=e.n)
476+
472477
elif self.stdin.has_focus:
473478
return self.stdin.process_event(e)
474479

@@ -540,7 +545,7 @@ def process_key_event(self, e):
540545
elif e in ("<Shift-TAB>",):
541546
self.on_tab(back=True)
542547
elif e in key_dispatch[self.config.undo_key]: #ctrl-r for undo
543-
self.undo()
548+
self.prompt_undo()
544549
elif e in key_dispatch[self.config.save_key]: # ctrl-s for save
545550
greenlet.greenlet(self.write2file).switch()
546551
elif e in key_dispatch[self.config.pastebin_key]: # F8 for pastebin
@@ -1293,6 +1298,13 @@ def take_back_buffer_line(self):
12931298
self._current_line = indent * ' '
12941299
self.cursor_offset = len(self.current_line)
12951300

1301+
def prompt_undo(self):
1302+
def prompt_for_undo():
1303+
n = BpythonRepl.prompt_undo(self)
1304+
if n > 0:
1305+
self.request_undo(n=n)
1306+
greenlet.greenlet(prompt_for_undo).switch()
1307+
12961308
def reevaluate(self, insert_into_history=False):
12971309
"""bpython.Repl.undo calls this"""
12981310
if self.watcher: self.watcher.reset()

bpython/repl.py

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import sys
3535
import tempfile
3636
import textwrap
37+
import time
3738
import traceback
3839
import unicodedata
3940
from itertools import takewhile
@@ -53,6 +54,25 @@
5354
import bpython.autocomplete as autocomplete
5455

5556

57+
class RuntimeTimer(object):
58+
def __init__(self):
59+
self.reset_timer()
60+
61+
def __enter__(self):
62+
self.start = time.time()
63+
64+
def __exit__(self, ty, val, tb):
65+
self.last_command = time.time() - self.start
66+
self.running_time += self.last_command
67+
return False
68+
69+
def reset_timer(self):
70+
self.running_time = 0.0
71+
self.last_command = 0.0
72+
73+
def estimate(self):
74+
return self.running_time - self.last_command
75+
5676
class Interpreter(code.InteractiveInterpreter):
5777

5878
def __init__(self, locals=None, encoding=None):
@@ -68,16 +88,28 @@ def __init__(self, locals=None, encoding=None):
6888
self.syntaxerror_callback = None
6989
# Unfortunately code.InteractiveInterpreter is a classic class, so no super()
7090
code.InteractiveInterpreter.__init__(self, locals)
91+
self.timer = RuntimeTimer()
92+
93+
def reset_running_time(self):
94+
self.running_time = 0
95+
96+
if py3:
7197

72-
if not py3:
98+
def runsource(self, source, filename="<input>", symbol="single"):
99+
with self.timer:
100+
return code.InteractiveInterpreter.runsource(self, source,
101+
filename, symbol)
102+
103+
else:
73104

74105
def runsource(self, source, filename='<input>', symbol='single',
75106
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)
107+
with self.timer:
108+
if encode:
109+
source = '# coding: %s\n%s' % (self.encoding,
110+
source.encode(self.encoding))
111+
return code.InteractiveInterpreter.runsource(self, source,
112+
filename, symbol)
81113

82114
def showsyntaxerror(self, filename=None):
83115
"""Override the regular handler, the code's copied and pasted from
@@ -791,14 +823,44 @@ def insert_into_history(self, s):
791823
except RuntimeError as e:
792824
self.interact.notify(str(e))
793825

826+
def prompt_undo(self):
827+
"""Returns how many lines to undo, 0 means don't undo"""
828+
self.config.single_undo_time = 1
829+
if self.interp.timer.estimate() < self.config.single_undo_time:
830+
return 1
831+
est = self.interp.timer.estimate()
832+
n = self.interact.file_prompt(
833+
_("Undo how many lines? (Undo will take up to ~%.1f seconds) [1]")
834+
% (est,))
835+
try:
836+
if n == '':
837+
n = '1'
838+
n = int(n)
839+
except ValueError:
840+
self.interact.notify(_('Undo canceled'), .1)
841+
return 0
842+
else:
843+
if n == 0:
844+
self.interact.notify(_('Undo canceled'), .1)
845+
return 0
846+
if n == 1:
847+
self.interact.notify(_('Undoing 1 line... (est. %.1f seconds)')
848+
% (est,), .1)
849+
else:
850+
self.interact.notify(_('Undoing %d lines... (est. %.1f seconds)')
851+
% (n, est), .1)
852+
return n
853+
794854
def undo(self, n=1):
795-
"""Go back in the undo history n steps and call reeavluate()
855+
"""Go back in the undo history n steps and call reevaluate()
796856
Note that in the program this is called "Rewind" because I
797857
want it to be clear that this is by no means a true undo
798858
implementation, it is merely a convenience bonus."""
799859
if not self.history:
800860
return None
801861

862+
self.interp.timer.reset_timer()
863+
802864
if len(self.history) < n:
803865
n = len(self.history)
804866

0 commit comments

Comments
 (0)
X Tutup