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