1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3import * as ra from './lsp_ext';
4import * as path from 'path';
5
6import { Ctx, Cmd } from './ctx';
7import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
8import { spawnSync } from 'child_process';
9import { RunnableQuickPick, selectRunnable, createTask, createArgs } from './run';
10import { AstInspector } from './ast_inspector';
11import { isRustDocument, isCargoTomlDocument, sleep, isRustEditor } from './util';
12import { startDebugSession, makeDebugConfig } from './debug';
13import { LanguageClient } from 'vscode-languageclient/node';
14
15export * from './ast_inspector';
16export * from './run';
17
18export function analyzerStatus(ctx: Ctx): Cmd {
19    const tdcp = new class implements vscode.TextDocumentContentProvider {
20        readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
21        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
22
23        provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
24            if (!vscode.window.activeTextEditor) return '';
25
26            const params: ra.AnalyzerStatusParams = {};
27            const doc = ctx.activeRustEditor?.document;
28            if (doc != null) {
29                params.textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(doc);
30            }
31            return ctx.client.sendRequest(ra.analyzerStatus, params);
32        }
33
34        get onDidChange(): vscode.Event<vscode.Uri> {
35            return this.eventEmitter.event;
36        }
37    }();
38
39    ctx.pushCleanup(
40        vscode.workspace.registerTextDocumentContentProvider(
41            'rust-analyzer-status',
42            tdcp,
43        ),
44    );
45
46    return async () => {
47        const document = await vscode.workspace.openTextDocument(tdcp.uri);
48        tdcp.eventEmitter.fire(tdcp.uri);
49        void await vscode.window.showTextDocument(document, {
50            viewColumn: vscode.ViewColumn.Two,
51            preserveFocus: true
52        });
53    };
54}
55
56export function memoryUsage(ctx: Ctx): Cmd {
57    const tdcp = new class implements vscode.TextDocumentContentProvider {
58        readonly uri = vscode.Uri.parse('rust-analyzer-memory://memory');
59        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
60
61        provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
62            if (!vscode.window.activeTextEditor) return '';
63
64            return ctx.client.sendRequest(ra.memoryUsage).then((mem: any) => {
65                return 'Per-query memory usage:\n' + mem + '\n(note: database has been cleared)';
66            });
67        }
68
69        get onDidChange(): vscode.Event<vscode.Uri> {
70            return this.eventEmitter.event;
71        }
72    }();
73
74    ctx.pushCleanup(
75        vscode.workspace.registerTextDocumentContentProvider(
76            'rust-analyzer-memory',
77            tdcp,
78        ),
79    );
80
81    return async () => {
82        tdcp.eventEmitter.fire(tdcp.uri);
83        const document = await vscode.workspace.openTextDocument(tdcp.uri);
84        return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
85    };
86}
87
88export function shuffleCrateGraph(ctx: Ctx): Cmd {
89    return async () => {
90        const client = ctx.client;
91        if (!client) return;
92
93        await client.sendRequest(ra.shuffleCrateGraph);
94    };
95}
96
97export function matchingBrace(ctx: Ctx): Cmd {
98    return async () => {
99        const editor = ctx.activeRustEditor;
100        const client = ctx.client;
101        if (!editor || !client) return;
102
103        const response = await client.sendRequest(ra.matchingBrace, {
104            textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
105            positions: editor.selections.map(s =>
106                client.code2ProtocolConverter.asPosition(s.active),
107            ),
108        });
109        editor.selections = editor.selections.map((sel, idx) => {
110            const active = client.protocol2CodeConverter.asPosition(
111                response[idx],
112            );
113            const anchor = sel.isEmpty ? active : sel.anchor;
114            return new vscode.Selection(anchor, active);
115        });
116        editor.revealRange(editor.selection);
117    };
118}
119
120export function joinLines(ctx: Ctx): Cmd {
121    return async () => {
122        const editor = ctx.activeRustEditor;
123        const client = ctx.client;
124        if (!editor || !client) return;
125
126        const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
127            ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
128            textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
129        });
130        await editor.edit((builder) => {
131            client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => {
132                builder.replace(edit.range, edit.newText);
133            });
134        });
135    };
136}
137
138export function moveItemUp(ctx: Ctx): Cmd {
139    return moveItem(ctx, ra.Direction.Up);
140}
141
142export function moveItemDown(ctx: Ctx): Cmd {
143    return moveItem(ctx, ra.Direction.Down);
144}
145
146export function moveItem(ctx: Ctx, direction: ra.Direction): Cmd {
147    return async () => {
148        const editor = ctx.activeRustEditor;
149        const client = ctx.client;
150        if (!editor || !client) return;
151
152        const lcEdits = await client.sendRequest(ra.moveItem, {
153            range: client.code2ProtocolConverter.asRange(editor.selection),
154            textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
155            direction
156        });
157
158        if (!lcEdits) return;
159
160        const edits = client.protocol2CodeConverter.asTextEdits(lcEdits);
161        await applySnippetTextEdits(editor, edits);
162    };
163}
164
165export function onEnter(ctx: Ctx): Cmd {
166    async function handleKeypress() {
167        const editor = ctx.activeRustEditor;
168        const client = ctx.client;
169
170        if (!editor || !client) return false;
171
172        const lcEdits = await client.sendRequest(ra.onEnter, {
173            textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
174            position: client.code2ProtocolConverter.asPosition(
175                editor.selection.active,
176            ),
177        }).catch((_error: any) => {
178            // client.handleFailedRequest(OnEnterRequest.type, error, null);
179            return null;
180        });
181        if (!lcEdits) return false;
182
183        const edits = client.protocol2CodeConverter.asTextEdits(lcEdits);
184        await applySnippetTextEdits(editor, edits);
185        return true;
186    }
187
188    return async () => {
189        if (await handleKeypress()) return;
190
191        await vscode.commands.executeCommand('default:type', { text: '\n' });
192    };
193}
194
195export function parentModule(ctx: Ctx): Cmd {
196    return async () => {
197        const editor = vscode.window.activeTextEditor;
198        const client = ctx.client;
199        if (!editor || !client) return;
200        if (!(isRustDocument(editor.document) || isCargoTomlDocument(editor.document))) return;
201
202        const locations = await client.sendRequest(ra.parentModule, {
203            textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
204            position: client.code2ProtocolConverter.asPosition(
205                editor.selection.active,
206            ),
207        });
208        if (!locations) return;
209
210        if (locations.length === 1) {
211            const loc = locations[0];
212
213            const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
214            const range = client.protocol2CodeConverter.asRange(loc.targetRange);
215
216            const doc = await vscode.workspace.openTextDocument(uri);
217            const e = await vscode.window.showTextDocument(doc);
218            e.selection = new vscode.Selection(range.start, range.start);
219            e.revealRange(range, vscode.TextEditorRevealType.InCenter);
220        } else {
221            const uri = editor.document.uri.toString();
222            const position = client.code2ProtocolConverter.asPosition(editor.selection.active);
223            await showReferencesImpl(client, uri, position, locations.map(loc => lc.Location.create(loc.targetUri, loc.targetRange)));
224        }
225    };
226}
227
228export function openCargoToml(ctx: Ctx): Cmd {
229    return async () => {
230        const editor = ctx.activeRustEditor;
231        const client = ctx.client;
232        if (!editor || !client) return;
233
234        const response = await client.sendRequest(ra.openCargoToml, {
235            textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
236        });
237        if (!response) return;
238
239        const uri = client.protocol2CodeConverter.asUri(response.uri);
240        const range = client.protocol2CodeConverter.asRange(response.range);
241
242        const doc = await vscode.workspace.openTextDocument(uri);
243        const e = await vscode.window.showTextDocument(doc);
244        e.selection = new vscode.Selection(range.start, range.start);
245        e.revealRange(range, vscode.TextEditorRevealType.InCenter);
246    };
247}
248
249export function ssr(ctx: Ctx): Cmd {
250    return async () => {
251        const editor = vscode.window.activeTextEditor;
252        const client = ctx.client;
253        if (!editor || !client) return;
254
255        const position = editor.selection.active;
256        const selections = editor.selections;
257        const textDocument = ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document);
258
259        const options: vscode.InputBoxOptions = {
260            value: "() ==>> ()",
261            prompt: "Enter request, for example 'Foo($a) ==>> Foo::new($a)' ",
262            validateInput: async (x: string) => {
263                try {
264                    await client.sendRequest(ra.ssr, {
265                        query: x, parseOnly: true, textDocument, position, selections,
266                    });
267                } catch (e) {
268                    return e.toString();
269                }
270                return null;
271            }
272        };
273        const request = await vscode.window.showInputBox(options);
274        if (!request) return;
275
276        await vscode.window.withProgress({
277            location: vscode.ProgressLocation.Notification,
278            title: "Structured search replace in progress...",
279            cancellable: false,
280        }, async (_progress, _token) => {
281            const edit = await client.sendRequest(ra.ssr, {
282                query: request, parseOnly: false, textDocument, position, selections,
283            });
284
285            await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
286        });
287    };
288}
289
290export function serverVersion(ctx: Ctx): Cmd {
291    return async () => {
292        const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
293        const versionString = stdout.slice(`rust-analyzer `.length).trim();
294
295        void vscode.window.showInformationMessage(
296            `rust-analyzer version: ${versionString}`
297        );
298    };
299}
300
301export function toggleInlayHints(ctx: Ctx): Cmd {
302    return async () => {
303        await vscode
304            .workspace
305            .getConfiguration(`${ctx.config.rootSection}.inlayHints`)
306            .update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Global);
307    };
308}
309
310// Opens the virtual file that will show the syntax tree
311//
312// The contents of the file come from the `TextDocumentContentProvider`
313export function syntaxTree(ctx: Ctx): Cmd {
314    const tdcp = new class implements vscode.TextDocumentContentProvider {
315        readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
316        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
317        constructor() {
318            vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
319            vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
320        }
321
322        private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
323            if (isRustDocument(event.document)) {
324                // We need to order this after language server updates, but there's no API for that.
325                // Hence, good old sleep().
326                void sleep(10).then(() => this.eventEmitter.fire(this.uri));
327            }
328        }
329        private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
330            if (editor && isRustEditor(editor)) {
331                this.eventEmitter.fire(this.uri);
332            }
333        }
334
335        provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
336            const rustEditor = ctx.activeRustEditor;
337            if (!rustEditor) return '';
338
339            // When the range based query is enabled we take the range of the selection
340            const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
341                ? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
342                : null;
343
344            const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
345            return ctx.client.sendRequest(ra.syntaxTree, params, ct);
346        }
347
348        get onDidChange(): vscode.Event<vscode.Uri> {
349            return this.eventEmitter.event;
350        }
351    };
352
353    void new AstInspector(ctx);
354
355    ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
356    ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
357        brackets: [["[", ")"]],
358    }));
359
360    return async () => {
361        const editor = vscode.window.activeTextEditor;
362        const rangeEnabled = !!editor && !editor.selection.isEmpty;
363
364        const uri = rangeEnabled
365            ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
366            : tdcp.uri;
367
368        const document = await vscode.workspace.openTextDocument(uri);
369
370        tdcp.eventEmitter.fire(uri);
371
372        void await vscode.window.showTextDocument(document, {
373            viewColumn: vscode.ViewColumn.Two,
374            preserveFocus: true
375        });
376    };
377}
378
379// Opens the virtual file that will show the HIR of the function containing the cursor position
380//
381// The contents of the file come from the `TextDocumentContentProvider`
382export function viewHir(ctx: Ctx): Cmd {
383    const tdcp = new class implements vscode.TextDocumentContentProvider {
384        readonly uri = vscode.Uri.parse('rust-analyzer://viewHir/hir.txt');
385        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
386        constructor() {
387            vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
388            vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
389        }
390
391        private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
392            if (isRustDocument(event.document)) {
393                // We need to order this after language server updates, but there's no API for that.
394                // Hence, good old sleep().
395                void sleep(10).then(() => this.eventEmitter.fire(this.uri));
396            }
397        }
398        private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
399            if (editor && isRustEditor(editor)) {
400                this.eventEmitter.fire(this.uri);
401            }
402        }
403
404        provideTextDocumentContent(_uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
405            const rustEditor = ctx.activeRustEditor;
406            const client = ctx.client;
407            if (!rustEditor || !client) return '';
408
409            const params = {
410                textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(rustEditor.document),
411                position: client.code2ProtocolConverter.asPosition(
412                    rustEditor.selection.active,
413                ),
414            };
415            return client.sendRequest(ra.viewHir, params, ct);
416        }
417
418        get onDidChange(): vscode.Event<vscode.Uri> {
419            return this.eventEmitter.event;
420        }
421    };
422
423    ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
424
425    return async () => {
426        const document = await vscode.workspace.openTextDocument(tdcp.uri);
427        tdcp.eventEmitter.fire(tdcp.uri);
428        void await vscode.window.showTextDocument(document, {
429            viewColumn: vscode.ViewColumn.Two,
430            preserveFocus: true
431        });
432    };
433}
434
435export function viewItemTree(ctx: Ctx): Cmd {
436    const tdcp = new class implements vscode.TextDocumentContentProvider {
437        readonly uri = vscode.Uri.parse('rust-analyzer://viewItemTree/itemtree.rs');
438        readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
439        constructor() {
440            vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
441            vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
442        }
443
444        private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
445            if (isRustDocument(event.document)) {
446                // We need to order this after language server updates, but there's no API for that.
447                // Hence, good old sleep().
448                void sleep(10).then(() => this.eventEmitter.fire(this.uri));
449            }
450        }
451        private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
452            if (editor && isRustEditor(editor)) {
453                this.eventEmitter.fire(this.uri);
454            }
455        }
456
457        provideTextDocumentContent(_uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
458            const rustEditor = ctx.activeRustEditor;
459            const client = ctx.client;
460            if (!rustEditor || !client) return '';
461
462            const params = {
463                textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(rustEditor.document),
464            };
465            return client.sendRequest(ra.viewItemTree, params, ct);
466        }
467
468        get onDidChange(): vscode.Event<vscode.Uri> {
469            return this.eventEmitter.event;
470        }
471    };
472
473    ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
474
475    return async () => {
476        const document = await vscode.workspace.openTextDocument(tdcp.uri);
477        tdcp.eventEmitter.fire(tdcp.uri);
478        void await vscode.window.showTextDocument(document, {
479            viewColumn: vscode.ViewColumn.Two,
480            preserveFocus: true
481        });
482    };
483}
484
485function crateGraph(ctx: Ctx, full: boolean): Cmd {
486    return async () => {
487        const nodeModulesPath = vscode.Uri.file(path.join(ctx.extensionPath, "node_modules"));
488
489        const panel = vscode.window.createWebviewPanel("rust-analyzer.crate-graph", "rust-analyzer crate graph", vscode.ViewColumn.Two, {
490            enableScripts: true,
491            retainContextWhenHidden: true,
492            localResourceRoots: [nodeModulesPath]
493        });
494        const params = {
495            full: full,
496        };
497
498        const dot = await ctx.client.sendRequest(ra.viewCrateGraph, params);
499        const uri = panel.webview.asWebviewUri(nodeModulesPath);
500
501        const html = `
502            <!DOCTYPE html>
503            <meta charset="utf-8">
504            <head>
505                <style>
506                    /* Fill the entire view */
507                    html, body { margin:0; padding:0; overflow:hidden }
508                    svg { position:fixed; top:0; left:0; height:100%; width:100% }
509
510                    /* Disable the graphviz backgroud and fill the polygons */
511                    .graph > polygon { display:none; }
512                    :is(.node,.edge) polygon { fill: white; }
513
514                    /* Invert the line colours for dark themes */
515                    body:not(.vscode-light) .edge path { stroke: white; }
516                </style>
517            </head>
518            <body>
519                <script type="text/javascript" src="${uri}/d3/dist/d3.min.js"></script>
520                <script type="javascript/worker" src="${uri}/@hpcc-js/wasm/dist/index.min.js"></script>
521                <script type="text/javascript" src="${uri}/d3-graphviz/build/d3-graphviz.min.js"></script>
522                <div id="graph"></div>
523                <script>
524                    let graph = d3.select("#graph")
525                                  .graphviz()
526                                  .fit(true)
527                                  .zoomScaleExtent([0.1, Infinity])
528                                  .renderDot(\`${dot}\`);
529
530                    d3.select(window).on("click", (event) => {
531                        if (event.ctrlKey) {
532                            graph.resetZoom(d3.transition().duration(100));
533                        }
534                    });
535                </script>
536            </body>
537            `;
538
539        panel.webview.html = html;
540    };
541}
542
543export function viewCrateGraph(ctx: Ctx): Cmd {
544    return crateGraph(ctx, false);
545}
546
547export function viewFullCrateGraph(ctx: Ctx): Cmd {
548    return crateGraph(ctx, true);
549}
550
551// Opens the virtual file that will show the syntax tree
552//
553// The contents of the file come from the `TextDocumentContentProvider`
554export function expandMacro(ctx: Ctx): Cmd {
555    function codeFormat(expanded: ra.ExpandedMacro): string {
556        let result = `// Recursive expansion of ${expanded.name}! macro\n`;
557        result += '// ' + '='.repeat(result.length - 3);
558        result += '\n\n';
559        result += expanded.expansion;
560
561        return result;
562    }
563
564    const tdcp = new class implements vscode.TextDocumentContentProvider {
565        uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs');
566        eventEmitter = new vscode.EventEmitter<vscode.Uri>();
567        async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
568            const editor = vscode.window.activeTextEditor;
569            const client = ctx.client;
570            if (!editor || !client) return '';
571
572            const position = editor.selection.active;
573
574            const expanded = await client.sendRequest(ra.expandMacro, {
575                textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document),
576                position,
577            });
578
579            if (expanded == null) return 'Not available';
580
581            return codeFormat(expanded);
582        }
583
584        get onDidChange(): vscode.Event<vscode.Uri> {
585            return this.eventEmitter.event;
586        }
587    }();
588
589    ctx.pushCleanup(
590        vscode.workspace.registerTextDocumentContentProvider(
591            'rust-analyzer',
592            tdcp,
593        ),
594    );
595
596    return async () => {
597        const document = await vscode.workspace.openTextDocument(tdcp.uri);
598        tdcp.eventEmitter.fire(tdcp.uri);
599        return vscode.window.showTextDocument(
600            document,
601            vscode.ViewColumn.Two,
602            true,
603        );
604    };
605}
606
607export function reloadWorkspace(ctx: Ctx): Cmd {
608    return async () => ctx.client.sendRequest(ra.reloadWorkspace);
609}
610
611async function showReferencesImpl(client: LanguageClient, uri: string, position: lc.Position, locations: lc.Location[]) {
612    if (client) {
613        await vscode.commands.executeCommand(
614            'editor.action.showReferences',
615            vscode.Uri.parse(uri),
616            client.protocol2CodeConverter.asPosition(position),
617            locations.map(client.protocol2CodeConverter.asLocation),
618        );
619    }
620}
621
622export function showReferences(ctx: Ctx): Cmd {
623    return async (uri: string, position: lc.Position, locations: lc.Location[]) => {
624        await showReferencesImpl(ctx.client, uri, position, locations);
625    };
626}
627
628export function applyActionGroup(_ctx: Ctx): Cmd {
629    return async (actions: { label: string; arguments: lc.CodeAction }[]) => {
630        const selectedAction = await vscode.window.showQuickPick(actions);
631        if (!selectedAction) return;
632        await vscode.commands.executeCommand(
633            'rust-analyzer.resolveCodeAction',
634            selectedAction.arguments,
635        );
636    };
637}
638
639export function gotoLocation(ctx: Ctx): Cmd {
640    return async (locationLink: lc.LocationLink) => {
641        const client = ctx.client;
642        if (client) {
643            const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri);
644            let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange);
645            // collapse the range to a cursor position
646            range = range.with({ end: range.start });
647
648            await vscode.window.showTextDocument(uri, { selection: range });
649        }
650    };
651}
652
653export function openDocs(ctx: Ctx): Cmd {
654    return async () => {
655
656        const client = ctx.client;
657        const editor = vscode.window.activeTextEditor;
658        if (!editor || !client) {
659            return;
660        };
661
662        const position = editor.selection.active;
663        const textDocument = { uri: editor.document.uri.toString() };
664
665        const doclink = await client.sendRequest(ra.openDocs, { position, textDocument });
666
667        if (doclink != null) {
668            await vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink));
669        }
670    };
671
672}
673
674export function resolveCodeAction(ctx: Ctx): Cmd {
675    const client = ctx.client;
676    return async (params: lc.CodeAction) => {
677        params.command = undefined;
678        const item = await client.sendRequest(lc.CodeActionResolveRequest.type, params);
679        if (!item.edit) {
680            return;
681        }
682        const itemEdit = item.edit;
683        const edit = client.protocol2CodeConverter.asWorkspaceEdit(itemEdit);
684        // filter out all text edits and recreate the WorkspaceEdit without them so we can apply
685        // snippet edits on our own
686        const lcFileSystemEdit = { ...itemEdit, documentChanges: itemEdit.documentChanges?.filter(change => "kind" in change) };
687        const fileSystemEdit = client.protocol2CodeConverter.asWorkspaceEdit(lcFileSystemEdit);
688        await vscode.workspace.applyEdit(fileSystemEdit);
689        await applySnippetWorkspaceEdit(edit);
690    };
691}
692
693export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
694    return async (edit: vscode.WorkspaceEdit) => {
695        await applySnippetWorkspaceEdit(edit);
696    };
697}
698
699export function run(ctx: Ctx): Cmd {
700    let prevRunnable: RunnableQuickPick | undefined;
701
702    return async () => {
703        const item = await selectRunnable(ctx, prevRunnable);
704        if (!item) return;
705
706        item.detail = 'rerun';
707        prevRunnable = item;
708        const task = await createTask(item.runnable, ctx.config);
709        return await vscode.tasks.executeTask(task);
710    };
711}
712
713export function peekTests(ctx: Ctx): Cmd {
714    const client = ctx.client;
715
716    return async () => {
717        const editor = ctx.activeRustEditor;
718        if (!editor || !client) return;
719
720        await vscode.window.withProgress({
721            location: vscode.ProgressLocation.Notification,
722            title: "Looking for tests...",
723            cancellable: false,
724        }, async (_progress, _token) => {
725            const uri = editor.document.uri.toString();
726            const position = client.code2ProtocolConverter.asPosition(
727                editor.selection.active,
728            );
729
730            const tests = await client.sendRequest(ra.relatedTests, {
731                textDocument: { uri: uri },
732                position: position,
733            });
734            const locations: lc.Location[] = tests.map(it =>
735                lc.Location.create(it.runnable.location!.targetUri, it.runnable.location!.targetSelectionRange));
736
737            await showReferencesImpl(client, uri, position, locations);
738        });
739    };
740}
741
742
743export function runSingle(ctx: Ctx): Cmd {
744    return async (runnable: ra.Runnable) => {
745        const editor = ctx.activeRustEditor;
746        if (!editor) return;
747
748        const task = await createTask(runnable, ctx.config);
749        task.group = vscode.TaskGroup.Build;
750        task.presentationOptions = {
751            reveal: vscode.TaskRevealKind.Always,
752            panel: vscode.TaskPanelKind.Dedicated,
753            clear: true,
754        };
755
756        return vscode.tasks.executeTask(task);
757    };
758}
759
760export function copyRunCommandLine(ctx: Ctx) {
761    let prevRunnable: RunnableQuickPick | undefined;
762    return async () => {
763        const item = await selectRunnable(ctx, prevRunnable);
764        if (!item) return;
765        const args = createArgs(item.runnable);
766        const commandLine = ["cargo", ...args].join(" ");
767        await vscode.env.clipboard.writeText(commandLine);
768        await vscode.window.showInformationMessage("Cargo invocation copied to the clipboard.");
769    };
770}
771
772export function debug(ctx: Ctx): Cmd {
773    let prevDebuggee: RunnableQuickPick | undefined;
774
775    return async () => {
776        const item = await selectRunnable(ctx, prevDebuggee, true);
777        if (!item) return;
778
779        item.detail = 'restart';
780        prevDebuggee = item;
781        return await startDebugSession(ctx, item.runnable);
782    };
783}
784
785export function debugSingle(ctx: Ctx): Cmd {
786    return async (config: ra.Runnable) => {
787        await startDebugSession(ctx, config);
788    };
789}
790
791export function newDebugConfig(ctx: Ctx): Cmd {
792    return async () => {
793        const item = await selectRunnable(ctx, undefined, true, false);
794        if (!item) return;
795
796        await makeDebugConfig(ctx, item.runnable);
797    };
798}
799