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