Skip to content

Commit af04e0a

Browse files
committed
Add a teminal link provider for resolving package: URIs in terminal
Fixes #2099. Fixes #2581.
1 parent f75f5ce commit af04e0a

3 files changed

Lines changed: 126 additions & 0 deletions

File tree

‎src/extension/extension.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import { PubGlobal } from "./pub/global";
8888
import { StatusBarVersionTracker } from "./sdk/status_bar_version_tracker";
8989
import { checkForStandardDartSdkUpdates } from "./sdk/update_check";
9090
import { SdkUtils } from "./sdk/utils";
91+
import { DartTerminalLinkProvider } from "./terminal/link_provider";
9192
import { handleNewProjects, showUserPrompts } from "./user_prompts";
9293
import * as util from "./utils";
9394
import { addToLogHeader, clearLogHeader, getExtensionLogPath, getLogHeader } from "./utils/log";
@@ -514,6 +515,8 @@ export async function activate(context: vs.ExtensionContext, isRestart: boolean
514515
context.subscriptions.push(new LspEditCommands(lspAnalyzer));
515516
}
516517

518+
context.subscriptions.push(vs.window.registerTerminalLinkProvider(new DartTerminalLinkProvider(logger, workspaceContext)));
519+
517520
// Register our view providers.
518521
const dartPackagesProvider = new DartPackagesProvider(logger, workspaceContext);
519522
const packagesTreeView = vs.window.createTreeView("dartPackages", { treeDataProvider: dartPackagesProvider });
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import * as vs from "vscode";
2+
import { Logger } from "../../shared/interfaces";
3+
import { PackageMap } from "../../shared/pub/package_map";
4+
import { findProjectFolders, fsPath } from "../../shared/utils/fs";
5+
import { getDartWorkspaceFolders } from "../../shared/vscode/utils";
6+
import { WorkspaceContext } from "../../shared/workspace";
7+
8+
const packageUriPattern = new RegExp("(?<uri>package:\\S+[\\/]\\S+\\.dart)(?::(?<line>\\d+):(?<col>\\d+))?", "mg");
9+
10+
export class DartTerminalLinkProvider implements vs.TerminalLinkProvider<DartTerminalLink> {
11+
packageMaps: { [key: string]: PackageMap } | undefined;
12+
packageMapDiscovery: Promise<void> | undefined;
13+
14+
constructor(private readonly logger: Logger, private readonly context: WorkspaceContext) {
15+
context.events.onPackageMapChange.listen(() => {
16+
this.packageMaps = undefined;
17+
this.packageMapDiscovery = undefined;
18+
});
19+
}
20+
21+
private async discoverPackageMaps(): Promise<void> {
22+
if (this.packageMapDiscovery) {
23+
return this.packageMapDiscovery;
24+
}
25+
this.packageMapDiscovery = new Promise(async (resolve) => {
26+
const topLevelFolders = getDartWorkspaceFolders().map((w) => fsPath(w.uri));
27+
const projectFolders = (await findProjectFolders(topLevelFolders, { requirePubspec: true }))
28+
this.packageMaps = {};
29+
for (const projectFolder of projectFolders) {
30+
this.packageMaps[projectFolder] = PackageMap.loadForProject(this.logger, projectFolder);
31+
}
32+
resolve();
33+
});
34+
return this.packageMapDiscovery;
35+
}
36+
37+
private isKnownPackage(packageName: string) {
38+
return !!Object.values(this.packageMaps).find((m) => m.packages[packageName]);
39+
}
40+
41+
private resolvePackageUri(uri: string): string | undefined {
42+
for (var packageMap of Object.values(this.packageMaps)) {
43+
const filePath = packageMap.resolvePackageUri(uri);
44+
if (filePath)
45+
return filePath;
46+
}
47+
return undefined;
48+
}
49+
50+
public async provideTerminalLinks(context: vs.TerminalLinkContext, token: vs.CancellationToken): Promise<DartTerminalLink[]> {
51+
if (!this.packageMaps)
52+
await this.discoverPackageMaps();
53+
54+
const results: DartTerminalLink[] = [];
55+
packageUriPattern.lastIndex = -1;
56+
let result: RegExpExecArray | null;
57+
// tslint:disable-next-line: no-conditional-assignment
58+
while (result = packageUriPattern.exec(context.line)) {
59+
let uri: vs.Uri | undefined;
60+
try {
61+
uri = vs.Uri.parse(result.groups.uri, true);
62+
} catch (e) {
63+
this.logger.error(e);
64+
continue;
65+
}
66+
if (!uri)
67+
continue;
68+
69+
const packageName = uri.path.split('/')[0];
70+
if (!this.isKnownPackage(packageName))
71+
continue;
72+
const line = result.groups.line ? parseInt(result.groups.line) : undefined;
73+
const col = result.groups.col ? parseInt(result.groups.col) : undefined;
74+
const startIndex = result.index;
75+
const length = result[0].length;
76+
77+
results.push({
78+
startIndex,
79+
length,
80+
tooltip: 'Open file in editor',
81+
packageName,
82+
uri: result.groups.uri,
83+
line,
84+
col,
85+
});
86+
}
87+
88+
return results;
89+
}
90+
91+
public handleTerminalLink(link: DartTerminalLink): vs.ProviderResult<void> {
92+
const filePath = this.resolvePackageUri(link.uri);
93+
if (!filePath) {
94+
vs.window.showErrorMessage(`Unable to find root for package ${link.packageName}`);
95+
return;
96+
}
97+
98+
vs.commands.executeCommand("_dart.jumpToLineColInUri", vs.Uri.file(filePath), link.line, link.col);
99+
}
100+
}
101+
102+
interface DartTerminalLink extends vs.TerminalLink {
103+
startIndex: number;
104+
length: number;
105+
tooltip: string;
106+
packageName: string;
107+
uri: string;
108+
line: number | undefined;
109+
col: number | undefined;
110+
}

‎src/shared/pub/package_map.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,19 @@ export abstract class PackageMap {
1414
return file as string | (undefined extends T ? undefined : never);
1515
}
1616

17+
public static loadForProject(logger: Logger, projectFolder: string): PackageMap {
18+
const paths = [
19+
".dart_tool/package_config.json",
20+
".packages",
21+
];
22+
for (const p of paths) {
23+
const fullP = path.join(projectFolder, p);
24+
if (fs.existsSync(fullP))
25+
return this.load(logger, fullP);
26+
}
27+
return new MissingPackageMap();
28+
}
29+
1730
public static load(logger: Logger, file: string | undefined): PackageMap {
1831
if (!file)
1932
return new MissingPackageMap();

0 commit comments

Comments
 (0)