X Tutup
Skip to content

Commit fe49677

Browse files
indent instead of removing empty pasted lines
only indents, doesn't add lines for mutliple commands in one paste only works correctly for pastes beginning on an empty line
1 parent 81d6044 commit fe49677

File tree

9 files changed

+179
-82
lines changed

9 files changed

+179
-82
lines changed

bpython/curtsiesfrontend/interpreter.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,19 @@ def format(self, tbtext, lexer):
153153
else:
154154
cur_line.append((token,text))
155155
assert cur_line == [], cur_line
156+
157+
158+
def code_finished_will_parse(s, compiler):
159+
"""Returns a tuple of whether the buffer could be complete and whether it will parse
160+
161+
True, True means code block is finished and no predicted parse error
162+
True, False means code block is finished because a parse error is predicted
163+
False, True means code block is unfinished
164+
False, False isn't possible - an predicted error makes code block done"""
165+
try:
166+
finished = bool(compiler(s))
167+
code_will_parse = True
168+
except (ValueError, SyntaxError, OverflowError):
169+
finished = True
170+
code_will_parse = False
171+
return finished, code_will_parse
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Tools for preparing code to be run in the REPL (removing blank lines, etc)"""
2+
import re
3+
4+
from bpython.curtsiesfrontend.interpreter import code_finished_will_parse
5+
6+
#TODO specifically catch IndentationErrors instead of any syntax errors
7+
8+
def indent_empty_lines(s, compiler):
9+
"""Indents blank lines that would otherwise cause early compilation
10+
11+
Only really works if starting on a new line"""
12+
lines = s.split('\n')
13+
ends_with_newline = False
14+
if lines and not lines[-1]:
15+
ends_with_newline = True
16+
lines.pop()
17+
result_lines = []
18+
19+
for p_line, line, n_line in zip([''] + lines[:-1], lines, lines[1:] + ['']):
20+
if len(line) == 0:
21+
p_indent = re.match(r'\s*', p_line).group()
22+
n_indent = re.match(r'\s*', n_line).group()
23+
result_lines.append(min([p_indent, n_indent], key=len) + line)
24+
else:
25+
result_lines.append(line)
26+
27+
return '\n'.join(result_lines) + ('\n' if ends_with_newline else '')
28+

bpython/curtsiesfrontend/repl.py

Lines changed: 6 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import code
21
import contextlib
32
import errno
43
import functools
@@ -17,7 +16,6 @@
1716
from pygments import format
1817
from bpython._py3compat import PythonLexer
1918
from pygments.formatters import TerminalFormatter
20-
from interpreter import Interp
2119

2220
import blessings
2321

@@ -45,6 +43,8 @@
4543
from bpython.curtsiesfrontend import events as bpythonevents
4644
from bpython.curtsiesfrontend.parse import parse as bpythonparse
4745
from bpython.curtsiesfrontend.parse import func_for_letter, color_for_letter
46+
from bpython.curtsiesfrontend.preprocess import indent_empty_lines
47+
from bpython.curtsiesfrontend.interpreter import Interp, code_finished_will_parse
4848

4949
#TODO other autocomplete modes (also fix in other bpython implementations)
5050

@@ -457,7 +457,7 @@ def process_control_event(self, e):
457457
if ctrl_char is not None:
458458
return self.process_event(ctrl_char)
459459
simple_events = just_simple_events(e.events)
460-
source = bad_empty_lines_removed(''.join(simple_events))
460+
source = indent_empty_lines(''.join(simple_events), self.interp.compile)
461461

462462
with self.in_paste_mode():
463463
for ee in source:
@@ -713,7 +713,7 @@ def send_session_to_external_editor(self, filename=None):
713713
text = self.send_to_external_editor(for_editor)
714714
lines = text.split('\n')
715715
from_editor = [line for line in lines if line[:4] != '### ']
716-
source = bad_empty_lines_removed('\n'.join(from_editor))
716+
source = indent_empty_lines('\n'.join(from_editor), self.interp.compile)
717717
self.history = source.split('\n')
718718
self.reevaluate(insert_into_history=True)
719719
self.current_line = lines[-1][4:]
@@ -828,7 +828,8 @@ def push(self, line, insert_into_history=True):
828828
code_to_run = '\n'.join(self.buffer)
829829

830830
logger.debug('running %r in interpreter', self.buffer)
831-
c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer))
831+
c, code_will_parse = code_finished_will_parse('\n'.join(self.buffer),
832+
self.interp.compile)
832833
self.saved_predicted_parse_error = not code_will_parse
833834
if c:
834835
logger.debug('finished - buffer cleared')
@@ -1429,63 +1430,6 @@ def just_simple_events(event_list):
14291430
simple_events.append(e)
14301431
return simple_events
14311432

1432-
def code_finished_will_parse(s):
1433-
"""Returns a tuple of whether the buffer could be complete and whether it will parse
1434-
1435-
True, True means code block is finished and no predicted parse error
1436-
True, False means code block is finished because a parse error is predicted
1437-
False, True means code block is unfinished
1438-
False, False isn't possible - an predicted error makes code block done"""
1439-
try:
1440-
finished = bool(code.compile_command(s))
1441-
code_will_parse = True
1442-
except (ValueError, SyntaxError, OverflowError):
1443-
finished = True
1444-
code_will_parse = False
1445-
return finished, code_will_parse
1446-
1447-
def bad_empty_lines_removed(s):
1448-
"""Removes empty lines that would cause unfinished input to be evaluated"""
1449-
# If there's a syntax error followed by an empty line, remove the empty line
1450-
lines = s.split('\n')
1451-
#TODO this should be our interpreter object making this decision so it
1452-
# can be compiler directive (__future__ statement) -aware
1453-
#TODO specifically catch IndentationErrors instead of any syntax errors
1454-
1455-
current_block = []
1456-
complete_blocks = []
1457-
for i, line in enumerate(s.split('\n')):
1458-
current_block.append(line)
1459-
could_be_finished, valid = code_finished_will_parse('\n'.join(current_block))
1460-
if could_be_finished and valid:
1461-
complete_blocks.append(current_block)
1462-
current_block = []
1463-
continue
1464-
elif could_be_finished and not valid:
1465-
if complete_blocks:
1466-
complete_blocks[-1].extend(current_block)
1467-
current_block = complete_blocks.pop()
1468-
if len(current_block) < 2:
1469-
return s #TODO return partial result instead of giving up
1470-
last_line = current_block.pop(len(current_block) - 2)
1471-
assert not last_line, last_line
1472-
new_finished, new_valid = code_finished_will_parse('\n'.join(current_block))
1473-
if new_valid and new_finished:
1474-
complete_blocks.append(current_block)
1475-
current_block = []
1476-
elif new_valid:
1477-
continue
1478-
else:
1479-
return s #TODO return partial result instead of giving up
1480-
1481-
else:
1482-
return s #TODO return partial result instead of giving up
1483-
else:
1484-
continue
1485-
return '\n'.join(['\n'.join(block)
1486-
for block in complete_blocks + [current_block]
1487-
if block])
1488-
14891433

14901434
#TODO this needs some work to function again and be useful for embedding
14911435
def simple_repl():

bpython/test/fodder/__init__.py

Whitespace-only changes.

bpython/test/fodder/original.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# careful: whitespace is very important in this file
2+
# also, this code runs - so everything should be a noop
3+
4+
class BlankLineBetweenMethods(object):
5+
def method1(self):
6+
pass
7+
8+
def method2(self):
9+
pass
10+
11+
def BlankLineInFunction(self):
12+
return 7
13+
14+
pass
15+
16+
#StartTest-blank_lines_in_for_loop
17+
for i in range(2):
18+
pass
19+
20+
pass
21+
#EndTest
22+

bpython/test/fodder/processed.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#careful! Whitespace is very important in this file
2+
3+
class BlankLineBetweenMethods(object):
4+
def method1(self):
5+
pass
6+
7+
def method2(self):
8+
pass
9+
10+
def BlankLineInFunction(self):
11+
return 7
12+
13+
pass
14+
15+
#StartTest-blank_lines_in_for_loop
16+
for i in range(2):
17+
pass
18+
19+
pass
20+
#EndTest

bpython/test/test_curtsies_repl.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# coding: utf8
22
import code
3+
from contextlib import contextmanager
4+
from functools import partial
35
import os
6+
from StringIO import StringIO
47
import sys
58
import tempfile
6-
from contextlib import contextmanager
7-
from StringIO import StringIO
89

910
import unittest
1011
try:
@@ -16,6 +17,7 @@ def skip(f):
1617
py3 = (sys.version_info[0] == 3)
1718

1819
from bpython.curtsiesfrontend import repl as curtsiesrepl
20+
from bpython.curtsiesfrontend import interpreter
1921
from bpython import config
2022
from bpython import args
2123

@@ -28,24 +30,28 @@ def setup_config(conf):
2830
setattr(config_struct, key, value)
2931
return config_struct
3032

33+
3134
class TestCurtsiesRepl(unittest.TestCase):
3235

3336
def setUp(self):
3437
self.repl = create_repl()
3538

39+
def cfwp(self, source):
40+
return interpreter.code_finished_will_parse(source, self.repl.interp.compile)
41+
3642
def test_code_finished_will_parse(self):
3743
self.repl.buffer = ['1 + 1']
38-
self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True))
44+
self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True))
3945
self.repl.buffer = ['def foo(x):']
40-
self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (False, True))
46+
self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (False, True))
4147
self.repl.buffer = ['def foo(x)']
42-
self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False))
48+
self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False))
4349
self.repl.buffer = ['def foo(x):', 'return 1']
44-
self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, False))
50+
self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, False))
4551
self.repl.buffer = ['def foo(x):', ' return 1']
46-
self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True))
52+
self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True))
4753
self.repl.buffer = ['def foo(x):', ' return 1', '']
48-
self.assertTrue(curtsiesrepl.code_finished_will_parse('\n'.join(self.repl.buffer)), (True, True))
54+
self.assertTrue(self.cfwp('\n'.join(self.repl.buffer)), (True, True))
4955

5056
def test_external_communication(self):
5157
self.assertEqual(type(self.repl.help_text()), type(b''))

bpython/test/test_interpreter.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import unittest
22

33
from bpython.curtsiesfrontend import interpreter
4-
from bpython.curtsiesfrontend.repl import bad_empty_lines_removed
54
from curtsies.fmtfuncs import *
65

76
class TestInterpreter(unittest.TestCase):
@@ -42,14 +41,3 @@ def g():
4241
self.assertEquals(str(plain('').join(a)), str(expected))
4342
self.assertEquals(plain('').join(a), expected)
4443

45-
class TestPreprocessing(unittest.TestCase):
46-
def test_bad_empty_lines_removed(self):
47-
self.assertEqual(bad_empty_lines_removed("def foo():\n"
48-
" return 1\n"
49-
"\n"
50-
" pass\n"),
51-
"def foo():\n"
52-
" return 1\n"
53-
" pass\n")
54-
55-

bpython/test/test_preprocess.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from code import compile_command as compiler
2+
from functools import partial
3+
import difflib
4+
import inspect
5+
import re
6+
import unittest
7+
8+
from bpython.curtsiesfrontend.interpreter import code_finished_will_parse
9+
from bpython.curtsiesfrontend.preprocess import indent_empty_lines
10+
11+
from bpython.test.fodder import original as original, processed
12+
13+
indent_empty = partial(indent_empty_lines, compiler=compiler)
14+
15+
16+
def get_fodder_source(test_name):
17+
pattern = r'#StartTest-%s\n(.*?)#EndTest' % (test_name,)
18+
print repr(pattern)
19+
orig, xformed = [re.search(pattern, inspect.getsource(module), re.DOTALL)
20+
for module in [original, processed]]
21+
22+
if not orig:
23+
raise ValueError("Can't locate test %s in original fodder file" % (test_name,))
24+
if not xformed:
25+
raise ValueError("Can't locate test %s in processed fodder file" % (test_name,))
26+
return orig.group(1), xformed.group(1)
27+
28+
29+
class TestPreprocessing(unittest.TestCase):
30+
31+
def assertCompiles(self, source):
32+
finished, parsable = code_finished_will_parse(source, compiler)
33+
return finished and parsable
34+
35+
def test_indent_empty_lines_nops(self):
36+
self.assertEqual(indent_empty('hello'), 'hello')
37+
38+
def assertShowWhitespaceEqual(self, a, b):
39+
self.assertEqual(
40+
indent_empty(a), b,
41+
''.join(difflib.context_diff(a.replace(' ', '~').splitlines(True),
42+
b.replace(' ', '~').splitlines(True),
43+
fromfile='original',
44+
tofile='processed',
45+
n=5)))
46+
47+
def assertDefinitionIndented(self, obj):
48+
name = obj.__name__
49+
obj2 = getattr(processed, name)
50+
orig = inspect.getsource(obj)
51+
xformed = inspect.getsource(obj2)
52+
self.assertShowWhitespaceEqual(indent_empty(orig), xformed)
53+
self.assertCompiles(xformed)
54+
55+
def assertLinesIndented(self, test_name):
56+
orig, xformed = get_fodder_source(test_name)
57+
self.assertShowWhitespaceEqual(indent_empty(orig), xformed)
58+
self.assertCompiles(xformed)
59+
60+
def assertIndented(self, obj_or_name):
61+
if isinstance(obj_or_name, str):
62+
self.assertLinesIndented(obj_or_name)
63+
else:
64+
self.assertDefinitionIndented(obj_or_name)
65+
66+
def test_empty_line_between_methods(self):
67+
self.assertIndented(original.BlankLineBetweenMethods)
68+
69+
def test_empty_line_within_class(self):
70+
self.assertIndented(original.BlankLineInFunction)
71+
72+
def test_blank_lines_in_for_loop(self):
73+
self.assertIndented('blank_lines_in_for_loop')

0 commit comments

Comments
 (0)
X Tutup