X Tutup
Skip to content

Commit 023ebe8

Browse files
committed
Merge remote-tracking branch 'thomasballinger/curtsies-fix'
Signed-off-by: Sebastian Ramacher <sebastian+dev@ramacher.at> Conflicts: bpython/config.py doc/sphinx/source/configuration-options.rst setup.py
2 parents 3fc47b7 + 047dd06 commit 023ebe8

File tree

14 files changed

+1851
-2
lines changed

14 files changed

+1851
-2
lines changed

bpython/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def loadini(struct, configfile):
8282
'delete': 'C-d',
8383
'down_one_line': 'C-n',
8484
'exit': '',
85+
'external_editor': 'F7',
8586
'last_output': 'F9',
8687
'pastebin': 'F8',
8788
'save': 'C-s',
@@ -95,6 +96,10 @@ def loadini(struct, configfile):
9596
'suggestion_width': 0.8,
9697
'trim_prompts': False,
9798
},
99+
'curtsies': {
100+
'list_above' : False,
101+
'fill_terminal' : False,
102+
},
98103
'gtk': {
99104
'font': 'monospace 10',
100105
'color_scheme': 'default'}})
@@ -135,6 +140,7 @@ def loadini(struct, configfile):
135140
struct.delete_key = config.get('keyboard', 'delete')
136141
struct.exit_key = config.get('keyboard', 'exit')
137142
struct.last_output_key = config.get('keyboard', 'last_output')
143+
struct.external_editor_key = config.get('keyboard', 'external_editor')
138144

139145
struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm')
140146
struct.pastebin_private = config.getboolean('general', 'pastebin_private')
@@ -157,6 +163,9 @@ def loadini(struct, configfile):
157163

158164
struct.gtk_font = config.get('gtk', 'font')
159165

166+
struct.curtsies_list_above = config.getboolean('curtsies', 'list_above')
167+
struct.curtsies_fill_terminal = config.getboolean('curtsies', 'fill_terminal')
168+
160169
color_scheme_name = config.get('general', 'color_scheme')
161170
color_gtk_scheme_name = config.get('gtk', 'color_scheme')
162171

bpython/curtsies.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from __future__ import absolute_import
2+
3+
import sys
4+
import os
5+
from optparse import Option
6+
7+
import curtsies
8+
import curtsies.window
9+
import curtsies.terminal
10+
import curtsies.events
11+
Window = curtsies.window.Window
12+
Terminal = curtsies.terminal.Terminal
13+
14+
from bpython.curtsiesfrontend.repl import Repl
15+
from bpython.curtsiesfrontend.coderunner import SystemExitFromCodeThread
16+
from bpython import args as bpargs
17+
from bpython.translations import _
18+
19+
def main(args=None, locals_=None, banner=None):
20+
config, options, exec_args = bpargs.parse(args, (
21+
'scroll options', None, [
22+
Option('--log', '-L', action='store_true',
23+
help=_("log debug messages to scroll.log")),
24+
Option('--type', '-t', action='store_true',
25+
help=_("enter lines of file as though interactively typed")),
26+
]))
27+
if options.log:
28+
import logging
29+
logging.basicConfig(filename='scroll.log', level=logging.DEBUG)
30+
31+
# do parsing before doing any frontend stuff
32+
with Terminal(paste_mode=True) as tc:
33+
with Window(tc, keep_last_line=True, hide_cursor=False) as term:
34+
#TODO why need to make repl first
35+
with Repl(config=config,
36+
locals_=locals_,
37+
stuff_a_refresh_request=tc.stuff_a_refresh_request,
38+
banner=banner) as repl:
39+
rows, columns = tc.get_screen_size()
40+
repl.width = columns
41+
repl.height = rows
42+
43+
def process_event(e):
44+
try:
45+
repl.process_event(e)
46+
except SystemExitFromCodeThread:
47+
#Get rid of nasty constant
48+
array, cursor_pos = repl.paint(about_to_exit=2)
49+
term.render_to_terminal(array, cursor_pos)
50+
raise
51+
except SystemExit:
52+
array, cursor_pos = repl.paint(about_to_exit=True)
53+
term.render_to_terminal(array, cursor_pos)
54+
raise
55+
else:
56+
array, cursor_pos = repl.paint()
57+
scrolled = term.render_to_terminal(array, cursor_pos)
58+
repl.scroll_offset += scrolled
59+
# Could this be calculated in the repl, avoiding this
60+
# two-way communication?
61+
62+
exit_value = 0
63+
if exec_args:
64+
assert options, "don't pass in exec_args without options"
65+
if options.type:
66+
repl.process_event(tc.get_event()) #first event will always be a window size set
67+
paste = curtsies.events.PasteEvent()
68+
old_argv, sys.argv = sys.argv, exec_args
69+
paste.events.extend(open(exec_args[0]).read())
70+
sys.path.insert(0, os.path.abspath(os.path.dirname(exec_args[0])))
71+
process_event(paste)
72+
else:
73+
try:
74+
# THIS IS NORMAL PYTHON
75+
#TODO replace this so that stdout is properly harvested for display and rewind works
76+
bpargs.exec_code(repl.interp, exec_args)
77+
except SystemExit, e:
78+
exit_value = e.args
79+
if not options.interactive:
80+
#TODO treat this properly: no prompt should ever display, but stdout should!
81+
array, cursor_pos = repl.paint(about_to_exit=True)
82+
term.render_to_terminal(array, cursor_pos)
83+
raise SystemExit(exit_value)
84+
else:
85+
sys.path.insert(0, '') # expected for interactive sessions (python does it)
86+
87+
while True:
88+
process_event(tc.get_event())
89+
90+
91+
if __name__ == '__main__':
92+
sys.exit(main())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import code
2+
import signal
3+
import sys
4+
import greenlet
5+
import logging
6+
7+
class SigintHappened(object):
8+
pass
9+
10+
class SystemExitFromCodeThread(SystemExit):
11+
pass
12+
13+
class CodeRunner(object):
14+
"""Runs user code in an interpreter, taking care of stdout/in/err"""
15+
def __init__(self, interp=None, stuff_a_refresh_request=lambda:None):
16+
self.interp = interp or code.InteractiveInterpreter()
17+
self.source = None
18+
self.main_greenlet = greenlet.getcurrent()
19+
self.code_greenlet = None
20+
self.stuff_a_refresh_request = stuff_a_refresh_request
21+
self.code_is_waiting = False
22+
self.sigint_happened = False
23+
self.orig_sigint_handler = None
24+
25+
@property
26+
def running(self):
27+
return self.source and self.code_greenlet
28+
29+
def load_code(self, source):
30+
"""Prep code to be run"""
31+
self.source = source
32+
self.code_greenlet = None
33+
34+
def _unload_code(self):
35+
"""Called when done running code"""
36+
self.source = None
37+
self.code_greenlet = None
38+
self.code_is_waiting = False
39+
40+
def run_code(self, for_code=None):
41+
"""Returns Truthy values if code finishes, False otherwise
42+
43+
if for_code is provided, send that value to the code greenlet
44+
if source code is complete, returns "done"
45+
if source code is incomplete, returns "unfinished"
46+
"""
47+
if self.code_greenlet is None:
48+
assert self.source is not None
49+
self.code_greenlet = greenlet.greenlet(self._blocking_run_code)
50+
self.orig_sigint_handler = signal.getsignal(signal.SIGINT)
51+
signal.signal(signal.SIGINT, self.sigint_handler)
52+
request = self.code_greenlet.switch()
53+
else:
54+
assert self.code_is_waiting
55+
self.code_is_waiting = False
56+
signal.signal(signal.SIGINT, self.sigint_handler)
57+
if self.sigint_happened:
58+
self.sigint_happened = False
59+
request = self.code_greenlet.switch(SigintHappened)
60+
else:
61+
request = self.code_greenlet.switch(for_code)
62+
63+
if request in ['wait', 'refresh']:
64+
self.code_is_waiting = True
65+
if request == 'refresh':
66+
self.stuff_a_refresh_request()
67+
return False
68+
elif request in ['done', 'unfinished']:
69+
self._unload_code()
70+
signal.signal(signal.SIGINT, self.orig_sigint_handler)
71+
self.orig_sigint_handler = None
72+
return request
73+
elif request in ['SystemExit']: #use the object?
74+
self._unload_code()
75+
raise SystemExitFromCodeThread()
76+
else:
77+
raise ValueError("Not a valid value from code greenlet: %r" % request)
78+
79+
def sigint_handler(self, *args):
80+
if greenlet.getcurrent() is self.code_greenlet:
81+
logging.debug('sigint while running user code!')
82+
raise KeyboardInterrupt()
83+
else:
84+
logging.debug('sigint while fufilling code request sigint handler running!')
85+
self.sigint_happened = True
86+
87+
def _blocking_run_code(self):
88+
try:
89+
unfinished = self.interp.runsource(self.source)
90+
except SystemExit:
91+
return 'SystemExit'
92+
return 'unfinished' if unfinished else 'done'
93+
94+
def wait_and_get_value(self):
95+
"""Return the argument passed in to .run_code(for_code)
96+
97+
Nothing means calls to run_code must be...
98+
"""
99+
value = self.main_greenlet.switch('wait')
100+
if value is SigintHappened:
101+
raise KeyboardInterrupt()
102+
return value
103+
104+
def refresh_and_get_value(self):
105+
"""Returns the argument passed in to .run_code(for_code) """
106+
value = self.main_greenlet.switch('refresh')
107+
if value is SigintHappened:
108+
raise KeyboardInterrupt()
109+
return value
110+
111+
class FakeOutput(object):
112+
def __init__(self, coderunner, please):
113+
self.coderunner = coderunner
114+
self.please = please
115+
def write(self, *args, **kwargs):
116+
self.please(*args, **kwargs)
117+
return self.coderunner.refresh_and_get_value()
118+
119+
def test_simple():
120+
orig_stdout = sys.stdout
121+
orig_stderr = sys.stderr
122+
c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush())
123+
stdout = FakeOutput(c, orig_stdout.write)
124+
sys.stdout = stdout
125+
c.load_code('1 + 1')
126+
c.run_code()
127+
c.run_code()
128+
c.run_code()
129+
130+
def test_exception():
131+
orig_stdout = sys.stdout
132+
orig_stderr = sys.stderr
133+
c = CodeRunner(stuff_a_refresh_request=lambda: orig_stdout.flush() or orig_stderr.flush())
134+
def ctrlc():
135+
raise KeyboardInterrupt()
136+
stdout = FakeOutput(c, lambda x: ctrlc())
137+
sys.stdout = stdout
138+
c.load_code('1 + 1')
139+
c.run_code()
140+
141+
if __name__ == '__main__':
142+
test_simple()
143+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class NotImplementedError(NotImplementedError):
2+
def __init__(self, msg=None):
3+
if msg is None:
4+
super(NotImplementedError, self).__init__("Implement it and submit a pull request!")
5+
else:
6+
super(NotImplementedError, self).__init__(msg)

0 commit comments

Comments
 (0)
X Tutup