##// END OF EJS Templates
Merge pull request #6297 from takluyver/is-complete-request...
Min RK -
r18369:d236bec1 merge
parent child Browse files
Show More
@@ -208,6 +208,8 b' class InputSplitter(object):'
208 208 _full_dedent = False
209 209 # Boolean indicating whether the current block is complete
210 210 _is_complete = None
211 # Boolean indicating whether the current block has an unrecoverable syntax error
212 _is_invalid = False
211 213
212 214 def __init__(self):
213 215 """Create a new InputSplitter instance.
@@ -223,6 +225,7 b' class InputSplitter(object):'
223 225 self.source = ''
224 226 self.code = None
225 227 self._is_complete = False
228 self._is_invalid = False
226 229 self._full_dedent = False
227 230
228 231 def source_reset(self):
@@ -232,6 +235,42 b' class InputSplitter(object):'
232 235 self.reset()
233 236 return out
234 237
238 def check_complete(self, source):
239 """Return whether a block of code is ready to execute, or should be continued
240
241 This is a non-stateful API, and will reset the state of this InputSplitter.
242
243 Parameters
244 ----------
245 source : string
246 Python input code, which can be multiline.
247
248 Returns
249 -------
250 status : str
251 One of 'complete', 'incomplete', or 'invalid' if source is not a
252 prefix of valid code.
253 indent_spaces : int or None
254 The number of spaces by which to indent the next line of code. If
255 status is not 'incomplete', this is None.
256 """
257 self.reset()
258 try:
259 self.push(source)
260 except SyntaxError:
261 # Transformers in IPythonInputSplitter can raise SyntaxError,
262 # which push() will not catch.
263 return 'invalid', None
264 else:
265 if self._is_invalid:
266 return 'invalid', None
267 elif self.push_accepts_more():
268 return 'incomplete', self.indent_spaces
269 else:
270 return 'complete', None
271 finally:
272 self.reset()
273
235 274 def push(self, lines):
236 275 """Push one or more lines of input.
237 276
@@ -261,6 +300,7 b' class InputSplitter(object):'
261 300 # exception is raised in compilation, we don't mislead by having
262 301 # inconsistent code/source attributes.
263 302 self.code, self._is_complete = None, None
303 self._is_invalid = False
264 304
265 305 # Honor termination lines properly
266 306 if source.endswith('\\\n'):
@@ -277,6 +317,7 b' class InputSplitter(object):'
277 317 except (SyntaxError, OverflowError, ValueError, TypeError,
278 318 MemoryError):
279 319 self._is_complete = True
320 self._is_invalid = True
280 321 else:
281 322 # Compilation didn't produce any exceptions (though it may not have
282 323 # given a complete code object)
@@ -342,6 +342,13 b' class InputSplitterTestCase(unittest.TestCase):'
342 342 isp.push(r"(1 \ ")
343 343 self.assertFalse(isp.push_accepts_more())
344 344
345 def test_check_complete(self):
346 isp = self.isp
347 self.assertEqual(isp.check_complete("a = 1"), ('complete', None))
348 self.assertEqual(isp.check_complete("for a in range(5):"), ('incomplete', 4))
349 self.assertEqual(isp.check_complete("raise = 2"), ('invalid', None))
350 self.assertEqual(isp.check_complete("a = [1,\n2,"), ('incomplete', 0))
351
345 352 class InteractiveLoopTestCase(unittest.TestCase):
346 353 """Tests for an interactive loop like a python shell.
347 354 """
@@ -194,6 +194,7 b' class ShellChannel(ZMQSocketChannel):'
194 194 'history',
195 195 'kernel_info',
196 196 'shutdown',
197 'is_complete',
197 198 ]
198 199
199 200 def __init__(self, context, session, address):
@@ -396,6 +397,10 b' class ShellChannel(ZMQSocketChannel):'
396 397 self._queue_send(msg)
397 398 return msg['header']['msg_id']
398 399
400 def is_complete(self, code):
401 msg = self.session.msg('is_complete_request', {'code': code})
402 self._queue_send(msg)
403 return msg['header']['msg_id']
399 404
400 405
401 406 class IOPubChannel(ZMQSocketChannel):
@@ -205,3 +205,20 b' def test_help_output():'
205 205 """ipython kernel --help-all works"""
206 206 tt.help_all_output_test('kernel')
207 207
208 def test_is_complete():
209 with kernel() as kc:
210 # There are more test cases for this in core - here we just check
211 # that the kernel exposes the interface correctly.
212 kc.is_complete('2+2')
213 reply = kc.get_shell_msg(block=True, timeout=TIMEOUT)
214 assert reply['content']['status'] == 'complete'
215
216 # SyntaxError should mean it's complete
217 kc.is_complete('raise = 2')
218 reply = kc.get_shell_msg(block=True, timeout=TIMEOUT)
219 assert reply['content']['status'] == 'invalid'
220
221 kc.is_complete('a = [1,\n2,')
222 reply = kc.get_shell_msg(block=True, timeout=TIMEOUT)
223 assert reply['content']['status'] == 'incomplete'
224 assert reply['content']['indent'] == '' No newline at end of file
@@ -160,6 +160,18 b' class KernelInfoReply(Reference):'
160 160 banner = Unicode()
161 161
162 162
163 class IsCompleteReply(Reference):
164 status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'))
165
166 def check(self, d):
167 Reference.check(self, d)
168 if d['status'] == 'incomplete':
169 IsCompleteReplyIncomplete().check(d)
170
171 class IsCompleteReplyIncomplete(Reference):
172 indent = Unicode()
173
174
163 175 # IOPub messages
164 176
165 177 class ExecuteInput(Reference):
@@ -189,6 +201,7 b' references = {'
189 201 'status' : Status(),
190 202 'complete_reply' : CompleteReply(),
191 203 'kernel_info_reply': KernelInfoReply(),
204 'is_complete_reply': IsCompleteReply(),
192 205 'execute_input' : ExecuteInput(),
193 206 'execute_result' : ExecuteResult(),
194 207 'error' : Error(),
@@ -382,6 +395,12 b' def test_single_payload():'
382 395 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
383 396 nt.assert_equal(len(next_input_pls), 1)
384 397
398 def test_is_complete():
399 flush_channels()
400
401 msg_id = KC.is_complete("a = 1")
402 reply = KC.get_shell_msg(timeout=TIMEOUT)
403 validate_message(reply, 'is_complete_reply', msg_id)
385 404
386 405 # IOPub channel
387 406
@@ -232,6 +232,13 b' class IPythonKernel(KernelBase):'
232 232 self.shell.exit_now = True
233 233 return dict(status='ok', restart=restart)
234 234
235 def do_is_complete(self, code):
236 status, indent_spaces = self.shell.input_transformer_manager.check_complete(code)
237 r = {'status': status}
238 if status == 'incomplete':
239 r['indent'] = ' ' * indent_spaces
240 return r
241
235 242 def do_apply(self, content, bufs, msg_id, reply_metadata):
236 243 shell = self.shell
237 244 try:
@@ -114,7 +114,7 b' class Kernel(SingletonConfigurable):'
114 114 'inspect_request', 'history_request',
115 115 'kernel_info_request',
116 116 'connect_request', 'shutdown_request',
117 'apply_request',
117 'apply_request', 'is_complete_request',
118 118 ]
119 119 self.shell_handlers = {}
120 120 for msg_type in msg_types:
@@ -479,6 +479,22 b' class Kernel(SingletonConfigurable):'
479 479 kernel.
480 480 """
481 481 return {'status': 'ok', 'restart': restart}
482
483 def is_complete_request(self, stream, ident, parent):
484 content = parent['content']
485 code = content['code']
486
487 reply_content = self.do_is_complete(code)
488 reply_content = json_clean(reply_content)
489 reply_msg = self.session.send(stream, 'is_complete_reply',
490 reply_content, parent, ident)
491 self.log.debug("%s", reply_msg)
492
493 def do_is_complete(self, code):
494 """Override in subclasses to find completions.
495 """
496 return {'status' : 'unknown',
497 }
482 498
483 499 #---------------------------------------------------------------------------
484 500 # Engine methods
@@ -573,6 +573,51 b' Message type: ``history_reply``::'
573 573 'history' : list,
574 574 }
575 575
576 .. _msging_is_complete:
577
578 Code completeness
579 -----------------
580
581 .. versionadded:: 5.0
582
583 When the user enters a line in a console style interface, the console must
584 decide whether to immediately execute the current code, or whether to show a
585 continuation prompt for further input. For instance, in Python ``a = 5`` would
586 be executed immediately, while ``for i in range(5):`` would expect further input.
587
588 There are four possible replies:
589
590 - *complete* code is ready to be executed
591 - *incomplete* code should prompt for another line
592 - *invalid* code will typically be sent for execution, so that the user sees the
593 error soonest.
594 - *unknown* - if the kernel is not able to determine this. The frontend should
595 also handle the kernel not replying promptly. It may default to sending the
596 code for execution, or it may implement simple fallback heuristics for whether
597 to execute the code (e.g. execute after a blank line).
598
599 Frontends may have ways to override this, forcing the code to be sent for
600 execution or forcing a continuation prompt.
601
602 Message type: ``is_complete_request``::
603
604 content = {
605 # The code entered so far as a multiline string
606 'code' : str,
607 }
608
609 Message type: ``is_complete_reply``::
610
611 content = {
612 # One of 'complete', 'incomplete', 'invalid', 'unknown'
613 'status' : str,
614
615 # If status is 'incomplete', indent should contain the characters to use
616 # to indent the next line. This is only a hint: frontends may ignore it
617 # and use their own autoindentation rules. For other statuses, this
618 # field does not exist.
619 'indent': str,
620 }
576 621
577 622 Connect
578 623 -------
@@ -141,6 +141,17 b' relevant section of the :doc:`messaging spec <messaging>`.'
141 141
142 142 :ref:`msging_history` messages
143 143
144 .. method:: do_is_complete(code)
145
146 Is code entered in a console-like interface complete and ready to execute,
147 or should a continuation prompt be shown?
148
149 :param str code: The code entered so far - possibly multiple lines
150
151 .. seealso::
152
153 :ref:`msging_is_complete` messages
154
144 155 .. method:: do_shutdown(restart)
145 156
146 157 Shutdown the kernel. You only need to handle your own clean up - the kernel
General Comments 0
You need to be logged in to leave comments. Login now