CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/codemirror/extensions/sagews.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
// I think the extensions are all used to support Sage worksheets...
7
8
import * as CodeMirror from "codemirror";
9
import { defaults, required } from "@cocalc/util/misc";
10
import { IS_MOBILE } from "../../feature";
11
declare var $;
12
13
// Apply a CodeMirror changeObj to this editing buffer.
14
CodeMirror.defineExtension("apply_changeObj", function (changeObj) {
15
// @ts-ignore
16
const editor = this;
17
editor.replaceRange(changeObj.text, changeObj.from, changeObj.to);
18
if (changeObj.next != null) {
19
return editor.apply_changeObj(changeObj.next);
20
}
21
});
22
23
// This is an improved rewrite of simple-hint.js from the CodeMirror3 distribution.
24
// It is used only by sage worksheets and nothing else, currently.
25
CodeMirror.defineExtension("showCompletions", function (opts: {
26
from: CodeMirror.Position;
27
to: CodeMirror.Position;
28
completions: string[];
29
target: string;
30
completions_size?: number;
31
}): void {
32
const { from, to, completions, target, completions_size } = defaults(opts, {
33
from: required,
34
to: required,
35
completions: required,
36
target: required,
37
completions_size: 20,
38
});
39
40
if (completions.length === 0) {
41
return;
42
}
43
44
// @ts-ignore
45
const editor = this;
46
const start_cursor_pos = editor.getCursor();
47
const insert = function (str: string): void {
48
const pos = editor.getCursor();
49
from.line = pos.line;
50
to.line = pos.line;
51
const shift = pos.ch - start_cursor_pos.ch;
52
from.ch += shift;
53
to.ch += shift;
54
editor.replaceRange(str, from, to);
55
};
56
57
if (completions.length === 1) {
58
// do not include target in appended completion if it has a '*'
59
if (target.indexOf("*") === -1) {
60
insert(target + completions[0]);
61
} else {
62
insert(completions[0]);
63
}
64
return;
65
}
66
67
const sel = $("<select>").css("width", "auto");
68
const complete = $("<div>").addClass("webapp-completions").append(sel);
69
for (let c of completions) {
70
// do not include target in appended completion if it has a '*'
71
if (target.indexOf("*") === -1) {
72
sel.append($("<option>").text(target + c));
73
} else {
74
sel.append($("<option>").text(c));
75
}
76
}
77
sel.find(":first").attr("selected", true);
78
sel.attr("size", Math.min(completions_size, completions.length));
79
const pos = editor.cursorCoords(from);
80
81
complete.css({
82
left: pos.left + "px",
83
top: pos.bottom + "px",
84
});
85
$("body").append(complete);
86
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
87
const winW =
88
window.innerWidth ||
89
Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
90
if (winW - pos.left < sel.attr("clientWidth")) {
91
complete.css({ left: pos.left - sel.attr("clientWidth") + "px" });
92
}
93
// Hide scrollbar
94
if (completions.length <= completions_size) {
95
complete.css({ width: sel.attr("clientWidth") - 1 + "px" });
96
}
97
98
let done = false;
99
100
const close = function () {
101
if (done) {
102
return;
103
}
104
done = true;
105
complete.remove();
106
};
107
108
const pick = function () {
109
insert(sel.val());
110
close();
111
if (!IS_MOBILE) {
112
return setTimeout(() => editor.focus(), 50);
113
}
114
};
115
116
sel.blur(pick);
117
sel.dblclick(pick);
118
if (!IS_MOBILE) {
119
// do not do this on mobile, since it makes it unusable!
120
sel.click(pick);
121
}
122
sel.keydown(function (event) {
123
const code = event.keyCode;
124
switch (code) {
125
case 13: // enter
126
pick();
127
return false;
128
case 27:
129
close();
130
editor.focus();
131
return false;
132
default:
133
if (
134
code !== 38 &&
135
code !== 40 &&
136
code !== 33 &&
137
code !== 34 &&
138
!(CodeMirror as any).isModifierKey(event)
139
) {
140
close();
141
editor.focus();
142
// Pass to CodeMirror (e.g., backspace)
143
return editor.triggerOnKeyDown(event);
144
}
145
}
146
});
147
sel.focus();
148
});
149
150
function get_inspect_dialog(editor) {
151
const dialog = $(`\
152
<div class="webapp-codemirror-introspect modal"
153
data-backdrop="static" tabindex="-1" role="dialog" aria-hidden="true">
154
<div class="modal-dialog" style="width:90%">
155
<div class="modal-content">
156
<div class="modal-header">
157
<button type="button" class="close" aria-hidden="true">
158
<span style="font-size:20pt;">×</span>
159
</button>
160
<h4><div class="webapp-codemirror-introspect-title"></div></h4>
161
</div>
162
163
<div class="webapp-codemirror-introspect-content-source-code cm-s-default">
164
</div>
165
<div class="webapp-codemirror-introspect-content-docstring cm-s-default">
166
</div>
167
168
169
<div class="modal-footer">
170
<button class="btn btn-close btn-default">Close</button>
171
</div>
172
</div>
173
</div>
174
</div>\
175
`);
176
dialog.modal();
177
dialog.data("editor", editor);
178
179
dialog.find("button").click(function () {
180
dialog.modal("hide");
181
dialog.remove(); // also remove; we no longer have any use for this element!
182
});
183
184
// see http://stackoverflow.com/questions/8363802/bind-a-function-to-twitter-bootstrap-modal-close
185
dialog.on("hidden.bs.modal", function () {
186
dialog.data("editor").focus?.();
187
dialog.data("editor", 0);
188
});
189
190
return dialog;
191
}
192
193
CodeMirror.defineExtension("showIntrospect", function (opts: {
194
from: CodeMirror.Position;
195
content: string;
196
type: string;
197
target: string;
198
}): void {
199
opts = defaults(opts, {
200
from: required,
201
content: required,
202
type: required, // 'docstring', 'source-code' -- FUTURE:
203
target: required,
204
});
205
// @ts-ignore
206
const editor = this;
207
if (typeof opts.content !== "string") {
208
// If for some reason the content isn't a string (e.g., undefined or an object or something else),
209
// convert it a string, which will display fine.
210
opts.content = `${JSON.stringify(opts.content)}`;
211
}
212
const element = get_inspect_dialog(editor);
213
element.find(".webapp-codemirror-introspect-title").text(opts.target);
214
element.show();
215
let elt;
216
if (opts.type === "source-code") {
217
elt = element.find(".webapp-codemirror-introspect-content-source-code")[0];
218
if (elt != null) {
219
// see https://github.com/sagemathinc/cocalc/issues/1993
220
(CodeMirror as any).runMode(opts.content, "python", elt);
221
}
222
} else {
223
elt = element.find(".webapp-codemirror-introspect-content-docstring")[0];
224
if (elt != null) {
225
// see https://github.com/sagemathinc/cocalc/issues/1993
226
(CodeMirror as any).runMode(opts.content, "text/x-rst", elt);
227
}
228
}
229
});
230
231
232
233