QuickEval and other debug commands
This commit is contained in:
@@ -351,6 +351,30 @@
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"title": "CodeQL: Run Query on Selected Database"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.debugQuery",
|
||||
"title": "CodeQL: Debug Query"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.debugQueryContextEditor",
|
||||
"title": "CodeQL: Debug Query"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.startDebuggingSelection",
|
||||
"title": "CodeQL: Debug Selection"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.startDebuggingSelectionContextEditor",
|
||||
"title": "CodeQL: Debug Selection"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.continueDebuggingSelection",
|
||||
"title": "CodeQL: Debug Selection"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.continueDebuggingSelectionContextEditor",
|
||||
"title": "CodeQL: Debug Selection"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"title": "CodeQL: Run Query on Multiple Databases"
|
||||
@@ -1072,6 +1096,30 @@
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.debugQuery",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql && !inDebugMode"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.debugQueryContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.startDebuggingSelection",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && debugState == inactive && debugConfigurationType == codeql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.startDebuggingSelectionContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.continueDebuggingSelection",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && debugState == stopped && debugType == codeql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.continueDebuggingSelectionContextEditor",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabases",
|
||||
"when": "resourceLangId == ql && resourceExtname == .ql"
|
||||
@@ -1388,7 +1436,7 @@
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "codeQL.runQueryContextEditor",
|
||||
"when": "editorLangId == ql && resourceExtname == .ql"
|
||||
"when": "editorLangId == ql && resourceExtname == .ql && !inDebugMode"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.runQueryOnMultipleDatabasesContextEditor",
|
||||
@@ -1408,11 +1456,19 @@
|
||||
},
|
||||
{
|
||||
"command": "codeQL.quickEvalContextEditor",
|
||||
"when": "editorLangId == ql"
|
||||
"when": "editorLangId == ql && debugState == inactive"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.debug.quickEvalContextEditor",
|
||||
"when": "config.codeQL.canary && editorLangId == ql"
|
||||
"command": "codeQL.debugQueryContextEditor",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && resourceExtname == .ql && !inDebugMode"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.startDebuggingSelectionContextEditor",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && debugState == inactive && debugConfigurationType == codeql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.continueDebuggingSelectionContextEditor",
|
||||
"when": "config.codeQL.canary && editorLangId == ql && debugState == stopped && debugType == codeql"
|
||||
},
|
||||
{
|
||||
"command": "codeQL.openReferencedFileContextEditor",
|
||||
|
||||
@@ -101,8 +101,12 @@ export type LocalQueryCommands = {
|
||||
|
||||
// Debugger commands
|
||||
export type DebuggerCommands = {
|
||||
"codeQL.debug.quickEval": (uri: Uri) => Promise<void>;
|
||||
"codeQL.debug.quickEvalContextEditor": (uri: Uri) => Promise<void>;
|
||||
"codeQL.debugQuery": (uri: Uri) => Promise<void>;
|
||||
"codeQL.debugQueryContextEditor": (uri: Uri) => Promise<void>;
|
||||
"codeQL.startDebuggingSelection": () => Promise<void>;
|
||||
"codeQL.startDebuggingSelectionContextEditor": () => Promise<void>;
|
||||
"codeQL.continueDebuggingSelection": () => Promise<void>;
|
||||
"codeQL.continueDebuggingSelectionContextEditor": () => Promise<void>;
|
||||
};
|
||||
|
||||
export type ResultsViewCommands = {
|
||||
|
||||
@@ -15,6 +15,7 @@ export type OutputEvent = DebugProtocol.OutputEvent &
|
||||
export interface EvaluationStartedEventBody {
|
||||
id: string;
|
||||
outputDir: string;
|
||||
quickEvalPosition: Position | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,6 +49,13 @@ export type AnyEvent =
|
||||
|
||||
export type Request = DebugProtocol.Request & { type: "request" };
|
||||
|
||||
export interface QuickEvalRequest extends Request {
|
||||
command: "codeql-quickeval";
|
||||
arguments: {
|
||||
quickEvalPosition: Position;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DebugResultRequest extends Request {
|
||||
command: "codeql-debug-result";
|
||||
arguments: undefined;
|
||||
@@ -56,13 +64,19 @@ export interface DebugResultRequest extends Request {
|
||||
export type InitializeRequest = DebugProtocol.InitializeRequest &
|
||||
Request & { command: "initialize" };
|
||||
|
||||
export type AnyRequest = InitializeRequest | DebugResultRequest;
|
||||
export type AnyRequest =
|
||||
| InitializeRequest
|
||||
| DebugResultRequest
|
||||
| QuickEvalRequest;
|
||||
|
||||
export type Response = DebugProtocol.Response & { type: "response" };
|
||||
|
||||
export type InitializeResponse = DebugProtocol.InitializeResponse &
|
||||
Response & { command: "initialize" };
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface QuickEvalResponse extends Response {}
|
||||
|
||||
export type AnyResponse = InitializeResponse;
|
||||
|
||||
export type AnyProtocolMessage = AnyEvent | AnyRequest | AnyResponse;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
ContinuedEvent,
|
||||
Event,
|
||||
ExitedEvent,
|
||||
InitializedEvent,
|
||||
@@ -13,7 +14,12 @@ import { Disposable } from "vscode";
|
||||
import { CancellationTokenSource } from "vscode-jsonrpc";
|
||||
import { BaseLogger, LogOptions, queryServerLogger } from "../common";
|
||||
import { QueryResultType } from "../pure/new-messages";
|
||||
import { CoreQueryResults, CoreQueryRun, QueryRunner } from "../queryRunner";
|
||||
import {
|
||||
CoreCompletedQuery,
|
||||
CoreQueryResults,
|
||||
CoreQueryRun,
|
||||
QueryRunner,
|
||||
} from "../queryRunner";
|
||||
import * as CodeQLDebugProtocol from "./debug-protocol";
|
||||
|
||||
// More complete implementations of `Event` for certain events, because the classes from
|
||||
@@ -77,11 +83,16 @@ class EvaluationStartedEvent
|
||||
public readonly event = "codeql-evaluation-started";
|
||||
public readonly body: CodeQLDebugProtocol.EvaluationStartedEventBody;
|
||||
|
||||
constructor(id: string, outputDir: string) {
|
||||
constructor(
|
||||
id: string,
|
||||
outputDir: string,
|
||||
quickEvalPosition: CodeQLDebugProtocol.Position | undefined,
|
||||
) {
|
||||
super("codeql-evaluation-started");
|
||||
this.body = {
|
||||
id,
|
||||
outputDir,
|
||||
quickEvalPosition,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -254,7 +265,55 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
// Send the response immediately. We'll send a "stopped" message when the evaluation is complete.
|
||||
this.sendResponse(response);
|
||||
|
||||
void this.evaluate();
|
||||
void this.evaluate(this.args.quickEvalPosition);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.unexpectedState(response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected nextRequest(
|
||||
response: DebugProtocol.NextResponse,
|
||||
_args: DebugProtocol.NextArguments,
|
||||
_request?: DebugProtocol.Request,
|
||||
): void {
|
||||
this.stepRequest(response);
|
||||
}
|
||||
|
||||
protected stepInRequest(
|
||||
response: DebugProtocol.StepInResponse,
|
||||
_args: DebugProtocol.StepInArguments,
|
||||
_request?: DebugProtocol.Request,
|
||||
): void {
|
||||
this.stepRequest(response);
|
||||
}
|
||||
|
||||
protected stepOutRequest(
|
||||
response: DebugProtocol.Response,
|
||||
_args: DebugProtocol.StepOutArguments,
|
||||
_request?: DebugProtocol.Request,
|
||||
): void {
|
||||
this.stepRequest(response);
|
||||
}
|
||||
|
||||
protected stepBackRequest(
|
||||
response: DebugProtocol.StepBackResponse,
|
||||
_args: DebugProtocol.StepBackArguments,
|
||||
_request?: DebugProtocol.Request,
|
||||
): void {
|
||||
this.stepRequest(response);
|
||||
}
|
||||
|
||||
private stepRequest(response: DebugProtocol.Response): void {
|
||||
switch (this.state) {
|
||||
case "stopped":
|
||||
this.sendResponse(response);
|
||||
// We don't do anything with stepping yet, so just announce that we've stopped without
|
||||
// actually doing anything.
|
||||
// We don't even send the `EvaluationCompletedEvent`.
|
||||
this.reportStopped();
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -276,7 +335,7 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
// Send the response immediately. We'll send a "stopped" message when the evaluation is complete.
|
||||
this.sendResponse(response);
|
||||
|
||||
void this.evaluate();
|
||||
void this.evaluate(undefined);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -333,6 +392,50 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
super.stackTraceRequest(response, _args, _request);
|
||||
}
|
||||
|
||||
protected customRequest(
|
||||
command: string,
|
||||
response: CodeQLDebugProtocol.Response,
|
||||
args: any,
|
||||
request?: DebugProtocol.Request,
|
||||
): void {
|
||||
switch (command) {
|
||||
case "codeql-quickeval": {
|
||||
this.quickEvalRequest(
|
||||
response,
|
||||
<CodeQLDebugProtocol.QuickEvalRequest["arguments"]>args,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
super.customRequest(command, response, args, request);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected quickEvalRequest(
|
||||
response: CodeQLDebugProtocol.QuickEvalResponse,
|
||||
args: CodeQLDebugProtocol.QuickEvalRequest["arguments"],
|
||||
): void {
|
||||
switch (this.state) {
|
||||
case "stopped":
|
||||
// Send the response immediately. We'll send a "stopped" message when the evaluation is complete.
|
||||
this.sendResponse(response);
|
||||
|
||||
// For built-in requests that are expected to cause execution (`launch`, `continue`, `step`, etc.),
|
||||
// the adapter does not send a `continued` event because the client already knows that's what
|
||||
// is supposed to happen. For a custom request, though, we have to notify the client.
|
||||
this.sendEvent(new ContinuedEvent(QUERY_THREAD_ID, true));
|
||||
|
||||
void this.evaluate(args.quickEvalPosition);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.unexpectedState(response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a `BaseLogger` that sends output to the debug console. */
|
||||
private createLogger(): BaseLogger {
|
||||
return {
|
||||
@@ -348,7 +451,9 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
* This function is invoked from the `launch` and `continue` handlers, without awaiting its
|
||||
* result.
|
||||
*/
|
||||
private async evaluate(): Promise<void> {
|
||||
private async evaluate(
|
||||
quickEvalPosition: CodeQLDebugProtocol.Position | undefined,
|
||||
): Promise<void> {
|
||||
const args = this.args!;
|
||||
|
||||
this.tokenSource = new CancellationTokenSource();
|
||||
@@ -359,7 +464,7 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
args.database,
|
||||
{
|
||||
queryPath: args.query,
|
||||
quickEvalPosition: args.quickEvalPosition,
|
||||
quickEvalPosition,
|
||||
},
|
||||
true,
|
||||
args.additionalPacks,
|
||||
@@ -377,33 +482,38 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
new EvaluationStartedEvent(
|
||||
this.queryRun.id,
|
||||
this.queryRun.outputDir.querySaveDir,
|
||||
quickEvalPosition,
|
||||
),
|
||||
);
|
||||
|
||||
// Report progress via the debugger protocol.
|
||||
const progressStart = new ProgressStartEvent(
|
||||
this.queryRun.id,
|
||||
"Running query",
|
||||
undefined,
|
||||
0,
|
||||
);
|
||||
progressStart.body.cancellable = true;
|
||||
this.sendEvent(progressStart);
|
||||
|
||||
try {
|
||||
const result = await this.queryRun.evaluate(
|
||||
(p) => {
|
||||
const progressUpdate = new ProgressUpdateEvent(
|
||||
this.queryRun!.id,
|
||||
p.message,
|
||||
(p.step * 100) / p.maxStep,
|
||||
);
|
||||
this.sendEvent(progressUpdate);
|
||||
},
|
||||
this.tokenSource!.token,
|
||||
this.createLogger(),
|
||||
// Report progress via the debugger protocol.
|
||||
const progressStart = new ProgressStartEvent(
|
||||
this.queryRun.id,
|
||||
"Running query",
|
||||
undefined,
|
||||
0,
|
||||
);
|
||||
|
||||
progressStart.body.cancellable = true;
|
||||
this.sendEvent(progressStart);
|
||||
let result: CoreCompletedQuery;
|
||||
try {
|
||||
result = await this.queryRun.evaluate(
|
||||
(p) => {
|
||||
const progressUpdate = new ProgressUpdateEvent(
|
||||
this.queryRun!.id,
|
||||
p.message,
|
||||
(p.step * 100) / p.maxStep,
|
||||
);
|
||||
this.sendEvent(progressUpdate);
|
||||
},
|
||||
this.tokenSource!.token,
|
||||
this.createLogger(),
|
||||
);
|
||||
} finally {
|
||||
// Report the end of the progress
|
||||
this.sendEvent(new ProgressEndEvent(this.queryRun!.id));
|
||||
}
|
||||
this.completeEvaluation(result);
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : "Unknown error";
|
||||
@@ -426,8 +536,6 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
): void {
|
||||
this.lastResult = result;
|
||||
|
||||
// Report the end of the progress
|
||||
this.sendEvent(new ProgressEndEvent(this.queryRun!.id));
|
||||
// Report the evaluation result
|
||||
this.sendEvent(new EvaluationCompletedEvent(result));
|
||||
if (result.resultType !== QueryResultType.SUCCESS) {
|
||||
@@ -437,6 +545,12 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
this.sendEvent(outputEvent);
|
||||
}
|
||||
|
||||
this.reportStopped();
|
||||
|
||||
this.queryRun = undefined;
|
||||
}
|
||||
|
||||
private reportStopped(): void {
|
||||
if (this.terminateOnComplete) {
|
||||
this.terminateAndExit();
|
||||
} else {
|
||||
@@ -445,7 +559,6 @@ export class QLDebugSession extends LoggingDebugSession implements Disposable {
|
||||
|
||||
this.state = "stopped";
|
||||
}
|
||||
this.queryRun = undefined;
|
||||
}
|
||||
|
||||
private terminateAndExit(): void {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { basename } from "path";
|
||||
import {
|
||||
DebugAdapterTracker,
|
||||
DebugAdapterTrackerFactory,
|
||||
@@ -17,7 +18,11 @@ import { LocalQueries, LocalQueryRun } from "../local-queries";
|
||||
import { DisposableObject } from "../pure/disposable-object";
|
||||
import { CompletedLocalQueryInfo } from "../query-results";
|
||||
import { CoreQueryResults } from "../queryRunner";
|
||||
import { QueryOutputDir } from "../run-queries-shared";
|
||||
import {
|
||||
getQuickEvalContext,
|
||||
QueryOutputDir,
|
||||
validateQueryUri,
|
||||
} from "../run-queries-shared";
|
||||
import { QLResolvedDebugConfiguration } from "./debug-configuration";
|
||||
import * as CodeQLDebugProtocol from "./debug-protocol";
|
||||
|
||||
@@ -74,6 +79,14 @@ class QLDebugAdapterTracker
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
public async quickEval(): Promise<void> {
|
||||
const args: CodeQLDebugProtocol.QuickEvalRequest["arguments"] = {
|
||||
quickEvalPosition: (await getQuickEvalContext(undefined))
|
||||
.quickEvalPosition,
|
||||
};
|
||||
await this.session.customRequest("codeql-quickeval", args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a message handler to be executed once all other pending message handlers have completed.
|
||||
*
|
||||
@@ -105,10 +118,10 @@ class QLDebugAdapterTracker
|
||||
);
|
||||
|
||||
const quickEval =
|
||||
this.configuration.quickEvalPosition !== undefined
|
||||
body.quickEvalPosition !== undefined
|
||||
? {
|
||||
quickEvalPosition: this.configuration.quickEvalPosition,
|
||||
quickEvalText: "quickeval!!!", // TODO: Have the debug adapter return the range, and extract the text from the editor.
|
||||
quickEvalPosition: body.quickEvalPosition,
|
||||
quickEvalText: "<Quick Evaluation>", // TODO: Have the debug adapter return the range, and extract the text from the editor.
|
||||
}
|
||||
: undefined;
|
||||
this.localQueryRun = await this.localQueries.createLocalQueryRun(
|
||||
@@ -155,8 +168,15 @@ export class DebuggerUI
|
||||
|
||||
public getCommands(): DebuggerCommands {
|
||||
return {
|
||||
"codeQL.debug.quickEval": this.quickEval.bind(this),
|
||||
"codeQL.debug.quickEvalContextEditor": this.quickEval.bind(this),
|
||||
"codeQL.debugQuery": this.debugQuery.bind(this),
|
||||
"codeQL.debugQueryContextEditor": this.debugQuery.bind(this),
|
||||
"codeQL.startDebuggingSelectionContextEditor":
|
||||
this.startDebuggingSelection.bind(this),
|
||||
"codeQL.startDebuggingSelection": this.startDebuggingSelection.bind(this),
|
||||
"codeQL.continueDebuggingSelection":
|
||||
this.continueDebuggingSelection.bind(this),
|
||||
"codeQL.continueDebuggingSelectionContextEditor":
|
||||
this.continueDebuggingSelection.bind(this),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -181,7 +201,20 @@ export class DebuggerUI
|
||||
this.sessions.delete(session.id);
|
||||
}
|
||||
|
||||
private async quickEval(_uri: Uri): Promise<void> {
|
||||
private async debugQuery(uri: Uri): Promise<void> {
|
||||
const queryPath = validateQueryUri(uri, false);
|
||||
|
||||
// Start debugging with a default configuration that just specifies the query path.
|
||||
await debug.startDebugging(undefined, {
|
||||
name: basename(queryPath),
|
||||
type: "codeql",
|
||||
request: "launch",
|
||||
query: queryPath,
|
||||
});
|
||||
}
|
||||
|
||||
private async startDebuggingSelection(): Promise<void> {
|
||||
// Launch the currently selected debug configuration, but specifying QuickEval mode.
|
||||
await commands.executeCommand("workbench.action.debug.start", {
|
||||
config: {
|
||||
quickEval: true,
|
||||
@@ -189,6 +222,15 @@ export class DebuggerUI
|
||||
});
|
||||
}
|
||||
|
||||
private async continueDebuggingSelection(): Promise<void> {
|
||||
const activeTracker = this.activeTracker;
|
||||
if (activeTracker === undefined) {
|
||||
throw new Error("No CodeQL debug session is active.");
|
||||
}
|
||||
|
||||
await activeTracker.quickEval();
|
||||
}
|
||||
|
||||
private getTrackerForSession(
|
||||
session: DebugSession,
|
||||
): QLDebugAdapterTracker | undefined {
|
||||
|
||||
Reference in New Issue
Block a user