aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/commands
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src/commands')
-rw-r--r--editors/code/src/commands/analyzer_status.ts51
-rw-r--r--editors/code/src/commands/expand_macro.ts66
-rw-r--r--editors/code/src/commands/index.ts53
-rw-r--r--editors/code/src/commands/join_lines.ts18
-rw-r--r--editors/code/src/commands/matching_brace.ts27
-rw-r--r--editors/code/src/commands/on_enter.ts34
-rw-r--r--editors/code/src/commands/parent_module.ts29
-rw-r--r--editors/code/src/commands/runnables.ts185
-rw-r--r--editors/code/src/commands/server_version.ts15
-rw-r--r--editors/code/src/commands/ssr.ts32
-rw-r--r--editors/code/src/commands/syntax_tree.ts263
11 files changed, 0 insertions, 773 deletions
diff --git a/editors/code/src/commands/analyzer_status.ts b/editors/code/src/commands/analyzer_status.ts
deleted file mode 100644
index 09daa3402..000000000
--- a/editors/code/src/commands/analyzer_status.ts
+++ /dev/null
@@ -1,51 +0,0 @@
1import * as vscode from 'vscode';
2
3import * as ra from '../rust-analyzer-api';
4import { Ctx, Cmd } from '../ctx';
5
6// Shows status of rust-analyzer (for debugging)
7export function analyzerStatus(ctx: Ctx): Cmd {
8 let poller: NodeJS.Timer | undefined = undefined;
9 const tdcp = new TextDocumentContentProvider(ctx);
10
11 ctx.pushCleanup(
12 vscode.workspace.registerTextDocumentContentProvider(
13 'rust-analyzer-status',
14 tdcp,
15 ),
16 );
17
18 ctx.pushCleanup({
19 dispose() {
20 if (poller !== undefined) {
21 clearInterval(poller);
22 }
23 },
24 });
25
26 return async () => {
27 if (poller === undefined) {
28 poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000);
29 }
30 const document = await vscode.workspace.openTextDocument(tdcp.uri);
31 return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
32 };
33}
34
35class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
36 readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
37 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
38
39 constructor(private readonly ctx: Ctx) {
40 }
41
42 provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
43 if (!vscode.window.activeTextEditor) return '';
44
45 return this.ctx.client.sendRequest(ra.analyzerStatus, null);
46 }
47
48 get onDidChange(): vscode.Event<vscode.Uri> {
49 return this.eventEmitter.event;
50 }
51}
diff --git a/editors/code/src/commands/expand_macro.ts b/editors/code/src/commands/expand_macro.ts
deleted file mode 100644
index 23f2ef1d5..000000000
--- a/editors/code/src/commands/expand_macro.ts
+++ /dev/null
@@ -1,66 +0,0 @@
1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api';
3
4import { Ctx, Cmd } from '../ctx';
5
6// Opens the virtual file that will show the syntax tree
7//
8// The contents of the file come from the `TextDocumentContentProvider`
9export function expandMacro(ctx: Ctx): Cmd {
10 const tdcp = new TextDocumentContentProvider(ctx);
11 ctx.pushCleanup(
12 vscode.workspace.registerTextDocumentContentProvider(
13 'rust-analyzer',
14 tdcp,
15 ),
16 );
17
18 return async () => {
19 const document = await vscode.workspace.openTextDocument(tdcp.uri);
20 tdcp.eventEmitter.fire(tdcp.uri);
21 return vscode.window.showTextDocument(
22 document,
23 vscode.ViewColumn.Two,
24 true,
25 );
26 };
27}
28
29function codeFormat(expanded: ra.ExpandedMacro): string {
30 let result = `// Recursive expansion of ${expanded.name}! macro\n`;
31 result += '// ' + '='.repeat(result.length - 3);
32 result += '\n\n';
33 result += expanded.expansion;
34
35 return result;
36}
37
38class TextDocumentContentProvider
39 implements vscode.TextDocumentContentProvider {
40 uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs');
41 eventEmitter = new vscode.EventEmitter<vscode.Uri>();
42
43 constructor(private readonly ctx: Ctx) {
44 }
45
46 async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
47 const editor = vscode.window.activeTextEditor;
48 const client = this.ctx.client;
49 if (!editor || !client) return '';
50
51 const position = editor.selection.active;
52
53 const expanded = await client.sendRequest(ra.expandMacro, {
54 textDocument: { uri: editor.document.uri.toString() },
55 position,
56 });
57
58 if (expanded == null) return 'Not available';
59
60 return codeFormat(expanded);
61 }
62
63 get onDidChange(): vscode.Event<vscode.Uri> {
64 return this.eventEmitter.event;
65 }
66}
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
deleted file mode 100644
index bdb7fc3b0..000000000
--- a/editors/code/src/commands/index.ts
+++ /dev/null
@@ -1,53 +0,0 @@
1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3import * as ra from '../rust-analyzer-api';
4
5import { Ctx, Cmd } from '../ctx';
6import * as sourceChange from '../source_change';
7
8export * from './analyzer_status';
9export * from './matching_brace';
10export * from './join_lines';
11export * from './on_enter';
12export * from './parent_module';
13export * from './syntax_tree';
14export * from './expand_macro';
15export * from './runnables';
16export * from './ssr';
17export * from './server_version';
18
19export function collectGarbage(ctx: Ctx): Cmd {
20 return async () => ctx.client.sendRequest(ra.collectGarbage, null);
21}
22
23export function showReferences(ctx: Ctx): Cmd {
24 return (uri: string, position: lc.Position, locations: lc.Location[]) => {
25 const client = ctx.client;
26 if (client) {
27 vscode.commands.executeCommand(
28 'editor.action.showReferences',
29 vscode.Uri.parse(uri),
30 client.protocol2CodeConverter.asPosition(position),
31 locations.map(client.protocol2CodeConverter.asLocation),
32 );
33 }
34 };
35}
36
37export function applySourceChange(ctx: Ctx): Cmd {
38 return async (change: ra.SourceChange) => {
39 await sourceChange.applySourceChange(ctx, change);
40 };
41}
42
43export function selectAndApplySourceChange(ctx: Ctx): Cmd {
44 return async (changes: ra.SourceChange[]) => {
45 if (changes.length === 1) {
46 await sourceChange.applySourceChange(ctx, changes[0]);
47 } else if (changes.length > 0) {
48 const selectedChange = await vscode.window.showQuickPick(changes);
49 if (!selectedChange) return;
50 await sourceChange.applySourceChange(ctx, selectedChange);
51 }
52 };
53}
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
deleted file mode 100644
index de0614653..000000000
--- a/editors/code/src/commands/join_lines.ts
+++ /dev/null
@@ -1,18 +0,0 @@
1import * as ra from '../rust-analyzer-api';
2
3import { Ctx, Cmd } from '../ctx';
4import { applySourceChange } from '../source_change';
5
6export function joinLines(ctx: Ctx): Cmd {
7 return async () => {
8 const editor = ctx.activeRustEditor;
9 const client = ctx.client;
10 if (!editor || !client) return;
11
12 const change = await client.sendRequest(ra.joinLines, {
13 range: client.code2ProtocolConverter.asRange(editor.selection),
14 textDocument: { uri: editor.document.uri.toString() },
15 });
16 await applySourceChange(ctx, change);
17 };
18}
diff --git a/editors/code/src/commands/matching_brace.ts b/editors/code/src/commands/matching_brace.ts
deleted file mode 100644
index a60776e2d..000000000
--- a/editors/code/src/commands/matching_brace.ts
+++ /dev/null
@@ -1,27 +0,0 @@
1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api';
3
4import { Ctx, Cmd } from '../ctx';
5
6export function matchingBrace(ctx: Ctx): Cmd {
7 return async () => {
8 const editor = ctx.activeRustEditor;
9 const client = ctx.client;
10 if (!editor || !client) return;
11
12 const response = await client.sendRequest(ra.findMatchingBrace, {
13 textDocument: { uri: editor.document.uri.toString() },
14 offsets: editor.selections.map(s =>
15 client.code2ProtocolConverter.asPosition(s.active),
16 ),
17 });
18 editor.selections = editor.selections.map((sel, idx) => {
19 const active = client.protocol2CodeConverter.asPosition(
20 response[idx],
21 );
22 const anchor = sel.isEmpty ? active : sel.anchor;
23 return new vscode.Selection(anchor, active);
24 });
25 editor.revealRange(editor.selection);
26 };
27}
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts
deleted file mode 100644
index 285849db7..000000000
--- a/editors/code/src/commands/on_enter.ts
+++ /dev/null
@@ -1,34 +0,0 @@
1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api';
3
4import { applySourceChange } from '../source_change';
5import { Cmd, Ctx } from '../ctx';
6
7async function handleKeypress(ctx: Ctx) {
8 const editor = ctx.activeRustEditor;
9 const client = ctx.client;
10
11 if (!editor || !client) return false;
12
13 const change = await client.sendRequest(ra.onEnter, {
14 textDocument: { uri: editor.document.uri.toString() },
15 position: client.code2ProtocolConverter.asPosition(
16 editor.selection.active,
17 ),
18 }).catch(_error => {
19 // client.logFailedRequest(OnEnterRequest.type, error);
20 return null;
21 });
22 if (!change) return false;
23
24 await applySourceChange(ctx, change);
25 return true;
26}
27
28export function onEnter(ctx: Ctx): Cmd {
29 return async () => {
30 if (await handleKeypress(ctx)) return;
31
32 await vscode.commands.executeCommand('default:type', { text: '\n' });
33 };
34}
diff --git a/editors/code/src/commands/parent_module.ts b/editors/code/src/commands/parent_module.ts
deleted file mode 100644
index 8f78ddd71..000000000
--- a/editors/code/src/commands/parent_module.ts
+++ /dev/null
@@ -1,29 +0,0 @@
1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api';
3
4import { Ctx, Cmd } from '../ctx';
5
6export function parentModule(ctx: Ctx): Cmd {
7 return async () => {
8 const editor = ctx.activeRustEditor;
9 const client = ctx.client;
10 if (!editor || !client) return;
11
12 const response = await client.sendRequest(ra.parentModule, {
13 textDocument: { uri: editor.document.uri.toString() },
14 position: client.code2ProtocolConverter.asPosition(
15 editor.selection.active,
16 ),
17 });
18 const loc = response[0];
19 if (loc == null) return;
20
21 const uri = client.protocol2CodeConverter.asUri(loc.uri);
22 const range = client.protocol2CodeConverter.asRange(loc.range);
23
24 const doc = await vscode.workspace.openTextDocument(uri);
25 const e = await vscode.window.showTextDocument(doc);
26 e.selection = new vscode.Selection(range.start, range.start);
27 e.revealRange(range, vscode.TextEditorRevealType.InCenter);
28 };
29}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
deleted file mode 100644
index d77e8188c..000000000
--- a/editors/code/src/commands/runnables.ts
+++ /dev/null
@@ -1,185 +0,0 @@
1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3import * as ra from '../rust-analyzer-api';
4import * as os from "os";
5
6import { Ctx, Cmd } from '../ctx';
7import { Cargo } from '../cargo';
8
9export function run(ctx: Ctx): Cmd {
10 let prevRunnable: RunnableQuickPick | undefined;
11
12 return async () => {
13 const editor = ctx.activeRustEditor;
14 const client = ctx.client;
15 if (!editor || !client) return;
16
17 const textDocument: lc.TextDocumentIdentifier = {
18 uri: editor.document.uri.toString(),
19 };
20
21 const runnables = await client.sendRequest(ra.runnables, {
22 textDocument,
23 position: client.code2ProtocolConverter.asPosition(
24 editor.selection.active,
25 ),
26 });
27 const items: RunnableQuickPick[] = [];
28 if (prevRunnable) {
29 items.push(prevRunnable);
30 }
31 for (const r of runnables) {
32 if (
33 prevRunnable &&
34 JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
35 ) {
36 continue;
37 }
38 items.push(new RunnableQuickPick(r));
39 }
40 const item = await vscode.window.showQuickPick(items);
41 if (!item) return;
42
43 item.detail = 'rerun';
44 prevRunnable = item;
45 const task = createTask(item.runnable);
46 return await vscode.tasks.executeTask(task);
47 };
48}
49
50export function runSingle(ctx: Ctx): Cmd {
51 return async (runnable: ra.Runnable) => {
52 const editor = ctx.activeRustEditor;
53 if (!editor) return;
54
55 const task = createTask(runnable);
56 task.group = vscode.TaskGroup.Build;
57 task.presentationOptions = {
58 reveal: vscode.TaskRevealKind.Always,
59 panel: vscode.TaskPanelKind.Dedicated,
60 clear: true,
61 };
62
63 return vscode.tasks.executeTask(task);
64 };
65}
66
67function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): vscode.DebugConfiguration {
68 return {
69 type: "lldb",
70 request: "launch",
71 name: config.label,
72 cargo: {
73 args: config.args,
74 },
75 args: config.extraArgs,
76 cwd: config.cwd,
77 sourceMap: sourceFileMap
78 };
79}
80
81const debugOutput = vscode.window.createOutputChannel("Debug");
82
83async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): Promise<vscode.DebugConfiguration> {
84 debugOutput.clear();
85
86 const cargo = new Cargo(config.cwd || '.', debugOutput);
87 const executable = await cargo.executableFromArgs(config.args);
88
89 // if we are here, there were no compilation errors.
90 return {
91 type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg',
92 request: "launch",
93 name: config.label,
94 program: executable,
95 args: config.extraArgs,
96 cwd: config.cwd,
97 sourceFileMap: sourceFileMap,
98 };
99}
100
101export function debugSingle(ctx: Ctx): Cmd {
102 return async (config: ra.Runnable) => {
103 const editor = ctx.activeRustEditor;
104 if (!editor) return;
105
106 const lldbId = "vadimcn.vscode-lldb";
107 const cpptoolsId = "ms-vscode.cpptools";
108
109 const debugEngineId = ctx.config.debug.engine;
110 let debugEngine = null;
111 if (debugEngineId === "auto") {
112 debugEngine = vscode.extensions.getExtension(lldbId);
113 if (!debugEngine) {
114 debugEngine = vscode.extensions.getExtension(cpptoolsId);
115 }
116 }
117 else {
118 debugEngine = vscode.extensions.getExtension(debugEngineId);
119 }
120
121 if (!debugEngine) {
122 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=${lldbId})`
123 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=${cpptoolsId}) extension for debugging.`);
124 return;
125 }
126
127 const debugConfig = lldbId === debugEngine.id
128 ? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap)
129 : await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap);
130
131 return vscode.debug.startDebugging(undefined, debugConfig);
132 };
133}
134
135class RunnableQuickPick implements vscode.QuickPickItem {
136 public label: string;
137 public description?: string | undefined;
138 public detail?: string | undefined;
139 public picked?: boolean | undefined;
140
141 constructor(public runnable: ra.Runnable) {
142 this.label = runnable.label;
143 }
144}
145
146interface CargoTaskDefinition extends vscode.TaskDefinition {
147 type: 'cargo';
148 label: string;
149 command: string;
150 args: string[];
151 env?: { [key: string]: string };
152}
153
154function createTask(spec: ra.Runnable): vscode.Task {
155 const TASK_SOURCE = 'Rust';
156 const definition: CargoTaskDefinition = {
157 type: 'cargo',
158 label: spec.label,
159 command: spec.bin,
160 args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args,
161 env: spec.env,
162 };
163
164 const execOption: vscode.ShellExecutionOptions = {
165 cwd: spec.cwd || '.',
166 env: definition.env,
167 };
168 const exec = new vscode.ShellExecution(
169 definition.command,
170 definition.args,
171 execOption,
172 );
173
174 const f = vscode.workspace.workspaceFolders![0];
175 const t = new vscode.Task(
176 definition,
177 f,
178 definition.label,
179 TASK_SOURCE,
180 exec,
181 ['$rustc'],
182 );
183 t.presentationOptions.clear = true;
184 return t;
185}
diff --git a/editors/code/src/commands/server_version.ts b/editors/code/src/commands/server_version.ts
deleted file mode 100644
index d64ac726e..000000000
--- a/editors/code/src/commands/server_version.ts
+++ /dev/null
@@ -1,15 +0,0 @@
1import * as vscode from "vscode";
2import { spawnSync } from "child_process";
3import { Ctx, Cmd } from '../ctx';
4
5export function serverVersion(ctx: Ctx): Cmd {
6 return async () => {
7 const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
8 const commitHash = stdout.slice(`rust-analyzer `.length).trim();
9 const { releaseTag } = ctx.config.package;
10
11 void vscode.window.showInformationMessage(
12 `rust-analyzer version: ${releaseTag ?? "unreleased"} (${commitHash})`
13 );
14 };
15}
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts
deleted file mode 100644
index 6fee051fd..000000000
--- a/editors/code/src/commands/ssr.ts
+++ /dev/null
@@ -1,32 +0,0 @@
1import * as vscode from 'vscode';
2import * as ra from "../rust-analyzer-api";
3
4import { Ctx, Cmd } from '../ctx';
5import { applySourceChange } from '../source_change';
6
7export function ssr(ctx: Ctx): Cmd {
8 return async () => {
9 const client = ctx.client;
10 if (!client) return;
11
12 const options: vscode.InputBoxOptions = {
13 value: "() ==>> ()",
14 prompt: "EnteR request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
15 validateInput: async (x: string) => {
16 try {
17 await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
18 } catch (e) {
19 return e.toString();
20 }
21 return null;
22 }
23 };
24 const request = await vscode.window.showInputBox(options);
25
26 if (!request) return;
27
28 const change = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
29
30 await applySourceChange(ctx, change);
31 };
32}
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts
deleted file mode 100644
index cfcf47b2f..000000000
--- a/editors/code/src/commands/syntax_tree.ts
+++ /dev/null
@@ -1,263 +0,0 @@
1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api';
3
4import { Ctx, Cmd, Disposable } from '../ctx';
5import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util';
6
7const AST_FILE_SCHEME = "rust-analyzer";
8
9// Opens the virtual file that will show the syntax tree
10//
11// The contents of the file come from the `TextDocumentContentProvider`
12export function syntaxTree(ctx: Ctx): Cmd {
13 const tdcp = new TextDocumentContentProvider(ctx);
14
15 void new AstInspector(ctx);
16
17 ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
18 ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
19 brackets: [["[", ")"]],
20 }));
21
22 return async () => {
23 const editor = vscode.window.activeTextEditor;
24 const rangeEnabled = !!editor && !editor.selection.isEmpty;
25
26 const uri = rangeEnabled
27 ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
28 : tdcp.uri;
29
30 const document = await vscode.workspace.openTextDocument(uri);
31
32 tdcp.eventEmitter.fire(uri);
33
34 void await vscode.window.showTextDocument(document, {
35 viewColumn: vscode.ViewColumn.Two,
36 preserveFocus: true
37 });
38 };
39}
40
41class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
42 readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
43 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
44
45
46 constructor(private readonly ctx: Ctx) {
47 vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
48 vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
49 }
50
51 private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
52 if (isRustDocument(event.document)) {
53 // We need to order this after language server updates, but there's no API for that.
54 // Hence, good old sleep().
55 void sleep(10).then(() => this.eventEmitter.fire(this.uri));
56 }
57 }
58 private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
59 if (editor && isRustEditor(editor)) {
60 this.eventEmitter.fire(this.uri);
61 }
62 }
63
64 provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
65 const rustEditor = this.ctx.activeRustEditor;
66 if (!rustEditor) return '';
67
68 // When the range based query is enabled we take the range of the selection
69 const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
70 ? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
71 : null;
72
73 const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
74 return this.ctx.client.sendRequest(ra.syntaxTree, params, ct);
75 }
76
77 get onDidChange(): vscode.Event<vscode.Uri> {
78 return this.eventEmitter.event;
79 }
80}
81
82
83// FIXME: consider implementing this via the Tree View API?
84// https://code.visualstudio.com/api/extension-guides/tree-view
85class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable {
86 private readonly astDecorationType = vscode.window.createTextEditorDecorationType({
87 borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'),
88 borderStyle: "solid",
89 borderWidth: "2px",
90
91 });
92 private rustEditor: undefined | RustEditor;
93
94 // Lazy rust token range -> syntax tree file range.
95 private readonly rust2Ast = new Lazy(() => {
96 const astEditor = this.findAstTextEditor();
97 if (!this.rustEditor || !astEditor) return undefined;
98
99 const buf: [vscode.Range, vscode.Range][] = [];
100 for (let i = 0; i < astEditor.document.lineCount; ++i) {
101 const astLine = astEditor.document.lineAt(i);
102
103 // Heuristically look for nodes with quoted text (which are token nodes)
104 const isTokenNode = astLine.text.lastIndexOf('"') >= 0;
105 if (!isTokenNode) continue;
106
107 const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text);
108 if (!rustRange) continue;
109
110 buf.push([rustRange, this.findAstNodeRange(astLine)]);
111 }
112 return buf;
113 });
114
115 constructor(ctx: Ctx) {
116 ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this));
117 ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this));
118 vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions);
119 vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
120 vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
121
122 ctx.pushCleanup(this);
123 }
124 dispose() {
125 this.setRustEditor(undefined);
126 }
127
128 private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
129 if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) {
130 this.rust2Ast.reset();
131 }
132 }
133
134 private onDidCloseTextDocument(doc: vscode.TextDocument) {
135 if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
136 this.setRustEditor(undefined);
137 }
138 }
139
140 private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) {
141 if (!this.findAstTextEditor()) {
142 this.setRustEditor(undefined);
143 return;
144 }
145 this.setRustEditor(editors.find(isRustEditor));
146 }
147
148 private findAstTextEditor(): undefined | vscode.TextEditor {
149 return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME);
150 }
151
152 private setRustEditor(newRustEditor: undefined | RustEditor) {
153 if (this.rustEditor && this.rustEditor !== newRustEditor) {
154 this.rustEditor.setDecorations(this.astDecorationType, []);
155 this.rust2Ast.reset();
156 }
157 this.rustEditor = newRustEditor;
158 }
159
160 // additional positional params are omitted
161 provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult<vscode.DefinitionLink[]> {
162 if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return;
163
164 const astEditor = this.findAstTextEditor();
165 if (!astEditor) return;
166
167 const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos));
168 if (!rust2AstRanges) return;
169
170 const [rustFileRange, astFileRange] = rust2AstRanges;
171
172 astEditor.revealRange(astFileRange);
173 astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end);
174
175 return [{
176 targetRange: astFileRange,
177 targetUri: astEditor.document.uri,
178 originSelectionRange: rustFileRange,
179 targetSelectionRange: astFileRange,
180 }];
181 }
182
183 // additional positional params are omitted
184 provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
185 if (!this.rustEditor) return;
186
187 const astFileLine = doc.lineAt(hoverPosition.line);
188
189 const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text);
190 if (!rustFileRange) return;
191
192 this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]);
193 this.rustEditor.revealRange(rustFileRange);
194
195 const rustSourceCode = this.rustEditor.document.getText(rustFileRange);
196 const astFileRange = this.findAstNodeRange(astFileLine);
197
198 return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange);
199 }
200
201 private findAstNodeRange(astLine: vscode.TextLine): vscode.Range {
202 const lineOffset = astLine.range.start;
203 const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
204 const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
205 return new vscode.Range(begin, end);
206 }
207
208 private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
209 const parsedRange = /\[(\d+); (\d+)\)/.exec(astLine);
210 if (!parsedRange) return;
211
212 const [begin, end] = parsedRange
213 .slice(1)
214 .map(off => this.positionAt(doc, +off));
215
216 return new vscode.Range(begin, end);
217 }
218
219 // Memoize the last value, otherwise the CPU is at 100% single core
220 // with quadratic lookups when we build rust2Ast cache
221 cache?: { doc: vscode.TextDocument; offset: number; line: number };
222
223 positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position {
224 if (doc.eol === vscode.EndOfLine.LF) {
225 return doc.positionAt(targetOffset);
226 }
227
228 // Shitty workaround for crlf line endings
229 // We are still in this prehistoric era of carriage returns here...
230
231 let line = 0;
232 let offset = 0;
233
234 const cache = this.cache;
235 if (cache?.doc === doc && cache.offset <= targetOffset) {
236 ({ line, offset } = cache);
237 }
238
239 while (true) {
240 const lineLenWithLf = doc.lineAt(line).text.length + 1;
241 if (offset + lineLenWithLf > targetOffset) {
242 this.cache = { doc, offset, line };
243 return doc.positionAt(targetOffset + line);
244 }
245 offset += lineLenWithLf;
246 line += 1;
247 }
248 }
249}
250
251class Lazy<T> {
252 val: undefined | T;
253
254 constructor(private readonly compute: () => undefined | T) { }
255
256 get() {
257 return this.val ?? (this.val = this.compute());
258 }
259
260 reset() {
261 this.val = undefined;
262 }
263}