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
*
5
*
6
* @module codecell
7
* @namespace codecell
8
* @class CodeCell
9
*/
10
11
12
define([
13
'base/js/namespace',
14
'jquery',
15
'base/js/utils',
16
'base/js/keyboard',
17
'services/config',
18
'notebook/js/cell',
19
'notebook/js/outputarea',
20
'notebook/js/completer',
21
'notebook/js/celltoolbar',
22
'codemirror/lib/codemirror',
23
'codemirror/mode/python/python',
24
'notebook/js/codemirror-ipython'
25
], function(IPython,
26
$,
27
utils,
28
keyboard,
29
configmod,
30
cell,
31
outputarea,
32
completer,
33
celltoolbar,
34
CodeMirror,
35
cmpython,
36
cmip
37
) {
38
"use strict";
39
40
var Cell = cell.Cell;
41
42
/* local util for codemirror */
43
var posEq = function(a, b) {return a.line === b.line && a.ch === b.ch;};
44
45
/**
46
*
47
* function to delete until previous non blanking space character
48
* or first multiple of 4 tabstop.
49
* @private
50
*/
51
CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
52
var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
53
if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
54
var cur = cm.getCursor(), line = cm.getLine(cur.line);
55
var tabsize = cm.getOption('tabSize');
56
var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
57
from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
58
var select = cm.getRange(from,cur);
59
if( select.match(/^\ +$/) !== null){
60
cm.replaceRange("",from,cur);
61
} else {
62
cm.deleteH(-1,"char");
63
}
64
};
65
66
var keycodes = keyboard.keycodes;
67
68
var CodeCell = function (kernel, options) {
69
/**
70
* Constructor
71
*
72
* A Cell conceived to write code.
73
*
74
* Parameters:
75
* kernel: Kernel instance
76
* The kernel doesn't have to be set at creation time, in that case
77
* it will be null and set_kernel has to be called later.
78
* options: dictionary
79
* Dictionary of keyword arguments.
80
* events: $(Events) instance
81
* config: dictionary
82
* keyboard_manager: KeyboardManager instance
83
* notebook: Notebook instance
84
* tooltip: Tooltip instance
85
*/
86
this.kernel = kernel || null;
87
this.notebook = options.notebook;
88
this.collapsed = false;
89
this.events = options.events;
90
this.tooltip = options.tooltip;
91
this.config = options.config;
92
this.class_config = new configmod.ConfigWithDefaults(this.config,
93
CodeCell.config_defaults, 'CodeCell');
94
95
// create all attributed in constructor function
96
// even if null for V8 VM optimisation
97
this.input_prompt_number = null;
98
this.celltoolbar = null;
99
this.output_area = null;
100
101
this.last_msg_id = null;
102
this.completer = null;
103
this.widget_views = [];
104
this._widgets_live = true;
105
106
Cell.apply(this,[{
107
config: $.extend({}, CodeCell.options_default),
108
keyboard_manager: options.keyboard_manager,
109
events: this.events}]);
110
111
// Attributes we want to override in this subclass.
112
this.cell_type = "code";
113
var that = this;
114
this.element.focusout(
115
function() { that.auto_highlight(); }
116
);
117
};
118
119
CodeCell.options_default = {
120
cm_config : {
121
extraKeys: {
122
"Tab" : "indentMore",
123
"Shift-Tab" : "indentLess",
124
"Backspace" : "delSpaceToPrevTabStop",
125
"Cmd-/" : "toggleComment",
126
"Ctrl-/" : "toggleComment"
127
},
128
mode: 'ipython',
129
theme: 'ipython',
130
matchBrackets: true,
131
autoCloseBrackets: true
132
},
133
highlight_modes : {
134
'magic_javascript' :{'reg':['^%%javascript']},
135
'magic_perl' :{'reg':['^%%perl']},
136
'magic_ruby' :{'reg':['^%%ruby']},
137
'magic_python' :{'reg':['^%%python3?']},
138
'magic_shell' :{'reg':['^%%bash']},
139
'magic_r' :{'reg':['^%%R']},
140
'magic_text/x-cython' :{'reg':['^%%cython']},
141
},
142
};
143
144
CodeCell.config_defaults = CodeCell.options_default;
145
146
CodeCell.msg_cells = {};
147
148
CodeCell.prototype = Object.create(Cell.prototype);
149
150
/** @method create_element */
151
CodeCell.prototype.create_element = function () {
152
Cell.prototype.create_element.apply(this, arguments);
153
var that = this;
154
155
var cell = $('<div></div>').addClass('cell code_cell');
156
cell.attr('tabindex','2');
157
158
var input = $('<div></div>').addClass('input');
159
var prompt = $('<div/>').addClass('prompt input_prompt');
160
var inner_cell = $('<div/>').addClass('inner_cell');
161
this.celltoolbar = new celltoolbar.CellToolbar({
162
cell: this,
163
notebook: this.notebook});
164
inner_cell.append(this.celltoolbar.element);
165
var input_area = $('<div/>').addClass('input_area');
166
this.code_mirror = new CodeMirror(input_area.get(0), this.cm_config);
167
// In case of bugs that put the keyboard manager into an inconsistent state,
168
// ensure KM is enabled when CodeMirror is focused:
169
this.code_mirror.on('focus', function () {
170
if (that.keyboard_manager) {
171
that.keyboard_manager.enable();
172
}
173
});
174
this.code_mirror.on('keydown', $.proxy(this.handle_keyevent,this));
175
$(this.code_mirror.getInputField()).attr("spellcheck", "false");
176
inner_cell.append(input_area);
177
input.append(prompt).append(inner_cell);
178
179
var widget_area = $('<div/>')
180
.addClass('widget-area')
181
.hide();
182
this.widget_area = widget_area;
183
var widget_prompt = $('<div/>')
184
.addClass('prompt')
185
.appendTo(widget_area);
186
var widget_subarea = $('<div/>')
187
.addClass('widget-subarea')
188
.appendTo(widget_area);
189
this.widget_subarea = widget_subarea;
190
var that = this;
191
var widget_clear_buton = $('<button />')
192
.addClass('close')
193
.html('&times;')
194
.click(function() {
195
widget_area.slideUp('', function(){
196
for (var i = 0; i < that.widget_views.length; i++) {
197
var view = that.widget_views[i];
198
view.remove();
199
200
// Remove widget live events.
201
view.off('comm:live', that._widget_live);
202
view.off('comm:dead', that._widget_dead);
203
}
204
that.widget_views = [];
205
widget_subarea.html('');
206
});
207
})
208
.appendTo(widget_prompt);
209
210
var output = $('<div></div>');
211
cell.append(input).append(widget_area).append(output);
212
this.element = cell;
213
this.output_area = new outputarea.OutputArea({
214
selector: output,
215
prompt_area: true,
216
events: this.events,
217
keyboard_manager: this.keyboard_manager});
218
this.completer = new completer.Completer(this, this.events);
219
};
220
221
/**
222
* Display a widget view in the cell.
223
*/
224
CodeCell.prototype.display_widget_view = function(view_promise) {
225
226
// Display a dummy element
227
var dummy = $('<div/>');
228
this.widget_subarea.append(dummy);
229
230
// Display the view.
231
var that = this;
232
return view_promise.then(function(view) {
233
that.widget_area.show();
234
dummy.replaceWith(view.$el);
235
that.widget_views.push(view);
236
237
// Check the live state of the view's model.
238
if (view.model.comm_live) {
239
that._widget_live(view);
240
} else {
241
that._widget_dead(view);
242
}
243
244
// Listen to comm live events for the view.
245
view.on('comm:live', that._widget_live, that);
246
view.on('comm:dead', that._widget_dead, that);
247
return view;
248
});
249
};
250
251
/**
252
* Handles when a widget loses it's comm connection.
253
* @param {WidgetView} view
254
*/
255
CodeCell.prototype._widget_dead = function(view) {
256
if (this._widgets_live) {
257
this._widgets_live = false;
258
this.widget_area.addClass('connection-problems');
259
}
260
261
};
262
263
/**
264
* Handles when a widget is connected to a live comm.
265
* @param {WidgetView} view
266
*/
267
CodeCell.prototype._widget_live = function(view) {
268
if (!this._widgets_live) {
269
// Check that the other widgets are live too. O(N) operation.
270
// Abort the function at the first dead widget found.
271
for (var i = 0; i < this.widget_views.length; i++) {
272
if (!this.widget_views[i].model.comm_live) return;
273
}
274
this._widgets_live = true;
275
this.widget_area.removeClass('connection-problems');
276
}
277
};
278
279
/** @method bind_events */
280
CodeCell.prototype.bind_events = function () {
281
Cell.prototype.bind_events.apply(this);
282
var that = this;
283
284
this.element.focusout(
285
function() { that.auto_highlight(); }
286
);
287
};
288
289
290
/**
291
* This method gets called in CodeMirror's onKeyDown/onKeyPress
292
* handlers and is used to provide custom key handling. Its return
293
* value is used to determine if CodeMirror should ignore the event:
294
* true = ignore, false = don't ignore.
295
* @method handle_codemirror_keyevent
296
*/
297
298
CodeCell.prototype.handle_codemirror_keyevent = function (editor, event) {
299
300
var that = this;
301
// whatever key is pressed, first, cancel the tooltip request before
302
// they are sent, and remove tooltip if any, except for tab again
303
var tooltip_closed = null;
304
if (event.type === 'keydown' && event.which !== keycodes.tab ) {
305
tooltip_closed = this.tooltip.remove_and_cancel_tooltip();
306
}
307
308
var cur = editor.getCursor();
309
if (event.keyCode === keycodes.enter){
310
this.auto_highlight();
311
}
312
313
if (event.which === keycodes.down && event.type === 'keypress' && this.tooltip.time_before_tooltip >= 0) {
314
// triger on keypress (!) otherwise inconsistent event.which depending on plateform
315
// browser and keyboard layout !
316
// Pressing '(' , request tooltip, don't forget to reappend it
317
// The second argument says to hide the tooltip if the docstring
318
// is actually empty
319
this.tooltip.pending(that, true);
320
} else if ( tooltip_closed && event.which === keycodes.esc && event.type === 'keydown') {
321
// If tooltip is active, cancel it. The call to
322
// remove_and_cancel_tooltip above doesn't pass, force=true.
323
// Because of this it won't actually close the tooltip
324
// if it is in sticky mode. Thus, we have to check again if it is open
325
// and close it with force=true.
326
if (!this.tooltip._hidden) {
327
this.tooltip.remove_and_cancel_tooltip(true);
328
}
329
// If we closed the tooltip, don't let CM or the global handlers
330
// handle this event.
331
event.codemirrorIgnore = true;
332
event._ipkmIgnore = true;
333
event.preventDefault();
334
return true;
335
} else if (event.keyCode === keycodes.tab && event.type === 'keydown' && event.shiftKey) {
336
if (editor.somethingSelected() || editor.getSelections().length !== 1){
337
var anchor = editor.getCursor("anchor");
338
var head = editor.getCursor("head");
339
if( anchor.line !== head.line){
340
return false;
341
}
342
}
343
var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
344
if (pre_cursor.trim() === "") {
345
// Don't show tooltip if the part of the line before the cursor
346
// is empty. In this case, let CodeMirror handle indentation.
347
return false;
348
}
349
this.tooltip.request(that);
350
event.codemirrorIgnore = true;
351
event.preventDefault();
352
return true;
353
} else if (event.keyCode === keycodes.tab && event.type === 'keydown') {
354
// Tab completion.
355
this.tooltip.remove_and_cancel_tooltip();
356
357
// completion does not work on multicursor, it might be possible though in some cases
358
if (editor.somethingSelected() || editor.getSelections().length > 1) {
359
return false;
360
}
361
var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
362
if (pre_cursor.trim() === "") {
363
// Don't autocomplete if the part of the line before the cursor
364
// is empty. In this case, let CodeMirror handle indentation.
365
return false;
366
} else {
367
event.codemirrorIgnore = true;
368
event.preventDefault();
369
this.completer.startCompletion();
370
return true;
371
}
372
}
373
374
// keyboard event wasn't one of those unique to code cells, let's see
375
// if it's one of the generic ones (i.e. check edit mode shortcuts)
376
return Cell.prototype.handle_codemirror_keyevent.apply(this, [editor, event]);
377
};
378
379
// Kernel related calls.
380
381
CodeCell.prototype.set_kernel = function (kernel) {
382
this.kernel = kernel;
383
};
384
385
/**
386
* Execute current code cell to the kernel
387
* @method execute
388
*/
389
CodeCell.prototype.execute = function (stop_on_error) {
390
if (!this.kernel || !this.kernel.is_connected()) {
391
console.log("Can't execute, kernel is not connected.");
392
return;
393
}
394
395
this.output_area.clear_output(false, true);
396
397
if (stop_on_error === undefined) {
398
stop_on_error = true;
399
}
400
401
// Clear widget area
402
for (var i = 0; i < this.widget_views.length; i++) {
403
var view = this.widget_views[i];
404
view.remove();
405
406
// Remove widget live events.
407
view.off('comm:live', this._widget_live);
408
view.off('comm:dead', this._widget_dead);
409
}
410
this.widget_views = [];
411
this.widget_subarea.html('');
412
this.widget_subarea.height('');
413
this.widget_area.height('');
414
this.widget_area.hide();
415
416
var old_msg_id = this.last_msg_id;
417
418
if (old_msg_id) {
419
this.kernel.clear_callbacks_for_msg(old_msg_id);
420
if (old_msg_id) {
421
delete CodeCell.msg_cells[old_msg_id];
422
}
423
}
424
if (this.get_text().trim().length === 0) {
425
// nothing to do
426
this.set_input_prompt(null);
427
return;
428
}
429
this.set_input_prompt('*');
430
this.element.addClass("running");
431
var callbacks = this.get_callbacks();
432
433
this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true,
434
stop_on_error : stop_on_error});
435
CodeCell.msg_cells[this.last_msg_id] = this;
436
this.render();
437
this.events.trigger('execute.CodeCell', {cell: this});
438
};
439
440
/**
441
* Construct the default callbacks for
442
* @method get_callbacks
443
*/
444
CodeCell.prototype.get_callbacks = function () {
445
var that = this;
446
return {
447
shell : {
448
reply : $.proxy(this._handle_execute_reply, this),
449
payload : {
450
set_next_input : $.proxy(this._handle_set_next_input, this),
451
page : $.proxy(this._open_with_pager, this)
452
}
453
},
454
iopub : {
455
output : function() {
456
that.output_area.handle_output.apply(that.output_area, arguments);
457
},
458
clear_output : function() {
459
that.output_area.handle_clear_output.apply(that.output_area, arguments);
460
},
461
},
462
input : $.proxy(this._handle_input_request, this)
463
};
464
};
465
466
CodeCell.prototype._open_with_pager = function (payload) {
467
this.events.trigger('open_with_text.Pager', payload);
468
};
469
470
/**
471
* @method _handle_execute_reply
472
* @private
473
*/
474
CodeCell.prototype._handle_execute_reply = function (msg) {
475
this.set_input_prompt(msg.content.execution_count);
476
this.element.removeClass("running");
477
this.events.trigger('set_dirty.Notebook', {value: true});
478
};
479
480
/**
481
* @method _handle_set_next_input
482
* @private
483
*/
484
CodeCell.prototype._handle_set_next_input = function (payload) {
485
var data = {'cell': this, 'text': payload.text, replace: payload.replace};
486
this.events.trigger('set_next_input.Notebook', data);
487
};
488
489
/**
490
* @method _handle_input_request
491
* @private
492
*/
493
CodeCell.prototype._handle_input_request = function (msg) {
494
this.output_area.append_raw_input(msg);
495
};
496
497
498
// Basic cell manipulation.
499
500
CodeCell.prototype.select = function () {
501
var cont = Cell.prototype.select.apply(this);
502
if (cont) {
503
this.code_mirror.refresh();
504
this.auto_highlight();
505
}
506
return cont;
507
};
508
509
CodeCell.prototype.render = function () {
510
var cont = Cell.prototype.render.apply(this);
511
// Always execute, even if we are already in the rendered state
512
return cont;
513
};
514
515
CodeCell.prototype.select_all = function () {
516
var start = {line: 0, ch: 0};
517
var nlines = this.code_mirror.lineCount();
518
var last_line = this.code_mirror.getLine(nlines-1);
519
var end = {line: nlines-1, ch: last_line.length};
520
this.code_mirror.setSelection(start, end);
521
};
522
523
524
CodeCell.prototype.collapse_output = function () {
525
this.output_area.collapse();
526
};
527
528
529
CodeCell.prototype.expand_output = function () {
530
this.output_area.expand();
531
this.output_area.unscroll_area();
532
};
533
534
CodeCell.prototype.scroll_output = function () {
535
this.output_area.expand();
536
this.output_area.scroll_if_long();
537
};
538
539
CodeCell.prototype.toggle_output = function () {
540
this.output_area.toggle_output();
541
};
542
543
CodeCell.prototype.toggle_output_scroll = function () {
544
this.output_area.toggle_scroll();
545
};
546
547
548
CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
549
var ns;
550
if (prompt_value === undefined || prompt_value === null) {
551
ns = "&nbsp;";
552
} else {
553
ns = encodeURIComponent(prompt_value);
554
}
555
return 'In&nbsp;[' + ns + ']:';
556
};
557
558
CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
559
var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
560
for(var i=1; i < lines_number; i++) {
561
html.push(['...:']);
562
}
563
return html.join('<br/>');
564
};
565
566
CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
567
568
569
CodeCell.prototype.set_input_prompt = function (number) {
570
var nline = 1;
571
if (this.code_mirror !== undefined) {
572
nline = this.code_mirror.lineCount();
573
}
574
this.input_prompt_number = number;
575
var prompt_html = CodeCell.input_prompt_function(this.input_prompt_number, nline);
576
// This HTML call is okay because the user contents are escaped.
577
this.element.find('div.input_prompt').html(prompt_html);
578
};
579
580
581
CodeCell.prototype.clear_input = function () {
582
this.code_mirror.setValue('');
583
};
584
585
586
CodeCell.prototype.get_text = function () {
587
return this.code_mirror.getValue();
588
};
589
590
591
CodeCell.prototype.set_text = function (code) {
592
return this.code_mirror.setValue(code);
593
};
594
595
596
CodeCell.prototype.clear_output = function (wait) {
597
this.output_area.clear_output(wait);
598
this.set_input_prompt();
599
};
600
601
602
// JSON serialization
603
604
CodeCell.prototype.fromJSON = function (data) {
605
Cell.prototype.fromJSON.apply(this, arguments);
606
if (data.cell_type === 'code') {
607
if (data.source !== undefined) {
608
this.set_text(data.source);
609
// make this value the starting point, so that we can only undo
610
// to this state, instead of a blank cell
611
this.code_mirror.clearHistory();
612
this.auto_highlight();
613
}
614
this.set_input_prompt(data.execution_count);
615
this.output_area.trusted = data.metadata.trusted || false;
616
this.output_area.fromJSON(data.outputs, data.metadata);
617
}
618
};
619
620
621
CodeCell.prototype.toJSON = function () {
622
var data = Cell.prototype.toJSON.apply(this);
623
data.source = this.get_text();
624
// is finite protect against undefined and '*' value
625
if (isFinite(this.input_prompt_number)) {
626
data.execution_count = this.input_prompt_number;
627
} else {
628
data.execution_count = null;
629
}
630
var outputs = this.output_area.toJSON();
631
data.outputs = outputs;
632
data.metadata.trusted = this.output_area.trusted;
633
data.metadata.collapsed = this.output_area.collapsed;
634
if (this.output_area.scroll_state === 'auto') {
635
delete data.metadata.scrolled;
636
} else {
637
data.metadata.scrolled = this.output_area.scroll_state;
638
}
639
return data;
640
};
641
642
/**
643
* handle cell level logic when a cell is unselected
644
* @method unselect
645
* @return is the action being taken
646
*/
647
CodeCell.prototype.unselect = function () {
648
var cont = Cell.prototype.unselect.apply(this);
649
if (cont) {
650
// When a code cell is usnelected, make sure that the corresponding
651
// tooltip and completer to that cell is closed.
652
this.tooltip.remove_and_cancel_tooltip(true);
653
if (this.completer !== null) {
654
this.completer.close();
655
}
656
}
657
return cont;
658
};
659
660
// Backwards compatability.
661
IPython.CodeCell = CodeCell;
662
663
return {'CodeCell': CodeCell};
664
});
665
666