Skip to content

Commit

Permalink
Finished switch to Webviews.
Browse files Browse the repository at this point in the history
Now also the message is in place and the full functionality is back, as it was with previewHTML.
  • Loading branch information
mike-lischke committed May 13, 2018
1 parent 6737d82 commit eebff63
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 170 deletions.
39 changes: 15 additions & 24 deletions misc/utils.js
Expand Up @@ -7,29 +7,23 @@

"use strict";

const vscode = acquireVsCodeApi();

function showMessage(message) {
const args = { message: message };
window.parent.postMessage({
command: "did-click-link",
data: `command:_antlr.showMessage?${encodeURIComponent(JSON.stringify(args))}`
}, "file://");
vscode.postMessage({ command: "alert", text: message });
}

function exportToSVG(type, name) {
// Doesn't save actually, but sends a command to our vscode extension.
// Only very few HTML messages are handled in the vscode webclient (and forwarded to registered listeners).
// We choose "did-click-link" (like the markdown preview extension does).
// Saving the SVG is delegated to the extension to allow asking the user for a target file.
const svg = document.querySelectorAll('svg')[0];
const args = {
command: "saveSVG",
name: name,
type: type,
svg: svg.outerHTML
};

window.parent.postMessage({
command: "did-click-link",
data: `command:_antlr.saveSVG?${encodeURIComponent(JSON.stringify(args))}`
}, "file://");
vscode.postMessage(args);
}

function exportToHTML(type, name) {
Expand All @@ -50,12 +44,9 @@ function exportToHTML(type, name) {
});

const html = workDocument.querySelectorAll('html')[0];
const args = { name: name, type: type, html: html.outerHTML };
const args = { command: "saveHTML", name: name, type: type, html: html.outerHTML };

window.parent.postMessage({
command: "did-click-link",
data: `command:_antlr.saveHTML?${encodeURIComponent(JSON.stringify(args))}`
}, "file://");
vscode.postMessage(args);
} catch (error) {
showMessage("JS Error: " + e);
}
Expand All @@ -65,19 +56,19 @@ function exportToHTML(type, name) {

// Used to send messages from the extension to this webview.
window.addEventListener('message', function (event) {
switch (event.data.action) {
case "saveATNState":
switch (event.data.command) {
case "cacheATNLayout": {
const args = {
command: "saveATNState",
nodes: nodes,
file: event.data.file,
rule: event.data.rule,
transform: topGroup.attr("transform")
};
window.parent.postMessage({
command: "did-click-link",
data: `command:_antlr.saveATNState?${encodeURIComponent(JSON.stringify(args))}`
}, "file://");
break;

vscode.postMessage(args);
break;
}
}
});
}());
117 changes: 4 additions & 113 deletions src/extension.ts
Expand Up @@ -24,7 +24,7 @@ import { SymbolProvider } from './frontend/SymbolProvider';
import { AntlrCodeLensProvider } from './frontend/CodeLensProvider';
import { AntlrCompletionItemProvider } from './frontend/CompletionItemProvider';
import { AntlrRailroadDiagramProvider } from './frontend/RailroadDiagramProvider';
import { AntlrATNGraphProvider, ATNStateEntry } from "./frontend/ATNGraphProvider";
import { AntlrATNGraphProvider } from "./frontend/ATNGraphProvider";
import { AntlrFormattingProvider } from "./frontend/FormattingProvider";
import { ImportsProvider } from "./frontend/ImportsProvider";
import { AntlrCallGraphProvider } from "./frontend/CallGraphProvider";
Expand All @@ -45,9 +45,6 @@ const ANTLR = { language: 'antlr', scheme: 'file' };
let diagnosticCollection = languages.createDiagnosticCollection('antlr');
let DiagnosticTypeMap: Map<DiagnosticType, DiagnosticSeverity> = new Map();

// All ATN state entries per file, per rule.
let atnStates: Map<string, Map<string, ATNStateEntry>> = new Map();

let backend: AntlrFacade;
let progress: ProgressIndicator;
let outputChannel: OutputChannel;
Expand Down Expand Up @@ -75,14 +72,7 @@ export function activate(context: ExtensionContext) {
if (document.languageId === "antlr") {
let antlrPath = path.join(path.dirname(document.fileName), ".antlr");
backend.generate(document.fileName, { outputDir: antlrPath, loadOnly: true });

let hash = Utils.hashFromPath(document.fileName);
let atnCacheFile = path.join(antlrPath, "cache", hash + ".atn");
if (fs.existsSync(atnCacheFile)) {
let data = fs.readFileSync(atnCacheFile, { encoding: "utf-8" });
let fileEntry = new Map(JSON.parse(data));
atnStates.set(hash, <Map<string, ATNStateEntry>>fileEntry);
}
AntlrATNGraphProvider.addStatesForGrammar(antlrPath, document.fileName);
}
}

Expand Down Expand Up @@ -171,101 +161,6 @@ export function activate(context: ExtensionContext) {
});
}));

// Used for debugging in JS files (console.log doesn't have any effect).
context.subscriptions.push(commands.registerCommand("_antlr.showMessage", (args: { message: string }) => {
window.showInformationMessage(args.message, { modal: true });
}));

// The export to SVG command.
context.subscriptions.push(commands.registerCommand("_antlr.saveSVG", (args: { name: string, type: string, svg: string }) => {
let css: string[] = [];
css.push(Utils.getMiscPath("light.css", context, true));
let customStyles = workspace.getConfiguration("antlr4")['customcss'];
if (customStyles && Array.isArray(customStyles)) {
for (let style of customStyles) {
css.push(style);
}
}

let svg = '<?xml version="1.0" standalone="no"?>\n'
for (let stylesheet of css) {
svg += `<?xml-stylesheet href="${path.basename(stylesheet)}" type="text/css"?>\n`;
}

svg += '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ' +
'"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' + args.svg;

try {
Utils.exportDataWithConfirmation(path.join(workspace.getConfiguration("antlr4." + args.type)["saveDir"] || "",
args.name + "." + args.type), { "SVG": ["svg"] }, svg, css);
} catch (error) {
window.showErrorMessage("Couldn't write SVG file: " + error);
}
}));

// The export to html command.
context.subscriptions.push(commands.registerCommand("_antlr.saveHTML", (args: { name: string, type: string, html: string }) => {
let css: string[] = [];
css.push(Utils.getMiscPath("light.css", context, true));
css.push(Utils.getMiscPath("dark.css", context, true));
let customStyles = workspace.getConfiguration("antlr4")['customcss'];
if (customStyles && Array.isArray(customStyles)) {
for (let style of customStyles) {
css.push(style);
}
}
try {
Utils.exportDataWithConfirmation(path.join(workspace.getConfiguration("antlr4." + args.type)["saveDir"] || "",
args.name + "." + args.type), { "HTML": ["html"] }, args.html, css);
} catch (error) {
window.showErrorMessage("Couldn't write HTML file: " + error);
}
}));

// The "save ATN state" notification.
context.subscriptions.push(commands.registerCommand('_antlr.saveATNState',
(args: { nodes: any, file: string, rule: string, transform: string }) => {

let hash = Utils.hashFromPath(args.file);
let basePath = path.dirname(args.file);
let atnCachePath = path.join(basePath, ".antlr/cache");

let fileEntry = atnStates.get(hash);
if (!fileEntry) {
fileEntry = new Map();
}

let scale = 1;
let translateX = 0;
let translateY = 0;
let temp = args.transform.split(/[(), ]/);
for (let i = 0; i < temp.length; ++i) {
if (temp[i] === "translate") {
translateX = Number(temp[++i]);
translateY = Number(temp[++i]);
} else if (temp[i] === "scale") {
scale = Number(temp[++i]);
}
}

// Convert the given translation back to what it was before applying the scaling, as that is what we need
// to specify when we restore the translation.
let ruleEntry: ATNStateEntry = { scale: scale, translation: { x: translateX / scale, y: translateY / scale }, states: [] };
for (let node of args.nodes) {
ruleEntry.states.push({ id: node.id, fx: node.fx, fy: node.fy });
}
fileEntry.set(args.rule, ruleEntry);
atnStates.set(hash, fileEntry);

fs.ensureDirSync(atnCachePath);
try {
fs.writeFileSync(path.join(atnCachePath, hash + ".atn"), JSON.stringify(Array.from(fileEntry)), { encoding: "utf-8" });
} catch (error) {
window.showErrorMessage("Couldn't write ATN state data for: " + args.file + "(" + hash + ")");
}
}
));

//----- Events -----

workspace.onDidOpenTextDocument((doc: TextDocument) => {
Expand Down Expand Up @@ -314,9 +209,7 @@ export function activate(context: ExtensionContext) {
window.onDidChangeTextEditorSelection((event: TextEditorSelectionChangeEvent) => {
if (event.textEditor.document.languageId === "antlr" && event.textEditor.document.uri.scheme === "file") {
diagramProvider.update(event.textEditor);

let hash = Utils.hashFromPath(event.textEditor.document.uri.fsPath);
atnGraphProvider.update(event.textEditor, false, atnStates.get(hash));
atnGraphProvider.update(event.textEditor, false);
}
});

Expand Down Expand Up @@ -419,9 +312,7 @@ export function activate(context: ExtensionContext) {
}

backend.generate(document.fileName, { outputDir: antlrPath, loadOnly: true }).then(() => {

let hash = Utils.hashFromPath(document.uri.fsPath);
atnGraphProvider.update(window.activeTextEditor, true, atnStates.get(hash));
atnGraphProvider.update(window.activeTextEditor, true);

progress.stopAnimation();
});
Expand Down
84 changes: 73 additions & 11 deletions src/frontend/ATNGraphProvider.ts
Expand Up @@ -8,6 +8,7 @@
'use strict';

import * as fs from "fs-extra";
import * as path from "path";

import { WebviewProvider, WebviewShowOptions } from "./WebviewProvider";
import { Utils } from "./Utils";
Expand All @@ -22,8 +23,18 @@ export class ATNStateEntry {

export class AntlrATNGraphProvider extends WebviewProvider {

// Set by the update method if there's cached state data for the current rule.
private cachedRuleStates: ATNStateEntry | undefined;
// All ATN state entries per file, per rule. Initially filled from the extension code.
public static atnStates: Map<string, Map<string, ATNStateEntry>> = new Map();

public static addStatesForGrammar(root: string, grammar: string) {
let hash = Utils.hashFromPath(grammar);
let atnCacheFile = path.join(root, "cache", hash + ".atn");
if (fs.existsSync(atnCacheFile)) {
let data = fs.readFileSync(atnCacheFile, { encoding: "utf-8" });
let fileEntry = new Map(JSON.parse(data));
AntlrATNGraphProvider.atnStates.set(hash, <Map<string, ATNStateEntry>>fileEntry);
}
}

public generateContent(source: TextEditor | Uri, options: WebviewShowOptions): string {
if (!this.currentRule) {
Expand Down Expand Up @@ -106,9 +117,12 @@ export class AntlrATNGraphProvider extends WebviewProvider {
return html;
};

public update(editor: TextEditor, forced: boolean = false, cachedStates?: Map<string, ATNStateEntry>) {
public update(editor: TextEditor, forced: boolean = false) {
let [currentRule, ruleIndex] = this.findCurrentRule(editor);
if (!this.lastEditor || this.lastEditor !== editor || this.currentRule !== currentRule || forced) {
let hash = Utils.hashFromPath(editor.document.fileName);
let cachedStates = AntlrATNGraphProvider.atnStates.get(hash);

if (!cachedStates || !currentRule) {
this.cachedRuleStates = undefined;
} else {
Expand All @@ -118,18 +132,16 @@ export class AntlrATNGraphProvider extends WebviewProvider {
// Update content only if this is the first invocation, editors were switched or
// the currently selected rule changed.
if (this.lastEditor) {
commands.executeCommand('_workbench.htmlPreview.postMessage',
this.lastEditor.document.uri, {
action: "saveATNState",
file: this.lastEditor.document.uri.fsPath,
rule: this.currentRule
}
).then((value) => {
if (this.sendMessage(editor, {
command: "cacheATNLayout",
file: editor.document.fileName,
rule: this.currentRule
})) {
this.lastEditor = editor;
this.currentRule = currentRule;
this.currentRuleIndex = ruleIndex;
super.update(editor);
});
};
} else {
this.lastEditor = editor;
this.currentRule = currentRule;
Expand All @@ -138,4 +150,54 @@ export class AntlrATNGraphProvider extends WebviewProvider {
}
}
}

protected handleMessage(message: any): boolean {
if (message.command == "saveATNState") {
// This is the bounce back from the script code for our call to `cacheATNState` triggered from
// the `update()` function.
let hash = Utils.hashFromPath(message.file);
let basePath = path.dirname(message.file);
let atnCachePath = path.join(basePath, ".antlr/cache");

let fileEntry = AntlrATNGraphProvider.atnStates.get(hash);
if (!fileEntry) {
fileEntry = new Map();
}

let scale = 1;
let translateX = 0;
let translateY = 0;
let temp = message.transform.split(/[(), ]/);
for (let i = 0; i < temp.length; ++i) {
if (temp[i] === "translate") {
translateX = Number(temp[++i]);
translateY = Number(temp[++i]);
} else if (temp[i] === "scale") {
scale = Number(temp[++i]);
}
}

// Convert the given translation back to what it was before applying the scaling, as that is what we need
// to specify when we restore the translation.
let ruleEntry: ATNStateEntry = { scale: scale, translation: { x: translateX / scale, y: translateY / scale }, states: [] };
for (let node of message.nodes) {
ruleEntry.states.push({ id: node.id, fx: node.fx, fy: node.fy });
}
fileEntry.set(message.rule, ruleEntry);
AntlrATNGraphProvider.atnStates.set(hash, fileEntry);

fs.ensureDirSync(atnCachePath);
try {
fs.writeFileSync(path.join(atnCachePath, hash + ".atn"), JSON.stringify(Array.from(fileEntry)), { encoding: "utf-8" });
} catch (error) {
window.showErrorMessage("Couldn't write ATN state data for: " + message.file + "(" + hash + ")");
}

return true;
}
return false;
}

// Set by the update method if there's cached state data for the current rule.
private cachedRuleStates: ATNStateEntry | undefined;
};

0 comments on commit eebff63

Please sign in to comment.