Add evaluateForEffect method to support expression evaluation for side effects
This commit is contained in:
@@ -131,4 +131,14 @@ export class DispatchingVisualizationBackend implements VisualizationBackend {
|
||||
)) || []
|
||||
);
|
||||
}
|
||||
|
||||
public async evaluateForEffect(
|
||||
expression: string
|
||||
): Promise<{ result: string } | { error: string }> {
|
||||
const backend = this.activeVisualizationBackend;
|
||||
if (!backend) {
|
||||
return { error: "No active debug session." };
|
||||
}
|
||||
return await backend.evaluateForEffect(expression);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@ export interface VisualizationBackend extends Disposable {
|
||||
readonly expressionLanguageId: string | undefined;
|
||||
|
||||
getCompletions(text: string, column: number): Promise<CompletionItem[]>;
|
||||
|
||||
/**
|
||||
* Evaluates `expression` in the current frame for its side effect
|
||||
* (e.g. an assignment) rather than to extract visualization data.
|
||||
*/
|
||||
evaluateForEffect(
|
||||
expression: string
|
||||
): Promise<{ result: string } | { error: string }>;
|
||||
}
|
||||
|
||||
export interface GetVisualizationDataArgs {
|
||||
@@ -80,4 +88,22 @@ export abstract class VisualizationBackendBase implements VisualizationBackend {
|
||||
column,
|
||||
});
|
||||
}
|
||||
|
||||
public async evaluateForEffect(
|
||||
expression: string
|
||||
): Promise<{ result: string } | { error: string }> {
|
||||
try {
|
||||
const frameId = this.debuggerView.getActiveStackFrameId(
|
||||
this.debugSession
|
||||
);
|
||||
const reply = await this.debugSession.evaluate({
|
||||
expression,
|
||||
frameId,
|
||||
context: "repl",
|
||||
});
|
||||
return { result: reply.result };
|
||||
} catch (error: any) {
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,14 @@ export interface VisualizationWatchModel {
|
||||
* This field is observable.
|
||||
*/
|
||||
readonly languageId: string | undefined;
|
||||
|
||||
/**
|
||||
* Evaluates `expression` in the current frame for its side effect
|
||||
* (e.g. an assignment) rather than to extract visualization data.
|
||||
*/
|
||||
evaluateForEffect(
|
||||
expression: string
|
||||
): Promise<{ result: string } | { error: string }>;
|
||||
}
|
||||
|
||||
export interface VisualizationWatchOptions {
|
||||
|
||||
@@ -44,6 +44,12 @@ export class VisualizationWatchModelImpl implements VisualizationWatchModel {
|
||||
public getCompletions(text: string, column: number): Promise<CompletionItem[]> {
|
||||
return this.visualizationBackend.getCompletions(text, column);
|
||||
}
|
||||
|
||||
public evaluateForEffect(
|
||||
expression: string
|
||||
): Promise<{ result: string } | { error: string }> {
|
||||
return this.visualizationBackend.evaluateForEffect(expression);
|
||||
}
|
||||
}
|
||||
|
||||
class ObservableVisualizationWatch implements VisualizationWatch {
|
||||
|
||||
@@ -103,6 +103,14 @@ export class WebviewConnection {
|
||||
completions,
|
||||
};
|
||||
},
|
||||
evaluateForEffect: async ({ expression }) => {
|
||||
throwIfNotAuthenticated();
|
||||
|
||||
const reply = await evaluationWatchService.evaluateForEffect(expression);
|
||||
return "error" in reply
|
||||
? { success: false, message: reply.error }
|
||||
: { success: true, message: reply.result };
|
||||
},
|
||||
});
|
||||
|
||||
this.client = client;
|
||||
|
||||
@@ -90,6 +90,23 @@ export const webviewContract = contract({
|
||||
completions: types.array(unchecked<CompletionItem>()),
|
||||
}),
|
||||
}),
|
||||
|
||||
// Evaluates `expression` for its side effect (e.g. an assignment
|
||||
// like `x = 5`) in the active debug session's current frame, using
|
||||
// the same DebugSessionProxy.evaluate() the read-only visualization
|
||||
// path already uses (context "repl", so debug adapters that only
|
||||
// truncate/sanitize "watch" context results still run this in full).
|
||||
// Used by custom visualizer scripts (see vis-plugins/node-table-
|
||||
// visualizer.js) to support editing values in place.
|
||||
evaluateForEffect: requestContract({
|
||||
params: types.type({
|
||||
expression: types.string,
|
||||
}),
|
||||
result: types.type({
|
||||
success: types.boolean,
|
||||
message: types.string,
|
||||
}),
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -161,6 +161,24 @@ export class Model {
|
||||
this.pollingTimer.onTick.sub(() => {
|
||||
this.refresh();
|
||||
});
|
||||
|
||||
// Exposed globally (not passed into custom visualizer scripts'
|
||||
// closures) for the same reason setVisualizationModule is below:
|
||||
// setCustomVisualizerScript's `eval()` runs plugin code in this
|
||||
// same window/global scope, so a plugin (see vis-plugins/node-
|
||||
// table-visualizer.js) can reach these to support editing values
|
||||
// in place, without threading a new parameter through the whole
|
||||
// register()/getVisualization()/render() plugin contract.
|
||||
(globalThis as any).debugVisualizerSetValue = (expression: string) =>
|
||||
this.evaluateForEffect(expression);
|
||||
(globalThis as any).debugVisualizerRefresh = () => this.refresh();
|
||||
}
|
||||
|
||||
async evaluateForEffect(expression: string): Promise<{ success: boolean; message: string }> {
|
||||
if (!this.server) {
|
||||
return { success: false, message: "Not connected to the debug visualizer server." };
|
||||
}
|
||||
return this.server.evaluateForEffect({ expression });
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
Reference in New Issue
Block a user