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