pxt-calliope/pxteditor.js

566 lines
26 KiB
JavaScript
Raw Normal View History

2017-08-19 17:16:35 +02:00
var pxt;
(function (pxt) {
var editor;
(function (editor) {
/**
* Binds incoming window messages to the project view.
* Requires the "allowParentController" flag in the pxtarget.json/appTheme object.
*
* When the project view receives a request (EditorMessageRequest),
* it starts the command and returns the result upon completion.
* The response (EditorMessageResponse) contains the request id and result.
* Some commands may be async, use the ``id`` field to correlate to the original request.
*/
function bindEditorMessages(projectView) {
if (!window.parent)
return;
window.addEventListener("message", function (msg) {
var data = msg.data;
if (!data || data.type != "pxteditor" || !data.action)
return false;
var p = Promise.resolve();
switch (data.action.toLowerCase()) {
// TODO: make async
case "switchjavascript":
p = p.then(function () { return projectView.openJavaScript(); });
break;
case "switchblocks":
p = p.then(function () { return projectView.openBlocks(); });
break;
case "startsimulator":
p = p.then(function () { return projectView.startSimulator(); });
break;
case "restartsimulator":
p = p.then(function () { return projectView.restartSimulator(); });
break;
case "hidesimulator":
p = p.then(function () { return projectView.collapseSimulator(); });
break;
case "showsimulator":
p = p.then(function () { return projectView.expandSimulator(); });
break;
case "stopsimulator": {
var stop_1 = data;
p = p.then(function () { return projectView.stopSimulator(stop_1.unload); });
break;
}
case "newproject": {
var create_1 = data;
p = p.then(function () { return projectView.newProject(create_1.options); });
break;
}
case "proxytosim": {
var simmsg_1 = data;
p = p.then(function () { return projectView.proxySimulatorMessage(simmsg_1.content); });
break;
}
}
p.done(function () { return sendResponse(data, true, undefined); }, function (err) { return sendResponse(data, false, err); });
return true;
}, false);
}
editor.bindEditorMessages = bindEditorMessages;
function sendResponse(request, success, error) {
window.parent.postMessage({
type: "pxteditor",
id: request.id,
success: success,
error: error
}, "*");
}
})(editor = pxt.editor || (pxt.editor = {}));
})(pxt || (pxt = {}));
var pxt;
(function (pxt) {
var storage;
(function (storage) {
var MemoryStorage = (function () {
function MemoryStorage() {
this.items = {};
}
MemoryStorage.prototype.removeItem = function (key) {
delete this.items[key];
};
MemoryStorage.prototype.getItem = function (key) {
return this.items[key];
};
MemoryStorage.prototype.setItem = function (key, value) {
this.items[key] = value;
};
MemoryStorage.prototype.clear = function () {
this.items = {};
};
return MemoryStorage;
}());
var LocalStorage = (function () {
function LocalStorage(storageId) {
this.storageId = storageId;
}
LocalStorage.prototype.targetKey = function (key) {
return this.storageId + '/' + key;
};
LocalStorage.prototype.removeItem = function (key) {
window.localStorage.removeItem(this.targetKey(key));
};
LocalStorage.prototype.getItem = function (key) {
return window.localStorage[this.targetKey(key)];
};
LocalStorage.prototype.setItem = function (key, value) {
window.localStorage[this.targetKey(key)] = value;
};
LocalStorage.prototype.clear = function () {
var prefix = this.targetKey('');
var keys = [];
for (var i = 0; i < window.localStorage.length; ++i) {
var key = window.localStorage.key(i);
if (key.indexOf(prefix) == 0)
keys.push(key);
}
keys.forEach(function (key) { return window.localStorage.removeItem(key); });
};
return LocalStorage;
}());
function storageId() {
var id = pxt.appTarget ? pxt.appTarget.id : window.pxtConfig ? window.pxtConfig.targetId : '';
return id;
}
storage.storageId = storageId;
var impl;
function init() {
if (impl)
return;
// test if local storage is supported
var sid = storageId();
var supported = false;
// no local storage in sandbox mode
if (!pxt.shell.isSandboxMode()) {
try {
window.localStorage[sid] = '1';
var v = window.localStorage[sid];
supported = true;
}
catch (e) { }
}
if (!supported) {
impl = new MemoryStorage();
pxt.debug('storage: in memory');
}
else {
impl = new LocalStorage(sid);
pxt.debug("storage: local under " + sid);
}
}
function setLocal(key, value) {
init();
impl.setItem(key, value);
}
storage.setLocal = setLocal;
function getLocal(key) {
init();
return impl.getItem(key);
}
storage.getLocal = getLocal;
function removeLocal(key) {
init();
impl.removeItem(key);
}
storage.removeLocal = removeLocal;
function clearLocal() {
init();
impl.clear();
}
storage.clearLocal = clearLocal;
})(storage = pxt.storage || (pxt.storage = {}));
})(pxt || (pxt = {}));
/// <reference path="../typings/globals/bluebird/index.d.ts"/>
/// <reference path="../localtypings/monaco.d.ts" />
/// <reference path="../built/pxtlib.d.ts"/>
var pxt;
(function (pxt) {
var vs;
(function (vs) {
function syncModels(mainPkg, libs, currFile, readOnly) {
if (readOnly)
return monaco.Promise.as(undefined);
var extraLibs = monaco.languages.typescript.typescriptDefaults.getExtraLibs();
var modelMap = {};
var toPopulate = [];
var definitions = {};
mainPkg.sortedDeps().forEach(function (pkg) {
pkg.getFiles().forEach(function (f) {
var fp = pkg.id + "/" + f;
var proto = "pkg:" + fp;
if (/\.(ts)$/.test(f) && fp != currFile) {
if (!monaco.languages.typescript.typescriptDefaults.getExtraLibs()[fp]) {
var content = pkg.readFile(f) || " ";
libs[fp] = monaco.languages.typescript.typescriptDefaults.addExtraLib(content, fp);
}
modelMap[fp] = "1";
// store which files we need to populate definitions for the monaco toolbox
toPopulate.push({ f: f, fp: fp });
}
});
});
// dispose of any extra libraries, the typescript worker will be killed as a result of this
Object.keys(extraLibs)
.filter(function (lib) { return /\.(ts)$/.test(lib) && !modelMap[lib]; })
.forEach(function (lib) {
libs[lib].dispose();
});
// populate definitions for the monaco toolbox
var promises = [];
toPopulate.forEach(function (populate) {
var promise = populateDefinitions(populate.f, populate.fp, definitions);
promises.push(promise);
});
return monaco.Promise.join(promises)
.then(function () {
return definitions;
});
}
vs.syncModels = syncModels;
function displayPartsToParameterSignature(parts) {
return "(" + parts.filter(function (part) { return part.kind == "parameterName"; }).map(function (part) { return part.text; }).join(", ") + ")";
}
function populateDefinitions(f, fp, definitions) {
var typeDefs = {};
return monaco.languages.typescript.getTypeScriptWorker().then(function (worker) {
return worker(monaco.Uri.parse(fp))
.then(function (client) {
return client.getNavigationBarItems(fp).then(function (items) {
return populateDefinitionsForKind(client, ts.ScriptElementKind.interfaceElement, items)
.then(function () { return populateDefinitionsForKind(client, ts.ScriptElementKind.classElement, items); })
.then(function () { return populateDefinitionsForKind(client, ts.ScriptElementKind.moduleElement, items); });
});
})
.then(function () {
Object.keys(definitions).forEach(function (name) {
var moduleDef = definitions[name];
if (moduleDef.vars) {
Object.keys(moduleDef.vars).forEach(function (typeString) {
var typeDef = typeDefs[typeString];
if (typeDef) {
Object.keys(typeDef.fns).forEach(function (functionName) {
var qName = typeString + "." + functionName;
if (moduleDef.fns[qName]) {
return;
}
var fn = typeDef.fns[functionName];
if (fn) {
fn.snippet = moduleDef.vars[typeString] + "." + fn.snippet;
moduleDef.fns[qName] = fn;
}
});
}
});
}
});
});
});
function populateDefinitionsForKind(client, kind, items) {
return monaco.Promise.join(items.filter(function (item) { return item.kind == kind; }).map(function (item) {
if (kind === ts.ScriptElementKind.moduleElement) {
if (!definitions[item.text]) {
definitions[item.text] = {
fns: {}
};
}
return populateNameDefinition(client, fp, item, definitions[item.text]);
}
else {
if (!typeDefs[item.text]) {
typeDefs[item.text] = {
fns: {}
};
}
return populateNameDefinition(client, fp, item, typeDefs[item.text]);
}
}));
function populateNameDefinition(client, fp, parent, definition) {
var promises = [];
// metadata promise
promises.push(client.getLeadingComments(fp, parent.spans[0].start)
.then(function (comments) {
if (comments) {
var meta_1 = pxtc.parseCommentString(comments);
if (meta_1) {
if (!definition.metaData) {
definition.metaData = meta_1;
}
else {
Object.keys(meta_1).forEach(function (k) { return definition.metaData[k] = meta_1[k]; });
}
}
}
}));
// function promises
promises.push(monaco.Promise.join(parent.childItems
.filter(function (item) { return (item.kind == ts.ScriptElementKind.functionElement ||
item.kind === ts.ScriptElementKind.memberFunctionElement) && isExported(item); })
.map(function (fn) {
// exported function
return client.getCompletionEntryDetailsAndSnippet(fp, fn.spans[0].start, fn.text, fn.text, parent.text)
.then(function (details) {
if (!details)
return;
return client.getLeadingComments(fp, fn.spans[0].start)
.then(function (comments) {
var meta;
if (comments)
meta = pxtc.parseCommentString(comments);
var comment = meta ? meta.jsDoc : ts.displayPartsToString(details[0].documentation);
definition.fns[fn.text] = {
sig: displayPartsToParameterSignature(details[0].displayParts),
snippet: details[1],
comment: comment,
metaData: meta
};
});
});
})));
if (kind === ts.ScriptElementKind.moduleElement) {
if (!definition.vars) {
definition.vars = {};
}
promises.push(monaco.Promise.join(parent.childItems.filter(function (v) { return v.kind === ts.ScriptElementKind.constElement && isExported(v); }).map(function (v) {
return client.getQuickInfoAtPosition(fp, v.spans[0].start)
.then(function (qInfo) {
if (qInfo) {
var typePart = qInfo.displayParts.filter(function (part) { return part.kind === "interfaceName" || part.kind === "className"; })[0];
if (typePart && !definition.vars[typePart.text]) {
definition.vars[typePart.text] = v.text;
}
}
});
})));
}
return monaco.Promise.join(promises);
function isExported(item) {
if (kind === ts.ScriptElementKind.interfaceElement) {
return true;
}
if (item.kind === ts.ScriptElementKind.memberFunctionElement && !item.kindModifiers) {
return true;
}
return item.kindModifiers.indexOf(ts.ScriptElementKindModifier.exportedModifier) !== -1 ||
item.kindModifiers.indexOf(ts.ScriptElementKindModifier.ambientModifier) !== -1;
}
}
}
}
function initMonacoAsync(element) {
return new Promise(function (resolve, reject) {
if (typeof (window.monaco) === 'object') {
// monaco is already loaded
resolve(createEditor(element));
return;
}
var onGotAmdLoader = function () {
window.require.config({ paths: { 'vs': pxt.webConfig.pxtCdnUrl + 'vs' } });
// Load monaco
window.require(['vs/editor/editor.main'], function () {
setupMonaco();
resolve(createEditor(element));
});
};
// Load AMD loader if necessary
if (!window.require) {
var loaderScript = document.createElement('script');
loaderScript.type = 'text/javascript';
loaderScript.src = pxt.webConfig.pxtCdnUrl + 'vs/loader.js';
loaderScript.addEventListener('load', onGotAmdLoader);
document.body.appendChild(loaderScript);
}
else {
onGotAmdLoader();
}
});
}
vs.initMonacoAsync = initMonacoAsync;
function setupMonaco() {
if (!monaco.languages.typescript)
return;
initAsmMonarchLanguage();
// validation settings
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSyntaxValidation: false,
noSemanticValidation: false
});
// compiler options
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
allowUnreachableCode: true,
noImplicitAny: true,
allowJs: false,
allowUnusedLabels: true,
target: monaco.languages.typescript.ScriptTarget.ES5,
outDir: "built",
rootDir: ".",
noLib: true,
mouseWheelZoom: true
});
// maximum idle time
monaco.languages.typescript.typescriptDefaults.setMaximunWorkerIdleTime(20 * 60 * 1000);
}
function createEditor(element) {
var inverted = pxt.appTarget.appTheme.invertedMonaco;
var editor = monaco.editor.create(element, {
model: null,
//ariaLabel: lf("JavaScript Editor"),
fontFamily: "'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'",
scrollBeyondLastLine: false,
language: "typescript",
experimentalScreenReader: true,
mouseWheelZoom: true,
tabCompletion: true,
wordBasedSuggestions: true,
lineNumbersMinChars: 3,
//automaticLayout: true,
mouseWheelScrollSensitivity: 0.5,
quickSuggestionsDelay: 200,
theme: inverted ? 'vs-dark' : 'vs'
});
editor.layout();
return editor;
}
vs.createEditor = createEditor;
function initAsmMonarchLanguage() {
monaco.languages.register({ id: 'asm', extensions: ['.asm'] });
monaco.languages.setMonarchTokensProvider('asm', {
// Set defaultToken to invalid to see what you do not tokenize yet
// defaultToken: 'invalid',
tokenPostfix: '',
//Extracted from http://infocenter.arm.com/help/topic/com.arm.doc.qrc0006e/QRC0006_UAL16.pdf
//Should be a superset of the instructions emitted
keywords: [
'movs', 'mov', 'adds', 'add', 'adcs', 'adr', 'subs', 'sbcs', 'sub', 'rsbs',
'muls', 'cmp', 'cmn', 'ands', 'eors', 'orrs', 'bics', 'mvns', 'tst', 'lsls',
'lsrs', 'asrs', 'rors', 'ldr', 'ldrh', 'ldrb', 'ldrsh', 'ldrsb', 'ldm',
'str', 'strh', 'strb', 'stm', 'push', 'pop', 'cbz', 'cbnz', 'b', 'bl', 'bx', 'blx',
'sxth', 'sxtb', 'uxth', 'uxtb', 'rev', 'rev16', 'revsh', 'svc', 'cpsid', 'cpsie',
'setend', 'bkpt', 'nop', 'sev', 'wfe', 'wfi', 'yield',
'beq', 'bne', 'bcs', 'bhs', 'bcc', 'blo', 'bmi', 'bpl', 'bvs', 'bvc', 'bhi', 'bls',
'bge', 'blt', 'bgt', 'ble', 'bal',
//Registers
'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15',
'pc', 'sp', 'lr'
],
typeKeywords: [
'.startaddr', '.hex', '.short', '.space', '.section', '.string', '.byte'
],
operators: [],
// Not all of these are valid in ARM Assembly
symbols: /[:\*]+/,
// C# style strings
escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
// The main tokenizer for our languages
tokenizer: {
root: [
// identifiers and keywords
[/(\.)?[a-z_$\.][\w$]*/, {
cases: {
'@typeKeywords': 'keyword',
'@keywords': 'keyword',
'@default': 'identifier'
}
}],
// whitespace
{ include: '@whitespace' },
// delimiters and operators
[/[{}()\[\]]/, '@brackets'],
[/[<>](?!@symbols)/, '@brackets'],
[/@symbols/, {
cases: {
'@operators': 'operator',
'@default': ''
}
}],
// @ annotations.
[/@\s*[a-zA-Z_\$][\w\$]*/, { token: 'annotation' }],
// numbers
//[/\d*\.\d+([eE][\-+]?\d+)?/, 'number.float'],
[/(#|(0[xX]))?[0-9a-fA-F]+/, 'number'],
// delimiter: after number because of .\d floats
[/[;,.]/, 'delimiter'],
// strings
[/"([^"\\]|\\.)*$/, 'string.invalid'],
[/"/, { token: 'string.quote', bracket: '@open', next: '@string' }],
// characters
[/'[^\\']'/, 'string'],
[/(')(@escapes)(')/, ['string', 'string.escape', 'string']],
[/'/, 'string.invalid']
],
comment: [],
string: [
[/[^\\"]+/, 'string'],
[/@escapes/, 'string.escape'],
[/\\./, 'string.escape.invalid'],
[/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }]
],
whitespace: [
[/[ \t\r\n]+/, 'white'],
[/\/\*/, 'comment', '@comment'],
[/;.*$/, 'comment'],
],
}
});
}
})(vs = pxt.vs || (pxt.vs = {}));
})(pxt || (pxt = {}));
var pxt;
(function (pxt) {
var shell;
(function (shell) {
(function (EditorLayoutType) {
EditorLayoutType[EditorLayoutType["IDE"] = 0] = "IDE";
EditorLayoutType[EditorLayoutType["Sandbox"] = 1] = "Sandbox";
EditorLayoutType[EditorLayoutType["Widget"] = 2] = "Widget";
})(shell.EditorLayoutType || (shell.EditorLayoutType = {}));
var EditorLayoutType = shell.EditorLayoutType;
var layoutType;
function init() {
if (layoutType !== undefined)
return;
var sandbox = /sandbox=1|#sandbox|#sandboxproject/i.test(window.location.href)
|| pxt.BrowserUtils.isIFrame();
var nosandbox = /nosandbox=1/i.test(window.location.href);
var layout = /editorlayout=(widget|sandbox|ide)/i.exec(window.location.href);
layoutType = EditorLayoutType.IDE;
if (nosandbox)
layoutType = EditorLayoutType.Widget;
else if (sandbox)
layoutType = EditorLayoutType.Sandbox;
if (layout) {
switch (layout[1].toLowerCase()) {
case "widget":
layoutType = EditorLayoutType.Widget;
break;
case "sandbox":
layoutType = EditorLayoutType.Sandbox;
break;
case "ide":
layoutType = EditorLayoutType.IDE;
break;
}
}
pxt.debug("shell: layout type " + EditorLayoutType[layoutType] + ", readonly " + isReadOnly());
}
function layoutTypeClass() {
init();
return pxt.shell.EditorLayoutType[layoutType].toLowerCase();
}
shell.layoutTypeClass = layoutTypeClass;
function isSandboxMode() {
init();
return layoutType == EditorLayoutType.Sandbox;
}
shell.isSandboxMode = isSandboxMode;
function isReadOnly() {
return isSandboxMode()
&& !/[?&]edit=1/i.test(window.location.href);
}
shell.isReadOnly = isReadOnly;
})(shell = pxt.shell || (pxt.shell = {}));
})(pxt || (pxt = {}));
/// <reference path="../typings/globals/bluebird/index.d.ts"/>
/// <reference path="../built/pxtlib.d.ts"/>