Skip to content

Commit ea5ae11

Browse files
committed
Merged bobf/bpython into default
2 parents 34f2704 + aa2d90c commit ea5ae11

20 files changed

+2089
-41
lines changed

AUTHORS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ bpython is written and maintained by Bob Farrell and Andreas Stuehrk
55

66
Other contributors are (in alphabetical order):
77

8+
* Thomas Ballinger <thomasballinger at gmail dot com>
89
* Federico Ceratto <federico dot ceratto at gmail dot com>
910
* Ingrid Cheung
1011
* Martha Girdler <martha at cheezburger dot com>
@@ -19,5 +20,4 @@ Other contributors are (in alphabetical order):
1920
* Simon de Vlieger <simon at ikanobori dot jp>
2021
* Marien Zwart <marien dot zwart at gmail dot com>
2122

22-
2323
Many thanks for all contributions!

bpython/cli.py

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import math
4949
import re
5050
import time
51+
import functools
5152

5253
import struct
5354
if platform.system() != 'Windows':
@@ -116,18 +117,31 @@ def calculate_screen_lines(tokens, width, cursor=0):
116117
pos %= width
117118
return lines
118119

120+
def forward_if_not_current(func):
121+
@functools.wraps(func)
122+
def newfunc(self, *args, **kwargs):
123+
dest = self.get_dest()
124+
if self is dest:
125+
return func(self, *args, **kwargs)
126+
else:
127+
return getattr(self.get_dest(), newfunc.__name__)(*args, **kwargs)
128+
return newfunc
129+
119130

120131
class FakeStream(object):
121132
"""Provide a fake file object which calls functions on the interface
122133
provided."""
123134

124-
def __init__(self, interface):
135+
def __init__(self, interface, get_dest):
125136
self.encoding = getpreferredencoding()
126137
self.interface = interface
138+
self.get_dest = get_dest
127139

140+
@forward_if_not_current
128141
def write(self, s):
129142
self.interface.write(s)
130143

144+
@forward_if_not_current
131145
def writelines(self, l):
132146
for s in l:
133147
self.write(s)
@@ -998,6 +1012,9 @@ def p_key(self, key):
9981012
self.do_exit = True
9991013
return None
10001014

1015+
elif key == '\x18':
1016+
return self.send_current_line_to_editor()
1017+
10011018
elif key[0:3] == 'PAD' and not key in ('PAD0', 'PADSTOP'):
10021019
pad_keys = {
10031020
'PADMINUS': '-',
@@ -1111,10 +1128,10 @@ def repl(self):
11111128
self.push('from bpython._internal import _help as help\n', False)
11121129

11131130
self.iy, self.ix = self.scr.getyx()
1114-
more = False
1131+
self.more = False
11151132
while not self.do_exit:
11161133
self.f_string = ''
1117-
self.prompt(more)
1134+
self.prompt(self.more)
11181135
try:
11191136
inp = self.get_line()
11201137
except KeyboardInterrupt:
@@ -1135,8 +1152,8 @@ def repl(self):
11351152
else:
11361153
self.stdout_hist += inp.encode(getpreferredencoding()) + '\n'
11371154
stdout_position = len(self.stdout_hist)
1138-
more = self.push(inp)
1139-
if not more:
1155+
self.more = self.push(inp)
1156+
if not self.more:
11401157
self.prev_block_finished = stdout_position
11411158
self.s = ''
11421159
return self.exit_value
@@ -1206,8 +1223,8 @@ def reevaluate(self):
12061223
# I decided it was easier to just do this manually
12071224
# than to make the print_line and history stuff more flexible.
12081225
self.scr.addstr('\n')
1209-
more = self.push(line)
1210-
self.prompt(more)
1226+
self.more = self.push(line)
1227+
self.prompt(self.more)
12111228
self.iy, self.ix = self.scr.getyx()
12121229

12131230
self.cpos = 0
@@ -1508,6 +1525,46 @@ def yank_from_buffer(self):
15081525
self.addstr(self.cut_buffer)
15091526
self.print_line(self.s, clr=True)
15101527

1528+
def send_current_line_to_editor(self):
1529+
lines = self.send_to_external_editor(self.s).split('\n')
1530+
self.s = ''
1531+
self.print_line(self.s)
1532+
while lines and not lines[-1]:
1533+
lines.pop()
1534+
if not lines:
1535+
return ''
1536+
1537+
self.f_string = ''
1538+
self.cpos = -1 # Set cursor position to -1 to prevent paren matching
1539+
1540+
self.iy, self.ix = self.scr.getyx()
1541+
self.evaluating = True
1542+
for line in lines:
1543+
if py3:
1544+
self.stdout_hist += line + '\n'
1545+
else:
1546+
self.stdout_hist += line.encode(getpreferredencoding()) + '\n'
1547+
self.history.append(line)
1548+
self.print_line(line)
1549+
self.s_hist[-1] += self.f_string
1550+
self.scr.addstr('\n')
1551+
self.more = self.push(line)
1552+
self.prompt(self.more)
1553+
self.iy, self.ix = self.scr.getyx()
1554+
self.evaluating = False
1555+
1556+
self.cpos = 0
1557+
indent = repl.next_indentation(self.s, self.config.tab_length)
1558+
self.s = ''
1559+
self.scr.refresh()
1560+
1561+
if self.buffer:
1562+
for _ in xrange(indent):
1563+
self.tab()
1564+
1565+
self.print_line(self.s)
1566+
self.scr.redrawwin()
1567+
return ''
15111568

15121569
class Statusbar(object):
15131570
"""This class provides the status bar at the bottom of the screen.
@@ -1876,11 +1933,11 @@ def main_curses(scr, args, config, interactive=True, locals_=None,
18761933
clirepl._C = cols
18771934

18781935
sys.stdin = FakeStdin(clirepl)
1879-
sys.stdout = FakeStream(clirepl)
1880-
sys.stderr = FakeStream(clirepl)
1936+
sys.stdout = FakeStream(clirepl, lambda: sys.stdout)
1937+
sys.stderr = FakeStream(clirepl, lambda: sys.stderr)
18811938

18821939
if args:
1883-
exit_value = 0
1940+
exit_value = ()
18841941
try:
18851942
bpython.args.exec_code(interpreter, args)
18861943
except SystemExit, e:
@@ -1899,6 +1956,9 @@ def main_curses(scr, args, config, interactive=True, locals_=None,
18991956
clirepl.write(banner)
19001957
clirepl.write('\n')
19011958
exit_value = clirepl.repl()
1959+
if hasattr(sys, 'exitfunc'):
1960+
sys.exitfunc()
1961+
delattr(sys, 'exitfunc')
19021962

19031963
main_win.erase()
19041964
main_win.refresh()

bpython/config.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ def loadini(struct, configfile):
7272
'pastebin_private': True,
7373
'pastebin_show_url': 'http://bpaste.net/show/$paste_id/',
7474
'pastebin_helper': '',
75-
'save_append_py': False
75+
'save_append_py': False,
76+
'editor': os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi'))
7677
},
7778
'keyboard': {
7879
'clear_line': 'C-u',
@@ -82,6 +83,7 @@ def loadini(struct, configfile):
8283
'delete': 'C-d',
8384
'down_one_line': 'C-n',
8485
'exit': '',
86+
'external_editor': 'F7',
8587
'last_output': 'F9',
8688
'pastebin': 'F8',
8789
'save': 'C-s',
@@ -95,6 +97,10 @@ def loadini(struct, configfile):
9597
'suggestion_width': 0.8,
9698
'trim_prompts': False,
9799
},
100+
'curtsies': {
101+
'list_above' : False,
102+
'fill_terminal' : False,
103+
},
98104
'gtk': {
99105
'font': 'monospace 10',
100106
'color_scheme': 'default'}})
@@ -116,6 +122,7 @@ def loadini(struct, configfile):
116122
struct.highlight_show_source = config.getboolean('general',
117123
'highlight_show_source')
118124
struct.hist_file = config.get('general', 'hist_file')
125+
struct.editor = config.get('general', 'editor')
119126
struct.hist_length = config.getint('general', 'hist_length')
120127
struct.hist_duplicates = config.getboolean('general', 'hist_duplicates')
121128
struct.flush_output = config.getboolean('general', 'flush_output')
@@ -135,6 +142,7 @@ def loadini(struct, configfile):
135142
struct.delete_key = config.get('keyboard', 'delete')
136143
struct.exit_key = config.get('keyboard', 'exit')
137144
struct.last_output_key = config.get('keyboard', 'last_output')
145+
struct.external_editor_key = config.get('keyboard', 'external_editor')
138146

139147
struct.pastebin_confirm = config.getboolean('general', 'pastebin_confirm')
140148
struct.pastebin_private = config.getboolean('general', 'pastebin_private')
@@ -157,6 +165,9 @@ def loadini(struct, configfile):
157165

158166
struct.gtk_font = config.get('gtk', 'font')
159167

168+
struct.curtsies_list_above = config.getboolean('curtsies', 'list_above')
169+
struct.curtsies_fill_terminal = config.getboolean('curtsies', 'fill_terminal')
170+
160171
color_scheme_name = config.get('general', 'color_scheme')
161172
color_gtk_scheme_name = config.get('gtk', 'color_scheme')
162173

bpython/curtsies.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from __future__ import absolute_import
2+
3+
import sys
4+
import code
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 SystemExitFromCodeGreenlet
16+
from bpython import args as bpargs
17+
from bpython.translations import _
18+
from bpython.importcompletion import find_iterator
19+
20+
def main(args=None, locals_=None, banner=None):
21+
config, options, exec_args = bpargs.parse(args, (
22+
'scroll options', None, [
23+
Option('--log', '-L', action='store_true',
24+
help=_("log debug messages to bpython-curtsies.log")),
25+
Option('--type', '-t', action='store_true',
26+
help=_("enter lines of file as though interactively typed")),
27+
]))
28+
if options.log:
29+
import logging
30+
logging.basicConfig(filename='scroll.log', level=logging.DEBUG)
31+
32+
interp = None
33+
paste = None
34+
if exec_args:
35+
assert options, "don't pass in exec_args without options"
36+
exit_value = 0
37+
if options.type:
38+
paste = curtsies.events.PasteEvent()
39+
sourcecode = open(exec_args[0]).read()
40+
paste.events.extend(sourcecode)
41+
else:
42+
try:
43+
interp = code.InteractiveInterpreter(locals=locals_)
44+
bpargs.exec_code(interp, exec_args)
45+
except SystemExit, e:
46+
exit_value = e.args
47+
if not options.interactive:
48+
raise SystemExit(exit_value)
49+
else:
50+
sys.path.insert(0, '') # expected for interactive sessions (vanilla python does it)
51+
52+
mainloop(config, locals_, banner, interp, paste)
53+
54+
def mainloop(config, locals_, banner, interp=None, paste=None):
55+
with Terminal(paste_mode=True) as tc:
56+
with Window(tc, keep_last_line=True, hide_cursor=False) as term:
57+
with Repl(config=config,
58+
locals_=locals_,
59+
request_refresh=tc.stuff_a_refresh_request,
60+
banner=banner,
61+
interp=interp) as repl:
62+
rows, columns = tc.get_screen_size()
63+
repl.width = columns
64+
repl.height = rows
65+
66+
def process_event(e):
67+
try:
68+
repl.process_event(e)
69+
except (SystemExitFromCodeGreenlet, SystemExit) as err:
70+
array, cursor_pos = repl.paint(about_to_exit=True, user_quit=isinstance(err, SystemExitFromCodeGreenlet))
71+
scrolled = term.render_to_terminal(array, cursor_pos)
72+
repl.scroll_offset += scrolled
73+
raise
74+
else:
75+
array, cursor_pos = repl.paint()
76+
scrolled = term.render_to_terminal(array, cursor_pos)
77+
repl.scroll_offset += scrolled
78+
79+
if paste:
80+
repl.process_event(term.get_annotated_event()) #first event will always be a window size set
81+
process_event(paste)
82+
83+
while True:
84+
process_event(term.get_annotated_event(idle=find_iterator))
85+
86+
if __name__ == '__main__':
87+
sys.exit(main())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

0 commit comments

Comments
 (0)