Skip to content

Commit

Permalink
Unify file access in debugger (#584)
Browse files Browse the repository at this point in the history
This is a workaround for windows which doesn't play well with URIs. The
DAP
`setDebuggerPathFormat`/`convertClientPathToDebugger`/`convertDebuggerPathToClient`
can't be used as there is a [bug requiring
nodejs](microsoft/vscode-debugadapter-node#298)
which prevents usage in web extensions. Once the bug is fixed we can
update the debugger to use URIs without this PR's retry conversion.

This PR also fixes an issue where if the user closed the file being
debugged or it was open as 'temporarily open' then we couldn't step back
into the code.
  • Loading branch information
idavis authored Aug 16, 2023
1 parent 99c94d5 commit 4f7e49e
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 53 deletions.
10 changes: 6 additions & 4 deletions vscode/src/common.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { DocumentFilter } from "vscode";
import { DocumentFilter, TextDocument, Uri } from "vscode";

export const qsharpLanguageId = "qsharp";
// Matches all Q# documents, including unsaved files, notebook cells, etc.
Expand All @@ -16,7 +16,9 @@ export const qsharpNotebookCellDocumentFilter: DocumentFilter = {
export const qsharpExtensionId = "qsharp-vscode";

export interface FileAccessor {
readFile(uri: string): Promise<Uint8Array>;
readFileAsString(uri: string): Promise<string>;
writeFile(uri: string, contents: Uint8Array): Promise<void>;
normalizePath(path: string): string;
convertToWindowsPathSeparator(path: string): string;
resolvePathToUri(path: string): Uri;
openPath(path: string): Promise<TextDocument>;
openUri(uri: Uri): Promise<TextDocument>;
}
30 changes: 23 additions & 7 deletions vscode/src/debugger/activate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,32 @@ class QsDebugConfigProvider implements vscode.DebugConfigurationProvider {
}
}

// The path normalization, fallbacks, and uri resolution are necessary
// due to https://github.com/microsoft/vscode-debugadapter-node/issues/298
// We can't specify that the debug adapter should use Uri for paths and can't
// use the DebugSession conversion functions because they don't work in the web.
export const workspaceFileAccessor: FileAccessor = {
async readFile(uri: string): Promise<Uint8Array> {
return await vscode.workspace.fs.readFile(vscode.Uri.parse(uri));
normalizePath(path: string): string {
return path.replace(/\\/g, "/");
},
async readFileAsString(uri: string): Promise<string> {
const contents = await this.readFile(uri);
return new TextDecoder().decode(contents);
convertToWindowsPathSeparator(path: string): string {
return path.replace(/\//g, "\\");
},
async writeFile(uri: string, contents: Uint8Array) {
await vscode.workspace.fs.writeFile(vscode.Uri.parse(uri), contents);
resolvePathToUri(path: string): vscode.Uri {
const normalizedPath = this.normalizePath(path);
return vscode.Uri.parse(normalizedPath, false);
},
async openPath(path: string): Promise<vscode.TextDocument> {
const uri: vscode.Uri = this.resolvePathToUri(path);
return this.openUri(uri);
},
async openUri(uri: vscode.Uri): Promise<vscode.TextDocument> {
try {
return await vscode.workspace.openTextDocument(uri);
} catch {
const path = this.convertToWindowsPathSeparator(uri.toString());
return await vscode.workspace.openTextDocument(vscode.Uri.file(path));
}
},
};

Expand Down
92 changes: 50 additions & 42 deletions vscode/src/debugger/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class QscDebugSession extends LoggingDebugSession {
private breakpoints: Map<string, DebugProtocol.Breakpoint[]>;
private variableHandles = new Handles<"locals" | "quantum">();
private failed: boolean;
private program: string;
private program: vscode.Uri;
private eventTarget: QscEventTarget;
private supportsVariableType = false;

Expand All @@ -62,7 +62,7 @@ export class QscDebugSession extends LoggingDebugSession {
) {
super();

this.program = vscode.Uri.parse(this.config.program).path;
this.program = fileAccessor.resolvePathToUri(this.config.program);
this.failed = false;
this.eventTarget = createDebugConsoleEventTarget((message) => {
this.writeToStdOut(message);
Expand All @@ -75,17 +75,20 @@ export class QscDebugSession extends LoggingDebugSession {
}

public async init(): Promise<void> {
const programText = await this.fileAccessor.readFileAsString(
this.config.program
);
const programText = (
await this.fileAccessor.openUri(this.program)
).getText();

const loaded = await this.debugService.loadSource(
this.program,
this.program.toString(),
programText
);
if (loaded) {
const locations = await this.debugService.getBreakpoints(this.program);
const locations = await this.debugService.getBreakpoints(
this.program.toString()
);
log.trace(`init breakpointLocations: %O`, locations);
this.breakpointLocations.set(this.program, locations);
this.breakpointLocations.set(this.program.toString(), locations);
} else {
log.warn(`compilation failed.`);
this.failed = true;
Expand Down Expand Up @@ -299,7 +302,7 @@ export class QscDebugSession extends LoggingDebugSession {

private getBreakpointIds(): number[] {
const bps: number[] = [];
for (const bp of this.breakpoints.get(this.program) ?? []) {
for (const bp of this.breakpoints.get(this.program.toString()) ?? []) {
if (bp && bp.id) {
bps.push(bp.id);
}
Expand Down Expand Up @@ -364,19 +367,20 @@ export class QscDebugSession extends LoggingDebugSession {
breakpoints: [],
};

const fileUri = vscode.Uri.file(args.source.path ?? "");
const file = await this.fileAccessor
.openPath(args.source.path ?? "")
.catch((e) => {
log.error(`Failed to open file: ${e}`);
const fileUri = this.fileAccessor.resolvePathToUri(
args.source.path ?? ""
);
log.trace(
"breakpointLocationsRequest, target file: " + fileUri.toString()
);
});

const file = vscode.workspace.textDocuments.find(
(td) => td.uri.path === fileUri.path
);
if (!file) {
for (const td of vscode.workspace.textDocuments) {
log.trace("breakpointLocationsRequest: potential file" + td.uri.path);
}
log.trace("breakpointLocationsRequest: target file" + fileUri.path);
}
const targetLineNumber = this.convertClientLineToDebugger(args.line);
if (fileUri && file && targetLineNumber < file.lineCount) {
if (file && targetLineNumber < file.lineCount) {
// Map request start/end line/column to file offset for debugger
const line = file.lineAt(targetLineNumber);
const lineRange = line.range;
Expand Down Expand Up @@ -406,7 +410,7 @@ export class QscDebugSession extends LoggingDebugSession {
// where the rest of the statement is on the next line(s)
const bps =
this.breakpointLocations
.get(fileUri.path)
.get(file.uri.toString())
?.filter((bp) => startOffset <= bp.lo && bp.hi <= endOffset) ?? [];

log.trace(`breakpointLocationsRequest: candidates %O`, bps);
Expand Down Expand Up @@ -439,26 +443,24 @@ export class QscDebugSession extends LoggingDebugSession {
): Promise<void> {
log.trace(`setBreakPointsRequest: %O`, args);

const fileUri = vscode.Uri.file(args.source.path ?? "");

const file = vscode.workspace.textDocuments.find(
(td) => td.uri.path === fileUri.path
);
if (!file) {
for (const td of vscode.workspace.textDocuments) {
log.trace("setBreakPointsRequest: potential file" + td.uri.path);
}
log.trace("setBreakPointsRequest: target file" + fileUri.path);
}
const file = await this.fileAccessor
.openPath(args.source.path ?? "")
.catch((e) => {
log.error(`setBreakPointsRequest - Failed to open file: ${e}`);
const fileUri = this.fileAccessor.resolvePathToUri(
args.source.path ?? ""
);
log.trace("setBreakPointsRequest, target file: " + fileUri.toString());
});

if (fileUri && file) {
if (file) {
log.trace(`setBreakPointsRequest: looking`);
this.breakpoints.set(fileUri.path, []);
this.breakpoints.set(file.uri.toString(), []);
log.trace(
`setBreakPointsRequest: files in cache %O`,
this.breakpointLocations.keys()
);
const locations = this.breakpointLocations.get(fileUri.path) ?? [];
const locations = this.breakpointLocations.get(file.uri.toString()) ?? [];
log.trace(`setBreakPointsRequest: got locations %O`, locations);
// convert the request line/column to file offset for debugger
const bpOffsets: [lo: number, hi: number][] = (args.breakpoints ?? [])
Expand Down Expand Up @@ -513,7 +515,7 @@ export class QscDebugSession extends LoggingDebugSession {
}

// Update our breakpoint list for the given file
this.breakpoints.set(fileUri.path, bps);
this.breakpoints.set(file.uri.toString(), bps);

response.body = {
breakpoints: bps,
Expand Down Expand Up @@ -544,11 +546,17 @@ export class QscDebugSession extends LoggingDebugSession {
const mappedStackFrames = await Promise.all(
debuggerStackFrames
.map(async (f, id) => {
const fileUri = vscode.Uri.file(f.path);
log.trace(`frames: fileUri %O`, fileUri);
const file = vscode.workspace.textDocuments.find(
(td) => td.uri.path === fileUri.path
);
log.trace(`frames: path %O`, f.path);

const file = await this.fileAccessor
.openPath(f.path ?? "")
.catch((e) => {
log.error(`stackTraceRequest - Failed to open file: ${e}`);
const fileUri = this.fileAccessor.resolvePathToUri(f.path ?? "");
log.trace(
"stackTraceRequest, target file: " + fileUri.toString()
);
});
if (file) {
log.trace(`frames: file %O`, file);
const start_pos = file.positionAt(f.lo);
Expand Down Expand Up @@ -582,7 +590,7 @@ export class QscDebugSession extends LoggingDebugSession {
scheme: qsharpLibraryUriScheme,
path: f.path,
});
const file = await vscode.workspace.openTextDocument(uri);
const file = await this.fileAccessor.openUri(uri);
const start_pos = file.positionAt(f.lo);
const end_pos = file.positionAt(f.hi);
const source = new Source(
Expand Down

0 comments on commit 4f7e49e

Please sign in to comment.