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