Merge pull request #747 from quantified-uncertainty/vscode-preview-and-grammar

Vscode ext 0.1.0: preview, configuration, grammar
This commit is contained in:
Ozzie Gooen 2022-06-20 14:16:09 -07:00 committed by GitHub
commit 3012aef683
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 343 additions and 73 deletions

View File

@ -18,6 +18,7 @@
"vega": "^5.22.1", "vega": "^5.22.1",
"vega-embed": "^6.21.0", "vega-embed": "^6.21.0",
"vega-lite": "^5.2.0", "vega-lite": "^5.2.0",
"vscode-uri": "^3.0.3",
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,3 +1,4 @@
/media/vendor /media/vendor
/out /out
/*.vsix /*.vsix
/syntaxes/*.json

View File

@ -0,0 +1,41 @@
(function () {
const vscode = acquireVsCodeApi();
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
function updateContent(text, showSettings) {
root.render(
React.createElement(squiggle_components.SquigglePlayground, {
code: text,
showEditor: false,
showTypes: Boolean(showSettings.showTypes),
showControls: Boolean(showSettings.showControls),
showSummary: Boolean(showSettings.showSummary),
})
);
}
// Handle messages sent from the extension to the webview
window.addEventListener("message", (event) => {
const message = event.data; // The json data that the extension sent
switch (message.type) {
case "update":
const { text, showSettings } = message;
// Update our webview's content
updateContent(text, showSettings);
// Then persist state information.
// This state is returned in the call to `vscode.getState` below when a webview is reloaded.
vscode.setState({ text, showSettings });
return;
}
});
const state = vscode.getState();
if (state) {
updateContent(state.text, state.showSettings);
}
})();

View File

@ -3,7 +3,7 @@
"displayName": "Squiggle", "displayName": "Squiggle",
"description": "Squiggle language support", "description": "Squiggle language support",
"license": "MIT", "license": "MIT",
"version": "0.0.4", "version": "0.1.0",
"publisher": "QURI", "publisher": "QURI",
"repository": { "repository": {
"type": "git", "type": "git",
@ -18,10 +18,29 @@
"Visualization" "Visualization"
], ],
"activationEvents": [ "activationEvents": [
"onCustomEditor:squiggle.wysiwyg" "onCustomEditor:squiggle.wysiwyg",
"onCommand:squiggle.preview"
], ],
"main": "./out/extension.js", "main": "./out/extension.js",
"contributes": { "contributes": {
"languages": [
{
"id": "squiggle",
"extensions": [
".squiggle"
],
"aliases": [
"Squiggle"
]
}
],
"grammars": [
{
"language": "squiggle",
"scopeName": "source.squiggle",
"path": "./syntaxes/squiggle.tmLanguage.json"
}
],
"customEditors": [ "customEditors": [
{ {
"viewType": "squiggle.wysiwyg", "viewType": "squiggle.wysiwyg",
@ -31,29 +50,82 @@
"filenamePattern": "*.squiggle" "filenamePattern": "*.squiggle"
} }
], ],
"priority": "default" "priority": "option"
} }
], ],
"commands": [] "commands": [
{
"command": "squiggle.preview",
"title": "Open Preview",
"category": "Squiggle",
"when": "editorLangId == squiggle",
"icon": "$(open-preview)"
}
],
"menus": {
"editor/title": [
{
"command": "squiggle.preview",
"when": "editorLangId == squiggle",
"group": "navigation"
}
],
"commandPalette": [
{
"command": "squiggle.preview",
"when": "editorLangId == squiggle"
}
]
},
"keybindings": [
{
"command": "squiggle.preview",
"key": "ctrl+k v",
"mac": "cmd+k v",
"when": "editorLangId == squiggle"
}
],
"configuration": {
"title": "Squiggle",
"properties": {
"squiggle.playground.showTypes": {
"type": "boolean",
"default": false,
"description": "Whether to show the types of outputs in the playground"
},
"squiggle.playground.showControls": {
"type": "boolean",
"default": false,
"description": "Whether to show the log scale controls in the playground"
},
"squiggle.playground.showSummary": {
"type": "boolean",
"default": false,
"description": "Whether to show the summary table in the playground"
}
}
}
}, },
"scripts": { "scripts": {
"vscode:prepublish": "yarn run compile", "vscode:prepublish": "yarn run compile",
"compile:tsc": "tsc -p ./", "compile:tsc": "tsc -p ./",
"compile:grammar": "js-yaml syntaxes/squiggle.tmLanguage.yaml >syntaxes/squiggle.tmLanguage.json",
"compile:vendor": "(cd ../squiggle-lang && yarn run build) && (cd ../components && yarn run bundle && yarn run build:css) && mkdir -p media/vendor && cp ../components/dist/bundle.js media/vendor/components.js && cp ../components/dist/main.css media/vendor/components.css && cp ../../node_modules/react/umd/react.production.min.js media/vendor/react.js && cp ../../node_modules/react-dom/umd/react-dom.production.min.js media/vendor/react-dom.js && cp ../website/static/img/quri-logo.png media/vendor/icon.png", "compile:vendor": "(cd ../squiggle-lang && yarn run build) && (cd ../components && yarn run bundle && yarn run build:css) && mkdir -p media/vendor && cp ../components/dist/bundle.js media/vendor/components.js && cp ../components/dist/main.css media/vendor/components.css && cp ../../node_modules/react/umd/react.production.min.js media/vendor/react.js && cp ../../node_modules/react-dom/umd/react-dom.production.min.js media/vendor/react-dom.js && cp ../website/static/img/quri-logo.png media/vendor/icon.png",
"compile": "yarn run compile:tsc && yarn run compile:vendor", "compile": "yarn run compile:tsc && yarn run compile:grammar && yarn run compile:vendor",
"watch": "tsc -watch -p ./", "watch": "tsc -watch -p ./",
"pretest": "yarn run compile && yarn run lint", "pretest": "yarn run compile && yarn run lint",
"lint": "eslint src --ext ts", "lint": "eslint src --ext ts",
"format": "eslint src --ext ts --fix" "format": "eslint src --ext ts --fix"
}, },
"devDependencies": { "devDependencies": {
"@types/vscode": "^1.68.0",
"@types/glob": "^7.2.0", "@types/glob": "^7.2.0",
"@types/node": "18.x", "@types/node": "18.x",
"@types/vscode": "^1.68.0",
"@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0", "@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.18.0", "eslint": "^8.18.0",
"glob": "^8.0.3", "glob": "^8.0.3",
"typescript": "^4.7.4" "typescript": "^4.7.4",
"js-yaml": "^4.1.0"
} }
} }

View File

@ -1,14 +1,5 @@
import * as vscode from "vscode"; import * as vscode from "vscode";
import { getWebviewContent } from "./utils";
function getNonce() {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider { export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
public static register(context: vscode.ExtensionContext): vscode.Disposable { public static register(context: vscode.ExtensionContext): vscode.Disposable {
@ -26,8 +17,6 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
/** /**
* Called when our custom editor is opened. * Called when our custom editor is opened.
*
*
*/ */
public async resolveCustomTextEditor( public async resolveCustomTextEditor(
document: vscode.TextDocument, document: vscode.TextDocument,
@ -37,7 +26,12 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
webviewPanel.webview.options = { webviewPanel.webview.options = {
enableScripts: true, enableScripts: true,
}; };
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview); webviewPanel.webview.html = getWebviewContent({
webview: webviewPanel.webview,
script: "media/wysiwygWebview.js",
title: "Squiggle Editor",
context: this.context,
});
function updateWebview() { function updateWebview() {
webviewPanel.webview.postMessage({ webviewPanel.webview.postMessage({
@ -79,57 +73,6 @@ export class SquiggleEditorProvider implements vscode.CustomTextEditorProvider {
updateWebview(); updateWebview();
} }
/**
* Get the static html used for the editor webviews.
*/
private getHtmlForWebview(webview: vscode.Webview): string {
// Local path to main script run in the webview
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(
this.context.extensionUri,
"media/vendor/components.css"
)
);
const scriptUris = [
// vendor files are copied over by `yarn run compile`
"media/vendor/react.js",
"media/vendor/react-dom.js",
"media/vendor/components.js",
"media/wysiwyg.js",
].map((script) =>
webview.asWebviewUri(
vscode.Uri.joinPath(this.context.extensionUri, script)
)
);
// Use a nonce to whitelist which scripts can be run
const nonce = getNonce();
return /* html */ `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--
Use a content security policy to only allow loading images from https or from our extension directory,
and only allow scripts that have a specific nonce.
-->
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-${nonce}' 'unsafe-eval';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet" />
<title>Squiggle Editor</title>
</head>
<body style="background-color: white; color: black; padding: 12px">
<div id="root"></div>
${scriptUris
.map((uri) => `<script nonce="${nonce}" src="${uri}"></script>`)
.join("")}
</body>
</html>`;
}
private updateTextDocument(document: vscode.TextDocument, text: string) { private updateTextDocument(document: vscode.TextDocument, text: string) {
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();

View File

@ -2,12 +2,15 @@
// Import the module and reference it with the alias vscode in your code below // Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode"; import * as vscode from "vscode";
import { SquiggleEditorProvider } from "./squiggleEditor"; import { SquiggleEditorProvider } from "./editor";
import { registerPreviewCommand } from "./preview";
// this method is called when your extension is activated // this method is called when your extension is activated
// your extension is activated the very first time the command is executed // your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(SquiggleEditorProvider.register(context)); context.subscriptions.push(SquiggleEditorProvider.register(context));
registerPreviewCommand(context);
} }
// this method is called when your extension is deactivated // this method is called when your extension is deactivated

View File

@ -0,0 +1,53 @@
import * as vscode from "vscode";
import * as uri from "vscode-uri";
import { getWebviewContent } from "./utils";
export const registerPreviewCommand = (context: vscode.ExtensionContext) => {
context.subscriptions.push(
vscode.commands.registerTextEditorCommand("squiggle.preview", (editor) => {
// Create and show a new webview
const title = `Preview ${uri.Utils.basename(editor.document.uri)}`;
const panel = vscode.window.createWebviewPanel(
"squigglePreview",
title,
vscode.ViewColumn.Beside,
{} // Webview options. More on these later.
);
panel.webview.options = {
enableScripts: true,
};
panel.webview.html = getWebviewContent({
context,
webview: panel.webview,
title,
script: "media/previewWebview.js",
});
const updateWebview = () => {
panel.webview.postMessage({
type: "update",
text: editor.document.getText(),
showSettings:
vscode.workspace.getConfiguration("squiggle").playground,
});
};
updateWebview();
const changeDocumentSubscription =
vscode.workspace.onDidChangeTextDocument((e) => {
if (e.document.uri.toString() === editor.document.uri.toString()) {
updateWebview();
}
});
// Make sure we get rid of the listener when our editor is closed.
panel.onDidDispose(() => {
changeDocumentSubscription.dispose();
});
})
);
};

View File

@ -0,0 +1,59 @@
import * as vscode from "vscode";
const getNonce = () => {
let text = "";
const possible =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
export const getWebviewContent = ({
webview,
title,
script,
context,
}: {
webview: vscode.Webview;
title: string;
script: string;
context: vscode.ExtensionContext;
}) => {
const nonce = getNonce();
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(context.extensionUri, "media/vendor/components.css")
);
const scriptUris = [
// vendor files are copied over by `yarn run compile`
"media/vendor/react.js",
"media/vendor/react-dom.js",
"media/vendor/components.js",
script,
].map((script) =>
webview.asWebviewUri(vscode.Uri.joinPath(context.extensionUri, script))
);
return `
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- Use a content security policy to only allow scripts that have a specific nonce. -->
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-${nonce}' 'unsafe-eval';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="${styleUri}" rel="stylesheet" />
<title>${title}</title>
</head>
<body style="background-color: white; color: black; padding: 8px 12px">
<div id="root"></div>
${scriptUris
.map((uri) => `<script nonce="${nonce}" src="${uri}"></script>`)
.join("")}
</body>
</html>
`;
};

View File

@ -0,0 +1,93 @@
scopeName: source.squiggle
patterns:
- include: "#statement"
- include: "#expression"
- include: "#comment-block"
- include: "#comment-line"
repository:
statement:
patterns:
- include: "#let"
- include: "#defun"
expression:
patterns:
- include: "#integer"
- include: "#float"
- include: "#string"
- include: "#block"
- include: "#function-call"
- include: "#keywords"
let:
match: ^\s*(\w+)\s*=
captures:
"1":
name: variable.other.squiggle
defun:
begin: ^\s*(\w+)\s*(\()
end: (\))\s*=
beginCaptures:
"1":
name: entity.name.function.squiggle
"2":
name: punctuation.definition.arguments.begin.squiggle
endCaptures:
"1":
name: punctuation.definition.arguments.end.squiggle
patterns:
- include: "#array-parameters"
array-parameters:
begin: \b([\$_a-z]+[\$_a-zA-Z0-9]*)
end: \s*(?:(,)|(?=\)))
beginCaptures:
"1":
name: variable.parameter.function.squiggle
function-call:
begin: (\w+)\s*(\()
end: (\))
beginCaptures:
"1":
name: entity.name.function.squiggle
"2":
name: punctuation.definition.arguments.begin.squiggle
endCaptures:
"1":
name: punctuation.definition.arguments.end.squiggle
patterns:
- include: "$self"
comment-block:
begin: /\*
end: \*/
name: comment.block.squiggle
comment-line:
patterns:
- include: "#comment-line-double-slash"
- include: "#comment-line-number-sign"
comment-line-double-slash:
match: //.*
name: comment.line.double-slash.squiggle
comment-line-number-sign:
match: "#.*"
name: comment.line.number-sign.squiggle
block:
begin: "{"
end: "}"
beginCaptures:
"0":
name: punctuation.definition.block.squiggle
endCaptures:
"0":
name: punctuation.definition.block.squiggle
patterns:
- include: "$self"
keywords:
match: \b(if|then|else|to)\b
name: keyword.control.squiggle
integer:
match: \b\d+([_a-zA-Z]+[_a-zA-Z0-9]*)?
name: constant.numeric.integer.squiggle
float:
match: \b(\d+\.\d*|\.?\d+)([eE]-?\d+)?([_a-zA-Z]+[_a-zA-Z0-9]*)?
name: constant.numeric.float.squiggle
string:
match: \".*?\"
name: string.quoted.double.squiggle

View File

@ -17534,7 +17534,6 @@ vega-embed@^6.21.0, vega-embed@^6.5.1:
vega-schema-url-parser "^2.2.0" vega-schema-url-parser "^2.2.0"
vega-themes "^2.10.0" vega-themes "^2.10.0"
vega-tooltip "^0.28.0" vega-tooltip "^0.28.0"
yallist "*"
vega-encode@~4.9.0: vega-encode@~4.9.0:
version "4.9.0" version "4.9.0"
@ -17894,6 +17893,11 @@ vm-browserify@^1.0.1:
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
vscode-uri@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84"
integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==
w3c-hr-time@^1.0.2: w3c-hr-time@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"