* 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:
9
.vscode/launch.json
vendored
9
.vscode/launch.json
vendored
@@ -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",
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
getDataExtractorApi,
|
||||
DataExtractorApiImpl,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataExtractorApiImpl } from "../";
|
||||
|
||||
describe("extractor", () => {
|
||||
it("should not crash", () => {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>`
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -85,8 +85,7 @@ export class Extension {
|
||||
|
||||
latestConnection.setExpression(selectedText);
|
||||
} else {
|
||||
// TODO pass expression
|
||||
// this.views.createNew();
|
||||
this.views.createNew(selectedText);
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user