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
*
7
* @module cell
8
* @namespace cell
9
* @class Cell
10
*/
11
12
13
define([
14
'base/js/namespace',
15
'jquery',
16
'base/js/utils',
17
'codemirror/lib/codemirror',
18
'codemirror/addon/edit/matchbrackets',
19
'codemirror/addon/edit/closebrackets',
20
'codemirror/addon/comment/comment'
21
], function(IPython, $, utils, CodeMirror, cm_match, cm_closeb, cm_comment) {
22
// TODO: remove IPython dependency here
23
"use strict";
24
25
var overlayHack = CodeMirror.scrollbarModel.native.prototype.overlayHack;
26
27
CodeMirror.scrollbarModel.native.prototype.overlayHack = function () {
28
overlayHack.apply(this, arguments);
29
// Reverse `min-height: 18px` scrollbar hack on OS X
30
// which causes a dead area, making it impossible to click on the last line
31
// when there is horizontal scrolling to do and the "show scrollbar only when scrolling" behavior
32
// is enabled.
33
// This, in turn, has the undesirable behavior of never showing the horizontal scrollbar,
34
// even when it should, which is less problematic, at least.
35
if (/Mac/.test(navigator.platform)) {
36
this.horiz.style.minHeight = "";
37
}
38
};
39
40
var Cell = function (options) {
41
/* Constructor
42
*
43
* The Base `Cell` class from which to inherit.
44
* @constructor
45
* @param:
46
* options: dictionary
47
* Dictionary of keyword arguments.
48
* events: $(Events) instance
49
* config: dictionary
50
* keyboard_manager: KeyboardManager instance
51
*/
52
options = options || {};
53
this.keyboard_manager = options.keyboard_manager;
54
this.events = options.events;
55
var config = utils.mergeopt(Cell, options.config);
56
// superclass default overwrite our default
57
58
this.placeholder = config.placeholder || '';
59
this.selected = false;
60
this.rendered = false;
61
this.mode = 'command';
62
63
// Metadata property
64
var that = this;
65
this._metadata = {};
66
Object.defineProperty(this, 'metadata', {
67
get: function() { return that._metadata; },
68
set: function(value) {
69
that._metadata = value;
70
if (that.celltoolbar) {
71
that.celltoolbar.rebuild();
72
}
73
}
74
});
75
76
// load this from metadata later ?
77
this.user_highlight = 'auto';
78
79
var _local_cm_config = {};
80
if(this.class_config){
81
_local_cm_config = this.class_config.get_sync('cm_config');
82
}
83
this.cm_config = utils.mergeopt({}, config.cm_config, _local_cm_config);
84
this.cell_id = utils.uuid();
85
this._options = config;
86
87
// For JS VM engines optimization, attributes should be all set (even
88
// to null) in the constructor, and if possible, if different subclass
89
// have new attributes with same name, they should be created in the
90
// same order. Easiest is to create and set to null in parent class.
91
92
this.element = null;
93
this.cell_type = this.cell_type || null;
94
this.code_mirror = null;
95
96
this.create_element();
97
if (this.element !== null) {
98
this.element.data("cell", this);
99
this.bind_events();
100
this.init_classes();
101
}
102
};
103
104
Cell.options_default = {
105
cm_config : {
106
indentUnit : 4,
107
readOnly: false,
108
theme: "default",
109
extraKeys: {
110
"Cmd-Right":"goLineRight",
111
"End":"goLineRight",
112
"Cmd-Left":"goLineLeft"
113
}
114
}
115
};
116
117
// FIXME: Workaround CM Bug #332 (Safari segfault on drag)
118
// by disabling drag/drop altogether on Safari
119
// https://github.com/codemirror/CodeMirror/issues/332
120
if (utils.browser[0] == "Safari") {
121
Cell.options_default.cm_config.dragDrop = false;
122
}
123
124
/**
125
* Empty. Subclasses must implement create_element.
126
* This should contain all the code to create the DOM element in notebook
127
* and will be called by Base Class constructor.
128
* @method create_element
129
*/
130
Cell.prototype.create_element = function () {
131
};
132
133
Cell.prototype.init_classes = function () {
134
/**
135
* Call after this.element exists to initialize the css classes
136
* related to selected, rendered and mode.
137
*/
138
if (this.selected) {
139
this.element.addClass('selected');
140
} else {
141
this.element.addClass('unselected');
142
}
143
if (this.rendered) {
144
this.element.addClass('rendered');
145
} else {
146
this.element.addClass('unrendered');
147
}
148
};
149
150
/**
151
* Subclasses can implement override bind_events.
152
* Be carefull to call the parent method when overwriting as it fires event.
153
* this will be triggerd after create_element in constructor.
154
* @method bind_events
155
*/
156
Cell.prototype.bind_events = function () {
157
var that = this;
158
// We trigger events so that Cell doesn't have to depend on Notebook.
159
that.element.click(function (event) {
160
if (!that.selected) {
161
that.events.trigger('select.Cell', {'cell':that});
162
}
163
});
164
that.element.focusin(function (event) {
165
if (!that.selected) {
166
that.events.trigger('select.Cell', {'cell':that});
167
}
168
});
169
if (this.code_mirror) {
170
this.code_mirror.on("change", function(cm, change) {
171
that.events.trigger("set_dirty.Notebook", {value: true});
172
});
173
}
174
if (this.code_mirror) {
175
this.code_mirror.on('focus', function(cm, change) {
176
that.events.trigger('edit_mode.Cell', {cell: that});
177
});
178
}
179
if (this.code_mirror) {
180
this.code_mirror.on('blur', function(cm, change) {
181
that.events.trigger('command_mode.Cell', {cell: that});
182
});
183
}
184
185
this.element.dblclick(function () {
186
if (that.selected === false) {
187
this.events.trigger('select.Cell', {'cell':that});
188
}
189
var cont = that.unrender();
190
if (cont) {
191
that.focus_editor();
192
}
193
});
194
};
195
196
/**
197
* This method gets called in CodeMirror's onKeyDown/onKeyPress
198
* handlers and is used to provide custom key handling.
199
*
200
* To have custom handling, subclasses should override this method, but still call it
201
* in order to process the Edit mode keyboard shortcuts.
202
*
203
* @method handle_codemirror_keyevent
204
* @param {CodeMirror} editor - The codemirror instance bound to the cell
205
* @param {event} event - key press event which either should or should not be handled by CodeMirror
206
* @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
207
*/
208
Cell.prototype.handle_codemirror_keyevent = function (editor, event) {
209
var shortcuts = this.keyboard_manager.edit_shortcuts;
210
211
var cur = editor.getCursor();
212
if((cur.line !== 0 || cur.ch !==0) && event.keyCode === 38){
213
event._ipkmIgnore = true;
214
}
215
var nLastLine = editor.lastLine();
216
if ((event.keyCode === 40) &&
217
((cur.line !== nLastLine) ||
218
(cur.ch !== editor.getLineHandle(nLastLine).text.length))
219
) {
220
event._ipkmIgnore = true;
221
}
222
// if this is an edit_shortcuts shortcut, the global keyboard/shortcut
223
// manager will handle it
224
if (shortcuts.handles(event)) {
225
return true;
226
}
227
228
return false;
229
};
230
231
232
/**
233
* Triger typsetting of math by mathjax on current cell element
234
* @method typeset
235
*/
236
Cell.prototype.typeset = function () {
237
utils.typeset(this.element);
238
};
239
240
/**
241
* handle cell level logic when a cell is selected
242
* @method select
243
* @return is the action being taken
244
*/
245
Cell.prototype.select = function () {
246
if (!this.selected) {
247
this.element.addClass('selected');
248
this.element.removeClass('unselected');
249
this.selected = true;
250
return true;
251
} else {
252
return false;
253
}
254
};
255
256
/**
257
* handle cell level logic when a cell is unselected
258
* @method unselect
259
* @return is the action being taken
260
*/
261
Cell.prototype.unselect = function () {
262
if (this.selected) {
263
this.element.addClass('unselected');
264
this.element.removeClass('selected');
265
this.selected = false;
266
return true;
267
} else {
268
return false;
269
}
270
};
271
272
/**
273
* should be overritten by subclass
274
* @method execute
275
*/
276
Cell.prototype.execute = function () {
277
return;
278
};
279
280
/**
281
* handle cell level logic when a cell is rendered
282
* @method render
283
* @return is the action being taken
284
*/
285
Cell.prototype.render = function () {
286
if (!this.rendered) {
287
this.element.addClass('rendered');
288
this.element.removeClass('unrendered');
289
this.rendered = true;
290
return true;
291
} else {
292
return false;
293
}
294
};
295
296
/**
297
* handle cell level logic when a cell is unrendered
298
* @method unrender
299
* @return is the action being taken
300
*/
301
Cell.prototype.unrender = function () {
302
if (this.rendered) {
303
this.element.addClass('unrendered');
304
this.element.removeClass('rendered');
305
this.rendered = false;
306
return true;
307
} else {
308
return false;
309
}
310
};
311
312
/**
313
* Delegates keyboard shortcut handling to either IPython keyboard
314
* manager when in command mode, or CodeMirror when in edit mode
315
*
316
* @method handle_keyevent
317
* @param {CodeMirror} editor - The codemirror instance bound to the cell
318
* @param {event} - key event to be handled
319
* @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
320
*/
321
Cell.prototype.handle_keyevent = function (editor, event) {
322
if (this.mode === 'command') {
323
return true;
324
} else if (this.mode === 'edit') {
325
return this.handle_codemirror_keyevent(editor, event);
326
}
327
};
328
329
/**
330
* @method at_top
331
* @return {Boolean}
332
*/
333
Cell.prototype.at_top = function () {
334
var cm = this.code_mirror;
335
var cursor = cm.getCursor();
336
if (cursor.line === 0 && cursor.ch === 0) {
337
return true;
338
}
339
return false;
340
};
341
342
/**
343
* @method at_bottom
344
* @return {Boolean}
345
* */
346
Cell.prototype.at_bottom = function () {
347
var cm = this.code_mirror;
348
var cursor = cm.getCursor();
349
if (cursor.line === (cm.lineCount()-1) && cursor.ch === cm.getLine(cursor.line).length) {
350
return true;
351
}
352
return false;
353
};
354
355
/**
356
* enter the command mode for the cell
357
* @method command_mode
358
* @return is the action being taken
359
*/
360
Cell.prototype.command_mode = function () {
361
if (this.mode !== 'command') {
362
this.mode = 'command';
363
return true;
364
} else {
365
return false;
366
}
367
};
368
369
/**
370
* enter the edit mode for the cell
371
* @method command_mode
372
* @return is the action being taken
373
*/
374
Cell.prototype.edit_mode = function () {
375
if (this.mode !== 'edit') {
376
this.mode = 'edit';
377
return true;
378
} else {
379
return false;
380
}
381
};
382
383
Cell.prototype.ensure_focused = function() {
384
if(this.element !== document.activeElement && !this.code_mirror.hasFocus()){
385
this.focus_cell();
386
}
387
}
388
389
/**
390
* Focus the cell in the DOM sense
391
* @method focus_cell
392
*/
393
Cell.prototype.focus_cell = function () {
394
this.element.focus();
395
};
396
397
/**
398
* Focus the editor area so a user can type
399
*
400
* NOTE: If codemirror is focused via a mouse click event, you don't want to
401
* call this because it will cause a page jump.
402
* @method focus_editor
403
*/
404
Cell.prototype.focus_editor = function () {
405
this.refresh();
406
this.code_mirror.focus();
407
};
408
409
/**
410
* Refresh codemirror instance
411
* @method refresh
412
*/
413
Cell.prototype.refresh = function () {
414
if (this.code_mirror) {
415
this.code_mirror.refresh();
416
}
417
};
418
419
/**
420
* should be overritten by subclass
421
* @method get_text
422
*/
423
Cell.prototype.get_text = function () {
424
};
425
426
/**
427
* should be overritten by subclass
428
* @method set_text
429
* @param {string} text
430
*/
431
Cell.prototype.set_text = function (text) {
432
};
433
434
/**
435
* should be overritten by subclass
436
* serialise cell to json.
437
* @method toJSON
438
**/
439
Cell.prototype.toJSON = function () {
440
var data = {};
441
// deepcopy the metadata so copied cells don't share the same object
442
data.metadata = JSON.parse(JSON.stringify(this.metadata));
443
data.cell_type = this.cell_type;
444
return data;
445
};
446
447
/**
448
* should be overritten by subclass
449
* @method fromJSON
450
**/
451
Cell.prototype.fromJSON = function (data) {
452
if (data.metadata !== undefined) {
453
this.metadata = data.metadata;
454
}
455
};
456
457
458
/**
459
* can the cell be split into two cells (false if not deletable)
460
* @method is_splittable
461
**/
462
Cell.prototype.is_splittable = function () {
463
return this.is_deletable();
464
};
465
466
467
/**
468
* can the cell be merged with other cells (false if not deletable)
469
* @method is_mergeable
470
**/
471
Cell.prototype.is_mergeable = function () {
472
return this.is_deletable();
473
};
474
475
/**
476
* is the cell deletable? only false (undeletable) if
477
* metadata.deletable is explicitly false -- everything else
478
* counts as true
479
*
480
* @method is_deletable
481
**/
482
Cell.prototype.is_deletable = function () {
483
if (this.metadata.deletable === false) {
484
return false;
485
}
486
return true;
487
};
488
489
/**
490
* @return {String} - the text before the cursor
491
* @method get_pre_cursor
492
**/
493
Cell.prototype.get_pre_cursor = function () {
494
var cursor = this.code_mirror.getCursor();
495
var text = this.code_mirror.getRange({line:0, ch:0}, cursor);
496
text = text.replace(/^\n+/, '').replace(/\n+$/, '');
497
return text;
498
};
499
500
501
/**
502
* @return {String} - the text after the cursor
503
* @method get_post_cursor
504
**/
505
Cell.prototype.get_post_cursor = function () {
506
var cursor = this.code_mirror.getCursor();
507
var last_line_num = this.code_mirror.lineCount()-1;
508
var last_line_len = this.code_mirror.getLine(last_line_num).length;
509
var end = {line:last_line_num, ch:last_line_len};
510
var text = this.code_mirror.getRange(cursor, end);
511
text = text.replace(/^\n+/, '').replace(/\n+$/, '');
512
return text;
513
};
514
515
/**
516
* Show/Hide CodeMirror LineNumber
517
* @method show_line_numbers
518
*
519
* @param value {Bool} show (true), or hide (false) the line number in CodeMirror
520
**/
521
Cell.prototype.show_line_numbers = function (value) {
522
this.code_mirror.setOption('lineNumbers', value);
523
this.code_mirror.refresh();
524
};
525
526
/**
527
* Toggle CodeMirror LineNumber
528
* @method toggle_line_numbers
529
**/
530
Cell.prototype.toggle_line_numbers = function () {
531
var val = this.code_mirror.getOption('lineNumbers');
532
this.show_line_numbers(!val);
533
};
534
535
/**
536
* Force codemirror highlight mode
537
* @method force_highlight
538
* @param {object} - CodeMirror mode
539
**/
540
Cell.prototype.force_highlight = function(mode) {
541
this.user_highlight = mode;
542
this.auto_highlight();
543
};
544
545
/**
546
* Trigger autodetection of highlight scheme for current cell
547
* @method auto_highlight
548
*/
549
Cell.prototype.auto_highlight = function () {
550
this._auto_highlight(this.class_config.get_sync('highlight_modes'));
551
};
552
553
/**
554
* Try to autodetect cell highlight mode, or use selected mode
555
* @methods _auto_highlight
556
* @private
557
* @param {String|object|undefined} - CodeMirror mode | 'auto'
558
**/
559
Cell.prototype._auto_highlight = function (modes) {
560
/**
561
*Here we handle manually selected modes
562
*/
563
var that = this;
564
var mode;
565
if( this.user_highlight !== undefined && this.user_highlight != 'auto' )
566
{
567
mode = this.user_highlight;
568
CodeMirror.autoLoadMode(this.code_mirror, mode);
569
this.code_mirror.setOption('mode', mode);
570
return;
571
}
572
var current_mode = this.code_mirror.getOption('mode', mode);
573
var first_line = this.code_mirror.getLine(0);
574
// loop on every pairs
575
for(mode in modes) {
576
var regs = modes[mode].reg;
577
// only one key every time but regexp can't be keys...
578
for(var i=0; i<regs.length; i++) {
579
// here we handle non magic_modes.
580
// TODO :
581
// On 3.0 and below, these things were regex.
582
// But now should be string for json-able config.
583
// We should get rid of assuming they might be already
584
// in a later version of IPython.
585
var re = regs[i];
586
if(typeof(re) === 'string'){
587
re = new RegExp(re)
588
}
589
if(first_line.match(re) !== null) {
590
if(current_mode == mode){
591
return;
592
}
593
if (mode.search('magic_') !== 0) {
594
utils.requireCodeMirrorMode(mode, function (spec) {
595
that.code_mirror.setOption('mode', spec);
596
});
597
return;
598
}
599
var open = modes[mode].open || "%%";
600
var close = modes[mode].close || "%%end";
601
var magic_mode = mode;
602
mode = magic_mode.substr(6);
603
if(current_mode == magic_mode){
604
return;
605
}
606
utils.requireCodeMirrorMode(mode, function (spec) {
607
// create on the fly a mode that switch between
608
// plain/text and something else, otherwise `%%` is
609
// source of some highlight issues.
610
CodeMirror.defineMode(magic_mode, function(config) {
611
return CodeMirror.multiplexingMode(
612
CodeMirror.getMode(config, 'text/plain'),
613
// always set something on close
614
{open: open, close: close,
615
mode: CodeMirror.getMode(config, spec),
616
delimStyle: "delimit"
617
}
618
);
619
});
620
that.code_mirror.setOption('mode', magic_mode);
621
});
622
return;
623
}
624
}
625
}
626
// fallback on default
627
var default_mode;
628
try {
629
default_mode = this._options.cm_config.mode;
630
} catch(e) {
631
default_mode = 'text/plain';
632
}
633
if( current_mode === default_mode){
634
return;
635
}
636
this.code_mirror.setOption('mode', default_mode);
637
};
638
639
var UnrecognizedCell = function (options) {
640
/** Constructor for unrecognized cells */
641
Cell.apply(this, arguments);
642
this.cell_type = 'unrecognized';
643
this.celltoolbar = null;
644
this.data = {};
645
646
Object.seal(this);
647
};
648
649
UnrecognizedCell.prototype = Object.create(Cell.prototype);
650
651
652
// cannot merge or split unrecognized cells
653
UnrecognizedCell.prototype.is_mergeable = function () {
654
return false;
655
};
656
657
UnrecognizedCell.prototype.is_splittable = function () {
658
return false;
659
};
660
661
UnrecognizedCell.prototype.toJSON = function () {
662
/**
663
* deepcopy the metadata so copied cells don't share the same object
664
*/
665
return JSON.parse(JSON.stringify(this.data));
666
};
667
668
UnrecognizedCell.prototype.fromJSON = function (data) {
669
this.data = data;
670
if (data.metadata !== undefined) {
671
this.metadata = data.metadata;
672
} else {
673
data.metadata = this.metadata;
674
}
675
this.element.find('.inner_cell').find("a").text("Unrecognized cell type: " + data.cell_type);
676
};
677
678
UnrecognizedCell.prototype.create_element = function () {
679
Cell.prototype.create_element.apply(this, arguments);
680
var cell = this.element = $("<div>").addClass('cell unrecognized_cell');
681
cell.attr('tabindex','2');
682
683
var prompt = $('<div/>').addClass('prompt input_prompt');
684
cell.append(prompt);
685
var inner_cell = $('<div/>').addClass('inner_cell');
686
inner_cell.append(
687
$("<a>")
688
.attr("href", "#")
689
.text("Unrecognized cell type")
690
);
691
cell.append(inner_cell);
692
this.element = cell;
693
};
694
695
UnrecognizedCell.prototype.bind_events = function () {
696
Cell.prototype.bind_events.apply(this, arguments);
697
var cell = this;
698
699
this.element.find('.inner_cell').find("a").click(function () {
700
cell.events.trigger('unrecognized_cell.Cell', {cell: cell});
701
});
702
};
703
704
// Backwards compatibility.
705
IPython.Cell = Cell;
706
707
return {
708
Cell: Cell,
709
UnrecognizedCell: UnrecognizedCell
710
};
711
});
712
713