##// END OF EJS Templates
Add support for append/replace mode after discussion with Evan....
Fernando Perez -
Show More
@@ -1,237 +1,259 b''
1 """Analysis of text input into executable blocks.
1 """Analysis of text input into executable blocks.
2
2
3 This is a simple example of how an interactive terminal-based client can use
3 This is a simple example of how an interactive terminal-based client can use
4 this tool::
4 this tool::
5
5
6 bb = BlockBreaker()
6 bb = BlockBreaker()
7 while not bb.interactive_block_ready():
7 while not bb.interactive_block_ready():
8 bb.push(raw_input('>>> '))
8 bb.push(raw_input('>>> '))
9 print 'Input source was:\n', bb.source,
9 print 'Input source was:\n', bb.source,
10 """
10 """
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2010 The IPython Development Team
12 # Copyright (C) 2010 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # stdlib
21 # stdlib
22 import codeop
22 import codeop
23 import re
23 import re
24 import sys
24 import sys
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Utilities
27 # Utilities
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29
29
30 # FIXME: move these utilities to the general ward...
30 # FIXME: move these utilities to the general ward...
31
31
32 # compiled regexps for autoindent management
32 # compiled regexps for autoindent management
33 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
33 dedent_re = re.compile(r'^\s+raise|^\s+return|^\s+pass')
34 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
34 ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
35
35
36
36
37 def num_ini_spaces(s):
37 def num_ini_spaces(s):
38 """Return the number of initial spaces in a string.
38 """Return the number of initial spaces in a string.
39
39
40 Note that tabs are counted as a single space. For now, we do *not* support
40 Note that tabs are counted as a single space. For now, we do *not* support
41 mixing of tabs and spaces in the user's input.
41 mixing of tabs and spaces in the user's input.
42
42
43 Parameters
43 Parameters
44 ----------
44 ----------
45 s : string
45 s : string
46 """
46 """
47
47
48 ini_spaces = ini_spaces_re.match(s)
48 ini_spaces = ini_spaces_re.match(s)
49 if ini_spaces:
49 if ini_spaces:
50 return ini_spaces.end()
50 return ini_spaces.end()
51 else:
51 else:
52 return 0
52 return 0
53
53
54
54
55 def remove_comments(src):
55 def remove_comments(src):
56 """Remove all comments from input source.
56 """Remove all comments from input source.
57
57
58 Note: comments are NOT recognized inside of strings!
58 Note: comments are NOT recognized inside of strings!
59
59
60 Parameters
60 Parameters
61 ----------
61 ----------
62 src : string
62 src : string
63 A single or multiline input string.
63 A single or multiline input string.
64
64
65 Returns
65 Returns
66 -------
66 -------
67 String with all Python comments removed.
67 String with all Python comments removed.
68 """
68 """
69
69
70 return re.sub('#.*', '', src)
70 return re.sub('#.*', '', src)
71
71
72
72
73 def get_input_encoding():
73 def get_input_encoding():
74 """Return the default standard input encoding."""
74 """Return the default standard input encoding."""
75 return getattr(sys.stdin, 'encoding', 'ascii')
75 return getattr(sys.stdin, 'encoding', 'ascii')
76
76
77 #-----------------------------------------------------------------------------
77 #-----------------------------------------------------------------------------
78 # Classes and functions
78 # Classes and functions
79 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
80
80
81 class BlockBreaker(object):
81 class BlockBreaker(object):
82 # Command compiler
82 # Command compiler
83 compile = None
83 compile = None
84 # Number of spaces of indentation
84 # Number of spaces of indentation
85 indent_spaces = 0
85 indent_spaces = 0
86 # String, indicating the default input encoding
86 # String, indicating the default input encoding
87 encoding = ''
87 encoding = ''
88 # String where the current full source input is stored, properly encoded
88 # String where the current full source input is stored, properly encoded
89 source = ''
89 source = ''
90 # Code object corresponding to the current source
90 # Code object corresponding to the current source
91 code = None
91 code = None
92 # Boolean indicating whether the current block is complete
92 # Boolean indicating whether the current block is complete
93 is_complete = None
93 is_complete = None
94
94 # Input mode
95 input_mode = 'append'
96
95 # Private attributes
97 # Private attributes
96
98
97 # List
99 # List
98 _buffer = None
100 _buffer = None
99
101
100 def __init__(self):
102 def __init__(self, input_mode=None):
103 """Create a new BlockBreaker instance.
104
105 Parameters
106 ----------
107 input_mode : str
108
109 One of 'append', 'replace', default is 'append'. This controls how
110 new inputs are used: in 'append' mode, they are appended to the
111 existing buffer and the whole buffer is compiled; in 'replace' mode,
112 each new input completely replaces all prior inputs. Replace mode is
113 thus equivalent to prepending a full reset() to every push() call.
114
115 In practice, line-oriented clients likely want to use 'append' mode
116 while block-oriented ones will want to use 'replace'.
117 """
101 self._buffer = []
118 self._buffer = []
102 self.compile = codeop.CommandCompiler()
119 self.compile = codeop.CommandCompiler()
103 self.encoding = get_input_encoding()
120 self.encoding = get_input_encoding()
121 self.input_mode = BlockBreaker.input_mode if input_mode is None \
122 else input_mode
104
123
105 def reset(self):
124 def reset(self):
106 """Reset the input buffer and associated state."""
125 """Reset the input buffer and associated state."""
107 self.indent_spaces = 0
126 self.indent_spaces = 0
108 self._buffer[:] = []
127 self._buffer[:] = []
109 self.source = ''
128 self.source = ''
110 self.code = None
129 self.code = None
111
130
112 def get_source(self, reset=False):
131 def get_source(self, reset=False):
113 """Return the input source.
132 """Return the input source.
114
133
115 Parameters
134 Parameters
116 ----------
135 ----------
117 reset : boolean
136 reset : boolean
118 If true, all state is reset and prior input forgotten.
137 If true, all state is reset and prior input forgotten.
119 """
138 """
120 out = self.source
139 out = self.source
121 if reset:
140 if reset:
122 self.reset()
141 self.reset()
123 return out
142 return out
124
143
125 def push(self, lines):
144 def push(self, lines):
126 """Push one ore more lines of input.
145 """Push one ore more lines of input.
127
146
128 This stores the given lines and returns a status code indicating
147 This stores the given lines and returns a status code indicating
129 whether the code forms a complete Python block or not.
148 whether the code forms a complete Python block or not.
130
149
131 Any exceptions generated in compilation are allowed to propagate.
150 Any exceptions generated in compilation are allowed to propagate.
132
151
133 Parameters
152 Parameters
134 ----------
153 ----------
135 lines : string
154 lines : string
136 One or more lines of Python input.
155 One or more lines of Python input.
137
156
138 Returns
157 Returns
139 -------
158 -------
140 is_complete : boolean
159 is_complete : boolean
141 True if the current input source (the result of the current input
160 True if the current input source (the result of the current input
142 plus prior inputs) forms a complete Python execution block. Note that
161 plus prior inputs) forms a complete Python execution block. Note that
143 this value is also stored as an attribute so it can be queried at any
162 this value is also stored as an attribute so it can be queried at any
144 time.
163 time.
145 """
164 """
165 if self.input_mode == 'replace':
166 self.reset()
167
146 # If the source code has leading blanks, add 'if 1:\n' to it
168 # If the source code has leading blanks, add 'if 1:\n' to it
147 # this allows execution of indented pasted code. It is tempting
169 # this allows execution of indented pasted code. It is tempting
148 # to add '\n' at the end of source to run commands like ' a=1'
170 # to add '\n' at the end of source to run commands like ' a=1'
149 # directly, but this fails for more complicated scenarios
171 # directly, but this fails for more complicated scenarios
150 if not self._buffer and lines[:1] in [' ', '\t']:
172 if not self._buffer and lines[:1] in [' ', '\t']:
151 lines = 'if 1:\n%s' % lines
173 lines = 'if 1:\n%s' % lines
152
174
153 self._store(lines)
175 self._store(lines)
154 source = self.source
176 source = self.source
155
177
156 # Before calling compile(), reset the code object to None so that if an
178 # Before calling compile(), reset the code object to None so that if an
157 # exception is raised in compilation, we don't mislead by having
179 # exception is raised in compilation, we don't mislead by having
158 # inconsistent code/source attributes.
180 # inconsistent code/source attributes.
159 self.code, self.is_complete = None, None
181 self.code, self.is_complete = None, None
160 self.code = self.compile(source)
182 self.code = self.compile(source)
161 # Compilation didn't produce any exceptions (though it may not have
183 # Compilation didn't produce any exceptions (though it may not have
162 # given a complete code object)
184 # given a complete code object)
163 if self.code is None:
185 if self.code is None:
164 self.is_complete = False
186 self.is_complete = False
165 else:
187 else:
166 self.is_complete = True
188 self.is_complete = True
167 self._update_indent(lines)
189 self._update_indent(lines)
168 return self.is_complete
190 return self.is_complete
169
191
170 def interactive_block_ready(self):
192 def interactive_block_ready(self):
171 """Return whether a block of interactive input is ready for execution.
193 """Return whether a block of interactive input is ready for execution.
172
194
173 This method is meant to be used by line-oriented frontends, who need to
195 This method is meant to be used by line-oriented frontends, who need to
174 guess whether a block is complete or not based solely on prior and
196 guess whether a block is complete or not based solely on prior and
175 current input lines. The BlockBreaker considers it has a complete
197 current input lines. The BlockBreaker considers it has a complete
176 interactive block when *all* of the following are true:
198 interactive block when *all* of the following are true:
177
199
178 1. The input compiles to a complete statement.
200 1. The input compiles to a complete statement.
179
201
180 2. The indentation level is flush-left (because if we are indented,
202 2. The indentation level is flush-left (because if we are indented,
181 like inside a function definition or for loop, we need to keep
203 like inside a function definition or for loop, we need to keep
182 reading new input).
204 reading new input).
183
205
184 3. There is one extra line consisting only of whitespace.
206 3. There is one extra line consisting only of whitespace.
185
207
186 Because of condition #3, this method should be used only by
208 Because of condition #3, this method should be used only by
187 *line-oriented* frontends, since it means that intermediate blank lines
209 *line-oriented* frontends, since it means that intermediate blank lines
188 are not allowed in function definitions (or any other indented block).
210 are not allowed in function definitions (or any other indented block).
189
211
190 Block-oriented frontends that have a separate keyboard event to
212 Block-oriented frontends that have a separate keyboard event to
191 indicate execution should use the :meth:`split_blocks` method instead.
213 indicate execution should use the :meth:`split_blocks` method instead.
192 """
214 """
193 if not self.is_complete:
215 if not self.is_complete:
194 return False
216 return False
195 if self.indent_spaces==0:
217 if self.indent_spaces==0:
196 return True
218 return True
197 last_line = self.source.splitlines()[-1]
219 last_line = self.source.splitlines()[-1]
198 if not last_line or last_line.isspace():
220 if not last_line or last_line.isspace():
199 return True
221 return True
200 else:
222 else:
201 return False
223 return False
202
224
203 def split_blocks(self, lines):
225 def split_blocks(self, lines):
204 """Split a multiline string into multiple input blocks"""
226 """Split a multiline string into multiple input blocks"""
205 raise NotImplementedError
227 raise NotImplementedError
206
228
207 #------------------------------------------------------------------------
229 #------------------------------------------------------------------------
208 # Private interface
230 # Private interface
209 #------------------------------------------------------------------------
231 #------------------------------------------------------------------------
210
232
211 def _update_indent(self, lines):
233 def _update_indent(self, lines):
212 """Keep track of the indent level."""
234 """Keep track of the indent level."""
213
235
214 for line in remove_comments(lines).splitlines():
236 for line in remove_comments(lines).splitlines():
215
237
216 if line and not line.isspace():
238 if line and not line.isspace():
217 if self.code is not None:
239 if self.code is not None:
218 inisp = num_ini_spaces(line)
240 inisp = num_ini_spaces(line)
219 if inisp < self.indent_spaces:
241 if inisp < self.indent_spaces:
220 self.indent_spaces = inisp
242 self.indent_spaces = inisp
221
243
222 if line[-1] == ':':
244 if line[-1] == ':':
223 self.indent_spaces += 4
245 self.indent_spaces += 4
224 elif dedent_re.match(line):
246 elif dedent_re.match(line):
225 self.indent_spaces -= 4
247 self.indent_spaces -= 4
226
248
227 def _store(self, lines):
249 def _store(self, lines):
228 """Store one or more lines of input.
250 """Store one or more lines of input.
229
251
230 If input lines are not newline-terminated, a newline is automatically
252 If input lines are not newline-terminated, a newline is automatically
231 appended."""
253 appended."""
232
254
233 if lines.endswith('\n'):
255 if lines.endswith('\n'):
234 self._buffer.append(lines)
256 self._buffer.append(lines)
235 else:
257 else:
236 self._buffer.append(lines+'\n')
258 self._buffer.append(lines+'\n')
237 self.source = ''.join(self._buffer).encode(self.encoding)
259 self.source = ''.join(self._buffer).encode(self.encoding)
@@ -1,173 +1,183 b''
1 """Tests for the blockbreaker module.
1 """Tests for the blockbreaker module.
2 """
2 """
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2010 The IPython Development Team
4 # Copyright (C) 2010 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # stdlib
13 # stdlib
14 import unittest
14 import unittest
15
15
16 # Third party
16 # Third party
17 import nose.tools as nt
17 import nose.tools as nt
18
18
19 # Our own
19 # Our own
20 from IPython.core import blockbreaker as BB
20 from IPython.core import blockbreaker as BB
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Tests
23 # Tests
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 def test_spaces():
25 def test_spaces():
26 tests = [('', 0),
26 tests = [('', 0),
27 (' ', 1),
27 (' ', 1),
28 ('\n', 0),
28 ('\n', 0),
29 (' \n', 1),
29 (' \n', 1),
30 ('x', 0),
30 ('x', 0),
31 (' x', 1),
31 (' x', 1),
32 (' x',2),
32 (' x',2),
33 (' x',4),
33 (' x',4),
34 # Note: tabs are counted as a single whitespace!
34 # Note: tabs are counted as a single whitespace!
35 ('\tx', 1),
35 ('\tx', 1),
36 ('\t x', 2),
36 ('\t x', 2),
37 ]
37 ]
38
38
39 for s, nsp in tests:
39 for s, nsp in tests:
40 nt.assert_equal(BB.num_ini_spaces(s), nsp)
40 nt.assert_equal(BB.num_ini_spaces(s), nsp)
41
41
42
42
43 def test_remove_comments():
43 def test_remove_comments():
44 tests = [('text', 'text'),
44 tests = [('text', 'text'),
45 ('text # comment', 'text '),
45 ('text # comment', 'text '),
46 ('text # comment\n', 'text \n'),
46 ('text # comment\n', 'text \n'),
47 ('text # comment \n', 'text \n'),
47 ('text # comment \n', 'text \n'),
48 ('line # c \nline\n','line \nline\n'),
48 ('line # c \nline\n','line \nline\n'),
49 ('line # c \nline#c2 \nline\nline #c\n\n',
49 ('line # c \nline#c2 \nline\nline #c\n\n',
50 'line \nline\nline\nline \n\n'),
50 'line \nline\nline\nline \n\n'),
51 ]
51 ]
52
52
53 for inp, out in tests:
53 for inp, out in tests:
54 nt.assert_equal(BB.remove_comments(inp), out)
54 nt.assert_equal(BB.remove_comments(inp), out)
55
55
56
56
57 def test_get_input_encoding():
57 def test_get_input_encoding():
58 encoding = BB.get_input_encoding()
58 encoding = BB.get_input_encoding()
59 nt.assert_true(isinstance(encoding, basestring))
59 nt.assert_true(isinstance(encoding, basestring))
60 # simple-minded check that at least encoding a simple string works with the
60 # simple-minded check that at least encoding a simple string works with the
61 # encoding we got.
61 # encoding we got.
62 nt.assert_equal('test'.encode(encoding), 'test')
62 nt.assert_equal('test'.encode(encoding), 'test')
63
63
64
64
65 class BlockBreakerTestCase(unittest.TestCase):
65 class BlockBreakerTestCase(unittest.TestCase):
66 def setUp(self):
66 def setUp(self):
67 self.bb = BB.BlockBreaker()
67 self.bb = BB.BlockBreaker()
68
68
69 def test_reset(self):
69 def test_reset(self):
70 bb = self.bb
70 bb = self.bb
71 bb.push('x=1')
71 bb.push('x=1')
72 bb.reset()
72 bb.reset()
73 self.assertEqual(bb._buffer, [])
73 self.assertEqual(bb._buffer, [])
74 self.assertEqual(bb.indent_spaces, 0)
74 self.assertEqual(bb.indent_spaces, 0)
75 self.assertEqual(bb.get_source(), '')
75 self.assertEqual(bb.get_source(), '')
76 self.assertEqual(bb.code, None)
76 self.assertEqual(bb.code, None)
77
77
78 def test_source(self):
78 def test_source(self):
79 self.bb._store('1')
79 self.bb._store('1')
80 self.bb._store('2')
80 self.bb._store('2')
81 out = self.bb.get_source()
81 out = self.bb.get_source()
82 self.assertEqual(out, '1\n2\n')
82 self.assertEqual(out, '1\n2\n')
83 out = self.bb.get_source(reset=True)
83 out = self.bb.get_source(reset=True)
84 self.assertEqual(out, '1\n2\n')
84 self.assertEqual(out, '1\n2\n')
85 self.assertEqual(self.bb._buffer, [])
85 self.assertEqual(self.bb._buffer, [])
86 out = self.bb.get_source()
86 out = self.bb.get_source()
87 self.assertEqual(out, '')
87 self.assertEqual(out, '')
88
88
89 def test_indent(self):
89 def test_indent(self):
90 bb = self.bb # shorthand
90 bb = self.bb # shorthand
91 bb.push('x=1')
91 bb.push('x=1')
92 self.assertEqual(bb.indent_spaces, 0)
92 self.assertEqual(bb.indent_spaces, 0)
93 bb.push('if 1:\n x=1')
93 bb.push('if 1:\n x=1')
94 self.assertEqual(bb.indent_spaces, 4)
94 self.assertEqual(bb.indent_spaces, 4)
95 bb.push('y=2\n')
95 bb.push('y=2\n')
96 self.assertEqual(bb.indent_spaces, 0)
96 self.assertEqual(bb.indent_spaces, 0)
97 bb.push('if 1:')
97 bb.push('if 1:')
98 self.assertEqual(bb.indent_spaces, 4)
98 self.assertEqual(bb.indent_spaces, 4)
99 bb.push(' x=1')
99 bb.push(' x=1')
100 self.assertEqual(bb.indent_spaces, 4)
100 self.assertEqual(bb.indent_spaces, 4)
101 # Blank lines shouldn't change the indent level
101 # Blank lines shouldn't change the indent level
102 bb.push(' '*2)
102 bb.push(' '*2)
103 self.assertEqual(bb.indent_spaces, 4)
103 self.assertEqual(bb.indent_spaces, 4)
104
104
105 def test_indent2(self):
105 def test_indent2(self):
106 bb = self.bb
106 bb = self.bb
107 # When a multiline statement contains parens or multiline strings, we
107 # When a multiline statement contains parens or multiline strings, we
108 # shouldn't get confused.
108 # shouldn't get confused.
109 bb.push("if 1:")
109 bb.push("if 1:")
110 bb.push(" x = (1+\n 2)")
110 bb.push(" x = (1+\n 2)")
111 self.assertEqual(bb.indent_spaces, 4)
111 self.assertEqual(bb.indent_spaces, 4)
112
112
113 def test_dedent(self):
113 def test_dedent(self):
114 bb = self.bb # shorthand
114 bb = self.bb # shorthand
115 bb.push('if 1:')
115 bb.push('if 1:')
116 self.assertEqual(bb.indent_spaces, 4)
116 self.assertEqual(bb.indent_spaces, 4)
117 bb.push(' pass')
117 bb.push(' pass')
118 self.assertEqual(bb.indent_spaces, 0)
118 self.assertEqual(bb.indent_spaces, 0)
119
119
120 def test_push(self):
120 def test_push(self):
121 bb = self.bb
121 bb = self.bb
122 bb.push('x=1')
122 bb.push('x=1')
123 self.assertTrue(bb.is_complete)
123 self.assertTrue(bb.is_complete)
124
124
125 def test_push2(self):
125 def test_push2(self):
126 bb = self.bb
126 bb = self.bb
127 bb.push('if 1:')
127 bb.push('if 1:')
128 self.assertFalse(bb.is_complete)
128 self.assertFalse(bb.is_complete)
129 for line in [' x=1', '# a comment', ' y=2']:
129 for line in [' x=1', '# a comment', ' y=2']:
130 bb.push(line)
130 bb.push(line)
131 self.assertTrue(bb.is_complete)
131 self.assertTrue(bb.is_complete)
132
132
133 def test_push3(self):
133 def test_push3(self):
134 """Test input with leading whitespace"""
134 """Test input with leading whitespace"""
135 bb = self.bb
135 bb = self.bb
136 bb.push(' x=1')
136 bb.push(' x=1')
137 bb.push(' y=2')
137 bb.push(' y=2')
138 self.assertEqual(bb.source, 'if 1:\n x=1\n y=2\n')
138 self.assertEqual(bb.source, 'if 1:\n x=1\n y=2\n')
139
139
140 def test_replace_mode(self):
141 bb = self.bb
142 bb.input_mode = 'replace'
143 bb.push('x=1')
144 self.assertEqual(bb.source, 'x=1\n')
145 bb.push('x=2')
146 self.assertEqual(bb.source, 'x=2\n')
147
140 def test_interactive_block_ready(self):
148 def test_interactive_block_ready(self):
141 bb = self.bb
149 bb = self.bb
142 bb.push('x=1')
150 bb.push('x=1')
143 self.assertTrue(bb.interactive_block_ready())
151 self.assertTrue(bb.interactive_block_ready())
144
152
145 def test_interactive_block_ready2(self):
153 def test_interactive_block_ready2(self):
146 bb = self.bb
154 bb = self.bb
147 bb.push('if 1:\n x=1')
155 bb.push('if 1:')
156 self.assertFalse(bb.interactive_block_ready())
157 bb.push(' x=1')
148 self.assertFalse(bb.interactive_block_ready())
158 self.assertFalse(bb.interactive_block_ready())
149 bb.push('')
159 bb.push('')
150 self.assertTrue(bb.interactive_block_ready())
160 self.assertTrue(bb.interactive_block_ready())
151
161
152 def test_interactive_block_ready3(self):
162 def test_interactive_block_ready3(self):
153 bb = self.bb
163 bb = self.bb
154 bb.push("x = (2+\n3)")
164 bb.push("x = (2+\n3)")
155 self.assertTrue(bb.interactive_block_ready())
165 self.assertTrue(bb.interactive_block_ready())
156
166
157 def test_interactive_block_ready4(self):
167 def test_interactive_block_ready4(self):
158 bb = self.bb
168 bb = self.bb
159 # When a multiline statement contains parens or multiline strings, we
169 # When a multiline statement contains parens or multiline strings, we
160 # shouldn't get confused.
170 # shouldn't get confused.
161 # FIXME: we should be able to better handle de-dents in statements like
171 # FIXME: we should be able to better handle de-dents in statements like
162 # multiline strings and multiline expressions (continued with \ or
172 # multiline strings and multiline expressions (continued with \ or
163 # parens). Right now we aren't handling the indentation tracking quite
173 # parens). Right now we aren't handling the indentation tracking quite
164 # correctly with this, though in practice it may not be too much of a
174 # correctly with this, though in practice it may not be too much of a
165 # problem. We'll need to see.
175 # problem. We'll need to see.
166 bb.push("if 1:")
176 bb.push("if 1:")
167 bb.push(" x = (2+")
177 bb.push(" x = (2+")
168 bb.push(" 3)")
178 bb.push(" 3)")
169 self.assertFalse(bb.interactive_block_ready())
179 self.assertFalse(bb.interactive_block_ready())
170 bb.push(" y = 3")
180 bb.push(" y = 3")
171 self.assertFalse(bb.interactive_block_ready())
181 self.assertFalse(bb.interactive_block_ready())
172 bb.push('')
182 bb.push('')
173 self.assertTrue(bb.interactive_block_ready())
183 self.assertTrue(bb.interactive_block_ready())
General Comments 0
You need to be logged in to leave comments. Login now