* Implements languageId support

* Implements typing of helper functions
* Shift+F1 opens a new debug visualizer window if none is opened yet
This commit is contained in:
Henning Dieterichs
2020-03-29 15:16:46 +02:00
parent 213f4cda8f
commit 42e58ce9cf
14 changed files with 158 additions and 50 deletions

9
.vscode/launch.json vendored
View File

@@ -1,6 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Launch Chrome",
"url": "http://localhost:8090",
"webRoot": "${workspaceFolder}/visualization-playground"
},
{
"name": "Run Extension (Hot Reload)",
"type": "extensionHost",
@@ -8,7 +15,7 @@
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/extension",
"${workspaceFolder}\\demos\\js"
"${workspaceFolder}\\demos\\php"
],
"env": {
"HOT_RELOAD": "true",

View File

@@ -1,7 +1,4 @@
import {
getDataExtractorApi,
DataExtractorApiImpl,
} from "@hediet/debug-visualizer-data-extraction";
import { DataExtractorApiImpl } from "../";
describe("extractor", () => {
it("should not crash", () => {

View File

@@ -20,7 +20,7 @@ export class ClientConnection {
private readonly client: typeof debugVisualizerUIContract["TClientInterface"];
constructor(
dataSource: EvaluationWatchService,
evaluationWatchService: EvaluationWatchService,
stream: WebSocketStream,
server: Server,
config: Config,
@@ -65,9 +65,12 @@ export class ClientConnection {
this.dispose.untrack(this.watcher).dispose();
}
this.watcher = this.dispose.track(
dataSource.createEvaluationWatcher(newExpression, {
preferredDataExtractor: oldPreferredDataExtractor,
})
evaluationWatchService.createEvaluationWatcher(
newExpression,
{
preferredDataExtractor: oldPreferredDataExtractor,
}
)
);
},
openInBrowser: async ({}) => {
@@ -98,7 +101,7 @@ export class ClientConnection {
getCompletions: async ({ text, column }) => {
throwIfNotAuthenticated();
const completions = await dataSource.getCompletions(
const completions = await evaluationWatchService.getCompletions(
text,
column
);
@@ -111,7 +114,7 @@ export class ClientConnection {
this.client = client;
this.dispose.track(
this.dispose.track([
Disposable.create(
autorun(() => {
if (this.watcher) {
@@ -120,8 +123,15 @@ export class ClientConnection {
});
}
})
)
);
),
Disposable.create(
autorun(() => {
client.updateLanguageId({
languageId: evaluationWatchService.languageId || null,
});
})
),
]);
stream.onClosed.then(() => {
this.dispose();

View File

@@ -16,6 +16,11 @@ export interface Evaluator {
| { kind: "data"; result: DataExtractionResult }
| { kind: "error"; message: FormattedMessage }
>;
/**
* The language that expressions must be written in.
*/
readonly languageId: string | undefined;
}
export interface EvaluationArgs {

View File

@@ -22,6 +22,8 @@ export class GenericEvaluationEngine implements EvaluationEngine {
}
export class GenericEvaluator implements Evaluator {
public readonly languageId = "text";
constructor(private readonly session: VsCodeDebugSession) {}
public async evaluate({

View File

@@ -33,6 +33,8 @@ export class JsEvaluationEngine implements EvaluationEngine {
}
class JsEvaluator implements Evaluator {
public readonly languageId = "javascript";
constructor(private readonly session: VsCodeDebugSession) {}
public async evaluate({

View File

@@ -8,6 +8,13 @@ export interface EvaluationWatchService {
): EvaluationWatcher;
getCompletions(text: string, column: number): Promise<CompletionItem[]>;
/**
* The language the expressions must be written in.
* `undefined`, if unknown.
* This field is observable.
*/
readonly languageId: string | undefined;
}
export interface EvaluationWatcherOptions {

View File

@@ -18,7 +18,7 @@ export class EvaluationWatchServiceImpl implements EvaluationWatchService {
constructor(
private readonly vsCodeDebuggerView: VsCodeDebuggerView,
private readonly dataExtractionProviderFactory: EvaluationEngine
private readonly evaluationEngine: EvaluationEngine
) {
this.dispose.track({
dispose: autorun(() => {
@@ -48,6 +48,21 @@ export class EvaluationWatchServiceImpl implements EvaluationWatchService {
this.watchers.delete(w);
}
private lastLanguageId: string | undefined = undefined;
get languageId(): string | undefined {
const session = this.vsCodeDebuggerView.activeDebugSession;
if (!session) {
return this.lastLanguageId;
}
const evaluator = this.evaluationEngine.createEvaluator(session);
if (!evaluator) {
return this.lastLanguageId;
}
this.lastLanguageId = evaluator.languageId;
return evaluator.languageId;
}
public async refresh(w: ObservableEvaluationWatcher): Promise<void> {
const session = this.vsCodeDebuggerView.activeDebugSession;
if (!session) {
@@ -59,10 +74,8 @@ export class EvaluationWatchServiceImpl implements EvaluationWatchService {
w._state = { kind: "loading" };
const extractionProvider = this.dataExtractionProviderFactory.createEvaluator(
session
);
if (!extractionProvider) {
const evaluator = this.evaluationEngine.createEvaluator(session);
if (!evaluator) {
w._state = {
kind: "error",
message: `The debug adapter "${session.session.type}" is not supported.`,
@@ -70,7 +83,7 @@ export class EvaluationWatchServiceImpl implements EvaluationWatchService {
return;
}
const result = await extractionProvider.evaluate({
const result = await evaluator.evaluate({
expression: w.expression,
frameId,
preferredExtractorId: w.preferredDataExtractor,

View File

@@ -27,7 +27,7 @@ export class WebViews {
});
}
public createNew() {
public createNew(expression: string | undefined = undefined) {
const panel = window.createWebviewPanel(
debugVisualizer,
"Debug Visualizer",
@@ -35,15 +35,18 @@ export class WebViews {
{ enableScripts: true }
);
this.setupView(panel);
this.setupView(panel, expression);
}
public restore(webviewPanel: WebviewPanel) {
this.setupView(webviewPanel);
}
private setupView(webviewPanel: WebviewPanel) {
webviewPanel.webview.html = getHtml(this.server);
private setupView(
webviewPanel: WebviewPanel,
expression: string | undefined = undefined
) {
webviewPanel.webview.html = getHtml(this.server, expression);
const view = new WebView(webviewPanel);
this.debugVisualizations.set(webviewPanel, view);
webviewPanel.onDidDispose(() => {
@@ -56,7 +59,10 @@ export class WebView {
constructor(private readonly webviewPanel: WebviewPanel) {}
}
export function getHtml(server: Server) {
export function getHtml(
server: Server,
expression: string | undefined = undefined
) {
const isDev = !!process.env.USE_DEV_UI;
return `
<html>
@@ -75,6 +81,7 @@ export function getHtml(server: Server) {
serverSecret: server.secret,
serverPort: server.port,
publicPath: server.publicPath,
expression,
})};
const api = window.VsCodeApi = acquireVsCodeApi();
window.addEventListener('message', event => {
@@ -94,7 +101,7 @@ export function getHtml(server: Server) {
${
isDev
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getIndexUrl(
{ mode: "webViewIFrame" }
{ mode: "webViewIFrame", expression }
)}"></iframe>`
: `<script type="text/javascript" src="${server.mainBundleUrl}"></script>`
}

View File

@@ -100,6 +100,11 @@ export const debugVisualizerUIContract = contract({
newState: unchecked<DataExtractionState>(),
}),
}),
updateLanguageId: notificationContract({
params: types.type({
languageId: types.union([types.null, types.string]),
}),
}),
setExpression: requestContract({
params: types.type({
expression: types.string,

View File

@@ -85,8 +85,7 @@ export class Extension {
latestConnection.setExpression(selectedText);
} else {
// TODO pass expression
// this.views.createNew();
this.views.createNew(selectedText);
}
}
)

View File

@@ -10,7 +10,7 @@ export class ExpressionInput extends React.Component<{ model: Model }> {
@observable private contentHeight: number | undefined = undefined;
private model = monaco.editor.createModel(
"",
"text",
"javascript",
monaco.Uri.parse(`file:///main.ts`)
);
@@ -54,6 +54,11 @@ export class ExpressionInput extends React.Component<{ model: Model }> {
}
});
@disposeOnUnmount
private _updateLanguageId = autorun(() => {
monaco.editor.setModelLanguage(this.model, this.props.model.languageId);
});
private readonly setEditorDiv = (editorDiv: HTMLDivElement) => {
if (!editorDiv) {
return;

View File

@@ -4,8 +4,11 @@ import {
} from "debug-visualizer/src/contract";
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
import { observable, action, computed, when } from "mobx";
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
import { observable, action, computed, when, runInAction } from "mobx";
import {
DataExtractorId,
ExtractedData,
} from "@hediet/debug-visualizer-data-extraction";
import {
Visualization,
VisualizationId,
@@ -21,6 +24,7 @@ declare const window: Window & {
serverSecret: string;
serverPort: number;
publicPath: string;
expression: string | undefined;
};
};
@@ -38,10 +42,15 @@ export class Model {
private serverSecret: string;
@observable expression: string = "";
@observable state: DataExtractionState | { kind: "noExpression" } = {
@observable state:
| DataExtractionState
| { kind: "noExpression" }
| { kind: "visualizationError"; data: ExtractedData } = {
kind: "noExpression",
};
@observable languageId: string = "text";
@observable private preferredVisualizationId:
| VisualizationId
| undefined = undefined;
@@ -51,6 +60,11 @@ export class Model {
this.preferredVisualizationId = id;
}
@action
public setVisualizationError(data: ExtractedData) {
this.state = { kind: "visualizationError", data: data };
}
@computed get visualizations():
| {
visualization: Visualization | undefined;
@@ -99,6 +113,10 @@ export class Model {
};
updateTheme();
if (data.expression !== undefined) {
this.setExpression(data.expression);
}
this.dispose.track(
startInterval(1000, () => {
updateTheme();
@@ -191,14 +209,19 @@ export class Model {
new ConsoleRpcLogger(),
{
updateState: async ({ newState }) => {
this._loading = newState.kind === "loading";
if (!this._loading) {
this.state = newState;
}
runInAction(() => {
this._loading = newState.kind === "loading";
if (!this._loading) {
this.state = newState;
}
});
},
setExpression: async ({ expression }) => {
this.setExpression(expression);
},
updateLanguageId: async ({ languageId }) => {
this.languageId = languageId || "text";
},
}
);
try {

View File

@@ -2,13 +2,23 @@ import * as monaco from "monaco-editor";
import { Model } from "./Model";
import { Disposable } from "@hediet/std/disposable";
import { autorun } from "mobx";
declare const require: {
(path: string): { default: string };
context: (
path: string,
includeSubDirs: boolean,
regex: RegExp
) => { (fileName: string): { default: string }; keys(): string[] };
};
export class MonacoBridge {
public readonly dispose = Disposable.fn();
constructor(private readonly model: Model) {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
noSemanticValidation: true,
noSyntaxValidation: true,
});
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES5,
@@ -19,29 +29,45 @@ export class MonacoBridge {
strict: true,
});
const es5Lib = require("!!raw-loader!./lib.es5.d.ts.txt")
.default as string;
const commonTypes = require("!!raw-loader!@hediet/debug-visualizer-data-extraction/src/CommonDataTypes.ts")
.default as string;
const es5Lib = require("!!raw-loader!./lib.es5.d.ts.txt").default;
const commonTypes = require.context(
"!!raw-loader!@hediet/debug-visualizer-data-extraction/dist/src/",
true,
/.*.d.ts$/
);
for (const file of commonTypes.keys()) {
console.log(file);
this.dispose.track(
monaco.languages.typescript.javascriptDefaults.addExtraLib(
commonTypes(file).default,
`file:///node_modules/debug-visualizer-data-extraction/${file}`
)
);
}
this.dispose.track([
monaco.languages.typescript.javascriptDefaults.addExtraLib(es5Lib),
monaco.languages.typescript.javascriptDefaults.addExtraLib(
commonTypes,
`file:///node_modules/CommonDataTypes/index.ts`
),
monaco.languages.typescript.javascriptDefaults.addExtraLib(
`declare function $asData(data: import("CommonDataTypes").CommonDataType): import("CommonDataTypes").CommonDataType;`,
`declare const hedietDbgVis: typeof import("debug-visualizer-data-extraction/js/helpers")`,
`file:///types.d.ts`
),
]);
this.dispose.track(
const debugSessionCompletionProvider = new DebugSessionCompletionProvider(
this.model
);
this.dispose.track([
monaco.languages.registerCompletionItemProvider(
"javascript",
new DebugSessionCompletionProvider(this.model)
)
);
debugSessionCompletionProvider
),
monaco.languages.registerCompletionItemProvider(
"text",
debugSessionCompletionProvider
),
]);
this.dispose.track({
dispose: autorun(() => {