Code improvements.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
"version": "2.0.0",
|
||||
"license": "GPL-3.0",
|
||||
"engines": {
|
||||
"vscode": "^1.35.0"
|
||||
"vscode": "^1.47.0"
|
||||
},
|
||||
"publisher": "hediet",
|
||||
"keywords": [
|
||||
@@ -108,7 +108,8 @@
|
||||
"open": "^7.0.2",
|
||||
"serve-static": "^1.14.1",
|
||||
"ws": "^7.2.1",
|
||||
"crypto-random-string": "^3.1.0"
|
||||
"crypto-random-string": "^3.1.0",
|
||||
"mobx-utils": "^5.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
@@ -116,7 +117,7 @@
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/serve-static": "^1.13.3",
|
||||
"@types/node": "^13.7.4",
|
||||
"@types/vscode": "1.35.0",
|
||||
"@types/vscode": "1.47.0",
|
||||
"tslint": "^6.0.0",
|
||||
"typescript": "^3.8.2",
|
||||
"webpack": "^4.41.6",
|
||||
|
||||
@@ -1,55 +1,50 @@
|
||||
import { workspace } from "vscode";
|
||||
import { workspace, window, ColorThemeKind } from "vscode";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { observable } from "mobx";
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
const useChromeKioskModeKey = "debugVisualizer.useChromeKioskMode";
|
||||
const debugAdapterConfigsKey = "debugVisualizer.debugAdapterConfigurations";
|
||||
import { VsCodeSetting, serializerWithDefault } from "./utils/VsCodeSettings";
|
||||
|
||||
export class Config {
|
||||
public dispose = Disposable.fn();
|
||||
|
||||
@observable
|
||||
private _useChromeKioskMode!: boolean;
|
||||
|
||||
@observable
|
||||
private _debugAdapterConfigs!: DebugAdapterConfigs;
|
||||
private readonly _useChromeKioskMode = new VsCodeSetting(
|
||||
"debugVisualizer.useChromeKioskMode",
|
||||
{ serializer: serializerWithDefault<boolean>(true) }
|
||||
);
|
||||
|
||||
public get useChromeKioskMode(): boolean {
|
||||
return this._useChromeKioskMode;
|
||||
return this._useChromeKioskMode.get();
|
||||
}
|
||||
|
||||
public get debugAdapterConfigs(): DebugAdapterConfigs {
|
||||
return this._debugAdapterConfigs;
|
||||
private readonly _debugAdapterConfigs = new VsCodeSetting(
|
||||
"debugVisualizer.debugAdapterConfigurations",
|
||||
{ serializer: serializerWithDefault<DebugAdapterConfigs>({}) }
|
||||
);
|
||||
|
||||
@observable
|
||||
private _vsCodeTheme = window.activeColorTheme;
|
||||
|
||||
public get theme(): "light" | "dark" {
|
||||
if (this._vsCodeTheme.kind === ColorThemeKind.Light) {
|
||||
return "light";
|
||||
} else if (this._vsCodeTheme.kind === ColorThemeKind.Dark) {
|
||||
return "dark";
|
||||
}
|
||||
return "light";
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.updateConfig();
|
||||
this.dispose.track(
|
||||
workspace.onDidChangeConfiguration(() => {
|
||||
this.updateConfig();
|
||||
window.onDidChangeActiveColorTheme(() => {
|
||||
this._vsCodeTheme = window.activeColorTheme;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private updateConfig(): void {
|
||||
const c = workspace.getConfiguration();
|
||||
|
||||
this._useChromeKioskMode = mapUndefined(
|
||||
c.get<boolean>(useChromeKioskModeKey),
|
||||
true
|
||||
);
|
||||
|
||||
this._debugAdapterConfigs = mapUndefined(
|
||||
c.get<DebugAdapterConfigs>(debugAdapterConfigsKey),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
public getDebugAdapterConfig(
|
||||
debugAdapterType: string
|
||||
): DebugAdapterConfig | undefined {
|
||||
const c = this.debugAdapterConfigs[debugAdapterType];
|
||||
const c = this._debugAdapterConfigs.get()[debugAdapterType];
|
||||
if (!c) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -65,13 +60,6 @@ export class Config {
|
||||
}
|
||||
}
|
||||
|
||||
function mapUndefined<T>(val: T | undefined, defaultVal: T) {
|
||||
if (val === undefined) {
|
||||
return defaultVal;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
type DebugAdapterConfigs = {
|
||||
[debugAdapter: string]: {
|
||||
context?: "watch" | "repl";
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
|
||||
|
||||
export class ComposedEvaluationEngine implements EvaluationEngine {
|
||||
constructor(public readonly engines: EvaluationEngine[]) {}
|
||||
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
|
||||
for (const f of this.engines) {
|
||||
const evaluator = f.createEvaluator(session);
|
||||
if (evaluator) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
|
||||
import { EvaluationEngine, Evaluator } from "./EvaluationEngine";
|
||||
import { Config, DebugAdapterConfig } from "../../Config";
|
||||
import { GenericEvaluator } from "./GenericEvaluationEngine";
|
||||
@@ -11,7 +11,7 @@ registerUpdateReconciler(module);
|
||||
export class ConfiguredEvaluationEngine implements EvaluationEngine {
|
||||
constructor(private readonly config: Config) {}
|
||||
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
|
||||
const config = this.config.getDebugAdapterConfig(session.session.type);
|
||||
if (!config) {
|
||||
return undefined;
|
||||
@@ -22,7 +22,7 @@ export class ConfiguredEvaluationEngine implements EvaluationEngine {
|
||||
|
||||
class ConfiguredEvaluator extends GenericEvaluator {
|
||||
constructor(
|
||||
session: VsCodeDebugSession,
|
||||
session: EnhancedDebugSession,
|
||||
private readonly config: DebugAdapterConfig
|
||||
) {
|
||||
super(session);
|
||||
|
||||
@@ -2,11 +2,11 @@ import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
|
||||
import { FormattedMessage } from "../../webviewContract";
|
||||
|
||||
export interface EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined;
|
||||
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined;
|
||||
}
|
||||
|
||||
export interface Evaluator {
|
||||
|
||||
@@ -2,21 +2,21 @@ import {
|
||||
DataExtractionResult,
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
|
||||
import {
|
||||
EvaluationEngine,
|
||||
Evaluator,
|
||||
EvaluationArgs,
|
||||
} from "./EvaluationEngine";
|
||||
import { parseEvaluationResultFromGenericDebugAdapter } from "./parseEvaluationResultFromGenericDebugAdapter";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { FormattedMessage } from "../../webviewContract";
|
||||
import { hotClass, registerUpdateReconciler } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class GenericEvaluationEngine implements EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
|
||||
return new GenericEvaluator(session);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export class GenericEvaluationEngine implements EvaluationEngine {
|
||||
export class GenericEvaluator implements Evaluator {
|
||||
public readonly languageId = "text";
|
||||
|
||||
constructor(private readonly session: VsCodeDebugSession) {}
|
||||
constructor(private readonly session: EnhancedDebugSession) {}
|
||||
|
||||
public async evaluate({
|
||||
expression,
|
||||
|
||||
@@ -5,20 +5,20 @@ import {
|
||||
getExpressionToInitializeDataExtractorApi,
|
||||
DataExtractionResult,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { VsCodeDebugSession } from "../../VsCodeDebugger";
|
||||
import { EnhancedDebugSession } from "../../debugger/EnhancedDebugSession";
|
||||
import {
|
||||
EvaluationEngine,
|
||||
Evaluator,
|
||||
EvaluationArgs,
|
||||
} from "./EvaluationEngine";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { FormattedMessage } from "../../webviewContract";
|
||||
import { registerUpdateReconciler, hotClass } from "@hediet/node-reload";
|
||||
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
@hotClass(module)
|
||||
export class JsEvaluationEngine implements EvaluationEngine {
|
||||
createEvaluator(session: VsCodeDebugSession): Evaluator | undefined {
|
||||
createEvaluator(session: EnhancedDebugSession): Evaluator | undefined {
|
||||
const supportedDebugAdapters = [
|
||||
"node",
|
||||
"node2",
|
||||
@@ -37,7 +37,7 @@ export class JsEvaluationEngine implements EvaluationEngine {
|
||||
class JsEvaluator implements Evaluator {
|
||||
public readonly languageId = "javascript";
|
||||
|
||||
constructor(private readonly session: VsCodeDebugSession) {}
|
||||
constructor(private readonly session: EnhancedDebugSession) {}
|
||||
|
||||
private getContext(): "copy" | "repl" {
|
||||
if (this.session.session.type.startsWith("pwa-")) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
DataExtractionResult,
|
||||
isExtractedData,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
import { FormattedMessage } from "../../contract";
|
||||
import { FormattedMessage } from "../../webviewContract";
|
||||
|
||||
export interface ParseEvaluationResultContext {
|
||||
debugAdapterType: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataExtractionState, CompletionItem } from "../contract";
|
||||
import { DataExtractionState, CompletionItem } from "../webviewContract";
|
||||
|
||||
export interface EvaluationWatchService {
|
||||
createEvaluationWatcher(
|
||||
|
||||
@@ -6,9 +6,9 @@ import {
|
||||
import { observable, autorun, action } from "mobx";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { DataExtractorId } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { DataExtractionState, CompletionItem } from "../contract";
|
||||
import { DataExtractionState, CompletionItem } from "../webviewContract";
|
||||
import { hotClass } from "@hediet/node-reload";
|
||||
import { VsCodeDebuggerView } from "../VsCodeDebugger";
|
||||
import { VsCodeDebuggerView } from "../debugger/VsCodeDebuggerView";
|
||||
import { EvaluationEngine } from "./EvaluationEngine/EvaluationEngine";
|
||||
|
||||
@hotClass(module)
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debug, DebugSession } from "vscode";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
import { CompletionItem } from "./contract";
|
||||
import { observable, runInAction, action } from "mobx";
|
||||
|
||||
export class VsCodeDebugger {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly sessions = new Map<DebugSession, VsCodeDebugSession>();
|
||||
|
||||
private readonly _onDidStartDebugSession = new EventEmitter<{
|
||||
session: VsCodeDebugSession;
|
||||
}>();
|
||||
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
|
||||
|
||||
public getDebugSession(session: DebugSession): VsCodeDebugSession {
|
||||
let result = this.sessions.get(session);
|
||||
if (!result) {
|
||||
result = new VsCodeDebugSession(session);
|
||||
this.sessions.set(session, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.dispose.track([
|
||||
debug.onDidStartDebugSession(session => {
|
||||
const e = this.sessions.get(session)!;
|
||||
this._onDidStartDebugSession.emit({ session: e });
|
||||
}),
|
||||
debug.onDidTerminateDebugSession(session => {
|
||||
this.sessions.delete(session);
|
||||
}),
|
||||
debug.registerDebugAdapterTrackerFactory("*", {
|
||||
createDebugAdapterTracker: session => {
|
||||
const extendedSession = this.getDebugSession(session);
|
||||
return {
|
||||
onDidSendMessage: async msg => {
|
||||
type Message =
|
||||
| StoppedEvent
|
||||
| ThreadsResponse
|
||||
| ContinueLikeResponse;
|
||||
|
||||
interface ContinueLikeResponse {
|
||||
type: "response";
|
||||
command:
|
||||
| "continue"
|
||||
| "stepIn"
|
||||
| "stepOut"
|
||||
| "next";
|
||||
}
|
||||
|
||||
interface StoppedEvent {
|
||||
type: "event";
|
||||
event: "stopped";
|
||||
body: {
|
||||
threadId: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface ThreadsResponse {
|
||||
type: "response";
|
||||
command: "threads";
|
||||
success: boolean;
|
||||
body: {
|
||||
threads: ThreadInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ThreadInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const m = msg as Message;
|
||||
if (m.type === "event") {
|
||||
if (m.event === "stopped") {
|
||||
const threadId = m.body.threadId;
|
||||
const r = await extendedSession[
|
||||
"getStackTrace"
|
||||
]({
|
||||
threadId,
|
||||
startFrame: 0,
|
||||
levels: 1,
|
||||
});
|
||||
extendedSession["activeStackFrame"] =
|
||||
r.stackFrames.length > 0
|
||||
? r.stackFrames[0].id
|
||||
: undefined;
|
||||
}
|
||||
} else if (m.type === "response") {
|
||||
if (
|
||||
m.command === "continue" ||
|
||||
m.command === "next" ||
|
||||
m.command === "stepIn" ||
|
||||
m.command === "stepOut"
|
||||
) {
|
||||
extendedSession[
|
||||
"activeStackFrame"
|
||||
] = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
interface StackFrame {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export class VsCodeDebugSession {
|
||||
@observable protected activeStackFrame: number | undefined;
|
||||
|
||||
constructor(public readonly session: DebugSession) {}
|
||||
|
||||
protected async getStackTrace(args: {
|
||||
threadId: number;
|
||||
startFrame?: number;
|
||||
levels?: number;
|
||||
}): Promise<{ totalFrames?: number; stackFrames: StackFrame[] }> {
|
||||
try {
|
||||
const reply = (await this.session.customRequest("stackTrace", {
|
||||
threadId: args.threadId,
|
||||
levels: args.levels,
|
||||
startFrame: args.startFrame || 0,
|
||||
})) as { totalFrames?: number; stackFrames: StackFrame[] };
|
||||
return reply;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async getCompletions(args: {
|
||||
text: string;
|
||||
column: number;
|
||||
frameId: number | undefined;
|
||||
}): Promise<CompletionItem[]> {
|
||||
try {
|
||||
const reply = await this.session.customRequest("completions", {
|
||||
text: args.text,
|
||||
frameId: args.frameId,
|
||||
column: args.column,
|
||||
});
|
||||
if (!reply) {
|
||||
return [];
|
||||
}
|
||||
return reply.targets;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given expression.
|
||||
* If context is "watch", long results are usually shortened.
|
||||
*/
|
||||
public async evaluate(args: {
|
||||
expression: string;
|
||||
frameId: number | undefined;
|
||||
context: "watch" | "repl" | "copy";
|
||||
}): Promise<{ result: string }> {
|
||||
const reply = await this.session.customRequest("evaluate", {
|
||||
expression: args.expression,
|
||||
frameId: args.frameId,
|
||||
|
||||
context: args.context,
|
||||
});
|
||||
return { result: reply.result };
|
||||
}
|
||||
}
|
||||
|
||||
export class VsCodeDebuggerView {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
@observable private _activeDebugSession: VsCodeDebugSession | undefined;
|
||||
|
||||
public get activeDebugSession(): VsCodeDebugSession | undefined {
|
||||
return this._activeDebugSession;
|
||||
}
|
||||
|
||||
public get activeFrameId(): number | undefined {
|
||||
if (!this._activeDebugSession) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._activeDebugSession["activeStackFrame"];
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private vsCodeDebugger: VsCodeDebugger) {
|
||||
this.dispose.track(
|
||||
debug.onDidChangeActiveDebugSession(activeSession => {
|
||||
this.updateActiveDebugSession(activeSession);
|
||||
})
|
||||
);
|
||||
this.updateActiveDebugSession(debug.activeDebugSession);
|
||||
}
|
||||
|
||||
@action
|
||||
private updateActiveDebugSession(activeSession: DebugSession | undefined) {
|
||||
if (!activeSession) {
|
||||
this._activeDebugSession = undefined;
|
||||
} else {
|
||||
const s = this.vsCodeDebugger.getDebugSession(activeSession);
|
||||
this._activeDebugSession = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
extension/src/debugger/EnhancedDebugSession.ts
Normal file
70
extension/src/debugger/EnhancedDebugSession.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { DebugSession } from "vscode";
|
||||
import { CompletionItem } from "../webviewContract";
|
||||
import { observable } from "mobx";
|
||||
|
||||
export class EnhancedDebugSession {
|
||||
@observable protected activeStackFrame: number | undefined;
|
||||
|
||||
constructor(public readonly session: DebugSession) {}
|
||||
|
||||
protected async getStackTrace(args: {
|
||||
threadId: number;
|
||||
startFrame?: number;
|
||||
levels?: number;
|
||||
}): Promise<{ totalFrames?: number; stackFrames: StackFrame[] }> {
|
||||
try {
|
||||
const reply = (await this.session.customRequest("stackTrace", {
|
||||
threadId: args.threadId,
|
||||
levels: args.levels,
|
||||
startFrame: args.startFrame || 0,
|
||||
})) as { totalFrames?: number; stackFrames: StackFrame[] };
|
||||
return reply;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async getCompletions(args: {
|
||||
text: string;
|
||||
column: number;
|
||||
frameId: number | undefined;
|
||||
}): Promise<CompletionItem[]> {
|
||||
try {
|
||||
const reply = await this.session.customRequest("completions", {
|
||||
text: args.text,
|
||||
frameId: args.frameId,
|
||||
column: args.column,
|
||||
});
|
||||
if (!reply) {
|
||||
return [];
|
||||
}
|
||||
return reply.targets;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the given expression.
|
||||
* If context is "watch", long results are usually shortened.
|
||||
*/
|
||||
|
||||
public async evaluate(args: {
|
||||
expression: string;
|
||||
frameId: number | undefined;
|
||||
context: "watch" | "repl" | "copy";
|
||||
}): Promise<{ result: string }> {
|
||||
const reply = await this.session.customRequest("evaluate", {
|
||||
expression: args.expression,
|
||||
frameId: args.frameId,
|
||||
context: args.context,
|
||||
});
|
||||
return { result: reply.result };
|
||||
}
|
||||
}
|
||||
interface StackFrame {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
116
extension/src/debugger/VsCodeDebugger.ts
Normal file
116
extension/src/debugger/VsCodeDebugger.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debug, DebugSession } from "vscode";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
import { runInAction } from "mobx";
|
||||
import { EnhancedDebugSession } from "./EnhancedDebugSession";
|
||||
|
||||
/**
|
||||
* Decorates DebugSession instances and emits an event for new debug sessions.
|
||||
*/
|
||||
export class VsCodeDebugger {
|
||||
public readonly dispose = Disposable.fn();
|
||||
private readonly sessions = new Map<DebugSession, EnhancedDebugSession>();
|
||||
|
||||
private readonly _onDidStartDebugSession = new EventEmitter<{
|
||||
session: EnhancedDebugSession;
|
||||
}>();
|
||||
public readonly onDidStartDebugSession = this._onDidStartDebugSession.asEvent();
|
||||
|
||||
public getEnhancedDebugSession(
|
||||
session: DebugSession
|
||||
): EnhancedDebugSession {
|
||||
let result = this.sessions.get(session);
|
||||
if (!result) {
|
||||
result = new EnhancedDebugSession(session);
|
||||
this.sessions.set(session, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.dispose.track([
|
||||
debug.onDidStartDebugSession(session => {
|
||||
const e = this.sessions.get(session)!;
|
||||
this._onDidStartDebugSession.emit({ session: e });
|
||||
}),
|
||||
debug.onDidTerminateDebugSession(session => {
|
||||
this.sessions.delete(session);
|
||||
}),
|
||||
debug.registerDebugAdapterTrackerFactory("*", {
|
||||
createDebugAdapterTracker: session => {
|
||||
const extendedSession = this.getEnhancedDebugSession(
|
||||
session
|
||||
);
|
||||
return {
|
||||
onDidSendMessage: async msg => {
|
||||
type Message =
|
||||
| StoppedEvent
|
||||
| ThreadsResponse
|
||||
| ContinueLikeResponse;
|
||||
|
||||
interface ContinueLikeResponse {
|
||||
type: "response";
|
||||
command:
|
||||
| "continue"
|
||||
| "stepIn"
|
||||
| "stepOut"
|
||||
| "next";
|
||||
}
|
||||
|
||||
interface StoppedEvent {
|
||||
type: "event";
|
||||
event: "stopped";
|
||||
body: {
|
||||
threadId: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface ThreadsResponse {
|
||||
type: "response";
|
||||
command: "threads";
|
||||
success: boolean;
|
||||
body: {
|
||||
threads: ThreadInfo[];
|
||||
};
|
||||
}
|
||||
|
||||
interface ThreadInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const m = msg as Message;
|
||||
if (m.type === "event") {
|
||||
if (m.event === "stopped") {
|
||||
const threadId = m.body.threadId;
|
||||
const r = await extendedSession[
|
||||
"getStackTrace"
|
||||
]({
|
||||
threadId,
|
||||
startFrame: 0,
|
||||
levels: 1,
|
||||
});
|
||||
extendedSession["activeStackFrame"] =
|
||||
r.stackFrames.length > 0
|
||||
? r.stackFrames[0].id
|
||||
: undefined;
|
||||
}
|
||||
} else if (m.type === "response") {
|
||||
if (
|
||||
m.command === "continue" ||
|
||||
m.command === "next" ||
|
||||
m.command === "stepIn" ||
|
||||
m.command === "stepOut"
|
||||
) {
|
||||
extendedSession[
|
||||
"activeStackFrame"
|
||||
] = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
]);
|
||||
}
|
||||
}
|
||||
48
extension/src/debugger/VsCodeDebuggerView.ts
Normal file
48
extension/src/debugger/VsCodeDebuggerView.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debug, DebugSession } from "vscode";
|
||||
import { observable, action } from "mobx";
|
||||
import { VsCodeDebugger } from "./VsCodeDebugger";
|
||||
import { EnhancedDebugSession } from "./EnhancedDebugSession";
|
||||
|
||||
/**
|
||||
* Tracks the currently active debug session and its currently active stack frame.
|
||||
* This is currently all faked. A proper API would be nice.
|
||||
*/
|
||||
export class VsCodeDebuggerView {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
@observable private _activeDebugSession: EnhancedDebugSession | undefined;
|
||||
|
||||
public get activeDebugSession(): EnhancedDebugSession | undefined {
|
||||
return this._activeDebugSession;
|
||||
}
|
||||
|
||||
public get activeFrameId(): number | undefined {
|
||||
if (!this._activeDebugSession) {
|
||||
return undefined;
|
||||
} else {
|
||||
return this._activeDebugSession["activeStackFrame"];
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private vsCodeDebugger: VsCodeDebugger) {
|
||||
this.dispose.track(
|
||||
debug.onDidChangeActiveDebugSession(activeSession => {
|
||||
this.updateActiveDebugSession(activeSession);
|
||||
})
|
||||
);
|
||||
this.updateActiveDebugSession(debug.activeDebugSession);
|
||||
}
|
||||
|
||||
@action
|
||||
private updateActiveDebugSession(activeSession: DebugSession | undefined) {
|
||||
if (!activeSession) {
|
||||
this._activeDebugSession = undefined;
|
||||
} else {
|
||||
const s = this.vsCodeDebugger.getEnhancedDebugSession(
|
||||
activeSession
|
||||
);
|
||||
this._activeDebugSession = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,11 @@ if (process.env.HOT_RELOAD) {
|
||||
}
|
||||
registerUpdateReconciler(module);
|
||||
|
||||
import { WebViews } from "./WebViews";
|
||||
import { Server } from "./Server";
|
||||
import { InternalWebviewManager } from "./webview/InternalWebviewManager";
|
||||
import { WebviewServer } from "./webview/WebviewServer";
|
||||
import { Config } from "./Config";
|
||||
import { VsCodeDebugger, VsCodeDebuggerView } from "./VsCodeDebugger";
|
||||
import { VsCodeDebugger } from "./debugger/VsCodeDebugger";
|
||||
import { VsCodeDebuggerView } from "./debugger/VsCodeDebuggerView";
|
||||
import { EvaluationWatchServiceImpl } from "./EvaluationWatchService";
|
||||
import {
|
||||
ComposedEvaluationEngine,
|
||||
@@ -24,11 +25,18 @@ import {
|
||||
ConfiguredEvaluationEngine,
|
||||
} from "./EvaluationWatchService/EvaluationEngine";
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
hotRequireExportedFn(module, Extension, Extension => new Extension())
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
||||
export class Extension {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
private readonly config = new Config();
|
||||
|
||||
private readonly debugger = this.dispose.track(new VsCodeDebugger());
|
||||
private readonly debuggerView = this.dispose.track(
|
||||
new VsCodeDebuggerView(this.debugger)
|
||||
@@ -43,8 +51,10 @@ export class Extension {
|
||||
])
|
||||
);
|
||||
|
||||
private readonly server = new Server(this.dataSource, this.config);
|
||||
private readonly views = this.dispose.track(new WebViews(this.server));
|
||||
private readonly server = new WebviewServer(this.dataSource, this.config);
|
||||
private readonly views = this.dispose.track(
|
||||
new InternalWebviewManager(this.server, this.config)
|
||||
);
|
||||
|
||||
constructor() {
|
||||
if (getReloadCount(module) > 0) {
|
||||
@@ -79,10 +89,10 @@ export class Extension {
|
||||
}
|
||||
|
||||
const connections = [...this.server.connections.values()];
|
||||
if (connections.length > 0) {
|
||||
const latestConnection =
|
||||
connections[connections.length - 1];
|
||||
const latestConnection =
|
||||
connections[connections.length - 1];
|
||||
|
||||
if (latestConnection) {
|
||||
latestConnection.setExpression(selectedText);
|
||||
} else {
|
||||
this.views.createNew(selectedText);
|
||||
@@ -92,11 +102,3 @@ export class Extension {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
context.subscriptions.push(
|
||||
hotRequireExportedFn(module, Extension, Extension => new Extension())
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {}
|
||||
|
||||
120
extension/src/utils/VsCodeSettings.ts
Normal file
120
extension/src/utils/VsCodeSettings.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { Uri, workspace, ConfigurationTarget, Disposable } from "vscode";
|
||||
import { fromResource } from "mobx-utils";
|
||||
import { computed, runInAction } from "mobx";
|
||||
import { EventEmitter } from "@hediet/std/events";
|
||||
|
||||
export interface Serializer<T> {
|
||||
deserialize: (val: any) => T;
|
||||
serializer: (val: T) => any;
|
||||
}
|
||||
|
||||
export function serializerWithDefault<T>(defaultValue: T): Serializer<T> {
|
||||
return {
|
||||
deserialize: val => (val === undefined ? defaultValue : val),
|
||||
serializer: val => val,
|
||||
};
|
||||
}
|
||||
|
||||
export class VsCodeSetting<T> {
|
||||
public get T(): T {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
public readonly serializer: Serializer<T>;
|
||||
public readonly scope: Uri | undefined;
|
||||
private readonly settingResource: VsCodeSettingResource;
|
||||
|
||||
public constructor(
|
||||
public readonly id: string,
|
||||
options: {
|
||||
serializer?: Serializer<T>;
|
||||
scope?: Uri;
|
||||
} = {}
|
||||
) {
|
||||
this.scope = options.scope;
|
||||
this.serializer = options.serializer || {
|
||||
deserialize: val => val,
|
||||
serializer: val => val,
|
||||
};
|
||||
this.settingResource = new VsCodeSettingResource(this.id, this.scope);
|
||||
}
|
||||
|
||||
public get(): T {
|
||||
const result = this.settingResource.value;
|
||||
return this.serializer.deserialize(result);
|
||||
}
|
||||
|
||||
public async set(value: T): Promise<void> {
|
||||
const value2 = this.serializer.serializer(value);
|
||||
const c = workspace.getConfiguration(undefined, this.scope);
|
||||
const result = c.inspect(this.id);
|
||||
let target: ConfigurationTarget;
|
||||
|
||||
if (
|
||||
result &&
|
||||
[result.workspaceFolderValue].some(i => i !== undefined)
|
||||
) {
|
||||
target = ConfigurationTarget.WorkspaceFolder;
|
||||
}
|
||||
if (result && [result.workspaceValue].some(i => i !== undefined)) {
|
||||
target = ConfigurationTarget.Workspace;
|
||||
} else {
|
||||
target = ConfigurationTarget.Global;
|
||||
}
|
||||
|
||||
await c.update(this.id, value2, target);
|
||||
}
|
||||
}
|
||||
|
||||
class VsCodeSettingResource {
|
||||
public static onConfigChange = new EventEmitter();
|
||||
|
||||
private subscription: Disposable | undefined;
|
||||
private readonly resource = fromResource<any>(
|
||||
update => {
|
||||
update(this.readValue());
|
||||
this.subscription = VsCodeSettingResource.onConfigChange.sub(() => {
|
||||
update(this.readValue());
|
||||
});
|
||||
},
|
||||
() => this.subscription!.dispose(),
|
||||
this.readValue()
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly id: string,
|
||||
private readonly scope: Uri | undefined
|
||||
) {}
|
||||
|
||||
private readValue(): any {
|
||||
const val = workspace
|
||||
.getConfiguration(undefined, this.scope)
|
||||
.get(this.id);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* This improves change detection.
|
||||
*/
|
||||
private readonly stringifiedSettingValue = computed(
|
||||
() => JSON.stringify(this.resource.current()),
|
||||
{
|
||||
name: `VsCodeSettingResource[${this.id}].value`,
|
||||
context: this,
|
||||
}
|
||||
);
|
||||
|
||||
public get value() {
|
||||
const v = this.stringifiedSettingValue.get();
|
||||
if (v === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return JSON.parse(v);
|
||||
}
|
||||
}
|
||||
|
||||
workspace.onDidChangeConfiguration(() => {
|
||||
runInAction("Update Configuration", () => {
|
||||
VsCodeSettingResource.onConfigChange.emit();
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,23 @@
|
||||
import { window, ViewColumn, WebviewPanel } from "vscode";
|
||||
import { Server } from "./Server";
|
||||
import { WebviewServer } from "./WebviewServer";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { WindowWithWebviewData } from "../webviewContract";
|
||||
import { Config } from "../Config";
|
||||
|
||||
export const debugVisualizer = "debugVisualizer";
|
||||
|
||||
export class WebViews {
|
||||
private readonly debugVisualizations = new Map<WebviewPanel, WebView>();
|
||||
export class InternalWebviewManager {
|
||||
private readonly openedWebviews = new Map<
|
||||
WebviewPanel,
|
||||
DebugVisualizerWebview
|
||||
>();
|
||||
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
constructor(private readonly server: Server) {
|
||||
constructor(
|
||||
private readonly server: WebviewServer,
|
||||
private readonly config: Config
|
||||
) {
|
||||
this.dispose.track(
|
||||
window.registerWebviewPanelSerializer(debugVisualizer, {
|
||||
deserializeWebviewPanel: async (panel, state) => {
|
||||
@@ -20,7 +28,7 @@ export class WebViews {
|
||||
|
||||
this.dispose.track({
|
||||
dispose: () => {
|
||||
for (const panel of this.debugVisualizations.keys()) {
|
||||
for (const panel of this.openedWebviews.keys()) {
|
||||
panel.dispose();
|
||||
}
|
||||
},
|
||||
@@ -28,7 +36,7 @@ export class WebViews {
|
||||
}
|
||||
|
||||
public createNew(expression: string | undefined = undefined) {
|
||||
const panel = window.createWebviewPanel(
|
||||
const webviewPanel = window.createWebviewPanel(
|
||||
debugVisualizer,
|
||||
"Debug Visualizer",
|
||||
ViewColumn.Two,
|
||||
@@ -44,33 +52,38 @@ export class WebViews {
|
||||
}
|
||||
);
|
||||
|
||||
this.setupView(panel, expression);
|
||||
this.initializeView(webviewPanel, expression);
|
||||
}
|
||||
|
||||
public restore(webviewPanel: WebviewPanel) {
|
||||
this.setupView(webviewPanel);
|
||||
private restore(webviewPanel: WebviewPanel) {
|
||||
this.initializeView(webviewPanel);
|
||||
}
|
||||
|
||||
private setupView(
|
||||
private initializeView(
|
||||
webviewPanel: WebviewPanel,
|
||||
expression: string | undefined = undefined
|
||||
) {
|
||||
webviewPanel.webview.html = getWebviewHtml(this.server, expression);
|
||||
const view = new WebView(webviewPanel);
|
||||
this.debugVisualizations.set(webviewPanel, view);
|
||||
webviewPanel.webview.html = getDebugVisualizerWebviewHtml(
|
||||
this.server,
|
||||
expression,
|
||||
this.config
|
||||
);
|
||||
const view = new DebugVisualizerWebview(webviewPanel);
|
||||
this.openedWebviews.set(webviewPanel, view);
|
||||
webviewPanel.onDidDispose(() => {
|
||||
this.debugVisualizations.delete(webviewPanel);
|
||||
this.openedWebviews.delete(webviewPanel);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class WebView {
|
||||
export class DebugVisualizerWebview {
|
||||
constructor(private readonly webviewPanel: WebviewPanel) {}
|
||||
}
|
||||
|
||||
export function getWebviewHtml(
|
||||
server: Server,
|
||||
expression: string | undefined = undefined
|
||||
function getDebugVisualizerWebviewHtml(
|
||||
server: WebviewServer,
|
||||
initialExpression: string | undefined = undefined,
|
||||
config: Config
|
||||
) {
|
||||
const isDev = !!process.env.USE_DEV_UI;
|
||||
return `
|
||||
@@ -86,12 +99,15 @@ export function getWebviewHtml(
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.webViewData = ${JSON.stringify({
|
||||
serverSecret: server.secret,
|
||||
serverPort: server.port,
|
||||
publicPath: server.publicPath,
|
||||
expression,
|
||||
})};
|
||||
Object.assign(window, ${JSON.stringify({
|
||||
webviewData: {
|
||||
serverSecret: server.secret,
|
||||
serverPort: server.port,
|
||||
publicPath: server.publicPath,
|
||||
expression: initialExpression,
|
||||
theme: config.theme,
|
||||
},
|
||||
} as WindowWithWebviewData)});
|
||||
const api = window.VsCodeApi = acquireVsCodeApi();
|
||||
window.addEventListener('message', event => {
|
||||
if (event.source === window.frames[0]) {
|
||||
@@ -109,10 +125,13 @@ export function getWebviewHtml(
|
||||
|
||||
${
|
||||
isDev
|
||||
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getIndexUrl(
|
||||
{ mode: "webViewIFrame", expression }
|
||||
? `<iframe sandbox="allow-top-navigation allow-scripts allow-same-origin allow-popups allow-pointer-lock allow-forms" src="${server.getWebviewPageUrl(
|
||||
{
|
||||
mode: "webviewIFrame",
|
||||
expression: initialExpression,
|
||||
}
|
||||
)}"></iframe>`
|
||||
: `<script type="text/javascript" src="${server.mainBundleUrl}"></script>`
|
||||
: `<script type="text/javascript" src="${server.webviewBundleUrl}"></script>`
|
||||
}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,28 +1,28 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { debugVisualizerUIContract } from "./contract";
|
||||
import { webviewContract } from "../webviewContract";
|
||||
import { ConsoleRpcLogger, RequestHandlingError } from "@hediet/typed-json-rpc";
|
||||
import {
|
||||
EvaluationWatcher,
|
||||
EvaluationWatchService,
|
||||
} from "./EvaluationWatchService";
|
||||
} from "../EvaluationWatchService";
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { observable, autorun } from "mobx";
|
||||
import { Server } from "./Server";
|
||||
import { WebviewServer } from "./WebviewServer";
|
||||
import * as open from "open";
|
||||
import chromeLauncher = require("chrome-launcher");
|
||||
import { Config } from "./Config";
|
||||
import { Config } from "../Config";
|
||||
|
||||
export class ClientConnection {
|
||||
export class WebviewConnection {
|
||||
public readonly dispose = Disposable.fn();
|
||||
@observable
|
||||
private watcher: EvaluationWatcher | undefined = undefined;
|
||||
|
||||
private readonly client: typeof debugVisualizerUIContract["TClientInterface"];
|
||||
private readonly client: typeof webviewContract["TClientInterface"];
|
||||
|
||||
constructor(
|
||||
evaluationWatchService: EvaluationWatchService,
|
||||
stream: WebSocketStream,
|
||||
server: Server,
|
||||
server: WebviewServer,
|
||||
config: Config,
|
||||
serverSecret: string
|
||||
) {
|
||||
@@ -34,10 +34,7 @@ export class ClientConnection {
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
client,
|
||||
channel,
|
||||
} = debugVisualizerUIContract.registerServerToStream(
|
||||
const { client, channel } = webviewContract.registerServerToStream(
|
||||
stream,
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
@@ -76,7 +73,7 @@ export class ClientConnection {
|
||||
openInBrowser: async ({}) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
const url = server.getIndexUrl({
|
||||
const url = server.getWebviewPageUrl({
|
||||
mode: "standalone",
|
||||
expression: this.watcher
|
||||
? this.watcher.expression
|
||||
@@ -141,6 +138,10 @@ export class ClientConnection {
|
||||
public setExpression(expression: string) {
|
||||
this.client.setExpression({ expression });
|
||||
}
|
||||
|
||||
public setTheme(theme: "light" | "dark"): void {
|
||||
this.client.setTheme({ theme });
|
||||
}
|
||||
}
|
||||
|
||||
async function launchChrome(url: string): Promise<boolean> {
|
||||
@@ -1,22 +1,27 @@
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { AddressInfo } from "net";
|
||||
import WebSocket = require("ws");
|
||||
import { ClientConnection } from "./ClientConnection";
|
||||
import { WebviewConnection } from "./WebviewConnection";
|
||||
import * as express from "express";
|
||||
import * as http from "http";
|
||||
import * as serveStatic from "serve-static";
|
||||
import { Config } from "./Config";
|
||||
import { Config } from "../Config";
|
||||
import cryptoRandomString = require("crypto-random-string");
|
||||
import { distPath } from "debug-visualizer-webview";
|
||||
import { EvaluationWatchService } from "./EvaluationWatchService/EvaluationWatchService";
|
||||
import { EvaluationWatchService } from "../EvaluationWatchService/EvaluationWatchService";
|
||||
import { URLSearchParams } from "url";
|
||||
import { autorun, reaction } from "mobx";
|
||||
|
||||
export class Server {
|
||||
export class WebviewServer {
|
||||
private readonly server: http.Server;
|
||||
public readonly secret = cryptoRandomString({ length: 20 });
|
||||
public readonly secret = cryptoRandomString({ length: 30 });
|
||||
|
||||
public readonly connections = new Set<ClientConnection>();
|
||||
public readonly connections = new Set<WebviewConnection>();
|
||||
|
||||
constructor(dataSource: EvaluationWatchService, config: Config) {
|
||||
constructor(
|
||||
dataSource: EvaluationWatchService,
|
||||
private readonly config: Config
|
||||
) {
|
||||
const app = express();
|
||||
|
||||
app.use(serveStatic(distPath));
|
||||
@@ -32,7 +37,7 @@ export class Server {
|
||||
const wss = new WebSocket.Server({ server: this.server });
|
||||
wss.on("connection", async ws => {
|
||||
const stream = new WebSocketStream(ws);
|
||||
const c = new ClientConnection(
|
||||
const c = new WebviewConnection(
|
||||
dataSource,
|
||||
stream,
|
||||
this,
|
||||
@@ -43,27 +48,42 @@ export class Server {
|
||||
await stream.onClosed;
|
||||
this.connections.delete(c);
|
||||
});
|
||||
|
||||
reaction(
|
||||
() => config.theme,
|
||||
theme => {
|
||||
for (const c of this.connections) {
|
||||
c.setTheme(theme);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public getIndexUrl(args: {
|
||||
public getWebviewPageUrl(args: {
|
||||
expression?: string;
|
||||
mode: "standalone" | "webViewIFrame";
|
||||
mode: "standalone" | "webviewIFrame";
|
||||
}): string {
|
||||
const port = process.env.USE_DEV_UI ? 8080 : this.port;
|
||||
const expr =
|
||||
args.expression !== undefined
|
||||
? `&expression=${encodeURIComponent(args.expression)}`
|
||||
: "";
|
||||
const inWebView =
|
||||
args.mode === "standalone" ? "" : "&mode=webViewIFrame";
|
||||
return `http://localhost:${port}/index.html?serverPort=${this.port}&serverSecret=${this.secret}${inWebView}${expr}`;
|
||||
const port = process.env.USE_DEV_UI ? 8081 : this.port;
|
||||
const params: Record<string, string> = {
|
||||
serverPort: this.port.toString(),
|
||||
serverSecret: this.secret,
|
||||
mode: args.mode,
|
||||
theme: this.config.theme,
|
||||
};
|
||||
if (args.expression !== undefined) {
|
||||
params.expression = args.expression;
|
||||
}
|
||||
|
||||
return `http://localhost:${port}/index.html?${new URLSearchParams(
|
||||
params
|
||||
).toString()}`;
|
||||
}
|
||||
|
||||
public get publicPath(): string {
|
||||
return `http://localhost:${this.port}/`;
|
||||
}
|
||||
|
||||
public get mainBundleUrl(): string {
|
||||
public get webviewBundleUrl(): string {
|
||||
return `${this.publicPath}main.js`;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,83 @@ import {
|
||||
DataExtractorId,
|
||||
} from "@hediet/debug-visualizer-data-extraction";
|
||||
|
||||
interface WebviewConfig {
|
||||
serverSecret: string;
|
||||
serverPort: number;
|
||||
publicPath: string;
|
||||
expression?: string;
|
||||
theme: "light" | "dark";
|
||||
}
|
||||
|
||||
export interface WebviewUrlParams {
|
||||
serverPort: string;
|
||||
serverSecret: string;
|
||||
mode: "standalone" | "webviewIFrame";
|
||||
expression: string;
|
||||
theme: "light" | "dark";
|
||||
}
|
||||
|
||||
export interface WindowWithWebviewData {
|
||||
webviewData?: WebviewConfig;
|
||||
}
|
||||
|
||||
export const webviewContract = contract({
|
||||
client: {
|
||||
updateState: notificationContract({
|
||||
params: types.type({
|
||||
newState: unchecked<DataExtractionState>(),
|
||||
}),
|
||||
}),
|
||||
updateLanguageId: notificationContract({
|
||||
params: types.type({
|
||||
languageId: types.union([types.null, types.string]),
|
||||
}),
|
||||
}),
|
||||
setExpression: requestContract({
|
||||
params: types.type({
|
||||
expression: types.string,
|
||||
}),
|
||||
}),
|
||||
setTheme: notificationContract({
|
||||
params: types.type({
|
||||
theme: types.union([
|
||||
types.literal("light"),
|
||||
types.literal("dark"),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
server: {
|
||||
authenticate: requestContract({
|
||||
params: types.type({
|
||||
secret: types.string,
|
||||
}),
|
||||
}),
|
||||
setPreferredDataExtractor: notificationContract({
|
||||
params: types.type({
|
||||
dataExtractorId: unchecked<DataExtractorId>(),
|
||||
}),
|
||||
}),
|
||||
setExpression: notificationContract({
|
||||
params: types.type({
|
||||
newExpression: types.string,
|
||||
}),
|
||||
}),
|
||||
refresh: notificationContract({}),
|
||||
openInBrowser: notificationContract({}),
|
||||
|
||||
getCompletions: requestContract({
|
||||
params: types.type({
|
||||
text: types.string,
|
||||
column: types.number,
|
||||
}),
|
||||
result: types.type({
|
||||
completions: types.array(unchecked<CompletionItem>()),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
function unchecked<T>(): types.Type<T, any, unknown> {
|
||||
return new types.Type<T, T, unknown>(
|
||||
"unchecked",
|
||||
@@ -92,52 +169,3 @@ export interface CompletionItem {
|
||||
*/
|
||||
length?: number;
|
||||
}
|
||||
|
||||
export const debugVisualizerUIContract = contract({
|
||||
client: {
|
||||
updateState: notificationContract({
|
||||
params: types.type({
|
||||
newState: unchecked<DataExtractionState>(),
|
||||
}),
|
||||
}),
|
||||
updateLanguageId: notificationContract({
|
||||
params: types.type({
|
||||
languageId: types.union([types.null, types.string]),
|
||||
}),
|
||||
}),
|
||||
setExpression: requestContract({
|
||||
params: types.type({
|
||||
expression: types.string,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
server: {
|
||||
authenticate: requestContract({
|
||||
params: types.type({
|
||||
secret: types.string,
|
||||
}),
|
||||
}),
|
||||
setPreferredDataExtractor: notificationContract({
|
||||
params: types.type({
|
||||
dataExtractorId: unchecked<DataExtractorId>(),
|
||||
}),
|
||||
}),
|
||||
setExpression: notificationContract({
|
||||
params: types.type({
|
||||
newExpression: types.string,
|
||||
}),
|
||||
}),
|
||||
refresh: notificationContract({}),
|
||||
openInBrowser: notificationContract({}),
|
||||
|
||||
getCompletions: requestContract({
|
||||
params: types.type({
|
||||
text: types.string,
|
||||
column: types.number,
|
||||
}),
|
||||
result: types.type({
|
||||
completions: types.array(unchecked<CompletionItem>()),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
require("ts-node").register();
|
||||
module.exports = require("./webpack.config.ts");
|
||||
@@ -10,13 +10,14 @@ export class App extends React.Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
if (this.model.runningMode !== "webView") {
|
||||
if (this.model.runningMode !== "webview") {
|
||||
if (this.model.theme === "light") {
|
||||
require("../vscode-light.scss");
|
||||
} else {
|
||||
require("../vscode-dark.scss");
|
||||
}
|
||||
}
|
||||
console.log(this.model.theme);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { DataExtractorInfo } from "@hediet/debug-visualizer-data-extraction";
|
||||
import { computed } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { Model } from "../model/Model";
|
||||
import { VisualizationId } from "@hediet/visualization";
|
||||
import { VisualizationId } from "@hediet/visualization-core";
|
||||
|
||||
@observer
|
||||
export class VisualizerHeaderDetails extends React.Component<{ model: Model }> {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import {
|
||||
debugVisualizerUIContract,
|
||||
webviewContract,
|
||||
DataExtractionState,
|
||||
} from "debug-visualizer/src/contract";
|
||||
WindowWithWebviewData,
|
||||
WebviewUrlParams,
|
||||
} from "debug-visualizer/src/webviewContract";
|
||||
import { WebSocketStream } from "@hediet/typed-json-rpc-websocket";
|
||||
import { ConsoleRpcLogger } from "@hediet/typed-json-rpc";
|
||||
import { observable, action, computed, when, runInAction } from "mobx";
|
||||
@@ -20,21 +22,14 @@ import { MonacoBridge } from "./MonacoBridge";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { startInterval, EventTimer } from "@hediet/std/timer";
|
||||
|
||||
declare const window: Window & {
|
||||
webViewData?: {
|
||||
serverSecret: string;
|
||||
serverPort: number;
|
||||
publicPath: string;
|
||||
expression: string | undefined;
|
||||
};
|
||||
};
|
||||
declare const window: Window & WindowWithWebviewData;
|
||||
|
||||
declare let __webpack_public_path__: string;
|
||||
|
||||
export class Model {
|
||||
public readonly dispose = Disposable.fn();
|
||||
public readonly runningMode: "standalone" | "webView" | "webViewIFrame" =
|
||||
"webView";
|
||||
public readonly runningMode: "standalone" | "webview" | "webviewIFrame" =
|
||||
"webview";
|
||||
|
||||
@observable
|
||||
public theme: "dark" | "light" = "light";
|
||||
@@ -103,7 +98,7 @@ export class Model {
|
||||
|
||||
@observable.ref
|
||||
public server:
|
||||
| typeof debugVisualizerUIContract.TServerInterface
|
||||
| typeof webviewContract.TServerInterface
|
||||
| undefined = undefined;
|
||||
|
||||
private readonly vsCodeApi = getVsCodeApi<{ expression: string }>();
|
||||
@@ -117,57 +112,46 @@ export class Model {
|
||||
private readonly _bridge = new MonacoBridge(this);
|
||||
|
||||
constructor() {
|
||||
if (window.webViewData) {
|
||||
const data = window.webViewData;
|
||||
if (window.webviewData) {
|
||||
const data = window.webviewData;
|
||||
this.port = data.serverPort;
|
||||
this.serverSecret = data.serverSecret;
|
||||
this.runningMode = "webView";
|
||||
this.runningMode = "webview";
|
||||
__webpack_public_path__ = data.publicPath;
|
||||
|
||||
const updateTheme = () => {
|
||||
const isLight = document.body.classList.contains(
|
||||
"vscode-light"
|
||||
);
|
||||
this.theme = isLight ? "light" : "dark";
|
||||
};
|
||||
updateTheme();
|
||||
this.theme = window.webviewData.theme;
|
||||
|
||||
if (data.expression !== undefined) {
|
||||
this.setExpression(data.expression);
|
||||
}
|
||||
|
||||
this.dispose.track(
|
||||
startInterval(1000, () => {
|
||||
updateTheme();
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const url = new URL(window.location.href);
|
||||
const portStr = url.searchParams.get("serverPort");
|
||||
const urlParams = (Object.fromEntries(
|
||||
url.searchParams.entries()
|
||||
) as unknown) as WebviewUrlParams;
|
||||
const portStr = urlParams.serverPort;
|
||||
if (!portStr) {
|
||||
throw new Error("No port given.");
|
||||
}
|
||||
this.port = parseInt(portStr);
|
||||
|
||||
const expr = url.searchParams.get("expression");
|
||||
const expr = urlParams.expression;
|
||||
if (expr) {
|
||||
this.setExpression(expr);
|
||||
}
|
||||
|
||||
const secret = url.searchParams.get("serverSecret");
|
||||
const secret = urlParams.serverSecret;
|
||||
if (!secret) {
|
||||
throw new Error("Server secret not set.");
|
||||
}
|
||||
this.serverSecret = secret;
|
||||
|
||||
const theme = url.searchParams.get("theme");
|
||||
if (theme && theme === "dark") {
|
||||
this.theme = "dark";
|
||||
} else {
|
||||
this.theme = "light";
|
||||
const theme = urlParams.theme;
|
||||
if (theme) {
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
const mode = url.searchParams.get("mode");
|
||||
const mode = urlParams.mode;
|
||||
if (mode) {
|
||||
this.runningMode = mode as any;
|
||||
} else {
|
||||
@@ -225,9 +209,7 @@ export class Model {
|
||||
host: "localhost",
|
||||
port: this.port,
|
||||
});
|
||||
const {
|
||||
server,
|
||||
} = debugVisualizerUIContract.getServerFromStream(
|
||||
const { server } = webviewContract.getServerFromStream(
|
||||
stream,
|
||||
new ConsoleRpcLogger(),
|
||||
{
|
||||
@@ -245,6 +227,9 @@ export class Model {
|
||||
updateLanguageId: async ({ languageId }) => {
|
||||
this.languageId = languageId || "text";
|
||||
},
|
||||
setTheme: async ({ theme }) => {
|
||||
this.theme = theme;
|
||||
},
|
||||
}
|
||||
);
|
||||
try {
|
||||
|
||||
13
yarn.lock
13
yarn.lock
@@ -994,10 +994,10 @@
|
||||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
"@types/vscode@1.35.0":
|
||||
version "1.35.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.35.0.tgz#b17526ef1e9df5cbf7253be6765a33fd518a2304"
|
||||
integrity sha512-Iyliuu8Hv4qy4TEaevQzChh9UsTEcuaKdcHXBbvJnoJSF5Td2yNENOrPK+vuOaXJJBhQZb4BNJKOxt6caaQR8A==
|
||||
"@types/vscode@1.47.0":
|
||||
version "1.47.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/vscode/-/vscode-1.47.0.tgz#4a4051c21ecaadcf383a2c4387bea282540e96de"
|
||||
integrity sha512-nJA37ykkz9FYA0ZOQUSc3OZnhuzEW2vUhUEo4MiduUo82jGwwcLfyvmgd/Q7b0WrZAAceojGhZybg319L24bTA==
|
||||
|
||||
"@types/webpack-sources@*":
|
||||
version "0.1.6"
|
||||
@@ -6624,6 +6624,11 @@ mobx-react@^6.1.8:
|
||||
dependencies:
|
||||
mobx-react-lite "^1.4.2"
|
||||
|
||||
mobx-utils@^5.6.1:
|
||||
version "5.6.1"
|
||||
resolved "https://registry.yarnpkg.com/mobx-utils/-/mobx-utils-5.6.1.tgz#b7d9184b7442fe704be367d4363a2e9961be28cc"
|
||||
integrity sha512-bpTJzM8MXniGnXCZY+ImjPDqBKQ3+G3g/QFSPtNkH6HM3x14DAPqKH7No7NDyhbXBMv3FaVetsgnoEPozbi45Q==
|
||||
|
||||
mobx@^5.10.0:
|
||||
version "5.13.0"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-5.13.0.tgz#0fd68f10aa5ff2d146a4ed9e145b53337cfbca59"
|
||||
|
||||
Reference in New Issue
Block a user