pyret-embed 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/build/web/css/editor.css +21 -0
- package/dist/build/web/editor.embed.html +35 -2
- package/dist/build/web/editor.html +34 -1
- package/dist/build/web/js/beforeBlocks.js +110 -8
- package/dist/build/web/js/beforePyret.js +205 -21
- package/dist/build/web/js/cpo-main-ts.js +862 -0
- package/dist/build/web/js/cpo-main.jarr +2 -2
- package/dist/build/web/js/cpo-main.jarr.gz.js +0 -0
- package/dist/build/web/js/cpo-main.jarr.js +2 -2
- package/dist/build/web/js/cpo-main.jarr.min +2 -2
- package/dist/build/web/js/events.js +24 -1
- package/dist/build/web/js/repl-ui.js +7 -2
- package/dist/build/web/js/ts-compiler-lib.js +585 -0
- package/dist/build/web/js/vega.min.js +1 -1
- package/dist/build/web/views/editor.html +34 -1
- package/dist/build/web/views/editor.selfcontained.html +285 -24
- package/dist/pyret.js +1 -1
- package/package.json +2 -1
- package/types/pyret.d.ts +1 -0
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
/*
|
|
2
|
+
TS-compiler flavor of cpo-main.js: the same editor/REPL UI, but with
|
|
3
|
+
compilation done by the TypeScript compiler bundle (window.PyretTSCompiler)
|
|
4
|
+
instead of the Pyret-hosted compiler modules. Execution still goes
|
|
5
|
+
through the builtin load-lib against the page realm, so results,
|
|
6
|
+
check blocks, and runtime errors render exactly as in cpo-main.js.
|
|
7
|
+
|
|
8
|
+
The long list of builtin requires below exists so that the built
|
|
9
|
+
ts jarr instantiates the same builtin:// realm as cpo-main.jarr:
|
|
10
|
+
user programs compiled at runtime link against these instances.
|
|
11
|
+
|
|
12
|
+
The UI sections of this file (the top of theModule and everything in
|
|
13
|
+
withRepl) are copied verbatim from cpo-main.js; the compiler plumbing
|
|
14
|
+
in between is TS-specific (see ts-compiler-lib.js).
|
|
15
|
+
*/
|
|
16
|
+
({
|
|
17
|
+
requires: [
|
|
18
|
+
{ "import-type": "builtin",
|
|
19
|
+
name: "runtime-lib"
|
|
20
|
+
},
|
|
21
|
+
{ "import-type": "builtin",
|
|
22
|
+
name: "load-lib"
|
|
23
|
+
},
|
|
24
|
+
{ "import-type": "dependency",
|
|
25
|
+
protocol: "js-file",
|
|
26
|
+
args: ["./ts-compiler-lib"]
|
|
27
|
+
},
|
|
28
|
+
{ "import-type": "dependency",
|
|
29
|
+
protocol: "js-file",
|
|
30
|
+
args: ["./repl-ui"]
|
|
31
|
+
},
|
|
32
|
+
{ "import-type": "dependency",
|
|
33
|
+
protocol: "js-file",
|
|
34
|
+
args: ["./text-handlers"]
|
|
35
|
+
},
|
|
36
|
+
{ "import-type": "builtin", name: "arrays" },
|
|
37
|
+
{ "import-type": "builtin", name: "ast" },
|
|
38
|
+
{ "import-type": "builtin", name: "base" },
|
|
39
|
+
{ "import-type": "builtin", name: "builtin-modules" },
|
|
40
|
+
{ "import-type": "builtin", name: "chart" },
|
|
41
|
+
{ "import-type": "builtin", name: "chart-lib" },
|
|
42
|
+
{ "import-type": "builtin", name: "charts" },
|
|
43
|
+
{ "import-type": "builtin", name: "charts-lib" },
|
|
44
|
+
{ "import-type": "builtin", name: "charts-util" },
|
|
45
|
+
{ "import-type": "builtin", name: "checker" },
|
|
46
|
+
{ "import-type": "builtin", name: "color" },
|
|
47
|
+
{ "import-type": "builtin", name: "constants" },
|
|
48
|
+
{ "import-type": "builtin", name: "contracts" },
|
|
49
|
+
{ "import-type": "builtin", name: "cpo-builtins" },
|
|
50
|
+
{ "import-type": "builtin", name: "csv" },
|
|
51
|
+
{ "import-type": "builtin", name: "csv-lib" },
|
|
52
|
+
{ "import-type": "builtin", name: "d3-lib" },
|
|
53
|
+
{ "import-type": "builtin", name: "d3-lib-list" },
|
|
54
|
+
{ "import-type": "builtin", name: "data-source" },
|
|
55
|
+
{ "import-type": "builtin", name: "dcic2024" },
|
|
56
|
+
{ "import-type": "builtin", name: "either" },
|
|
57
|
+
{ "import-type": "builtin", name: "empty-context" },
|
|
58
|
+
{ "import-type": "builtin", name: "equality" },
|
|
59
|
+
{ "import-type": "builtin", name: "error" },
|
|
60
|
+
{ "import-type": "builtin", name: "error-display" },
|
|
61
|
+
{ "import-type": "builtin", name: "essentials2020" },
|
|
62
|
+
{ "import-type": "builtin", name: "essentials2021" },
|
|
63
|
+
{ "import-type": "builtin", name: "essentials2024" },
|
|
64
|
+
{ "import-type": "builtin", name: "fetch" },
|
|
65
|
+
{ "import-type": "builtin", name: "ffi" },
|
|
66
|
+
{ "import-type": "builtin", name: "file" },
|
|
67
|
+
{ "import-type": "builtin", name: "filelib" },
|
|
68
|
+
{ "import-type": "builtin", name: "filesystem" },
|
|
69
|
+
{ "import-type": "builtin", name: "filesystem-internal" },
|
|
70
|
+
{ "import-type": "builtin", name: "format" },
|
|
71
|
+
{ "import-type": "builtin", name: "gdrive-sheets" },
|
|
72
|
+
{ "import-type": "builtin", name: "global" },
|
|
73
|
+
{ "import-type": "builtin", name: "image" },
|
|
74
|
+
{ "import-type": "builtin", name: "image-lib" },
|
|
75
|
+
{ "import-type": "builtin", name: "image-structs" },
|
|
76
|
+
{ "import-type": "builtin", name: "image-typed" },
|
|
77
|
+
{ "import-type": "builtin", name: "internal-image-shared" },
|
|
78
|
+
{ "import-type": "builtin", name: "internal-image-typed" },
|
|
79
|
+
{ "import-type": "builtin", name: "internal-image-untyped" },
|
|
80
|
+
{ "import-type": "builtin", name: "json" },
|
|
81
|
+
{ "import-type": "builtin", name: "json-structs" },
|
|
82
|
+
{ "import-type": "builtin", name: "lists" },
|
|
83
|
+
{ "import-type": "builtin", name: "make-image" },
|
|
84
|
+
{ "import-type": "builtin", name: "math" },
|
|
85
|
+
{ "import-type": "builtin", name: "matrices" },
|
|
86
|
+
{ "import-type": "builtin", name: "matrix-util" },
|
|
87
|
+
{ "import-type": "builtin", name: "multiple-regression" },
|
|
88
|
+
{ "import-type": "builtin", name: "option" },
|
|
89
|
+
{ "import-type": "builtin", name: "parse-pyret" },
|
|
90
|
+
{ "import-type": "builtin", name: "pathlib" },
|
|
91
|
+
{ "import-type": "builtin", name: "pick" },
|
|
92
|
+
{ "import-type": "builtin", name: "plot" },
|
|
93
|
+
{ "import-type": "builtin", name: "plot-lib" },
|
|
94
|
+
{ "import-type": "builtin", name: "plot-lib-list" },
|
|
95
|
+
{ "import-type": "builtin", name: "plot-list" },
|
|
96
|
+
{ "import-type": "builtin", name: "pprint" },
|
|
97
|
+
{ "import-type": "builtin", name: "reactor-events" },
|
|
98
|
+
{ "import-type": "builtin", name: "reactors" },
|
|
99
|
+
{ "import-type": "builtin", name: "render-error-display" },
|
|
100
|
+
{ "import-type": "builtin", name: "s-exp" },
|
|
101
|
+
{ "import-type": "builtin", name: "s-exp-structs" },
|
|
102
|
+
{ "import-type": "builtin", name: "sets" },
|
|
103
|
+
{ "import-type": "builtin", name: "sha" },
|
|
104
|
+
{ "import-type": "builtin", name: "source-map-lib" },
|
|
105
|
+
{ "import-type": "builtin", name: "srcloc" },
|
|
106
|
+
{ "import-type": "builtin", name: "starter2024" },
|
|
107
|
+
{ "import-type": "builtin", name: "statistics" },
|
|
108
|
+
{ "import-type": "builtin", name: "str-dict" },
|
|
109
|
+
{ "import-type": "builtin", name: "string-dict" },
|
|
110
|
+
{ "import-type": "builtin", name: "table" },
|
|
111
|
+
{ "import-type": "builtin", name: "tables" },
|
|
112
|
+
{ "import-type": "builtin", name: "timing" },
|
|
113
|
+
{ "import-type": "builtin", name: "type-logger" },
|
|
114
|
+
{ "import-type": "builtin", name: "valueskeleton" },
|
|
115
|
+
{ "import-type": "builtin", name: "vector-util" },
|
|
116
|
+
{ "import-type": "builtin", name: "world" },
|
|
117
|
+
{ "import-type": "builtin", name: "world-lib" }
|
|
118
|
+
],
|
|
119
|
+
nativeRequires: [
|
|
120
|
+
"cpo/cpo-builtin-modules",
|
|
121
|
+
"cpo/modal-prompt"
|
|
122
|
+
],
|
|
123
|
+
provides: {
|
|
124
|
+
values: {
|
|
125
|
+
"repl": "tany"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
theModule: function(runtime, namespace, uri,
|
|
129
|
+
runtimeLib, loadLib, tsLib, replUI, textHandlers
|
|
130
|
+
/* , ...parity builtins, cpoModules, modalPrompt */) {
|
|
131
|
+
// The parity builtin requires and the native requires arrive
|
|
132
|
+
// positionally after the named parameters; only the natives are used.
|
|
133
|
+
var allArgs = Array.prototype.slice.call(arguments);
|
|
134
|
+
var cpoModules = allArgs[allArgs.length - 2];
|
|
135
|
+
var modalPrompt = allArgs[allArgs.length - 1];
|
|
136
|
+
|
|
137
|
+
var replContainer = $("<div>").addClass("repl");
|
|
138
|
+
replContainer.attr("tabindex", "-1").attr('role', 'application');
|
|
139
|
+
//replContainer.attr("aria-hidden", "true");
|
|
140
|
+
$("#REPL").append(replContainer);
|
|
141
|
+
|
|
142
|
+
var logDetailedOption = $("#detailed-logging");
|
|
143
|
+
|
|
144
|
+
if(localSettings.getItem('log-detailed') !== null) {
|
|
145
|
+
logDetailedOption.prop("checked",
|
|
146
|
+
localSettings.getItem('log-detailed') == 'true');
|
|
147
|
+
} else {
|
|
148
|
+
localSettings.setItem('log-detailed', false);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
logDetailedOption.on('change', function () {
|
|
152
|
+
localSettings.setItem('log-detailed', this.checked);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
localSettings.change("log-detailed", function(_, newValue) {
|
|
156
|
+
logDetailedOption[0].checked = newValue == 'true';
|
|
157
|
+
logDetailedOption.attr('aria-pressed', '' + (newValue == 'true'));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
runtime.setParam("imgUrlProxy", function(s) {
|
|
161
|
+
var a = document.createElement("a");
|
|
162
|
+
a.href = s;
|
|
163
|
+
if(a.origin === window.APP_BASE_URL) {
|
|
164
|
+
return s;
|
|
165
|
+
}
|
|
166
|
+
else if(window.IMAGE_PROXY_BYPASS) {
|
|
167
|
+
return s;
|
|
168
|
+
}
|
|
169
|
+
/*
|
|
170
|
+
else if(a.hostname === "drive.google.com" && a.pathname === "/uc") {
|
|
171
|
+
return s;
|
|
172
|
+
}
|
|
173
|
+
*/
|
|
174
|
+
else {
|
|
175
|
+
return window.APP_BASE_URL + "/downloadImg?" + s;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
var gf = runtime.getField;
|
|
180
|
+
var gmf = function(m, f) { return gf(gf(m, "values"), f); };
|
|
181
|
+
|
|
182
|
+
// NOTE(joe): This line is "cheating" by mixing runtime levels,
|
|
183
|
+
// and uses the same runtime for the compiler and running code.
|
|
184
|
+
// Usually you can only get a new Runtime by calling create, but
|
|
185
|
+
// here we magic the current runtime into one.
|
|
186
|
+
var pyRuntime = gf(gf(runtimeLib, "internal").brandRuntime, "brand").app(
|
|
187
|
+
runtime.makeObject({
|
|
188
|
+
"runtime": runtime.makeOpaque(runtime)
|
|
189
|
+
}));
|
|
190
|
+
var pyRealm = gf(loadLib, "internal").makeRealm(cpoModules.getRealm());
|
|
191
|
+
|
|
192
|
+
var T = tsLib.tsCompiler();
|
|
193
|
+
var builtinSupport = tsLib.makeBuiltinSupport(runtime.getParam("staticModules"));
|
|
194
|
+
var executor = tsLib.makeExecutor(pyRuntime);
|
|
195
|
+
|
|
196
|
+
// Mirrors the stock editor's finder configuration (cpo-main.js): the
|
|
197
|
+
// url-file mode comes from the host page, and the root compile context
|
|
198
|
+
// seeds the load-path at "." (the open tab's directory, which is what
|
|
199
|
+
// the embedding host resolves every fs path against).
|
|
200
|
+
var urlFileMode = window.URL_FILE_MODE || "all-remote";
|
|
201
|
+
var tsRepl = T.repl.makeRepl(
|
|
202
|
+
executor,
|
|
203
|
+
builtinSupport.loadables,
|
|
204
|
+
pyRealm,
|
|
205
|
+
{ "load-path": "." },
|
|
206
|
+
tsLib.makeFinderFactory(builtinSupport, urlFileMode));
|
|
207
|
+
|
|
208
|
+
function tsOptions(options) {
|
|
209
|
+
var o = Object.assign({}, T.compileStructs.defaultCompileOptions);
|
|
210
|
+
o.checkMode = true;
|
|
211
|
+
o.checks = "main";
|
|
212
|
+
o.checkAll = !!options.checkAll;
|
|
213
|
+
o.typeCheck = !!options.typeCheck;
|
|
214
|
+
o.displayProgress = false;
|
|
215
|
+
o.log = function(_s, _toClear) {};
|
|
216
|
+
o.logError = function(s) { console.error(s); };
|
|
217
|
+
o.onCompile = function(_locator, loadable, _trace) { return loadable; };
|
|
218
|
+
return o;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
var currentCancellation = null;
|
|
222
|
+
function newCancellation() {
|
|
223
|
+
currentCancellation = T.repl.makeCancellation();
|
|
224
|
+
return currentCancellation;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
A cancelled run is a user break that happened to land before the program
|
|
229
|
+
became a Pyret computation, so it is reported as one: the same
|
|
230
|
+
ffi.userBreak the runtime raises.
|
|
231
|
+
*/
|
|
232
|
+
function reportRunEnd(ret, err) {
|
|
233
|
+
if (err instanceof T.repl.Cancelled) { tsLib.resolveWithUserBreak(ret); }
|
|
234
|
+
else { tsLib.resolveWithError(ret, err); }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var jsRepl = {
|
|
238
|
+
runtime: runtime,
|
|
239
|
+
/*
|
|
240
|
+
This should not be called while a Pyret stack is running
|
|
241
|
+
*/
|
|
242
|
+
restartInteractions: function(source, options) {
|
|
243
|
+
var ret = Q.defer();
|
|
244
|
+
var cancel = newCancellation();
|
|
245
|
+
setTimeout(function() {
|
|
246
|
+
var opts = tsOptions(options);
|
|
247
|
+
var defsLocator = tsRepl.makeDefinitionsLocator(
|
|
248
|
+
function() { return source; },
|
|
249
|
+
T.compileStructs.standardGlobals);
|
|
250
|
+
tsRepl.restartInteractions(defsLocator, opts, cancel)
|
|
251
|
+
.then(function(either) { tsLib.resolveWithEither(ret, either); })
|
|
252
|
+
.catch(function(err) { reportRunEnd(ret, err); });
|
|
253
|
+
}, 0);
|
|
254
|
+
return ret.promise;
|
|
255
|
+
},
|
|
256
|
+
run: function(str, name) {
|
|
257
|
+
var ret = Q.defer();
|
|
258
|
+
var cancel = newCancellation();
|
|
259
|
+
setTimeout(function() {
|
|
260
|
+
var locator = tsRepl.makeInteractionLocator(function() { return str; });
|
|
261
|
+
tsRepl.runInteraction(locator, cancel)
|
|
262
|
+
.then(function(either) { tsLib.resolveWithEither(ret, either); })
|
|
263
|
+
.catch(function(err) { reportRunEnd(ret, err); });
|
|
264
|
+
}, 0);
|
|
265
|
+
return ret.promise;
|
|
266
|
+
},
|
|
267
|
+
pause: function(afterPause) {
|
|
268
|
+
runtime.schedulePause(function(resumer) {
|
|
269
|
+
afterPause(resumer);
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
/*
|
|
273
|
+
breakAll() kills all Pyret computation; the cancellation
|
|
274
|
+
is for pure JS computation (the compiler) that is no longer
|
|
275
|
+
Pyret code on a break-able Pyret stack
|
|
276
|
+
*/
|
|
277
|
+
stop: function() {
|
|
278
|
+
if (currentCancellation !== null) { currentCancellation.cancel(); }
|
|
279
|
+
runtime.breakAll();
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
return withRepl(jsRepl);
|
|
284
|
+
|
|
285
|
+
function withRepl(repl) {
|
|
286
|
+
var runButton = $("#runButton");
|
|
287
|
+
|
|
288
|
+
var replWidget =
|
|
289
|
+
replUI.makeRepl(replContainer, repl, runtime, {
|
|
290
|
+
breakButton: $("#breakButton"),
|
|
291
|
+
runButton: runButton,
|
|
292
|
+
runDropdown: $('#runDropdown')
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// NOTE(joe): assigned on window for embedding API in events.js, and for debugging
|
|
296
|
+
// NOTE(joe): Some of the CPO internals use Q promises. The withResolvers pattern
|
|
297
|
+
// promotes these to real JS promises that will work with e.g. async functions.
|
|
298
|
+
window.RUN_CODE = CPO.RUN_CODE = async function(src) {
|
|
299
|
+
const result = doRunAction(src, true);
|
|
300
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
301
|
+
result.then(resolve);
|
|
302
|
+
result.catch(reject);
|
|
303
|
+
return promise;
|
|
304
|
+
};
|
|
305
|
+
window.RUN_INTERACTION = CPO.RUN_INTERACTION = async function(src) {
|
|
306
|
+
const result = replWidget.runner(src, true);
|
|
307
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
308
|
+
result.then(resolve);
|
|
309
|
+
result.catch(reject);
|
|
310
|
+
return promise;
|
|
311
|
+
};
|
|
312
|
+
window.replWidget = CPO.replWidget = replWidget;
|
|
313
|
+
|
|
314
|
+
/*
|
|
315
|
+
$("#runDropdown").click(function() {
|
|
316
|
+
$("#run-dropdown-content").toggle();
|
|
317
|
+
});
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
// CPO.editor is set in beforePyret.js
|
|
321
|
+
var editor = CPO.editor;
|
|
322
|
+
var currentAction = "run";
|
|
323
|
+
|
|
324
|
+
$("#select-run").click(function() {
|
|
325
|
+
runButton.text("Run");
|
|
326
|
+
currentAction = "run";
|
|
327
|
+
doRunAction(editor.cm.getValue(), false);
|
|
328
|
+
$('#runDropdown').attr('aria-expanded', 'false');
|
|
329
|
+
$("#run-dropdown-content").attr('aria-hidden', 'true').hide();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
$("#select-tc-run").click(function() {
|
|
333
|
+
runButton.text("Type-check and Run");
|
|
334
|
+
currentAction = "tc-and-run";
|
|
335
|
+
doRunAction(editor.cm.getValue(), false);
|
|
336
|
+
$('#runDropdown').attr('aria-expanded', 'false');
|
|
337
|
+
$("#run-dropdown-content").attr('aria-hidden', 'true').hide();
|
|
338
|
+
});
|
|
339
|
+
/*
|
|
340
|
+
$("#select-scsh").click(function() {
|
|
341
|
+
highlightMode = "scsh"; $("#run-dropdown-content").hide();});
|
|
342
|
+
$("#select-scmh").click(function() {
|
|
343
|
+
highlightMode = "scmh"; $("#run-dropdown-content").hide();});
|
|
344
|
+
$("#select-mcmh").click(function() {
|
|
345
|
+
highlightMode = "mcmh"; $("#run-dropdown-content").hide();});
|
|
346
|
+
*/
|
|
347
|
+
function doRunAction(src, synthetic) {
|
|
348
|
+
if(!synthetic) {
|
|
349
|
+
CPO.events.triggerOnRun();
|
|
350
|
+
}
|
|
351
|
+
editor.cm.operation(function() {
|
|
352
|
+
editor.cm.clearGutter("test-marker-gutter");
|
|
353
|
+
var marks = editor.cm.getAllMarks();
|
|
354
|
+
document.getElementById("main").dataset.highlights = "";
|
|
355
|
+
editor.cm.eachLine(function(lh){
|
|
356
|
+
editor.cm.removeLineClass(lh, "background");});
|
|
357
|
+
for(var i = 0; i < marks.length; i++) {
|
|
358
|
+
const attribs = marks[i].attributes;
|
|
359
|
+
if(!(attribs && attribs.useline)) {
|
|
360
|
+
marks[i].clear();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
var sheet = document.getElementById("highlight-styles").sheet;
|
|
365
|
+
for(var i=0; i< sheet.cssRules.length; i++) {
|
|
366
|
+
sheet.deleteRule(i);
|
|
367
|
+
}
|
|
368
|
+
switch (currentAction) {
|
|
369
|
+
case "run":
|
|
370
|
+
return replWidget.runCode(src, {check: true, cm: editor.cm});
|
|
371
|
+
case "tc-and-run":
|
|
372
|
+
return replWidget.runCode(src, {check: true, cm: editor.cm, "type-check": true});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
runButton.on("click", function() { doRunAction(editor.cm.getValue(), false); });
|
|
377
|
+
|
|
378
|
+
$(window).on("keyup", function(e) {
|
|
379
|
+
if(e.keyCode === 27) { // "ESC"
|
|
380
|
+
$("#help-keys").fadeOut(500);
|
|
381
|
+
e.stopImmediatePropagation();
|
|
382
|
+
e.preventDefault();
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
/* Documentation Overlay */
|
|
387
|
+
/*
|
|
388
|
+
NOTE(joe): Skipping this for now, until HTTPS solution for docs worked out
|
|
389
|
+
$("#docs").on("click", function(e){
|
|
390
|
+
$("#doc-containment").toggle();
|
|
391
|
+
e.stopImmediatePropagation();
|
|
392
|
+
e.preventDefault();
|
|
393
|
+
});
|
|
394
|
+
*/
|
|
395
|
+
|
|
396
|
+
$("#doc-close").on("click", function(e){
|
|
397
|
+
$("#doc-containment").toggle();
|
|
398
|
+
e.stopImmediatePropagation();
|
|
399
|
+
e.preventDefault();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
$("#doc-overlay").draggable({
|
|
403
|
+
start: fixIframe,
|
|
404
|
+
stop: fixIframe,
|
|
405
|
+
handle: "#doc-bar",
|
|
406
|
+
cancel: "#doc-close"
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
$("#doc-overlay").resizable({
|
|
410
|
+
handles: {
|
|
411
|
+
s:"#doc-bottom",
|
|
412
|
+
e: "#doc-right",
|
|
413
|
+
w:"#doc-left",
|
|
414
|
+
sw: "#doc-sw-corner",
|
|
415
|
+
se:"#doc-se-corner"},
|
|
416
|
+
start: fixIframe,
|
|
417
|
+
stop: fixIframe,
|
|
418
|
+
containment: "#doc-containment",
|
|
419
|
+
scroll: false
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
function fixIframe() {
|
|
423
|
+
$("#doc-cover").toggle();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
$('#font-plus').click(changeFont);
|
|
427
|
+
$('#font-minus').click(changeFont);
|
|
428
|
+
|
|
429
|
+
function changeFont(e){
|
|
430
|
+
fontSize = parseInt($('#main').css("font-size"));
|
|
431
|
+
if ($(e.target).is("#font-plus")) {
|
|
432
|
+
if (fontSize < 32) {
|
|
433
|
+
$('#main').css('font-size', '+=2');
|
|
434
|
+
} else if (fontSize < 55) {
|
|
435
|
+
$('#main').css('font-size', '+=4');
|
|
436
|
+
}
|
|
437
|
+
} else if ($(e.target).is("#font-minus")) {
|
|
438
|
+
if (fontSize > 32) {
|
|
439
|
+
$('#main').css('font-size', '-=4');
|
|
440
|
+
} else if (fontSize > 10) {
|
|
441
|
+
$('#main').css('font-size', '-=2');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
editor.refresh();
|
|
445
|
+
replWidget.refresh();
|
|
446
|
+
updateFontSizeMenuText();
|
|
447
|
+
}
|
|
448
|
+
function formatFontSizeMenuText(size) {
|
|
449
|
+
return "Font size: " + Math.round(parseFloat(size));
|
|
450
|
+
}
|
|
451
|
+
function updateFontSizeMenuText() {
|
|
452
|
+
$('#font-label').text(formatFontSizeMenuText($('#main').css("font-size")));
|
|
453
|
+
}
|
|
454
|
+
updateFontSizeMenuText();
|
|
455
|
+
|
|
456
|
+
var curTheme = document.getElementById("theme-select").value;
|
|
457
|
+
var themeSelect = $("#theme-select");
|
|
458
|
+
|
|
459
|
+
function applyTheme(theme) {
|
|
460
|
+
themeSelect.val(theme);
|
|
461
|
+
$("body").removeClass(curTheme).addClass(theme);
|
|
462
|
+
curTheme = theme;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
applyTheme(curTheme);
|
|
466
|
+
|
|
467
|
+
$("#theme").change(function(e) {
|
|
468
|
+
var value = e.target.value;
|
|
469
|
+
applyTheme(value);
|
|
470
|
+
|
|
471
|
+
// track theme in local settings
|
|
472
|
+
localSettings.setItem('theme', curTheme);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
localSettings.change("theme", function(_, newTheme) {
|
|
476
|
+
applyTheme(newTheme);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
$('.notificationArea').click(function() {$('.notificationArea span').fadeOut(1000);});
|
|
480
|
+
|
|
481
|
+
editor.cm.on('beforeChange', function(instance, changeObj){textHandlers.autoCorrect(instance, changeObj, editor.cm);});
|
|
482
|
+
|
|
483
|
+
// Resizable
|
|
484
|
+
var replHeight = $( "#REPL" ).height();
|
|
485
|
+
var editorEvenSplit = true;
|
|
486
|
+
$( "#REPL" ).resizable({
|
|
487
|
+
maxHeight: replHeight,
|
|
488
|
+
maxWidth: window.innerWidth - 128,
|
|
489
|
+
minHeight: replHeight,
|
|
490
|
+
minWidth: 100,
|
|
491
|
+
handles: {"w": "#handle"}});
|
|
492
|
+
|
|
493
|
+
$( "#REPL" ).on( "resize", leftResize);
|
|
494
|
+
$( "#REPL" ).on( "resize", function() {editorEvenSplit = false;});
|
|
495
|
+
|
|
496
|
+
function leftResize(event, ui) {
|
|
497
|
+
var leftWidth = (window.innerWidth - ui.size.width)
|
|
498
|
+
$(".replMain").css("width", leftWidth + "px");
|
|
499
|
+
editor.refresh();
|
|
500
|
+
replWidget.refresh();
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
$( "#REPL" ).on( "resizestop", toPercent);
|
|
504
|
+
|
|
505
|
+
var rightResizePct = 50;
|
|
506
|
+
var leftResizePct = 50;
|
|
507
|
+
|
|
508
|
+
function toPercent(event, ui) {
|
|
509
|
+
rightResizePct = (ui.size.width / window.innerWidth) * 100
|
|
510
|
+
leftResizePct = 100 - rightResizePct
|
|
511
|
+
setEditorSize(leftResizePct, rightResizePct);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
$( window ).resize( function() {
|
|
515
|
+
$( "#REPL" ).resizable( "option", "maxWidth", window.innerWidth - 128);
|
|
516
|
+
});
|
|
517
|
+
// End Resizable
|
|
518
|
+
|
|
519
|
+
function setEditorSize(leftPct, rightPct) {
|
|
520
|
+
$( "#REPL" ).css( "width", rightPct + "%");
|
|
521
|
+
$( "#REPL" ).css( "left", leftPct + "%");
|
|
522
|
+
$(".replMain").css("width", leftPct + "%");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function toggleEditorSize() {
|
|
526
|
+
if(editorEvenSplit) {
|
|
527
|
+
editorEvenSplit = false;
|
|
528
|
+
setEditorSize(leftResizePct, rightResizePct);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
editorEvenSplit = true;
|
|
532
|
+
setEditorSize("50", "50");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// save
|
|
537
|
+
// On Mac mod ends up mapping to command+s whereas on Windows and Linux it maps to ctrl+s.
|
|
538
|
+
// Saving has a special condition: when embedded we want the Ctrl-S to
|
|
539
|
+
// propagate up. We could fire a special “save” event, but for contexts
|
|
540
|
+
// like VScode it is nice to have the “real” Cmd-S event fire to get
|
|
541
|
+
// good default behavior
|
|
542
|
+
if(!PYRET_IS_EMBEDDED) {
|
|
543
|
+
Mousetrap.bindGlobal('mod+s', function(e) {
|
|
544
|
+
CPO.save();
|
|
545
|
+
e.stopImmediatePropagation();
|
|
546
|
+
e.preventDefault();
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// resize, Toggle sizing of the editor window between 50% and last resize
|
|
551
|
+
Mousetrap.bindGlobal('ctrl+m', function(e){
|
|
552
|
+
toggleEditorSize();
|
|
553
|
+
e.stopImmediatePropagation();
|
|
554
|
+
e.preventDefault();
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// run the definitions area
|
|
558
|
+
Mousetrap.bindGlobal('ctrl+enter', function(e){
|
|
559
|
+
doRunAction(editor.cm.getValue(), false);
|
|
560
|
+
CPO.autoSave();
|
|
561
|
+
e.stopImmediatePropagation();
|
|
562
|
+
e.preventDefault();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
function reciteHelp() {
|
|
566
|
+
CPO.sayAndForget(
|
|
567
|
+
"Press Escape to exit help. " +
|
|
568
|
+
"Control question mark: recite help. " +
|
|
569
|
+
"Control s: save. " +
|
|
570
|
+
"F6 and shift-F6: cycle focus through regions. " +
|
|
571
|
+
"F7 or Control enter: run the code in the definitions window. " +
|
|
572
|
+
"F11: insert image. " +
|
|
573
|
+
"Control left: move cursor left by one word. " +
|
|
574
|
+
"Control right: move cursor right by one word. " +
|
|
575
|
+
"Alt left: if cursor is just before a right parenthesis or end keyword, " +
|
|
576
|
+
"move left to matching delimiter, " +
|
|
577
|
+
"otherwise move left by one word. " +
|
|
578
|
+
"Alt right: like alt left, but move right. " +
|
|
579
|
+
"Escape left: synonym for alt left, in case alt key is used by browser. " +
|
|
580
|
+
"Escape right: synonym for alt right."
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// pull up help menu
|
|
585
|
+
Mousetrap.bindGlobal('ctrl+shift+/', function(e) {
|
|
586
|
+
$("#help-keys").fadeIn(100);
|
|
587
|
+
reciteHelp();
|
|
588
|
+
e.stopImmediatePropagation();
|
|
589
|
+
e.preventDefault();
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
$('#ctrl-question').click(function() {
|
|
593
|
+
$('#help-keys').fadeIn(100);
|
|
594
|
+
reciteHelp();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
Mousetrap.bindGlobal('f6', function(e) {
|
|
598
|
+
// cycle focus (forward)
|
|
599
|
+
CPO.cycleFocus();
|
|
600
|
+
e.stopImmediatePropagation();
|
|
601
|
+
e.preventDefault();
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
Mousetrap.bindGlobal('shift+f6', function(e) {
|
|
605
|
+
// cycle focus backward
|
|
606
|
+
CPO.cycleFocus(true);
|
|
607
|
+
e.stopImmediatePropagation();
|
|
608
|
+
e.preventDefault();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
Mousetrap.bindGlobal('shift+tab', function(e) {
|
|
612
|
+
// cycle focus backward
|
|
613
|
+
//console.log('mouse shift+tab')
|
|
614
|
+
CPO.cycleFocus(true);
|
|
615
|
+
e.stopImmediatePropagation();
|
|
616
|
+
e.preventDefault();
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
Mousetrap.bindGlobal('f7', function(e) {
|
|
620
|
+
doRunAction(editor.cm.getValue(), false);
|
|
621
|
+
CPO.autoSave();
|
|
622
|
+
e.stopImmediatePropagation();
|
|
623
|
+
e.preventDefault();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
Mousetrap.bindGlobal('f8', function(e) {
|
|
627
|
+
$('#breakButton').click();
|
|
628
|
+
e.stopImmediatePropagation();
|
|
629
|
+
e.preventDefault();
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
Mousetrap.bindGlobal('f9', function(e) {
|
|
633
|
+
var sc = $('#shareContainer');
|
|
634
|
+
if (sc) {
|
|
635
|
+
var sl = sc[0].childNodes[0];
|
|
636
|
+
sl.click();
|
|
637
|
+
}
|
|
638
|
+
e.stopImmediatePropagation();
|
|
639
|
+
e.preventDefault();
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
Mousetrap.bindGlobal('f11', function(e) {
|
|
643
|
+
$('#insert').click();
|
|
644
|
+
e.stopImmediatePropagation();
|
|
645
|
+
e.preventDefault();
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
// Used for image definition naming (identifier: "img" + curImg)
|
|
649
|
+
var curImg = 0;
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Sets curImg to a value which will not clash with the code in
|
|
653
|
+
* contents (note: this is done conservatively -- the estimation
|
|
654
|
+
* is "dumb" in that it pays no attention to token types)
|
|
655
|
+
*/
|
|
656
|
+
function inferCurImg(contents) {
|
|
657
|
+
var query = /img([0-9]+)[\s\n\r]*=/g;
|
|
658
|
+
var maxSoFar = 0;
|
|
659
|
+
var res;
|
|
660
|
+
while((res = query.exec(contents)) !== null) {
|
|
661
|
+
maxSoFar = Math.max(maxSoFar, Number(res[1]));
|
|
662
|
+
}
|
|
663
|
+
curImg = maxSoFar + 1;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
var photoPrompt = function(count) {
|
|
667
|
+
var plural = count > 1;
|
|
668
|
+
return new modalPrompt({
|
|
669
|
+
title: "Import options",
|
|
670
|
+
style: "radio",
|
|
671
|
+
submitText: "Import",
|
|
672
|
+
cancelText: "Close",
|
|
673
|
+
options: [
|
|
674
|
+
{
|
|
675
|
+
message: "Value" + (plural ? "s" : ""),
|
|
676
|
+
value: "values",
|
|
677
|
+
example: 'image-url("<URL>")'
|
|
678
|
+
+ (plural ? '\n'
|
|
679
|
+
+ (count > 2 ? '# ' + (count-2) + ' more...\n': '')
|
|
680
|
+
+ 'image-url("<URL>")'
|
|
681
|
+
: '')
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
message: "Definition" + (plural ? "s" : ""),
|
|
685
|
+
value: "defs",
|
|
686
|
+
example: 'image' + (plural ? '0' : '') + ' = image-url("<URL>")'
|
|
687
|
+
+ (plural ?
|
|
688
|
+
(count > 2 ? '\n# ' + (count-2) + ' more...' : '')
|
|
689
|
+
+ '\nimage' + (count-1) + ' = image-url("<URL>")'
|
|
690
|
+
: '')
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
message: "List",
|
|
694
|
+
value: "list",
|
|
695
|
+
example: '[list: image-url("<URL>")'
|
|
696
|
+
+ (plural ?
|
|
697
|
+
',\n' + (count > 2 ? ' # ' + (count-2) + ' more...\n' : '')
|
|
698
|
+
+ ' image-url("<URL>")]'
|
|
699
|
+
: ']')
|
|
700
|
+
}]
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
var lastSave = 0;
|
|
705
|
+
function handlePickerData(documents, picker, drive) {
|
|
706
|
+
// File loaded
|
|
707
|
+
if (documents[0][picker.Document.TYPE] === "file") {
|
|
708
|
+
var id = documents[0][picker.Document.ID];
|
|
709
|
+
function load(here) {
|
|
710
|
+
if(here) {
|
|
711
|
+
|
|
712
|
+
window.CPO.save().then(function() {
|
|
713
|
+
var p = drive.getFileById(id);
|
|
714
|
+
|
|
715
|
+
window.CPO.showShareContainer(p);
|
|
716
|
+
history.pushState(null, null, "#program=" + id);
|
|
717
|
+
window.CPO.loadProgram(p).then(function(contents) {
|
|
718
|
+
window.CPO.editor.cm.setValue(contents);
|
|
719
|
+
window.CPO.editor.cm.clearHistory();
|
|
720
|
+
});
|
|
721
|
+
})
|
|
722
|
+
.fail(function(err) {
|
|
723
|
+
window.flashMessage("Currently unable to save, try opening that file in a new tab");
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
window.open(window.APP_BASE_URL + "/editor#program=" + id, "_blank");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function openFile(id) {
|
|
731
|
+
var filePrompt = new modalPrompt({
|
|
732
|
+
title: "Where would you like to open the file?",
|
|
733
|
+
style: "tiles",
|
|
734
|
+
hideSubmit: true,
|
|
735
|
+
options: [
|
|
736
|
+
{
|
|
737
|
+
message: "Open here",
|
|
738
|
+
details: "The current file will be saved first",
|
|
739
|
+
on: {click: function() {
|
|
740
|
+
load(true);
|
|
741
|
+
filePrompt.onClose();
|
|
742
|
+
}}
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
message: "Open in new tab",
|
|
746
|
+
details: "The current file will remain open in this tab",
|
|
747
|
+
on: {click: function() {
|
|
748
|
+
load(false);
|
|
749
|
+
filePrompt.onClose();
|
|
750
|
+
}}
|
|
751
|
+
}]
|
|
752
|
+
});
|
|
753
|
+
filePrompt.show();
|
|
754
|
+
}
|
|
755
|
+
openFile(documents[0][picker.Document.ID]);
|
|
756
|
+
}
|
|
757
|
+
// Picture loaded
|
|
758
|
+
else if (documents[0][picker.Document.TYPE] === picker.Type.PHOTO) {
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
photoPrompt(documents.length).show(function(res) {
|
|
762
|
+
// Name of event for CM undo history
|
|
763
|
+
var origin = "+insertImage" + curImg;
|
|
764
|
+
var asValues = (res === "values");
|
|
765
|
+
var asDefs = (res === "defs");
|
|
766
|
+
var asList = (res === "list");
|
|
767
|
+
if (!(asValues || asDefs || asList)) {
|
|
768
|
+
// Check for garbage and log it
|
|
769
|
+
if (res !== null) {
|
|
770
|
+
console.warn("Unknown photoPrompt response: ", res);
|
|
771
|
+
}
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
// http://stackoverflow.com/questions/23733455/inserting-a-new-text-at-given-cursor-position
|
|
775
|
+
var cm = CPO.editor.cm;
|
|
776
|
+
var doc = cm.getDoc();
|
|
777
|
+
function placeInEditor(str) {
|
|
778
|
+
var cursor = doc.getCursor();
|
|
779
|
+
var line = doc.getLine(cursor.line);
|
|
780
|
+
var pos = {
|
|
781
|
+
line: cursor.line,
|
|
782
|
+
ch: line.length
|
|
783
|
+
};
|
|
784
|
+
doc.replaceRange(str, pos, undefined, origin);
|
|
785
|
+
reindent(cursor.line);
|
|
786
|
+
}
|
|
787
|
+
function reindent(line) {
|
|
788
|
+
cm.indentLine(line || doc.getCursor().line);
|
|
789
|
+
}
|
|
790
|
+
function emitNewline() {
|
|
791
|
+
var cursor = doc.getCursor();
|
|
792
|
+
placeInEditor('\n');
|
|
793
|
+
// FIXME: Dunno why this happens.
|
|
794
|
+
if (cursor.line === doc.getCursor().line) {
|
|
795
|
+
doc.setCursor({line: cursor.line + 1, ch: 0});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function emitLn(s) {
|
|
799
|
+
placeInEditor(s);
|
|
800
|
+
emitNewline();
|
|
801
|
+
}
|
|
802
|
+
function onEmptyLine() {
|
|
803
|
+
var cursor = doc.getCursor("to");
|
|
804
|
+
var line = doc.getLine(cursor.line);
|
|
805
|
+
return (/^\s*$/.test(line));
|
|
806
|
+
}
|
|
807
|
+
// Make newline at cursor position if we are not on an empty line
|
|
808
|
+
if (onEmptyLine()) {
|
|
809
|
+
reindent();
|
|
810
|
+
} else {
|
|
811
|
+
emitNewline();
|
|
812
|
+
}
|
|
813
|
+
if (asList) {
|
|
814
|
+
placeInEditor("[list:");
|
|
815
|
+
}
|
|
816
|
+
documents.forEach(function(d, idx) {
|
|
817
|
+
var pathToImg = '"' + window.APP_BASE_URL + "/shared-image-contents?sharedImageId="
|
|
818
|
+
+ d.id + '"';
|
|
819
|
+
var outstr = asDefs ? ("img" + curImg + " = ") : "";
|
|
820
|
+
++curImg;
|
|
821
|
+
outstr += "image-url(" + pathToImg + ")";
|
|
822
|
+
var isLast = (idx === (documents.length - 1));
|
|
823
|
+
if (asList) {
|
|
824
|
+
if (idx === 0) {
|
|
825
|
+
// The space after ":" gets eaten, so we need to enter it here
|
|
826
|
+
outstr = ' ' + outstr;
|
|
827
|
+
}
|
|
828
|
+
outstr += isLast ? "]" : ",";
|
|
829
|
+
}
|
|
830
|
+
if (isLast) {
|
|
831
|
+
placeInEditor(outstr);
|
|
832
|
+
} else {
|
|
833
|
+
emitLn(outstr);
|
|
834
|
+
}
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
catch(e) {
|
|
839
|
+
console.error("The show() function failed: ", e);
|
|
840
|
+
}
|
|
841
|
+
} else {
|
|
842
|
+
flashError("Invalid file type: " + documents[0][picker.Document.TYPE]);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
var insertPicker = new FilePicker({
|
|
846
|
+
onLoaded: function() {
|
|
847
|
+
$("#insert").attr("disabled", false);
|
|
848
|
+
insertPicker.openOn($("#insert")[0], "click");
|
|
849
|
+
},
|
|
850
|
+
onSelect: handlePickerData,
|
|
851
|
+
onError: flashError,
|
|
852
|
+
onInternalError: stickError,
|
|
853
|
+
views: ["imageView"],
|
|
854
|
+
title: "Select images"
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
return runtime.makeModuleReturn({
|
|
858
|
+
repl: runtime.makeOpaque(repl)
|
|
859
|
+
}, {});
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
})
|