##// END OF EJS Templates
update message spec adapter per review...
MinRK -
Show More
@@ -1,211 +1,218 b''
1 """Tornado handlers for kernels."""
1 """Tornado handlers for kernels."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import logging
6 import logging
7 from tornado import web
7 from tornado import web
8
8
9 from zmq.utils import jsonapi
9 from zmq.utils import jsonapi
10
10
11 from IPython.utils.jsonutil import date_default
11 from IPython.utils.jsonutil import date_default
12 from IPython.utils.py3compat import string_types
12 from IPython.utils.py3compat import string_types
13 from IPython.html.utils import url_path_join, url_escape
13 from IPython.html.utils import url_path_join, url_escape
14
14
15 from ...base.handlers import IPythonHandler, json_errors
15 from ...base.handlers import IPythonHandler, json_errors
16 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
16 from ...base.zmqhandlers import AuthenticatedZMQStreamHandler
17
17
18 from IPython.core.release import kernel_protocol_version
18 from IPython.core.release import kernel_protocol_version
19
19
20 class MainKernelHandler(IPythonHandler):
20 class MainKernelHandler(IPythonHandler):
21
21
22 @web.authenticated
22 @web.authenticated
23 @json_errors
23 @json_errors
24 def get(self):
24 def get(self):
25 km = self.kernel_manager
25 km = self.kernel_manager
26 self.finish(jsonapi.dumps(km.list_kernels()))
26 self.finish(jsonapi.dumps(km.list_kernels()))
27
27
28 @web.authenticated
28 @web.authenticated
29 @json_errors
29 @json_errors
30 def post(self):
30 def post(self):
31 km = self.kernel_manager
31 km = self.kernel_manager
32 kernel_id = km.start_kernel()
32 kernel_id = km.start_kernel()
33 model = km.kernel_model(kernel_id)
33 model = km.kernel_model(kernel_id)
34 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
34 location = url_path_join(self.base_url, 'api', 'kernels', kernel_id)
35 self.set_header('Location', url_escape(location))
35 self.set_header('Location', url_escape(location))
36 self.set_status(201)
36 self.set_status(201)
37 self.finish(jsonapi.dumps(model))
37 self.finish(jsonapi.dumps(model))
38
38
39
39
40 class KernelHandler(IPythonHandler):
40 class KernelHandler(IPythonHandler):
41
41
42 SUPPORTED_METHODS = ('DELETE', 'GET')
42 SUPPORTED_METHODS = ('DELETE', 'GET')
43
43
44 @web.authenticated
44 @web.authenticated
45 @json_errors
45 @json_errors
46 def get(self, kernel_id):
46 def get(self, kernel_id):
47 km = self.kernel_manager
47 km = self.kernel_manager
48 km._check_kernel_id(kernel_id)
48 km._check_kernel_id(kernel_id)
49 model = km.kernel_model(kernel_id)
49 model = km.kernel_model(kernel_id)
50 self.finish(jsonapi.dumps(model))
50 self.finish(jsonapi.dumps(model))
51
51
52 @web.authenticated
52 @web.authenticated
53 @json_errors
53 @json_errors
54 def delete(self, kernel_id):
54 def delete(self, kernel_id):
55 km = self.kernel_manager
55 km = self.kernel_manager
56 km.shutdown_kernel(kernel_id)
56 km.shutdown_kernel(kernel_id)
57 self.set_status(204)
57 self.set_status(204)
58 self.finish()
58 self.finish()
59
59
60
60
61 class KernelActionHandler(IPythonHandler):
61 class KernelActionHandler(IPythonHandler):
62
62
63 @web.authenticated
63 @web.authenticated
64 @json_errors
64 @json_errors
65 def post(self, kernel_id, action):
65 def post(self, kernel_id, action):
66 km = self.kernel_manager
66 km = self.kernel_manager
67 if action == 'interrupt':
67 if action == 'interrupt':
68 km.interrupt_kernel(kernel_id)
68 km.interrupt_kernel(kernel_id)
69 self.set_status(204)
69 self.set_status(204)
70 if action == 'restart':
70 if action == 'restart':
71 km.restart_kernel(kernel_id)
71 km.restart_kernel(kernel_id)
72 model = km.kernel_model(kernel_id)
72 model = km.kernel_model(kernel_id)
73 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_url, kernel_id))
73 self.set_header('Location', '{0}api/kernels/{1}'.format(self.base_url, kernel_id))
74 self.write(jsonapi.dumps(model))
74 self.write(jsonapi.dumps(model))
75 self.finish()
75 self.finish()
76
76
77
77
78 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
78 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
79
79
80 def create_stream(self):
80 def create_stream(self):
81 km = self.kernel_manager
81 km = self.kernel_manager
82 meth = getattr(km, 'connect_%s' % self.channel)
82 meth = getattr(km, 'connect_%s' % self.channel)
83 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
83 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
84 # Create a kernel_info channel to query the kernel protocol version.
85 # This channel will be closed after the kernel_info reply is received.
84 self.kernel_info_channel = None
86 self.kernel_info_channel = None
85 self.kernel_info_channel = km.connect_shell(self.kernel_id)
87 self.kernel_info_channel = km.connect_shell(self.kernel_id)
86 self.kernel_info_channel.on_recv(self._handle_kernel_info)
88 self.kernel_info_channel.on_recv(self._handle_kernel_info_reply)
87 self._request_kernel_info()
89 self._request_kernel_info()
88
90
89 def _request_kernel_info(self):
91 def _request_kernel_info(self):
92 """send a request for kernel_info"""
90 self.log.debug("requesting kernel info")
93 self.log.debug("requesting kernel info")
91 self.session.send(self.kernel_info_channel, "kernel_info_request")
94 self.session.send(self.kernel_info_channel, "kernel_info_request")
92
95
93 def _handle_kernel_info(self, msg):
96 def _handle_kernel_info_reply(self, msg):
97 """process the kernel_info_reply
98
99 enabling msg spec adaptation, if necessary
100 """
94 idents,msg = self.session.feed_identities(msg)
101 idents,msg = self.session.feed_identities(msg)
95 try:
102 try:
96 msg = self.session.unserialize(msg)
103 msg = self.session.unserialize(msg)
97 except:
104 except:
98 self.log.error("Bad kernel_info reply", exc_info=True)
105 self.log.error("Bad kernel_info reply", exc_info=True)
99 self._request_kernel_info()
106 self._request_kernel_info()
100 return
107 return
101 else:
108 else:
102 if msg['msg_type'] != 'kernel_info_reply' or 'protocol_version' not in msg['content']:
109 if msg['msg_type'] != 'kernel_info_reply' or 'protocol_version' not in msg['content']:
103 self.log.error("Kernel info request failed, assuming current %s", msg['content'])
110 self.log.error("Kernel info request failed, assuming current %s", msg['content'])
104 else:
111 else:
105 protocol_version = msg['content']['protocol_version']
112 protocol_version = msg['content']['protocol_version']
106 if protocol_version != kernel_protocol_version:
113 if protocol_version != kernel_protocol_version:
107 self.session.adapt_version = int(protocol_version.split('.')[0])
114 self.session.adapt_version = int(protocol_version.split('.')[0])
108 self.log.info("adapting kernel to %s" % protocol_version)
115 self.log.info("adapting kernel to %s" % protocol_version)
109 self.kernel_info_channel.close()
116 self.kernel_info_channel.close()
110 self.kernel_info_channel = None
117 self.kernel_info_channel = None
111
118
112
119
113 def initialize(self, *args, **kwargs):
120 def initialize(self, *args, **kwargs):
114 self.zmq_stream = None
121 self.zmq_stream = None
115
122
116 def on_first_message(self, msg):
123 def on_first_message(self, msg):
117 try:
124 try:
118 super(ZMQChannelHandler, self).on_first_message(msg)
125 super(ZMQChannelHandler, self).on_first_message(msg)
119 except web.HTTPError:
126 except web.HTTPError:
120 self.close()
127 self.close()
121 return
128 return
122 try:
129 try:
123 self.create_stream()
130 self.create_stream()
124 except web.HTTPError:
131 except web.HTTPError:
125 # WebSockets don't response to traditional error codes so we
132 # WebSockets don't response to traditional error codes so we
126 # close the connection.
133 # close the connection.
127 if not self.stream.closed():
134 if not self.stream.closed():
128 self.stream.close()
135 self.stream.close()
129 self.close()
136 self.close()
130 else:
137 else:
131 self.zmq_stream.on_recv(self._on_zmq_reply)
138 self.zmq_stream.on_recv(self._on_zmq_reply)
132
139
133 def on_message(self, msg):
140 def on_message(self, msg):
134 msg = jsonapi.loads(msg)
141 msg = jsonapi.loads(msg)
135 self.session.send(self.zmq_stream, msg)
142 self.session.send(self.zmq_stream, msg)
136
143
137 def on_close(self):
144 def on_close(self):
138 # This method can be called twice, once by self.kernel_died and once
145 # This method can be called twice, once by self.kernel_died and once
139 # from the WebSocket close event. If the WebSocket connection is
146 # from the WebSocket close event. If the WebSocket connection is
140 # closed before the ZMQ streams are setup, they could be None.
147 # closed before the ZMQ streams are setup, they could be None.
141 if self.zmq_stream is not None and not self.zmq_stream.closed():
148 if self.zmq_stream is not None and not self.zmq_stream.closed():
142 self.zmq_stream.on_recv(None)
149 self.zmq_stream.on_recv(None)
143 # close the socket directly, don't wait for the stream
150 # close the socket directly, don't wait for the stream
144 socket = self.zmq_stream.socket
151 socket = self.zmq_stream.socket
145 self.zmq_stream.close()
152 self.zmq_stream.close()
146 socket.close()
153 socket.close()
147
154
148
155
149 class IOPubHandler(ZMQChannelHandler):
156 class IOPubHandler(ZMQChannelHandler):
150 channel = 'iopub'
157 channel = 'iopub'
151
158
152 def create_stream(self):
159 def create_stream(self):
153 super(IOPubHandler, self).create_stream()
160 super(IOPubHandler, self).create_stream()
154 km = self.kernel_manager
161 km = self.kernel_manager
155 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
162 km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
156 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
163 km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
157
164
158 def on_close(self):
165 def on_close(self):
159 km = self.kernel_manager
166 km = self.kernel_manager
160 if self.kernel_id in km:
167 if self.kernel_id in km:
161 km.remove_restart_callback(
168 km.remove_restart_callback(
162 self.kernel_id, self.on_kernel_restarted,
169 self.kernel_id, self.on_kernel_restarted,
163 )
170 )
164 km.remove_restart_callback(
171 km.remove_restart_callback(
165 self.kernel_id, self.on_restart_failed, 'dead',
172 self.kernel_id, self.on_restart_failed, 'dead',
166 )
173 )
167 super(IOPubHandler, self).on_close()
174 super(IOPubHandler, self).on_close()
168
175
169 def _send_status_message(self, status):
176 def _send_status_message(self, status):
170 msg = self.session.msg("status",
177 msg = self.session.msg("status",
171 {'execution_state': status}
178 {'execution_state': status}
172 )
179 )
173 self.write_message(jsonapi.dumps(msg, default=date_default))
180 self.write_message(jsonapi.dumps(msg, default=date_default))
174
181
175 def on_kernel_restarted(self):
182 def on_kernel_restarted(self):
176 logging.warn("kernel %s restarted", self.kernel_id)
183 logging.warn("kernel %s restarted", self.kernel_id)
177 self._send_status_message('restarting')
184 self._send_status_message('restarting')
178
185
179 def on_restart_failed(self):
186 def on_restart_failed(self):
180 logging.error("kernel %s restarted failed!", self.kernel_id)
187 logging.error("kernel %s restarted failed!", self.kernel_id)
181 self._send_status_message('dead')
188 self._send_status_message('dead')
182
189
183 def on_message(self, msg):
190 def on_message(self, msg):
184 """IOPub messages make no sense"""
191 """IOPub messages make no sense"""
185 pass
192 pass
186
193
187
194
188 class ShellHandler(ZMQChannelHandler):
195 class ShellHandler(ZMQChannelHandler):
189 channel = 'shell'
196 channel = 'shell'
190
197
191
198
192 class StdinHandler(ZMQChannelHandler):
199 class StdinHandler(ZMQChannelHandler):
193 channel = 'stdin'
200 channel = 'stdin'
194
201
195
202
196 #-----------------------------------------------------------------------------
203 #-----------------------------------------------------------------------------
197 # URL to handler mappings
204 # URL to handler mappings
198 #-----------------------------------------------------------------------------
205 #-----------------------------------------------------------------------------
199
206
200
207
201 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
208 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
202 _kernel_action_regex = r"(?P<action>restart|interrupt)"
209 _kernel_action_regex = r"(?P<action>restart|interrupt)"
203
210
204 default_handlers = [
211 default_handlers = [
205 (r"/api/kernels", MainKernelHandler),
212 (r"/api/kernels", MainKernelHandler),
206 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
213 (r"/api/kernels/%s" % _kernel_id_regex, KernelHandler),
207 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
214 (r"/api/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
208 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
215 (r"/api/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
209 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
216 (r"/api/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
210 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
217 (r"/api/kernels/%s/stdin" % _kernel_id_regex, StdinHandler)
211 ]
218 ]
@@ -1,376 +1,385 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 // Completer
4 // Completer
5 //
5 //
6 // Completer is be a class that takes a cell instance.
6 // Completer is be a class that takes a cell instance.
7
7
8 var IPython = (function (IPython) {
8 var IPython = (function (IPython) {
9 // that will prevent us from misspelling
9 // that will prevent us from misspelling
10 "use strict";
10 "use strict";
11
11
12 // easier key mapping
12 // easier key mapping
13 var keycodes = IPython.keyboard.keycodes;
13 var keycodes = IPython.keyboard.keycodes;
14 var utils = IPython.utils;
14 var utils = IPython.utils;
15
15
16 var prepend_n_prc = function(str, n) {
16 var prepend_n_prc = function(str, n) {
17 for( var i =0 ; i< n ; i++){
17 for( var i =0 ; i< n ; i++){
18 str = '%'+str ;
18 str = '%'+str ;
19 }
19 }
20 return str;
20 return str;
21 };
21 };
22
22
23 var _existing_completion = function(item, completion_array){
23 var _existing_completion = function(item, completion_array){
24 for( var i=0; i < completion_array.length; i++) {
24 for( var i=0; i < completion_array.length; i++) {
25 if (completion_array[i].trim().substr(-item.length) == item) {
25 if (completion_array[i].trim().substr(-item.length) == item) {
26 return true;
26 return true;
27 }
27 }
28 }
28 }
29 return false;
29 return false;
30 };
30 };
31
31
32 // what is the common start of all completions
32 // what is the common start of all completions
33 function shared_start(B, drop_prct) {
33 function shared_start(B, drop_prct) {
34 if (B.length == 1) {
34 if (B.length == 1) {
35 return B[0];
35 return B[0];
36 }
36 }
37 var A = [];
37 var A = [];
38 var common;
38 var common;
39 var min_lead_prct = 10;
39 var min_lead_prct = 10;
40 for (var i = 0; i < B.length; i++) {
40 for (var i = 0; i < B.length; i++) {
41 var str = B[i].str;
41 var str = B[i].str;
42 var localmin = 0;
42 var localmin = 0;
43 if(drop_prct === true){
43 if(drop_prct === true){
44 while ( str.substr(0, 1) == '%') {
44 while ( str.substr(0, 1) == '%') {
45 localmin = localmin+1;
45 localmin = localmin+1;
46 str = str.substring(1);
46 str = str.substring(1);
47 }
47 }
48 }
48 }
49 min_lead_prct = Math.min(min_lead_prct, localmin);
49 min_lead_prct = Math.min(min_lead_prct, localmin);
50 A.push(str);
50 A.push(str);
51 }
51 }
52
52
53 if (A.length > 1) {
53 if (A.length > 1) {
54 var tem1, tem2, s;
54 var tem1, tem2, s;
55 A = A.slice(0).sort();
55 A = A.slice(0).sort();
56 tem1 = A[0];
56 tem1 = A[0];
57 s = tem1.length;
57 s = tem1.length;
58 tem2 = A.pop();
58 tem2 = A.pop();
59 while (s && tem2.indexOf(tem1) == -1) {
59 while (s && tem2.indexOf(tem1) == -1) {
60 tem1 = tem1.substring(0, --s);
60 tem1 = tem1.substring(0, --s);
61 }
61 }
62 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
62 if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
63 return {
63 return {
64 str:prepend_n_prc('', min_lead_prct),
64 str:prepend_n_prc('', min_lead_prct),
65 type: "computed",
65 type: "computed",
66 from: B[0].from,
66 from: B[0].from,
67 to: B[0].to
67 to: B[0].to
68 };
68 };
69 }
69 }
70 return {
70 return {
71 str: prepend_n_prc(tem1, min_lead_prct),
71 str: prepend_n_prc(tem1, min_lead_prct),
72 type: "computed",
72 type: "computed",
73 from: B[0].from,
73 from: B[0].from,
74 to: B[0].to
74 to: B[0].to
75 };
75 };
76 }
76 }
77 return null;
77 return null;
78 }
78 }
79
79
80
80
81 var Completer = function (cell) {
81 var Completer = function (cell) {
82 this.cell = cell;
82 this.cell = cell;
83 this.editor = cell.code_mirror;
83 this.editor = cell.code_mirror;
84 var that = this;
84 var that = this;
85 $([IPython.events]).on('status_busy.Kernel', function () {
85 $([IPython.events]).on('status_busy.Kernel', function () {
86 that.skip_kernel_completion = true;
86 that.skip_kernel_completion = true;
87 });
87 });
88 $([IPython.events]).on('status_idle.Kernel', function () {
88 $([IPython.events]).on('status_idle.Kernel', function () {
89 that.skip_kernel_completion = false;
89 that.skip_kernel_completion = false;
90 });
90 });
91 };
91 };
92
92
93 Completer.prototype.startCompletion = function () {
93 Completer.prototype.startCompletion = function () {
94 // call for a 'first' completion, that will set the editor and do some
94 // call for a 'first' completion, that will set the editor and do some
95 // special behavior like autopicking if only one completion available.
95 // special behavior like autopicking if only one completion available.
96 if (this.editor.somethingSelected()) return;
96 if (this.editor.somethingSelected()) return;
97 this.done = false;
97 this.done = false;
98 // use to get focus back on opera
98 // use to get focus back on opera
99 this.carry_on_completion(true);
99 this.carry_on_completion(true);
100 };
100 };
101
101
102
102
103 // easy access for julia to monkeypatch
103 // easy access for julia to monkeypatch
104 //
104 //
105 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
105 Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
106
106
107 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
107 Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
108 return Completer.reinvoke_re.test(pre_cursor);
108 return Completer.reinvoke_re.test(pre_cursor);
109 };
109 };
110
110
111 /**
111 /**
112 *
112 *
113 * pass true as parameter if this is the first invocation of the completer
113 * pass true as parameter if this is the first invocation of the completer
114 * this will prevent the completer to dissmiss itself if it is not on a
114 * this will prevent the completer to dissmiss itself if it is not on a
115 * word boundary like pressing tab after a space, and make it autopick the
115 * word boundary like pressing tab after a space, and make it autopick the
116 * only choice if there is only one which prevent from popping the UI. as
116 * only choice if there is only one which prevent from popping the UI. as
117 * well as fast-forwarding the typing if all completion have a common
117 * well as fast-forwarding the typing if all completion have a common
118 * shared start
118 * shared start
119 **/
119 **/
120 Completer.prototype.carry_on_completion = function (first_invocation) {
120 Completer.prototype.carry_on_completion = function (first_invocation) {
121 // Pass true as parameter if you want the completer to autopick when
121 // Pass true as parameter if you want the completer to autopick when
122 // only one completion. This function is automatically reinvoked at
122 // only one completion. This function is automatically reinvoked at
123 // each keystroke with first_invocation = false
123 // each keystroke with first_invocation = false
124 var cur = this.editor.getCursor();
124 var cur = this.editor.getCursor();
125 var line = this.editor.getLine(cur.line);
125 var line = this.editor.getLine(cur.line);
126 var pre_cursor = this.editor.getRange({
126 var pre_cursor = this.editor.getRange({
127 line: cur.line,
127 line: cur.line,
128 ch: cur.ch - 1
128 ch: cur.ch - 1
129 }, cur);
129 }, cur);
130
130
131 // we need to check that we are still on a word boundary
131 // we need to check that we are still on a word boundary
132 // because while typing the completer is still reinvoking itself
132 // because while typing the completer is still reinvoking itself
133 // so dismiss if we are on a "bad" caracter
133 // so dismiss if we are on a "bad" caracter
134 if (!this.reinvoke(pre_cursor) && !first_invocation) {
134 if (!this.reinvoke(pre_cursor) && !first_invocation) {
135 this.close();
135 this.close();
136 return;
136 return;
137 }
137 }
138
138
139 this.autopick = false;
139 this.autopick = false;
140 if (first_invocation) {
140 if (first_invocation) {
141 this.autopick = true;
141 this.autopick = true;
142 }
142 }
143
143
144 // We want a single cursor position.
144 // We want a single cursor position.
145 if (this.editor.somethingSelected()) {
145 if (this.editor.somethingSelected()) {
146 return;
146 return;
147 }
147 }
148
148
149 // one kernel completion came back, finish_completing will be called with the results
149 // one kernel completion came back, finish_completing will be called with the results
150 // we fork here and directly call finish completing if kernel is busy
150 // we fork here and directly call finish completing if kernel is busy
151 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
151 var cursor_pos = utils.to_absolute_cursor_pos(this.editor, cur);
152 if (this.skip_kernel_completion) {
152 if (this.skip_kernel_completion) {
153 this.finish_completing({ content: {
153 this.finish_completing({ content: {
154 matches: [],
154 matches: [],
155 cursor_start: cursor_pos,
155 cursor_start: cursor_pos,
156 cursor_end: cursor_pos,
156 cursor_end: cursor_pos,
157 }});
157 }});
158 } else {
158 } else {
159 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
159 this.cell.kernel.complete(this.editor.getValue(), cursor_pos,
160 $.proxy(this.finish_completing, this)
160 $.proxy(this.finish_completing, this)
161 );
161 );
162 }
162 }
163 };
163 };
164
164
165 Completer.prototype.finish_completing = function (msg) {
165 Completer.prototype.finish_completing = function (msg) {
166 // let's build a function that wrap all that stuff into what is needed
166 // let's build a function that wrap all that stuff into what is needed
167 // for the new completer:
167 // for the new completer:
168 var content = msg.content;
168 var content = msg.content;
169 var start = content.cursor_start;
169 var start = content.cursor_start;
170 var end = content.cursor_end;
170 var end = content.cursor_end;
171 var matches = content.matches;
171 var matches = content.matches;
172
172
173 var cur = this.editor.getCursor();
173 var cur = this.editor.getCursor();
174 if (end === null) {
175 // adapted message spec replies don't have cursor position info,
176 // interpret end=null as current position,
177 // and negative start relative to that
178 end = utils.to_absolute_cursor_pos(this.editor, cur);
179 if (start < 0) {
180 start = end + start;
181 }
182 }
174 var results = CodeMirror.contextHint(this.editor);
183 var results = CodeMirror.contextHint(this.editor);
175 var filtered_results = [];
184 var filtered_results = [];
176 //remove results from context completion
185 //remove results from context completion
177 //that are already in kernel completion
186 //that are already in kernel completion
178 var i;
187 var i;
179 for (i=0; i < results.length; i++) {
188 for (i=0; i < results.length; i++) {
180 if (!_existing_completion(results[i].str, matches)) {
189 if (!_existing_completion(results[i].str, matches)) {
181 filtered_results.push(results[i]);
190 filtered_results.push(results[i]);
182 }
191 }
183 }
192 }
184
193
185 // append the introspection result, in order, at at the beginning of
194 // append the introspection result, in order, at at the beginning of
186 // the table and compute the replacement range from current cursor
195 // the table and compute the replacement range from current cursor
187 // positon and matched_text length.
196 // positon and matched_text length.
188 for (i = matches.length - 1; i >= 0; --i) {
197 for (i = matches.length - 1; i >= 0; --i) {
189 filtered_results.unshift({
198 filtered_results.unshift({
190 str: matches[i],
199 str: matches[i],
191 type: "introspection",
200 type: "introspection",
192 from: utils.from_absolute_cursor_pos(this.editor, start),
201 from: utils.from_absolute_cursor_pos(this.editor, start),
193 to: utils.from_absolute_cursor_pos(this.editor, end)
202 to: utils.from_absolute_cursor_pos(this.editor, end)
194 });
203 });
195 }
204 }
196
205
197 // one the 2 sources results have been merge, deal with it
206 // one the 2 sources results have been merge, deal with it
198 this.raw_result = filtered_results;
207 this.raw_result = filtered_results;
199
208
200 // if empty result return
209 // if empty result return
201 if (!this.raw_result || !this.raw_result.length) return;
210 if (!this.raw_result || !this.raw_result.length) return;
202
211
203 // When there is only one completion, use it directly.
212 // When there is only one completion, use it directly.
204 if (this.autopick && this.raw_result.length == 1) {
213 if (this.autopick && this.raw_result.length == 1) {
205 this.insert(this.raw_result[0]);
214 this.insert(this.raw_result[0]);
206 return;
215 return;
207 }
216 }
208
217
209 if (this.raw_result.length == 1) {
218 if (this.raw_result.length == 1) {
210 // test if first and only completion totally matches
219 // test if first and only completion totally matches
211 // what is typed, in this case dismiss
220 // what is typed, in this case dismiss
212 var str = this.raw_result[0].str;
221 var str = this.raw_result[0].str;
213 var pre_cursor = this.editor.getRange({
222 var pre_cursor = this.editor.getRange({
214 line: cur.line,
223 line: cur.line,
215 ch: cur.ch - str.length
224 ch: cur.ch - str.length
216 }, cur);
225 }, cur);
217 if (pre_cursor == str) {
226 if (pre_cursor == str) {
218 this.close();
227 this.close();
219 return;
228 return;
220 }
229 }
221 }
230 }
222
231
223 if (!this.visible) {
232 if (!this.visible) {
224 this.complete = $('<div/>').addClass('completions');
233 this.complete = $('<div/>').addClass('completions');
225 this.complete.attr('id', 'complete');
234 this.complete.attr('id', 'complete');
226
235
227 // Currently webkit doesn't use the size attr correctly. See:
236 // Currently webkit doesn't use the size attr correctly. See:
228 // https://code.google.com/p/chromium/issues/detail?id=4579
237 // https://code.google.com/p/chromium/issues/detail?id=4579
229 this.sel = $('<select/>')
238 this.sel = $('<select/>')
230 .attr('tabindex', -1)
239 .attr('tabindex', -1)
231 .attr('multiple', 'true');
240 .attr('multiple', 'true');
232 this.complete.append(this.sel);
241 this.complete.append(this.sel);
233 this.visible = true;
242 this.visible = true;
234 $('body').append(this.complete);
243 $('body').append(this.complete);
235
244
236 //build the container
245 //build the container
237 var that = this;
246 var that = this;
238 this.sel.dblclick(function () {
247 this.sel.dblclick(function () {
239 that.pick();
248 that.pick();
240 });
249 });
241 this.sel.focus(function () {
250 this.sel.focus(function () {
242 that.editor.focus();
251 that.editor.focus();
243 });
252 });
244 this._handle_keydown = function (cm, event) {
253 this._handle_keydown = function (cm, event) {
245 that.keydown(event);
254 that.keydown(event);
246 };
255 };
247 this.editor.on('keydown', this._handle_keydown);
256 this.editor.on('keydown', this._handle_keydown);
248 this._handle_keypress = function (cm, event) {
257 this._handle_keypress = function (cm, event) {
249 that.keypress(event);
258 that.keypress(event);
250 };
259 };
251 this.editor.on('keypress', this._handle_keypress);
260 this.editor.on('keypress', this._handle_keypress);
252 }
261 }
253 this.sel.attr('size', Math.min(10, this.raw_result.length));
262 this.sel.attr('size', Math.min(10, this.raw_result.length));
254
263
255 // After everything is on the page, compute the postion.
264 // After everything is on the page, compute the postion.
256 // We put it above the code if it is too close to the bottom of the page.
265 // We put it above the code if it is too close to the bottom of the page.
257 var pos = this.editor.cursorCoords(
266 var pos = this.editor.cursorCoords(
258 utils.from_absolute_cursor_pos(this.editor, start)
267 utils.from_absolute_cursor_pos(this.editor, start)
259 );
268 );
260 var left = pos.left-3;
269 var left = pos.left-3;
261 var top;
270 var top;
262 var cheight = this.complete.height();
271 var cheight = this.complete.height();
263 var wheight = $(window).height();
272 var wheight = $(window).height();
264 if (pos.bottom+cheight+5 > wheight) {
273 if (pos.bottom+cheight+5 > wheight) {
265 top = pos.top-cheight-4;
274 top = pos.top-cheight-4;
266 } else {
275 } else {
267 top = pos.bottom+1;
276 top = pos.bottom+1;
268 }
277 }
269 this.complete.css('left', left + 'px');
278 this.complete.css('left', left + 'px');
270 this.complete.css('top', top + 'px');
279 this.complete.css('top', top + 'px');
271
280
272 // Clear and fill the list.
281 // Clear and fill the list.
273 this.sel.text('');
282 this.sel.text('');
274 this.build_gui_list(this.raw_result);
283 this.build_gui_list(this.raw_result);
275 return true;
284 return true;
276 };
285 };
277
286
278 Completer.prototype.insert = function (completion) {
287 Completer.prototype.insert = function (completion) {
279 this.editor.replaceRange(completion.str, completion.from, completion.to);
288 this.editor.replaceRange(completion.str, completion.from, completion.to);
280 };
289 };
281
290
282 Completer.prototype.build_gui_list = function (completions) {
291 Completer.prototype.build_gui_list = function (completions) {
283 for (var i = 0; i < completions.length; ++i) {
292 for (var i = 0; i < completions.length; ++i) {
284 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
293 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
285 this.sel.append(opt);
294 this.sel.append(opt);
286 }
295 }
287 this.sel.children().first().attr('selected', 'true');
296 this.sel.children().first().attr('selected', 'true');
288 this.sel.scrollTop(0);
297 this.sel.scrollTop(0);
289 };
298 };
290
299
291 Completer.prototype.close = function () {
300 Completer.prototype.close = function () {
292 this.done = true;
301 this.done = true;
293 $('#complete').remove();
302 $('#complete').remove();
294 this.editor.off('keydown', this._handle_keydown);
303 this.editor.off('keydown', this._handle_keydown);
295 this.editor.off('keypress', this._handle_keypress);
304 this.editor.off('keypress', this._handle_keypress);
296 this.visible = false;
305 this.visible = false;
297 };
306 };
298
307
299 Completer.prototype.pick = function () {
308 Completer.prototype.pick = function () {
300 this.insert(this.raw_result[this.sel[0].selectedIndex]);
309 this.insert(this.raw_result[this.sel[0].selectedIndex]);
301 this.close();
310 this.close();
302 };
311 };
303
312
304 Completer.prototype.keydown = function (event) {
313 Completer.prototype.keydown = function (event) {
305 var code = event.keyCode;
314 var code = event.keyCode;
306 var that = this;
315 var that = this;
307
316
308 // Enter
317 // Enter
309 if (code == keycodes.enter) {
318 if (code == keycodes.enter) {
310 CodeMirror.e_stop(event);
319 CodeMirror.e_stop(event);
311 this.pick();
320 this.pick();
312 // Escape or backspace
321 // Escape or backspace
313 } else if (code == keycodes.esc || code == keycodes.backspace) {
322 } else if (code == keycodes.esc || code == keycodes.backspace) {
314 CodeMirror.e_stop(event);
323 CodeMirror.e_stop(event);
315 this.close();
324 this.close();
316 } else if (code == keycodes.tab) {
325 } else if (code == keycodes.tab) {
317 //all the fastforwarding operation,
326 //all the fastforwarding operation,
318 //Check that shared start is not null which can append with prefixed completion
327 //Check that shared start is not null which can append with prefixed completion
319 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
328 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
320 // to erase py
329 // to erase py
321 var sh = shared_start(this.raw_result, true);
330 var sh = shared_start(this.raw_result, true);
322 if (sh) {
331 if (sh) {
323 this.insert(sh);
332 this.insert(sh);
324 }
333 }
325 this.close();
334 this.close();
326 //reinvoke self
335 //reinvoke self
327 setTimeout(function () {
336 setTimeout(function () {
328 that.carry_on_completion();
337 that.carry_on_completion();
329 }, 50);
338 }, 50);
330 } else if (code == keycodes.up || code == keycodes.down) {
339 } else if (code == keycodes.up || code == keycodes.down) {
331 // need to do that to be able to move the arrow
340 // need to do that to be able to move the arrow
332 // when on the first or last line ofo a code cell
341 // when on the first or last line ofo a code cell
333 CodeMirror.e_stop(event);
342 CodeMirror.e_stop(event);
334
343
335 var options = this.sel.find('option');
344 var options = this.sel.find('option');
336 var index = this.sel[0].selectedIndex;
345 var index = this.sel[0].selectedIndex;
337 if (code == keycodes.up) {
346 if (code == keycodes.up) {
338 index--;
347 index--;
339 }
348 }
340 if (code == keycodes.down) {
349 if (code == keycodes.down) {
341 index++;
350 index++;
342 }
351 }
343 index = Math.min(Math.max(index, 0), options.length-1);
352 index = Math.min(Math.max(index, 0), options.length-1);
344 this.sel[0].selectedIndex = index;
353 this.sel[0].selectedIndex = index;
345 } else if (code == keycodes.left || code == keycodes.right) {
354 } else if (code == keycodes.left || code == keycodes.right) {
346 this.close();
355 this.close();
347 }
356 }
348 };
357 };
349
358
350 Completer.prototype.keypress = function (event) {
359 Completer.prototype.keypress = function (event) {
351 // FIXME: This is a band-aid.
360 // FIXME: This is a band-aid.
352 // on keypress, trigger insertion of a single character.
361 // on keypress, trigger insertion of a single character.
353 // This simulates the old behavior of completion as you type,
362 // This simulates the old behavior of completion as you type,
354 // before events were disconnected and CodeMirror stopped
363 // before events were disconnected and CodeMirror stopped
355 // receiving events while the completer is focused.
364 // receiving events while the completer is focused.
356
365
357 var that = this;
366 var that = this;
358 var code = event.keyCode;
367 var code = event.keyCode;
359
368
360 // don't handle keypress if it's not a character (arrows on FF)
369 // don't handle keypress if it's not a character (arrows on FF)
361 // or ENTER/TAB
370 // or ENTER/TAB
362 if (event.charCode === 0 ||
371 if (event.charCode === 0 ||
363 code == keycodes.tab ||
372 code == keycodes.tab ||
364 code == keycodes.enter
373 code == keycodes.enter
365 ) return;
374 ) return;
366
375
367 this.close();
376 this.close();
368 this.editor.focus();
377 this.editor.focus();
369 setTimeout(function () {
378 setTimeout(function () {
370 that.carry_on_completion();
379 that.carry_on_completion();
371 }, 50);
380 }, 50);
372 };
381 };
373 IPython.Completer = Completer;
382 IPython.Completer = Completer;
374
383
375 return IPython;
384 return IPython;
376 }(IPython));
385 }(IPython));
@@ -1,329 +1,336 b''
1 """Adapters for IPython msg spec versions."""
1 """Adapters for IPython msg spec versions."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import json
6 import json
7
7
8 from IPython.core.release import kernel_protocol_version, kernel_protocol_version_info
8 from IPython.core.release import kernel_protocol_version_info
9 from IPython.utils.tokenutil import token_at_cursor
9 from IPython.utils.tokenutil import token_at_cursor
10
10
11
11
12 def code_to_line(code, cursor_pos):
12 def code_to_line(code, cursor_pos):
13 """Turn a multiline code block and cursor position into a single line
13 """Turn a multiline code block and cursor position into a single line
14 and new cursor position.
14 and new cursor position.
15
15
16 For adapting complete_ and object_info_requests.
16 For adapting complete_ and object_info_requests.
17 """
17 """
18 for line in code.splitlines(True):
18 for line in code.splitlines(True):
19 n = len(line)
19 n = len(line)
20 if cursor_pos > n:
20 if cursor_pos > n:
21 cursor_pos -= n
21 cursor_pos -= n
22 else:
22 else:
23 break
23 break
24 return line, cursor_pos
24 return line, cursor_pos
25
25
26
26
27 class Adapter(object):
27 class Adapter(object):
28 """Base class for adapting messages
28 """Base class for adapting messages
29
29
30 Override message_type(msg) methods to create adapters.
30 Override message_type(msg) methods to create adapters.
31 """
31 """
32
32
33 msg_type_map = {}
33 msg_type_map = {}
34
34
35 def update_header(self, msg):
35 def update_header(self, msg):
36 return msg
36 return msg
37
37
38 def update_metadata(self, msg):
38 def update_metadata(self, msg):
39 return msg
39 return msg
40
40
41 def update_msg_type(self, msg):
41 def update_msg_type(self, msg):
42 header = msg['header']
42 header = msg['header']
43 msg_type = header['msg_type']
43 msg_type = header['msg_type']
44 if msg_type in self.msg_type_map:
44 if msg_type in self.msg_type_map:
45 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
45 msg['msg_type'] = header['msg_type'] = self.msg_type_map[msg_type]
46 return msg
46 return msg
47
47
48 def handle_reply_status_error(msg):
49 """This will be called *instead of* the regular handler
50
51 on any reply with status != ok
52 """
53 return msg
54
48 def __call__(self, msg):
55 def __call__(self, msg):
49 msg = self.update_header(msg)
56 msg = self.update_header(msg)
50 msg = self.update_metadata(msg)
57 msg = self.update_metadata(msg)
51 msg = self.update_msg_type(msg)
58 msg = self.update_msg_type(msg)
52 header = msg['header']
59 header = msg['header']
53
60
54 handler = getattr(self, header['msg_type'], None)
61 handler = getattr(self, header['msg_type'], None)
55 if handler is None:
62 if handler is None:
56 return msg
63 return msg
57 # no transform for status=error
64
65 # handle status=error replies separately (no change, at present)
58 if msg['content'].get('status', None) in {'error', 'aborted'}:
66 if msg['content'].get('status', None) in {'error', 'aborted'}:
59 return msg
67 return self.handle_reply_status_error(msg)
60 return handler(msg)
68 return handler(msg)
61
69
62 def _version_str_to_list(version):
70 def _version_str_to_list(version):
63 """convert a version string to a list of ints
71 """convert a version string to a list of ints
64
72
65 non-int segments are excluded
73 non-int segments are excluded
66 """
74 """
67 v = []
75 v = []
68 for part in version.split('.'):
76 for part in version.split('.'):
69 try:
77 try:
70 v.append(int(part))
78 v.append(int(part))
71 except ValueError:
79 except ValueError:
72 pass
80 pass
73 return v
81 return v
74
82
75 class V5toV4(Adapter):
83 class V5toV4(Adapter):
76 """Adapt msg protocol v5 to v4"""
84 """Adapt msg protocol v5 to v4"""
77
85
78 version = '4.1'
86 version = '4.1'
79
87
80 msg_type_map = {
88 msg_type_map = {
81 'execute_result' : 'pyout',
89 'execute_result' : 'pyout',
82 'execute_input' : 'pyin',
90 'execute_input' : 'pyin',
83 'error' : 'pyerr',
91 'error' : 'pyerr',
84 'inspect_request' : 'object_info_request',
92 'inspect_request' : 'object_info_request',
85 'inspect_reply' : 'object_info_reply',
93 'inspect_reply' : 'object_info_reply',
86 }
94 }
87
95
88 def update_header(self, msg):
96 def update_header(self, msg):
89 msg['header'].pop('version', None)
97 msg['header'].pop('version', None)
90 return msg
98 return msg
91
99
92 # shell channel
100 # shell channel
93
101
94 def kernel_info_reply(self, msg):
102 def kernel_info_reply(self, msg):
95 content = msg['content']
103 content = msg['content']
96 content.pop('banner', None)
104 content.pop('banner', None)
97 for key in ('language_version', 'protocol_version'):
105 for key in ('language_version', 'protocol_version'):
98 if key in content:
106 if key in content:
99 content[key] = _version_str_to_list(content[key])
107 content[key] = _version_str_to_list(content[key])
100 if content.pop('implementation', '') == 'ipython' \
108 if content.pop('implementation', '') == 'ipython' \
101 and 'implementation_version' in content:
109 and 'implementation_version' in content:
102 content['ipython_version'] = content.pop('implmentation_version')
110 content['ipython_version'] = content.pop('implmentation_version')
103 content.pop('implementation_version', None)
111 content.pop('implementation_version', None)
104 content.setdefault("implmentation", content['language'])
112 content.setdefault("implmentation", content['language'])
105 return msg
113 return msg
106
114
107 def execute_request(self, msg):
115 def execute_request(self, msg):
108 content = msg['content']
116 content = msg['content']
109 content.setdefault('user_variables', [])
117 content.setdefault('user_variables', [])
110 return msg
118 return msg
111
119
112 def execute_reply(self, msg):
120 def execute_reply(self, msg):
113 content = msg['content']
121 content = msg['content']
114 content.setdefault('user_variables', {})
122 content.setdefault('user_variables', {})
115 # TODO: handle payloads
123 # TODO: handle payloads
116 return msg
124 return msg
117
125
118 def complete_request(self, msg):
126 def complete_request(self, msg):
119 content = msg['content']
127 content = msg['content']
120 code = content['code']
128 code = content['code']
121 cursor_pos = content['cursor_pos']
129 cursor_pos = content['cursor_pos']
122 line, cursor_pos = code_to_line(code, cursor_pos)
130 line, cursor_pos = code_to_line(code, cursor_pos)
123
131
124 new_content = msg['content'] = {}
132 new_content = msg['content'] = {}
125 new_content['text'] = ''
133 new_content['text'] = ''
126 new_content['line'] = line
134 new_content['line'] = line
127 new_content['block'] = None
135 new_content['block'] = None
128 new_content['cursor_pos'] = cursor_pos
136 new_content['cursor_pos'] = cursor_pos
129 return msg
137 return msg
130
138
131 def complete_reply(self, msg):
139 def complete_reply(self, msg):
132 content = msg['content']
140 content = msg['content']
133 cursor_start = content.pop('cursor_start')
141 cursor_start = content.pop('cursor_start')
134 cursor_end = content.pop('cursor_end')
142 cursor_end = content.pop('cursor_end')
135 match_len = cursor_end - cursor_start
143 match_len = cursor_end - cursor_start
136 content['matched_text'] = content['matches'][0][:match_len]
144 content['matched_text'] = content['matches'][0][:match_len]
137 content.pop('metadata', None)
145 content.pop('metadata', None)
138 return msg
146 return msg
139
147
140 def object_info_request(self, msg):
148 def object_info_request(self, msg):
141 content = msg['content']
149 content = msg['content']
142 code = content['code']
150 code = content['code']
143 cursor_pos = content['cursor_pos']
151 cursor_pos = content['cursor_pos']
144 line, _ = code_to_line(code, cursor_pos)
152 line, _ = code_to_line(code, cursor_pos)
145
153
146 new_content = msg['content'] = {'status' : 'ok'}
154 new_content = msg['content'] = {}
147 new_content['oname'] = token_at_cursor(code, cursor_pos)
155 new_content['oname'] = token_at_cursor(code, cursor_pos)
148 new_content['detail_level'] = content['detail_level']
156 new_content['detail_level'] = content['detail_level']
149 return msg
157 return msg
150
158
151 def object_info_reply(self, msg):
159 def object_info_reply(self, msg):
152 """inspect_reply can't be easily backward compatible"""
160 """inspect_reply can't be easily backward compatible"""
153 msg['content'] = {'found' : False, 'name' : 'unknown'}
161 msg['content'] = {'found' : False, 'name' : 'unknown'}
154 return msg
162 return msg
155
163
156 # iopub channel
164 # iopub channel
157
165
158 def display_data(self, msg):
166 def display_data(self, msg):
159 content = msg['content']
167 content = msg['content']
160 content.setdefault("source", "display")
168 content.setdefault("source", "display")
161 data = content['data']
169 data = content['data']
162 if 'application/json' in data:
170 if 'application/json' in data:
163 try:
171 try:
164 data['application/json'] = json.dumps(data['application/json'])
172 data['application/json'] = json.dumps(data['application/json'])
165 except Exception:
173 except Exception:
166 # warn?
174 # warn?
167 pass
175 pass
168 return msg
176 return msg
169
177
170 # stdin channel
178 # stdin channel
171
179
172 def input_request(self, msg):
180 def input_request(self, msg):
173 msg['content'].pop('password', None)
181 msg['content'].pop('password', None)
174 return msg
182 return msg
175
183
176 def _tuple_to_str(version):
177 return ".".join(map(str, version))
178
184
179 class V4toV5(Adapter):
185 class V4toV5(Adapter):
180 """Convert msg spec V4 to V5"""
186 """Convert msg spec V4 to V5"""
181 version = kernel_protocol_version
187 version = '5.0'
182
188
183 # invert message renames above
189 # invert message renames above
184 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
190 msg_type_map = {v:k for k,v in V5toV4.msg_type_map.items()}
185
191
186 def update_header(self, msg):
192 def update_header(self, msg):
187 msg['header']['version'] = self.version
193 msg['header']['version'] = self.version
188 return msg
194 return msg
189
195
190 # shell channel
196 # shell channel
191
197
192 def kernel_info_reply(self, msg):
198 def kernel_info_reply(self, msg):
193 content = msg['content']
199 content = msg['content']
194 for key in ('language_version', 'protocol_version', 'ipython_version'):
200 for key in ('language_version', 'protocol_version', 'ipython_version'):
195 if key in content:
201 if key in content:
196 content[key] = ".".join(map(str, content[key]))
202 content[key] = ".".join(map(str, content[key]))
197
203
198 if content['language'].startswith('python') and 'ipython_version' in content:
204 if content['language'].startswith('python') and 'ipython_version' in content:
199 content['implementation'] = 'ipython'
205 content['implementation'] = 'ipython'
200 content['implementation_version'] = content.pop('ipython_version')
206 content['implementation_version'] = content.pop('ipython_version')
201
207
202 content['banner'] = ''
208 content['banner'] = ''
203 return msg
209 return msg
204
210
205 def execute_request(self, msg):
211 def execute_request(self, msg):
206 content = msg['content']
212 content = msg['content']
207 user_variables = content.pop('user_variables', [])
213 user_variables = content.pop('user_variables', [])
208 user_expressions = content.setdefault('user_expressions', {})
214 user_expressions = content.setdefault('user_expressions', {})
209 for v in user_variables:
215 for v in user_variables:
210 user_expressions[v] = v
216 user_expressions[v] = v
211 return msg
217 return msg
212
218
213 def execute_reply(self, msg):
219 def execute_reply(self, msg):
214 content = msg['content']
220 content = msg['content']
215 user_expressions = content.setdefault('user_expressions', {})
221 user_expressions = content.setdefault('user_expressions', {})
216 user_variables = content.pop('user_variables', {})
222 user_variables = content.pop('user_variables', {})
217 if user_variables:
223 if user_variables:
218 user_expressions.update(user_variables)
224 user_expressions.update(user_variables)
219 return msg
225 return msg
220
226
221 def complete_request(self, msg):
227 def complete_request(self, msg):
222 old_content = msg['content']
228 old_content = msg['content']
223
229
224 new_content = msg['content'] = {}
230 new_content = msg['content'] = {}
225 new_content['code'] = old_content['line']
231 new_content['code'] = old_content['line']
226 new_content['cursor_pos'] = old_content['cursor_pos']
232 new_content['cursor_pos'] = old_content['cursor_pos']
227 return msg
233 return msg
228
234
229 def complete_reply(self, msg):
235 def complete_reply(self, msg):
230 # TODO: complete_reply needs more context than we have
236 # complete_reply needs more context than we have to get cursor_start and end.
231 # Maybe strip common prefix and give magic cursor_start = cursor_end = 0?
237 # use special value of `-1` to indicate to frontend that it should be at
238 # the current cursor position.
232 content = msg['content']
239 content = msg['content']
233 new_content = msg['content'] = {'status' : 'ok'}
240 new_content = msg['content'] = {'status' : 'ok'}
234 n = len(content['matched_text'])
241 new_content['matches'] = content['matches']
235 new_content['matches'] = [ m[n:] for m in content['matches'] ]
242 new_content['cursor_start'] = -len(content['matched_text'])
236 new_content['cursor_start'] = new_content['cursor_end'] = 0
243 new_content['cursor_end'] = None
237 new_content['metadata'] = {}
244 new_content['metadata'] = {}
238 return msg
245 return msg
239
246
240 def inspect_request(self, msg):
247 def inspect_request(self, msg):
241 content = msg['content']
248 content = msg['content']
242 name = content['oname']
249 name = content['oname']
243
250
244 new_content = msg['content'] = {}
251 new_content = msg['content'] = {}
245 new_content['code'] = name
252 new_content['code'] = name
246 new_content['cursor_pos'] = len(name)
253 new_content['cursor_pos'] = len(name)
247 new_content['detail_level'] = content['detail_level']
254 new_content['detail_level'] = content['detail_level']
248 return msg
255 return msg
249
256
250 def inspect_reply(self, msg):
257 def inspect_reply(self, msg):
251 """inspect_reply can't be easily backward compatible"""
258 """inspect_reply can't be easily backward compatible"""
252 content = msg['content']
259 content = msg['content']
253 new_content = msg['content'] = {'status' : 'ok'}
260 new_content = msg['content'] = {'status' : 'ok'}
254 found = new_content['found'] = content['found']
261 found = new_content['found'] = content['found']
255 new_content['name'] = content['name']
262 new_content['name'] = content['name']
256 new_content['data'] = data = {}
263 new_content['data'] = data = {}
257 new_content['metadata'] = {}
264 new_content['metadata'] = {}
258 if found:
265 if found:
259 lines = []
266 lines = []
260 for key in ('call_def', 'init_definition', 'definition'):
267 for key in ('call_def', 'init_definition', 'definition'):
261 if content.get(key, False):
268 if content.get(key, False):
262 lines.append(content[key])
269 lines.append(content[key])
263 break
270 break
264 for key in ('call_docstring', 'init_docstring', 'docstring'):
271 for key in ('call_docstring', 'init_docstring', 'docstring'):
265 if content.get(key, False):
272 if content.get(key, False):
266 lines.append(content[key])
273 lines.append(content[key])
267 break
274 break
268 if not lines:
275 if not lines:
269 lines.append("<empty docstring>")
276 lines.append("<empty docstring>")
270 data['text/plain'] = '\n'.join(lines)
277 data['text/plain'] = '\n'.join(lines)
271 return msg
278 return msg
272
279
273 # iopub channel
280 # iopub channel
274
281
275 def display_data(self, msg):
282 def display_data(self, msg):
276 content = msg['content']
283 content = msg['content']
277 content.pop("source", None)
284 content.pop("source", None)
278 data = content['data']
285 data = content['data']
279 if 'application/json' in data:
286 if 'application/json' in data:
280 try:
287 try:
281 data['application/json'] = json.loads(data['application/json'])
288 data['application/json'] = json.loads(data['application/json'])
282 except Exception:
289 except Exception:
283 # warn?
290 # warn?
284 pass
291 pass
285 return msg
292 return msg
286
293
287 # stdin channel
294 # stdin channel
288
295
289 def input_request(self, msg):
296 def input_request(self, msg):
290 msg['content'].setdefault('password', False)
297 msg['content'].setdefault('password', False)
291 return msg
298 return msg
292
299
293
300
294
301
295 def adapt(msg, to_version=kernel_protocol_version_info[0]):
302 def adapt(msg, to_version=kernel_protocol_version_info[0]):
296 """Adapt a single message to a target version
303 """Adapt a single message to a target version
297
304
298 Parameters
305 Parameters
299 ----------
306 ----------
300
307
301 msg : dict
308 msg : dict
302 An IPython message.
309 An IPython message.
303 to_version : int, optional
310 to_version : int, optional
304 The target major version.
311 The target major version.
305 If unspecified, adapt to the current version for IPython.
312 If unspecified, adapt to the current version for IPython.
306
313
307 Returns
314 Returns
308 -------
315 -------
309
316
310 msg : dict
317 msg : dict
311 An IPython message appropriate in the new version.
318 An IPython message appropriate in the new version.
312 """
319 """
313 header = msg['header']
320 header = msg['header']
314 if 'version' in header:
321 if 'version' in header:
315 from_version = int(header['version'].split('.')[0])
322 from_version = int(header['version'].split('.')[0])
316 else:
323 else:
317 # assume last version before adding the key to the header
324 # assume last version before adding the key to the header
318 from_version = 4
325 from_version = 4
319 adapter = adapters.get((from_version, to_version), None)
326 adapter = adapters.get((from_version, to_version), None)
320 if adapter is None:
327 if adapter is None:
321 return msg
328 return msg
322 return adapter(msg)
329 return adapter(msg)
323
330
324
331
325 # one adapter per major version from,to
332 # one adapter per major version from,to
326 adapters = {
333 adapters = {
327 (5,4) : V5toV4(),
334 (5,4) : V5toV4(),
328 (4,5) : V4toV5(),
335 (4,5) : V4toV5(),
329 } No newline at end of file
336 }
@@ -1,316 +1,314 b''
1 """Tests for adapting IPython msg spec versions"""
1 """Tests for adapting IPython msg spec versions"""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import copy
6 import copy
7 import json
7 import json
8 from unittest import TestCase
8 from unittest import TestCase
9 import nose.tools as nt
9 import nose.tools as nt
10
10
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4
11 from IPython.kernel.adapter import adapt, V4toV5, V5toV4
12 from IPython.kernel.zmq.session import Session
12 from IPython.kernel.zmq.session import Session
13
13
14
14
15 def test_default_version():
15 def test_default_version():
16 s = Session()
16 s = Session()
17 msg = s.msg("msg_type")
17 msg = s.msg("msg_type")
18 msg['header'].pop('version')
18 msg['header'].pop('version')
19 original = copy.deepcopy(msg)
19 original = copy.deepcopy(msg)
20 adapted = adapt(original)
20 adapted = adapt(original)
21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
21 nt.assert_equal(adapted['header']['version'], V4toV5.version)
22
22
23
23
24 class AdapterTest(TestCase):
24 class AdapterTest(TestCase):
25
25
26 def setUp(self):
26 def setUp(self):
27 self.session = Session()
27 self.session = Session()
28
28
29 def adapt(self, msg, version=None):
29 def adapt(self, msg, version=None):
30 original = copy.deepcopy(msg)
30 original = copy.deepcopy(msg)
31 adapted = adapt(msg, version or self.to_version)
31 adapted = adapt(msg, version or self.to_version)
32 return original, adapted
32 return original, adapted
33
33
34 def check_header(self, msg):
34 def check_header(self, msg):
35 pass
35 pass
36
36
37
37
38 class V4toV5TestCase(AdapterTest):
38 class V4toV5TestCase(AdapterTest):
39 from_version = 4
39 from_version = 4
40 to_version = 5
40 to_version = 5
41
41
42 def msg(self, msg_type, content):
42 def msg(self, msg_type, content):
43 """Create a v4 msg (same as v5, minus version header)"""
43 """Create a v4 msg (same as v5, minus version header)"""
44 msg = self.session.msg(msg_type, content)
44 msg = self.session.msg(msg_type, content)
45 msg['header'].pop('version')
45 msg['header'].pop('version')
46 return msg
46 return msg
47
47
48 def test_same_version(self):
48 def test_same_version(self):
49 msg = self.msg("execute_result",
49 msg = self.msg("execute_result",
50 content={'status' : 'ok'}
50 content={'status' : 'ok'}
51 )
51 )
52 original, adapted = self.adapt(msg, self.from_version)
52 original, adapted = self.adapt(msg, self.from_version)
53
53
54 self.assertEqual(original, adapted)
54 self.assertEqual(original, adapted)
55
55
56 def test_no_adapt(self):
56 def test_no_adapt(self):
57 msg = self.msg("input_reply", {'value' : 'some text'})
57 msg = self.msg("input_reply", {'value' : 'some text'})
58 v4, v5 = self.adapt(msg)
58 v4, v5 = self.adapt(msg)
59 self.assertEqual(v5['header']['version'], V4toV5.version)
59 self.assertEqual(v5['header']['version'], V4toV5.version)
60 v5['header'].pop('version')
60 v5['header'].pop('version')
61 self.assertEqual(v4, v5)
61 self.assertEqual(v4, v5)
62
62
63 def test_rename_type(self):
63 def test_rename_type(self):
64 for v5_type, v4_type in [
64 for v5_type, v4_type in [
65 ('execute_result', 'pyout'),
65 ('execute_result', 'pyout'),
66 ('execute_input', 'pyin'),
66 ('execute_input', 'pyin'),
67 ('error', 'pyerr'),
67 ('error', 'pyerr'),
68 ]:
68 ]:
69 msg = self.msg(v4_type, {'key' : 'value'})
69 msg = self.msg(v4_type, {'key' : 'value'})
70 v4, v5 = self.adapt(msg)
70 v4, v5 = self.adapt(msg)
71 self.assertEqual(v5['header']['version'], V4toV5.version)
71 self.assertEqual(v5['header']['version'], V4toV5.version)
72 self.assertEqual(v5['header']['msg_type'], v5_type)
72 self.assertEqual(v5['header']['msg_type'], v5_type)
73 self.assertEqual(v4['content'], v5['content'])
73 self.assertEqual(v4['content'], v5['content'])
74
74
75 def test_execute_request(self):
75 def test_execute_request(self):
76 msg = self.msg("execute_request", {
76 msg = self.msg("execute_request", {
77 'code' : 'a=5',
77 'code' : 'a=5',
78 'silent' : False,
78 'silent' : False,
79 'user_expressions' : {'a' : 'apple'},
79 'user_expressions' : {'a' : 'apple'},
80 'user_variables' : ['b'],
80 'user_variables' : ['b'],
81 })
81 })
82 v4, v5 = self.adapt(msg)
82 v4, v5 = self.adapt(msg)
83 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
83 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
84 v4c = v4['content']
84 v4c = v4['content']
85 v5c = v5['content']
85 v5c = v5['content']
86 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
86 self.assertEqual(v5c['user_expressions'], {'a' : 'apple', 'b': 'b'})
87 self.assertNotIn('user_variables', v5c)
87 self.assertNotIn('user_variables', v5c)
88 self.assertEqual(v5c['code'], v4c['code'])
88 self.assertEqual(v5c['code'], v4c['code'])
89
89
90 def test_complete_request(self):
90 def test_complete_request(self):
91 msg = self.msg("complete_request", {
91 msg = self.msg("complete_request", {
92 'text' : 'a.is',
92 'text' : 'a.is',
93 'line' : 'foo = a.is',
93 'line' : 'foo = a.is',
94 'block' : None,
94 'block' : None,
95 'cursor_pos' : 10,
95 'cursor_pos' : 10,
96 })
96 })
97 v4, v5 = self.adapt(msg)
97 v4, v5 = self.adapt(msg)
98 v4c = v4['content']
98 v4c = v4['content']
99 v5c = v5['content']
99 v5c = v5['content']
100 for key in ('text', 'line', 'block'):
100 for key in ('text', 'line', 'block'):
101 self.assertNotIn(key, v5c)
101 self.assertNotIn(key, v5c)
102 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
102 self.assertEqual(v5c['cursor_pos'], v4c['cursor_pos'])
103 self.assertEqual(v5c['code'], v4c['line'])
103 self.assertEqual(v5c['code'], v4c['line'])
104
104
105 def test_complete_reply(self):
105 def test_complete_reply(self):
106 msg = self.msg("complete_reply", {
106 msg = self.msg("complete_reply", {
107 'matched_text' : 'a.is',
107 'matched_text' : 'a.is',
108 'matches' : ['a.isalnum',
108 'matches' : ['a.isalnum',
109 'a.isalpha',
109 'a.isalpha',
110 'a.isdigit',
110 'a.isdigit',
111 'a.islower',
111 'a.islower',
112 ],
112 ],
113 })
113 })
114 v4, v5 = self.adapt(msg)
114 v4, v5 = self.adapt(msg)
115 v4c = v4['content']
115 v4c = v4['content']
116 v5c = v5['content']
116 v5c = v5['content']
117 n = len(v4c['matched_text'])
118 expected_matches = [ m[n:] for m in v4c['matches'] ]
119
117
120 self.assertEqual(v5c['matches'], expected_matches)
118 self.assertEqual(v5c['matches'], v4c['matches'])
121 self.assertEqual(v5c['metadata'], {})
119 self.assertEqual(v5c['metadata'], {})
122 self.assertEqual(v5c['cursor_start'], 0)
120 self.assertEqual(v5c['cursor_start'], -4)
123 self.assertEqual(v5c['cursor_end'], 0)
121 self.assertEqual(v5c['cursor_end'], None)
124
122
125 def test_object_info_request(self):
123 def test_object_info_request(self):
126 msg = self.msg("object_info_request", {
124 msg = self.msg("object_info_request", {
127 'oname' : 'foo',
125 'oname' : 'foo',
128 'detail_level' : 1,
126 'detail_level' : 1,
129 })
127 })
130 v4, v5 = self.adapt(msg)
128 v4, v5 = self.adapt(msg)
131 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
129 self.assertEqual(v5['header']['msg_type'], 'inspect_request')
132 v4c = v4['content']
130 v4c = v4['content']
133 v5c = v5['content']
131 v5c = v5['content']
134 self.assertEqual(v5c['code'], v4c['oname'])
132 self.assertEqual(v5c['code'], v4c['oname'])
135 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
133 self.assertEqual(v5c['cursor_pos'], len(v4c['oname']))
136 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
134 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
137
135
138 def test_object_info_reply(self):
136 def test_object_info_reply(self):
139 msg = self.msg("object_info_reply", {
137 msg = self.msg("object_info_reply", {
140 'name' : 'foo',
138 'name' : 'foo',
141 'found' : True,
139 'found' : True,
142 'status' : 'ok',
140 'status' : 'ok',
143 'definition' : 'foo(a=5)',
141 'definition' : 'foo(a=5)',
144 'docstring' : "the docstring",
142 'docstring' : "the docstring",
145 })
143 })
146 v4, v5 = self.adapt(msg)
144 v4, v5 = self.adapt(msg)
147 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
145 self.assertEqual(v5['header']['msg_type'], 'inspect_reply')
148 v4c = v4['content']
146 v4c = v4['content']
149 v5c = v5['content']
147 v5c = v5['content']
150 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status'])
148 self.assertEqual(sorted(v5c), [ 'data', 'found', 'metadata', 'name', 'status'])
151 text = v5c['data']['text/plain']
149 text = v5c['data']['text/plain']
152 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
150 self.assertEqual(text, '\n'.join([v4c['definition'], v4c['docstring']]))
153
151
154 # iopub channel
152 # iopub channel
155
153
156 def test_display_data(self):
154 def test_display_data(self):
157 jsondata = dict(a=5)
155 jsondata = dict(a=5)
158 msg = self.msg("display_data", {
156 msg = self.msg("display_data", {
159 'data' : {
157 'data' : {
160 'text/plain' : 'some text',
158 'text/plain' : 'some text',
161 'application/json' : json.dumps(jsondata)
159 'application/json' : json.dumps(jsondata)
162 },
160 },
163 'metadata' : {'text/plain' : { 'key' : 'value' }},
161 'metadata' : {'text/plain' : { 'key' : 'value' }},
164 })
162 })
165 v4, v5 = self.adapt(msg)
163 v4, v5 = self.adapt(msg)
166 v4c = v4['content']
164 v4c = v4['content']
167 v5c = v5['content']
165 v5c = v5['content']
168 self.assertEqual(v5c['metadata'], v4c['metadata'])
166 self.assertEqual(v5c['metadata'], v4c['metadata'])
169 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
167 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
170 self.assertEqual(v5c['data']['application/json'], jsondata)
168 self.assertEqual(v5c['data']['application/json'], jsondata)
171
169
172 # stdin channel
170 # stdin channel
173
171
174 def test_input_request(self):
172 def test_input_request(self):
175 msg = self.msg('input_request', {'prompt': "$>"})
173 msg = self.msg('input_request', {'prompt': "$>"})
176 v4, v5 = self.adapt(msg)
174 v4, v5 = self.adapt(msg)
177 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
175 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
178 self.assertFalse(v5['content']['password'])
176 self.assertFalse(v5['content']['password'])
179
177
180
178
181 class V5toV4TestCase(AdapterTest):
179 class V5toV4TestCase(AdapterTest):
182 from_version = 5
180 from_version = 5
183 to_version = 4
181 to_version = 4
184
182
185 def msg(self, msg_type, content):
183 def msg(self, msg_type, content):
186 return self.session.msg(msg_type, content)
184 return self.session.msg(msg_type, content)
187
185
188 def test_same_version(self):
186 def test_same_version(self):
189 msg = self.msg("execute_result",
187 msg = self.msg("execute_result",
190 content={'status' : 'ok'}
188 content={'status' : 'ok'}
191 )
189 )
192 original, adapted = self.adapt(msg, self.from_version)
190 original, adapted = self.adapt(msg, self.from_version)
193
191
194 self.assertEqual(original, adapted)
192 self.assertEqual(original, adapted)
195
193
196 def test_no_adapt(self):
194 def test_no_adapt(self):
197 msg = self.msg("input_reply", {'value' : 'some text'})
195 msg = self.msg("input_reply", {'value' : 'some text'})
198 v5, v4 = self.adapt(msg)
196 v5, v4 = self.adapt(msg)
199 self.assertNotIn('version', v4['header'])
197 self.assertNotIn('version', v4['header'])
200 v5['header'].pop('version')
198 v5['header'].pop('version')
201 self.assertEqual(v4, v5)
199 self.assertEqual(v4, v5)
202
200
203 def test_rename_type(self):
201 def test_rename_type(self):
204 for v5_type, v4_type in [
202 for v5_type, v4_type in [
205 ('execute_result', 'pyout'),
203 ('execute_result', 'pyout'),
206 ('execute_input', 'pyin'),
204 ('execute_input', 'pyin'),
207 ('error', 'pyerr'),
205 ('error', 'pyerr'),
208 ]:
206 ]:
209 msg = self.msg(v5_type, {'key' : 'value'})
207 msg = self.msg(v5_type, {'key' : 'value'})
210 v5, v4 = self.adapt(msg)
208 v5, v4 = self.adapt(msg)
211 self.assertEqual(v4['header']['msg_type'], v4_type)
209 self.assertEqual(v4['header']['msg_type'], v4_type)
212 nt.assert_not_in('version', v4['header'])
210 nt.assert_not_in('version', v4['header'])
213 self.assertEqual(v4['content'], v5['content'])
211 self.assertEqual(v4['content'], v5['content'])
214
212
215 def test_execute_request(self):
213 def test_execute_request(self):
216 msg = self.msg("execute_request", {
214 msg = self.msg("execute_request", {
217 'code' : 'a=5',
215 'code' : 'a=5',
218 'silent' : False,
216 'silent' : False,
219 'user_expressions' : {'a' : 'apple'},
217 'user_expressions' : {'a' : 'apple'},
220 })
218 })
221 v5, v4 = self.adapt(msg)
219 v5, v4 = self.adapt(msg)
222 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
220 self.assertEqual(v4['header']['msg_type'], v5['header']['msg_type'])
223 v4c = v4['content']
221 v4c = v4['content']
224 v5c = v5['content']
222 v5c = v5['content']
225 self.assertEqual(v4c['user_variables'], [])
223 self.assertEqual(v4c['user_variables'], [])
226 self.assertEqual(v5c['code'], v4c['code'])
224 self.assertEqual(v5c['code'], v4c['code'])
227
225
228 def test_complete_request(self):
226 def test_complete_request(self):
229 msg = self.msg("complete_request", {
227 msg = self.msg("complete_request", {
230 'code' : 'def foo():\n'
228 'code' : 'def foo():\n'
231 ' a.is\n'
229 ' a.is\n'
232 'foo()',
230 'foo()',
233 'cursor_pos': 19,
231 'cursor_pos': 19,
234 })
232 })
235 v5, v4 = self.adapt(msg)
233 v5, v4 = self.adapt(msg)
236 v4c = v4['content']
234 v4c = v4['content']
237 v5c = v5['content']
235 v5c = v5['content']
238 self.assertNotIn('code', v4c)
236 self.assertNotIn('code', v4c)
239 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
237 self.assertEqual(v4c['line'], v5c['code'].splitlines(True)[1])
240 self.assertEqual(v4c['cursor_pos'], 8)
238 self.assertEqual(v4c['cursor_pos'], 8)
241 self.assertEqual(v4c['text'], '')
239 self.assertEqual(v4c['text'], '')
242 self.assertEqual(v4c['block'], None)
240 self.assertEqual(v4c['block'], None)
243
241
244 def test_complete_reply(self):
242 def test_complete_reply(self):
245 msg = self.msg("complete_reply", {
243 msg = self.msg("complete_reply", {
246 'cursor_start' : 10,
244 'cursor_start' : 10,
247 'cursor_end' : 14,
245 'cursor_end' : 14,
248 'matches' : ['a.isalnum',
246 'matches' : ['a.isalnum',
249 'a.isalpha',
247 'a.isalpha',
250 'a.isdigit',
248 'a.isdigit',
251 'a.islower',
249 'a.islower',
252 ],
250 ],
253 'metadata' : {},
251 'metadata' : {},
254 })
252 })
255 v5, v4 = self.adapt(msg)
253 v5, v4 = self.adapt(msg)
256 v4c = v4['content']
254 v4c = v4['content']
257 v5c = v5['content']
255 v5c = v5['content']
258 self.assertEqual(v4c['matched_text'], 'a.is')
256 self.assertEqual(v4c['matched_text'], 'a.is')
259 self.assertEqual(v4c['matches'], v5c['matches'])
257 self.assertEqual(v4c['matches'], v5c['matches'])
260
258
261 def test_inspect_request(self):
259 def test_inspect_request(self):
262 msg = self.msg("inspect_request", {
260 msg = self.msg("inspect_request", {
263 'code' : 'def foo():\n'
261 'code' : 'def foo():\n'
264 ' apple\n'
262 ' apple\n'
265 'bar()',
263 'bar()',
266 'cursor_pos': 18,
264 'cursor_pos': 18,
267 'detail_level' : 1,
265 'detail_level' : 1,
268 })
266 })
269 v5, v4 = self.adapt(msg)
267 v5, v4 = self.adapt(msg)
270 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
268 self.assertEqual(v4['header']['msg_type'], 'object_info_request')
271 v4c = v4['content']
269 v4c = v4['content']
272 v5c = v5['content']
270 v5c = v5['content']
273 self.assertEqual(v4c['oname'], 'apple')
271 self.assertEqual(v4c['oname'], 'apple')
274 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
272 self.assertEqual(v5c['detail_level'], v4c['detail_level'])
275
273
276 def test_inspect_reply(self):
274 def test_inspect_reply(self):
277 msg = self.msg("inspect_reply", {
275 msg = self.msg("inspect_reply", {
278 'name' : 'foo',
276 'name' : 'foo',
279 'found' : True,
277 'found' : True,
280 'data' : {'text/plain' : 'some text'},
278 'data' : {'text/plain' : 'some text'},
281 'metadata' : {},
279 'metadata' : {},
282 })
280 })
283 v5, v4 = self.adapt(msg)
281 v5, v4 = self.adapt(msg)
284 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
282 self.assertEqual(v4['header']['msg_type'], 'object_info_reply')
285 v4c = v4['content']
283 v4c = v4['content']
286 v5c = v5['content']
284 v5c = v5['content']
287 self.assertEqual(sorted(v4c), ['found', 'name'])
285 self.assertEqual(sorted(v4c), ['found', 'name'])
288 self.assertEqual(v4c['found'], False)
286 self.assertEqual(v4c['found'], False)
289
287
290 # iopub channel
288 # iopub channel
291
289
292 def test_display_data(self):
290 def test_display_data(self):
293 jsondata = dict(a=5)
291 jsondata = dict(a=5)
294 msg = self.msg("display_data", {
292 msg = self.msg("display_data", {
295 'data' : {
293 'data' : {
296 'text/plain' : 'some text',
294 'text/plain' : 'some text',
297 'application/json' : jsondata,
295 'application/json' : jsondata,
298 },
296 },
299 'metadata' : {'text/plain' : { 'key' : 'value' }},
297 'metadata' : {'text/plain' : { 'key' : 'value' }},
300 })
298 })
301 v5, v4 = self.adapt(msg)
299 v5, v4 = self.adapt(msg)
302 v4c = v4['content']
300 v4c = v4['content']
303 v5c = v5['content']
301 v5c = v5['content']
304 self.assertEqual(v5c['metadata'], v4c['metadata'])
302 self.assertEqual(v5c['metadata'], v4c['metadata'])
305 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
303 self.assertEqual(v5c['data']['text/plain'], v4c['data']['text/plain'])
306 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
304 self.assertEqual(v4c['data']['application/json'], json.dumps(jsondata))
307
305
308 # stdin channel
306 # stdin channel
309
307
310 def test_input_request(self):
308 def test_input_request(self):
311 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
309 msg = self.msg('input_request', {'prompt': "$>", 'password' : True})
312 v5, v4 = self.adapt(msg)
310 v5, v4 = self.adapt(msg)
313 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
311 self.assertEqual(v5['content']['prompt'], v4['content']['prompt'])
314 self.assertNotIn('password', v4['content'])
312 self.assertNotIn('password', v4['content'])
315
313
316
314
General Comments 0
You need to be logged in to leave comments. Login now