Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50654 views
1
// function completer.
2
//
3
// completer should be a class that takes an cell instance
4
var IPython = (function (IPython) {
5
// that will prevent us from misspelling
6
"use strict";
7
8
// easier key mapping
9
var keycodes = IPython.keyboard.keycodes;
10
11
function prepend_n_prc(str, n) {
12
for( var i =0 ; i< n ; i++){
13
str = '%'+str ;
14
}
15
return str;
16
};
17
18
function _existing_completion(item, completion_array){
19
for( var i=0; i < completion_array.length; i++) {
20
if (completion_array[i].trim().substr(-item.length) == item) {
21
return true;
22
}
23
}
24
return false;
25
};
26
27
// what is the common start of all completions
28
function shared_start(B, drop_prct) {
29
if (B.length == 1) {
30
return B[0];
31
}
32
var A = [];
33
var common;
34
var min_lead_prct = 10;
35
for (var i = 0; i < B.length; i++) {
36
var str = B[i].str;
37
var localmin = 0;
38
if(drop_prct === true){
39
while ( str.substr(0, 1) == '%') {
40
localmin = localmin+1;
41
str = str.substring(1);
42
}
43
}
44
min_lead_prct = Math.min(min_lead_prct, localmin);
45
A.push(str);
46
}
47
48
if (A.length > 1) {
49
var tem1, tem2, s;
50
A = A.slice(0).sort();
51
tem1 = A[0];
52
s = tem1.length;
53
tem2 = A.pop();
54
while (s && tem2.indexOf(tem1) == -1) {
55
tem1 = tem1.substring(0, --s);
56
}
57
if (tem1 === "" || tem2.indexOf(tem1) !== 0) {
58
return {
59
str:prepend_n_prc('', min_lead_prct),
60
type: "computed",
61
from: B[0].from,
62
to: B[0].to
63
};
64
}
65
return {
66
str: prepend_n_prc(tem1, min_lead_prct),
67
type: "computed",
68
from: B[0].from,
69
to: B[0].to
70
};
71
}
72
return null;
73
}
74
75
76
var Completer = function (cell) {
77
this.cell = cell;
78
this.editor = cell.code_mirror;
79
var that = this;
80
$([IPython.events]).on('status_busy.Kernel', function () {
81
that.skip_kernel_completion = true;
82
});
83
$([IPython.events]).on('status_idle.Kernel', function () {
84
that.skip_kernel_completion = false;
85
});
86
};
87
88
Completer.prototype.startCompletion = function () {
89
// call for a 'first' completion, that will set the editor and do some
90
// special behavior like autopicking if only one completion available.
91
if (this.editor.somethingSelected()) return;
92
this.done = false;
93
// use to get focus back on opera
94
this.carry_on_completion(true);
95
};
96
97
98
// easy access for julia to monkeypatch
99
//
100
Completer.reinvoke_re = /[%0-9a-z._/\\:~-]/i;
101
102
Completer.prototype.reinvoke= function(pre_cursor, block, cursor){
103
return Completer.reinvoke_re.test(pre_cursor);
104
};
105
106
/**
107
*
108
* pass true as parameter if this is the first invocation of the completer
109
* this will prevent the completer to dissmiss itself if it is not on a
110
* word boundary like pressing tab after a space, and make it autopick the
111
* only choice if there is only one which prevent from popping the UI. as
112
* well as fast-forwarding the typing if all completion have a common
113
* shared start
114
**/
115
Completer.prototype.carry_on_completion = function (first_invocation) {
116
// Pass true as parameter if you want the completer to autopick when
117
// only one completion. This function is automatically reinvoked at
118
// each keystroke with first_invocation = false
119
var cur = this.editor.getCursor();
120
var line = this.editor.getLine(cur.line);
121
var pre_cursor = this.editor.getRange({
122
line: cur.line,
123
ch: cur.ch - 1
124
}, cur);
125
126
// we need to check that we are still on a word boundary
127
// because while typing the completer is still reinvoking itself
128
// so dismiss if we are on a "bad" caracter
129
if (!this.reinvoke(pre_cursor) && !first_invocation) {
130
this.close();
131
return;
132
}
133
134
this.autopick = false;
135
if (first_invocation) {
136
this.autopick = true;
137
}
138
139
// We want a single cursor position.
140
if (this.editor.somethingSelected()) {
141
return;
142
}
143
144
// one kernel completion came back, finish_completing will be called with the results
145
// we fork here and directly call finish completing if kernel is busy
146
if (this.skip_kernel_completion) {
147
this.finish_completing({
148
'matches': [],
149
matched_text: ""
150
});
151
} else {
152
this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
153
}
154
};
155
156
Completer.prototype.finish_completing = function (msg) {
157
// let's build a function that wrap all that stuff into what is needed
158
// for the new completer:
159
var content = msg.content;
160
var matched_text = content.matched_text;
161
var matches = content.matches;
162
163
var cur = this.editor.getCursor();
164
var results = CodeMirror.contextHint(this.editor);
165
var filtered_results = [];
166
//remove results from context completion
167
//that are already in kernel completion
168
for (var i=0; i < results.length; i++) {
169
if (!_existing_completion(results[i].str, matches)) {
170
filtered_results.push(results[i]);
171
}
172
}
173
174
// append the introspection result, in order, at at the beginning of
175
// the table and compute the replacement range from current cursor
176
// positon and matched_text length.
177
for (var i = matches.length - 1; i >= 0; --i) {
178
filtered_results.unshift({
179
str: matches[i],
180
type: "introspection",
181
from: {
182
line: cur.line,
183
ch: cur.ch - matched_text.length
184
},
185
to: {
186
line: cur.line,
187
ch: cur.ch
188
}
189
});
190
}
191
192
// one the 2 sources results have been merge, deal with it
193
this.raw_result = filtered_results;
194
195
// if empty result return
196
if (!this.raw_result || !this.raw_result.length) return;
197
198
// When there is only one completion, use it directly.
199
if (this.autopick && this.raw_result.length == 1) {
200
this.insert(this.raw_result[0]);
201
return;
202
}
203
204
if (this.raw_result.length == 1) {
205
// test if first and only completion totally matches
206
// what is typed, in this case dismiss
207
var str = this.raw_result[0].str;
208
var pre_cursor = this.editor.getRange({
209
line: cur.line,
210
ch: cur.ch - str.length
211
}, cur);
212
if (pre_cursor == str) {
213
this.close();
214
return;
215
}
216
}
217
218
if (!this.visible) {
219
this.complete = $('<div/>').addClass('completions');
220
this.complete.attr('id', 'complete');
221
222
// Currently webkit doesn't use the size attr correctly. See:
223
// https://code.google.com/p/chromium/issues/detail?id=4579
224
this.sel = $('<select/>')
225
.attr('tabindex', -1)
226
.attr('multiple', 'true');
227
this.complete.append(this.sel);
228
this.visible = true;
229
$('body').append(this.complete);
230
231
//build the container
232
var that = this;
233
this.sel.dblclick(function () {
234
that.pick();
235
});
236
this.sel.focus(function () {
237
that.editor.focus();
238
});
239
this._handle_keydown = function (cm, event) {
240
that.keydown(event);
241
};
242
this.editor.on('keydown', this._handle_keydown);
243
this._handle_keypress = function (cm, event) {
244
that.keypress(event);
245
};
246
this.editor.on('keypress', this._handle_keypress);
247
}
248
this.sel.attr('size', Math.min(10, this.raw_result.length));
249
250
// After everything is on the page, compute the postion.
251
// We put it above the code if it is too close to the bottom of the page.
252
cur.ch = cur.ch-matched_text.length;
253
var pos = this.editor.cursorCoords(cur);
254
var left = pos.left-3;
255
var top;
256
var cheight = this.complete.height();
257
var wheight = $(window).height();
258
if (pos.bottom+cheight+5 > wheight) {
259
top = pos.top-cheight-4;
260
} else {
261
top = pos.bottom+1;
262
}
263
this.complete.css('left', left + 'px');
264
this.complete.css('top', top + 'px');
265
266
// Clear and fill the list.
267
this.sel.text('');
268
this.build_gui_list(this.raw_result);
269
return true;
270
};
271
272
Completer.prototype.insert = function (completion) {
273
this.editor.replaceRange(completion.str, completion.from, completion.to);
274
};
275
276
Completer.prototype.build_gui_list = function (completions) {
277
for (var i = 0; i < completions.length; ++i) {
278
var opt = $('<option/>').text(completions[i].str).addClass(completions[i].type);
279
this.sel.append(opt);
280
}
281
this.sel.children().first().attr('selected', 'true');
282
this.sel.scrollTop(0);
283
};
284
285
Completer.prototype.close = function () {
286
this.done = true;
287
$('#complete').remove();
288
this.editor.off('keydown', this._handle_keydown);
289
this.editor.off('keypress', this._handle_keypress);
290
this.visible = false;
291
};
292
293
Completer.prototype.pick = function () {
294
this.insert(this.raw_result[this.sel[0].selectedIndex]);
295
this.close();
296
};
297
298
Completer.prototype.keydown = function (event) {
299
var code = event.keyCode;
300
var that = this;
301
302
// Enter
303
if (code == keycodes.enter) {
304
CodeMirror.e_stop(event);
305
this.pick();
306
// Escape or backspace
307
} else if (code == keycodes.esc || code == keycodes.backspace) {
308
CodeMirror.e_stop(event);
309
this.close();
310
} else if (code == keycodes.tab) {
311
//all the fastforwarding operation,
312
//Check that shared start is not null which can append with prefixed completion
313
// like %pylab , pylab have no shred start, and ff will result in py<tab><tab>
314
// to erase py
315
var sh = shared_start(this.raw_result, true);
316
if (sh) {
317
this.insert(sh);
318
}
319
this.close();
320
//reinvoke self
321
setTimeout(function () {
322
that.carry_on_completion();
323
}, 50);
324
} else if (code == keycodes.up || code == keycodes.down) {
325
// need to do that to be able to move the arrow
326
// when on the first or last line ofo a code cell
327
CodeMirror.e_stop(event);
328
329
var options = this.sel.find('option');
330
var index = this.sel[0].selectedIndex;
331
if (code == keycodes.up) {
332
index--;
333
}
334
if (code == keycodes.down) {
335
index++;
336
}
337
index = Math.min(Math.max(index, 0), options.length-1);
338
this.sel[0].selectedIndex = index;
339
} else if (code == keycodes.left || code == keycodes.right) {
340
this.close();
341
}
342
};
343
344
Completer.prototype.keypress = function (event) {
345
// FIXME: This is a band-aid.
346
// on keypress, trigger insertion of a single character.
347
// This simulates the old behavior of completion as you type,
348
// before events were disconnected and CodeMirror stopped
349
// receiving events while the completer is focused.
350
351
var that = this;
352
var code = event.keyCode;
353
354
// don't handle keypress if it's not a character (arrows on FF)
355
// or ENTER/TAB
356
if (event.charCode === 0 ||
357
code == keycodes.tab ||
358
code == keycodes.enter
359
) return;
360
361
this.close();
362
this.editor.focus();
363
setTimeout(function () {
364
that.carry_on_completion();
365
}, 50);
366
};
367
IPython.Completer = Completer;
368
369
return IPython;
370
}(IPython));
371
372