##// END OF EJS Templates
Allow path completion on notebook....
Matthias BUSSONNIER -
Show More
@@ -1,307 +1,307 b''
1 // function completer.
1 // function completer.
2 //
2 //
3 // completer should be a class that take an cell instance
3 // completer should be a class that take an cell instance
4 var IPython = (function (IPython) {
4 var IPython = (function (IPython) {
5 // that will prevent us from misspelling
5 // that will prevent us from misspelling
6 "use strict";
6 "use strict";
7
7
8 // easyier key mapping
8 // easyier key mapping
9 var key = IPython.utils.keycodes;
9 var key = IPython.utils.keycodes;
10
10
11 function prepend_n_prc(str, n) {
11 function prepend_n_prc(str, n) {
12 for( var i =0 ; i< n ; i++)
12 for( var i =0 ; i< n ; i++)
13 { str = '%'+str }
13 { str = '%'+str }
14 return str;
14 return str;
15 }
15 }
16
16
17 function _existing_completion(item, completion_array){
17 function _existing_completion(item, completion_array){
18 for( var c in completion_array ) {
18 for( var c in completion_array ) {
19 if(completion_array[c].substr(-item.length) == item)
19 if(completion_array[c].substr(-item.length) == item)
20 { return true; }
20 { return true; }
21 }
21 }
22 return false;
22 return false;
23 }
23 }
24
24
25 // what is the common start of all completions
25 // what is the common start of all completions
26 function shared_start(B, drop_prct) {
26 function shared_start(B, drop_prct) {
27 if (B.length == 1) {
27 if (B.length == 1) {
28 return B[0];
28 return B[0];
29 }
29 }
30 var A = new Array();
30 var A = new Array();
31 var common;
31 var common;
32 var min_lead_prct = 10;
32 var min_lead_prct = 10;
33 for (var i = 0; i < B.length; i++) {
33 for (var i = 0; i < B.length; i++) {
34 var str = B[i].str;
34 var str = B[i].str;
35 var localmin = 0;
35 var localmin = 0;
36 if(drop_prct == true){
36 if(drop_prct == true){
37 while ( str.substr(0, 1) == '%') {
37 while ( str.substr(0, 1) == '%') {
38 localmin = localmin+1;
38 localmin = localmin+1;
39 str = str.substring(1);
39 str = str.substring(1);
40 }
40 }
41 }
41 }
42 min_lead_prct = Math.min(min_lead_prct, localmin);
42 min_lead_prct = Math.min(min_lead_prct, localmin);
43 A.push(str);
43 A.push(str);
44 }
44 }
45
45
46 if (A.length > 1) {
46 if (A.length > 1) {
47 var tem1, tem2, s;
47 var tem1, tem2, s;
48 A = A.slice(0).sort();
48 A = A.slice(0).sort();
49 tem1 = A[0];
49 tem1 = A[0];
50 s = tem1.length;
50 s = tem1.length;
51 tem2 = A.pop();
51 tem2 = A.pop();
52 while (s && tem2.indexOf(tem1) == -1) {
52 while (s && tem2.indexOf(tem1) == -1) {
53 tem1 = tem1.substring(0, --s);
53 tem1 = tem1.substring(0, --s);
54 }
54 }
55 if (tem1 == "" || tem2.indexOf(tem1) != 0) {
55 if (tem1 == "" || tem2.indexOf(tem1) != 0) {
56 return prepend_n_prc('', min_lead_prct);
56 return prepend_n_prc('', min_lead_prct);
57 }
57 }
58 return {
58 return {
59 str: prepend_n_prc(tem1, min_lead_prct),
59 str: prepend_n_prc(tem1, min_lead_prct),
60 type: "computed",
60 type: "computed",
61 from: B[0].from,
61 from: B[0].from,
62 to: B[0].to
62 to: B[0].to
63 };
63 };
64 }
64 }
65 return null;
65 return null;
66 }
66 }
67
67
68
68
69 var Completer = function (cell) {
69 var Completer = function (cell) {
70 this.editor = cell.code_mirror;
70 this.editor = cell.code_mirror;
71 var that = this;
71 var that = this;
72 $([IPython.events]).on('status_busy.Kernel', function () {
72 $([IPython.events]).on('status_busy.Kernel', function () {
73 that.skip_kernel_completion = true;
73 that.skip_kernel_completion = true;
74 });
74 });
75 $([IPython.events]).on('status_idle.Kernel', function () {
75 $([IPython.events]).on('status_idle.Kernel', function () {
76 that.skip_kernel_completion = false;
76 that.skip_kernel_completion = false;
77 });
77 });
78
78
79
79
80 };
80 };
81
81
82
82
83
83
84 Completer.prototype.startCompletion = function () {
84 Completer.prototype.startCompletion = function () {
85 // call for a 'first' completion, that will set the editor and do some
85 // call for a 'first' completion, that will set the editor and do some
86 // special behaviour like autopicking if only one completion availlable
86 // special behaviour like autopicking if only one completion availlable
87 //
87 //
88 if (this.editor.somethingSelected()) return;
88 if (this.editor.somethingSelected()) return;
89 this.done = false;
89 this.done = false;
90 // use to get focus back on opera
90 // use to get focus back on opera
91 this.carry_on_completion(true);
91 this.carry_on_completion(true);
92 };
92 };
93
93
94 Completer.prototype.carry_on_completion = function (ff) {
94 Completer.prototype.carry_on_completion = function (ff) {
95 // Pass true as parameter if you want the commpleter to autopick when
95 // Pass true as parameter if you want the commpleter to autopick when
96 // only one completion. This function is automatically reinvoked at
96 // only one completion. This function is automatically reinvoked at
97 // each keystroke with ff = false
97 // each keystroke with ff = false
98 var cur = this.editor.getCursor();
98 var cur = this.editor.getCursor();
99 var line = this.editor.getLine(cur.line);
99 var line = this.editor.getLine(cur.line);
100 var pre_cursor = this.editor.getRange({
100 var pre_cursor = this.editor.getRange({
101 line: cur.line,
101 line: cur.line,
102 ch: cur.ch - 1
102 ch: cur.ch - 1
103 }, cur);
103 }, cur);
104
104
105 // we need to check that we are still on a word boundary
105 // we need to check that we are still on a word boundary
106 // because while typing the completer is still reinvoking itself
106 // because while typing the completer is still reinvoking itself
107 if (!/[0-9a-z._]/i.test(pre_cursor)) {
107 if (!/[0-9a-z._/\\:~-]/i.test(pre_cursor)) {
108 this.close();
108 this.close();
109 return;
109 return;
110 }
110 }
111
111
112 this.autopick = false;
112 this.autopick = false;
113 if (ff != 'undefined' && ff == true) {
113 if (ff != 'undefined' && ff == true) {
114 this.autopick = true;
114 this.autopick = true;
115 }
115 }
116
116
117 // We want a single cursor position.
117 // We want a single cursor position.
118 if (this.editor.somethingSelected()) return;
118 if (this.editor.somethingSelected()) return;
119
119
120 // one kernel completion came back, finish_completing will be called with the results
120 // one kernel completion came back, finish_completing will be called with the results
121 // we fork here and directly call finish completing if kernel is busy
121 // we fork here and directly call finish completing if kernel is busy
122 if (this.skip_kernel_completion == true) {
122 if (this.skip_kernel_completion == true) {
123 this.finish_completing({
123 this.finish_completing({
124 'matches': [],
124 'matches': [],
125 matched_text: ""
125 matched_text: ""
126 })
126 })
127 } else {
127 } else {
128 var callbacks = {
128 var callbacks = {
129 'complete_reply': $.proxy(this.finish_completing, this)
129 'complete_reply': $.proxy(this.finish_completing, this)
130 };
130 };
131 IPython.notebook.kernel.complete(line, cur.ch, callbacks);
131 IPython.notebook.kernel.complete(line, cur.ch, callbacks);
132 }
132 }
133 };
133 };
134
134
135 Completer.prototype.finish_completing = function (content) {
135 Completer.prototype.finish_completing = function (content) {
136 // let's build a function that wrap all that stuff into what is needed
136 // let's build a function that wrap all that stuff into what is needed
137 // for the new completer:
137 // for the new completer:
138 var matched_text = content.matched_text;
138 var matched_text = content.matched_text;
139 var matches = content.matches;
139 var matches = content.matches;
140
140
141 var cur = this.editor.getCursor();
141 var cur = this.editor.getCursor();
142 var results = CodeMirror.contextHint(this.editor);
142 var results = CodeMirror.contextHint(this.editor);
143 var filterd_results = Array();
143 var filterd_results = Array();
144 //remove results from context completion
144 //remove results from context completion
145 //that are already in kernel completion
145 //that are already in kernel completion
146 for(var elm in results) {
146 for(var elm in results) {
147 if(_existing_completion(results[elm]['str'], matches) == false)
147 if(_existing_completion(results[elm]['str'], matches) == false)
148 { filterd_results.push(results[elm]); }
148 { filterd_results.push(results[elm]); }
149 }
149 }
150
150
151 // append the introspection result, in order, at at the beginning of
151 // append the introspection result, in order, at at the beginning of
152 // the table and compute the replacement range from current cursor
152 // the table and compute the replacement range from current cursor
153 // positon and matched_text length.
153 // positon and matched_text length.
154 for (var i = matches.length - 1; i >= 0; --i) {
154 for (var i = matches.length - 1; i >= 0; --i) {
155 filterd_results.unshift({
155 filterd_results.unshift({
156 str: matches[i],
156 str: matches[i],
157 type: "introspection",
157 type: "introspection",
158 from: {
158 from: {
159 line: cur.line,
159 line: cur.line,
160 ch: cur.ch - matched_text.length
160 ch: cur.ch - matched_text.length
161 },
161 },
162 to: {
162 to: {
163 line: cur.line,
163 line: cur.line,
164 ch: cur.ch
164 ch: cur.ch
165 }
165 }
166 });
166 });
167 }
167 }
168
168
169 // one the 2 sources results have been merge, deal with it
169 // one the 2 sources results have been merge, deal with it
170 this.raw_result = filterd_results;
170 this.raw_result = filterd_results;
171
171
172 // if empty result return
172 // if empty result return
173 if (!this.raw_result || !this.raw_result.length) return;
173 if (!this.raw_result || !this.raw_result.length) return;
174
174
175 // When there is only one completion, use it directly.
175 // When there is only one completion, use it directly.
176 if (this.autopick == true && this.raw_result.length == 1) {
176 if (this.autopick == true && this.raw_result.length == 1) {
177 this.insert(this.raw_result[0]);
177 this.insert(this.raw_result[0]);
178 return;
178 return;
179 }
179 }
180
180
181 if (this.raw_result.length == 1) {
181 if (this.raw_result.length == 1) {
182 // test if first and only completion totally matches
182 // test if first and only completion totally matches
183 // what is typed, in this case dismiss
183 // what is typed, in this case dismiss
184 var str = this.raw_result[0].str;
184 var str = this.raw_result[0].str;
185 var pre_cursor = this.editor.getRange({
185 var pre_cursor = this.editor.getRange({
186 line: cur.line,
186 line: cur.line,
187 ch: cur.ch - str.length
187 ch: cur.ch - str.length
188 }, cur);
188 }, cur);
189 if (pre_cursor == str) {
189 if (pre_cursor == str) {
190 this.close();
190 this.close();
191 return;
191 return;
192 }
192 }
193 }
193 }
194
194
195 this.complete = $('<div/>').addClass('completions');
195 this.complete = $('<div/>').addClass('completions');
196 this.complete.attr('id', 'complete');
196 this.complete.attr('id', 'complete');
197
197
198 this.sel = $('<select/>').attr('multiple', 'true').attr('size', Math.min(10, this.raw_result.length));
198 this.sel = $('<select/>').attr('multiple', 'true').attr('size', Math.min(10, this.raw_result.length));
199 var pos = this.editor.cursorCoords();
199 var pos = this.editor.cursorCoords();
200
200
201 // TODO: I propose to remove enough horizontal pixel
201 // TODO: I propose to remove enough horizontal pixel
202 // to align the text later
202 // to align the text later
203 this.complete.css('left', pos.x + 'px');
203 this.complete.css('left', pos.x + 'px');
204 this.complete.css('top', pos.yBot + 'px');
204 this.complete.css('top', pos.yBot + 'px');
205 this.complete.append(this.sel);
205 this.complete.append(this.sel);
206
206
207 $('body').append(this.complete);
207 $('body').append(this.complete);
208 //build the container
208 //build the container
209 var that = this;
209 var that = this;
210 this.sel.dblclick(function () {
210 this.sel.dblclick(function () {
211 that.pick();
211 that.pick();
212 });
212 });
213 this.sel.blur(this.close);
213 this.sel.blur(this.close);
214 this.sel.keydown(function (event) {
214 this.sel.keydown(function (event) {
215 that.keydown(event);
215 that.keydown(event);
216 });
216 });
217
217
218 this.build_gui_list(this.raw_result);
218 this.build_gui_list(this.raw_result);
219
219
220 this.sel.focus();
220 this.sel.focus();
221 // Opera sometimes ignores focusing a freshly created node
221 // Opera sometimes ignores focusing a freshly created node
222 if (window.opera) setTimeout(function () {
222 if (window.opera) setTimeout(function () {
223 if (!this.done) this.sel.focus();
223 if (!this.done) this.sel.focus();
224 }, 100);
224 }, 100);
225 return true;
225 return true;
226 }
226 }
227
227
228 Completer.prototype.insert = function (completion) {
228 Completer.prototype.insert = function (completion) {
229 this.editor.replaceRange(completion.str, completion.from, completion.to);
229 this.editor.replaceRange(completion.str, completion.from, completion.to);
230 }
230 }
231
231
232 Completer.prototype.build_gui_list = function (completions) {
232 Completer.prototype.build_gui_list = function (completions) {
233 // Need to clear the all list
233 // Need to clear the all list
234 for (var i = 0; i < completions.length; ++i) {
234 for (var i = 0; i < completions.length; ++i) {
235 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
235 var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
236 this.sel.append(opt);
236 this.sel.append(opt);
237 }
237 }
238 this.sel.children().first().attr('selected', 'true');
238 this.sel.children().first().attr('selected', 'true');
239 }
239 }
240
240
241 Completer.prototype.close = function () {
241 Completer.prototype.close = function () {
242 if (this.done) return;
242 if (this.done) return;
243 this.done = true;
243 this.done = true;
244 $('.completions').remove();
244 $('.completions').remove();
245 }
245 }
246
246
247 Completer.prototype.pick = function () {
247 Completer.prototype.pick = function () {
248 this.insert(this.raw_result[this.sel[0].selectedIndex]);
248 this.insert(this.raw_result[this.sel[0].selectedIndex]);
249 this.close();
249 this.close();
250 var that = this;
250 var that = this;
251 setTimeout(function () {
251 setTimeout(function () {
252 that.editor.focus();
252 that.editor.focus();
253 }, 50);
253 }, 50);
254 }
254 }
255
255
256
256
257 Completer.prototype.keydown = function (event) {
257 Completer.prototype.keydown = function (event) {
258 var code = event.keyCode;
258 var code = event.keyCode;
259 var that = this;
259 var that = this;
260 // Enter
260 // Enter
261 if (code == key.ENTER) {
261 if (code == key.ENTER) {
262 CodeMirror.e_stop(event);
262 CodeMirror.e_stop(event);
263 this.pick();
263 this.pick();
264 }
264 }
265 // Escape or backspace
265 // Escape or backspace
266 else if (code == key.ESC) {
266 else if (code == key.ESC) {
267 CodeMirror.e_stop(event);
267 CodeMirror.e_stop(event);
268 this.close();
268 this.close();
269 this.editor.focus();
269 this.editor.focus();
270 } else if (code == key.SPACE || code == key.BACKSPACE) {
270 } else if (code == key.SPACE || code == key.BACKSPACE) {
271 this.close();
271 this.close();
272 this.editor.focus();
272 this.editor.focus();
273 } else if (code == key.TAB) {
273 } else if (code == key.TAB) {
274 //all the fastforwarding operation,
274 //all the fastforwarding operation,
275 //Check that shared start is not null which can append with prefixed completion
275 //Check that shared start is not null which can append with prefixed completion
276 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
276 // like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
277 // to erase py
277 // to erase py
278 var sh = shared_start(this.raw_result, true);
278 var sh = shared_start(this.raw_result, true);
279 if (sh) {
279 if (sh) {
280 this.insert(sh);
280 this.insert(sh);
281 }
281 }
282 this.close();
282 this.close();
283 CodeMirror.e_stop(event);
283 CodeMirror.e_stop(event);
284 this.editor.focus();
284 this.editor.focus();
285 //reinvoke self
285 //reinvoke self
286 setTimeout(function () {
286 setTimeout(function () {
287 that.carry_on_completion();
287 that.carry_on_completion();
288 }, 50);
288 }, 50);
289 } else if (code == key.UPARROW || code == key.DOWNARROW) {
289 } else if (code == key.UPARROW || code == key.DOWNARROW) {
290 // need to do that to be able to move the arrow
290 // need to do that to be able to move the arrow
291 // when on the first or last line ofo a code cell
291 // when on the first or last line ofo a code cell
292 event.stopPropagation();
292 event.stopPropagation();
293 } else if (code != key.UPARROW && code != key.DOWNARROW) {
293 } else if (code != key.UPARROW && code != key.DOWNARROW) {
294 this.close();
294 this.close();
295 this.editor.focus();
295 this.editor.focus();
296 //we give focus to the editor immediately and call sell in 50 ms
296 //we give focus to the editor immediately and call sell in 50 ms
297 setTimeout(function () {
297 setTimeout(function () {
298 that.carry_on_completion();
298 that.carry_on_completion();
299 }, 50);
299 }, 50);
300 }
300 }
301 }
301 }
302
302
303
303
304 IPython.Completer = Completer;
304 IPython.Completer = Completer;
305
305
306 return IPython;
306 return IPython;
307 }(IPython));
307 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now