##// 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 _full_dedent = False
208 _full_dedent = False
209 # Boolean indicating whether the current block is complete
209 # Boolean indicating whether the current block is complete
210 _is_complete = None
210 _is_complete = None
211 # Boolean indicating whether the current block has an unrecoverable syntax error
212 _is_invalid = False
211
213
212 def __init__(self):
214 def __init__(self):
213 """Create a new InputSplitter instance.
215 """Create a new InputSplitter instance.
@@ -223,6 +225,7 b' class InputSplitter(object):'
223 self.source = ''
225 self.source = ''
224 self.code = None
226 self.code = None
225 self._is_complete = False
227 self._is_complete = False
228 self._is_invalid = False
226 self._full_dedent = False
229 self._full_dedent = False
227
230
228 def source_reset(self):
231 def source_reset(self):
@@ -232,6 +235,42 b' class InputSplitter(object):'
232 self.reset()
235 self.reset()
233 return out
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 def push(self, lines):
274 def push(self, lines):
236 """Push one or more lines of input.
275 """Push one or more lines of input.
237
276
@@ -261,6 +300,7 b' class InputSplitter(object):'
261 # exception is raised in compilation, we don't mislead by having
300 # exception is raised in compilation, we don't mislead by having
262 # inconsistent code/source attributes.
301 # inconsistent code/source attributes.
263 self.code, self._is_complete = None, None
302 self.code, self._is_complete = None, None
303 self._is_invalid = False
264
304
265 # Honor termination lines properly
305 # Honor termination lines properly
266 if source.endswith('\\\n'):
306 if source.endswith('\\\n'):
@@ -277,6 +317,7 b' class InputSplitter(object):'
277 except (SyntaxError, OverflowError, ValueError, TypeError,
317 except (SyntaxError, OverflowError, ValueError, TypeError,
278 MemoryError):
318 MemoryError):
279 self._is_complete = True
319 self._is_complete = True
320 self._is_invalid = True
280 else:
321 else:
281 # Compilation didn't produce any exceptions (though it may not have
322 # Compilation didn't produce any exceptions (though it may not have
282 # given a complete code object)
323 # given a complete code object)
@@ -342,6 +342,13 b' class InputSplitterTestCase(unittest.TestCase):'
342 isp.push(r"(1 \ ")
342 isp.push(r"(1 \ ")
343 self.assertFalse(isp.push_accepts_more())
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 class InteractiveLoopTestCase(unittest.TestCase):
352 class InteractiveLoopTestCase(unittest.TestCase):
346 """Tests for an interactive loop like a python shell.
353 """Tests for an interactive loop like a python shell.
347 """
354 """
@@ -194,6 +194,7 b' class ShellChannel(ZMQSocketChannel):'
194 'history',
194 'history',
195 'kernel_info',
195 'kernel_info',
196 'shutdown',
196 'shutdown',
197 'is_complete',
197 ]
198 ]
198
199
199 def __init__(self, context, session, address):
200 def __init__(self, context, session, address):
@@ -396,6 +397,10 b' class ShellChannel(ZMQSocketChannel):'
396 self._queue_send(msg)
397 self._queue_send(msg)
397 return msg['header']['msg_id']
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 class IOPubChannel(ZMQSocketChannel):
406 class IOPubChannel(ZMQSocketChannel):
@@ -205,3 +205,20 b' def test_help_output():'
205 """ipython kernel --help-all works"""
205 """ipython kernel --help-all works"""
206 tt.help_all_output_test('kernel')
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 banner = Unicode()
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 # IOPub messages
175 # IOPub messages
164
176
165 class ExecuteInput(Reference):
177 class ExecuteInput(Reference):
@@ -189,6 +201,7 b' references = {'
189 'status' : Status(),
201 'status' : Status(),
190 'complete_reply' : CompleteReply(),
202 'complete_reply' : CompleteReply(),
191 'kernel_info_reply': KernelInfoReply(),
203 'kernel_info_reply': KernelInfoReply(),
204 'is_complete_reply': IsCompleteReply(),
192 'execute_input' : ExecuteInput(),
205 'execute_input' : ExecuteInput(),
193 'execute_result' : ExecuteResult(),
206 'execute_result' : ExecuteResult(),
194 'error' : Error(),
207 'error' : Error(),
@@ -382,6 +395,12 b' def test_single_payload():'
382 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
395 next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
383 nt.assert_equal(len(next_input_pls), 1)
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 # IOPub channel
405 # IOPub channel
387
406
@@ -232,6 +232,13 b' class IPythonKernel(KernelBase):'
232 self.shell.exit_now = True
232 self.shell.exit_now = True
233 return dict(status='ok', restart=restart)
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 def do_apply(self, content, bufs, msg_id, reply_metadata):
242 def do_apply(self, content, bufs, msg_id, reply_metadata):
236 shell = self.shell
243 shell = self.shell
237 try:
244 try:
@@ -114,7 +114,7 b' class Kernel(SingletonConfigurable):'
114 'inspect_request', 'history_request',
114 'inspect_request', 'history_request',
115 'kernel_info_request',
115 'kernel_info_request',
116 'connect_request', 'shutdown_request',
116 'connect_request', 'shutdown_request',
117 'apply_request',
117 'apply_request', 'is_complete_request',
118 ]
118 ]
119 self.shell_handlers = {}
119 self.shell_handlers = {}
120 for msg_type in msg_types:
120 for msg_type in msg_types:
@@ -480,6 +480,22 b' class Kernel(SingletonConfigurable):'
480 """
480 """
481 return {'status': 'ok', 'restart': restart}
481 return {'status': 'ok', 'restart': restart}
482
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 }
498
483 #---------------------------------------------------------------------------
499 #---------------------------------------------------------------------------
484 # Engine methods
500 # Engine methods
485 #---------------------------------------------------------------------------
501 #---------------------------------------------------------------------------
@@ -573,6 +573,51 b' Message type: ``history_reply``::'
573 'history' : list,
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 Connect
622 Connect
578 -------
623 -------
@@ -141,6 +141,17 b' relevant section of the :doc:`messaging spec <messaging>`.'
141
141
142 :ref:`msging_history` messages
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 .. method:: do_shutdown(restart)
155 .. method:: do_shutdown(restart)
145
156
146 Shutdown the kernel. You only need to handle your own clean up - the kernel
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