aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/commands.ts
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src/commands.ts')
-rw-r--r--editors/code/src/commands.ts416
1 files changed, 416 insertions, 0 deletions
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts
new file mode 100644
index 000000000..534d2a984
--- /dev/null
+++ b/editors/code/src/commands.ts
@@ -0,0 +1,416 @@
1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient';
3import * as ra from './lsp_ext';
4
5import { Ctx, Cmd } from './ctx';
6import { applySnippetWorkspaceEdit, applySnippetTextEdits } from './snippets';
7import { spawnSync } from 'child_process';
8import { RunnableQuickPick, selectRunnable, createTask } from './run';
9import { AstInspector } from './ast_inspector';
10import { isRustDocument, sleep, isRustEditor } from './util';
11import { startDebugSession, makeDebugConfig } from './debug';
12
13export * from './ast_inspector';
14export * from './run';
15
16export function analyzerStatus(ctx: Ctx): Cmd {
17 const tdcp = new class implements vscode.TextDocumentContentProvider {
18 readonly uri = vscode.Uri.parse('rust-analyzer-status://status');
19 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
20
21 provideTextDocumentContent(_uri: vscode.Uri): vscode.ProviderResult<string> {
22 if (!vscode.window.activeTextEditor) return '';
23
24 return ctx.client.sendRequest(ra.analyzerStatus, null);
25 }
26
27 get onDidChange(): vscode.Event<vscode.Uri> {
28 return this.eventEmitter.event;
29 }
30 }();
31
32 let poller: NodeJS.Timer | undefined = undefined;
33
34 ctx.pushCleanup(
35 vscode.workspace.registerTextDocumentContentProvider(
36 'rust-analyzer-status',
37 tdcp,
38 ),
39 );
40
41 ctx.pushCleanup({
42 dispose() {
43 if (poller !== undefined) {
44 clearInterval(poller);
45 }
46 },
47 });
48
49 return async () => {
50 if (poller === undefined) {
51 poller = setInterval(() => tdcp.eventEmitter.fire(tdcp.uri), 1000);
52 }
53 const document = await vscode.workspace.openTextDocument(tdcp.uri);
54 return vscode.window.showTextDocument(document, vscode.ViewColumn.Two, true);
55 };
56}
57
58export function matchingBrace(ctx: Ctx): Cmd {
59 return async () => {
60 const editor = ctx.activeRustEditor;
61 const client = ctx.client;
62 if (!editor || !client) return;
63
64 const response = await client.sendRequest(ra.matchingBrace, {
65 textDocument: { uri: editor.document.uri.toString() },
66 positions: editor.selections.map(s =>
67 client.code2ProtocolConverter.asPosition(s.active),
68 ),
69 });
70 editor.selections = editor.selections.map((sel, idx) => {
71 const active = client.protocol2CodeConverter.asPosition(
72 response[idx],
73 );
74 const anchor = sel.isEmpty ? active : sel.anchor;
75 return new vscode.Selection(anchor, active);
76 });
77 editor.revealRange(editor.selection);
78 };
79}
80
81export function joinLines(ctx: Ctx): Cmd {
82 return async () => {
83 const editor = ctx.activeRustEditor;
84 const client = ctx.client;
85 if (!editor || !client) return;
86
87 const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
88 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
89 textDocument: { uri: editor.document.uri.toString() },
90 });
91 editor.edit((builder) => {
92 client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => {
93 builder.replace(edit.range, edit.newText);
94 });
95 });
96 };
97}
98
99export function onEnter(ctx: Ctx): Cmd {
100 async function handleKeypress() {
101 const editor = ctx.activeRustEditor;
102 const client = ctx.client;
103
104 if (!editor || !client) return false;
105
106 const lcEdits = await client.sendRequest(ra.onEnter, {
107 textDocument: { uri: editor.document.uri.toString() },
108 position: client.code2ProtocolConverter.asPosition(
109 editor.selection.active,
110 ),
111 }).catch(_error => {
112 // client.logFailedRequest(OnEnterRequest.type, error);
113 return null;
114 });
115 if (!lcEdits) return false;
116
117 const edits = client.protocol2CodeConverter.asTextEdits(lcEdits);
118 await applySnippetTextEdits(editor, edits);
119 return true;
120 }
121
122 return async () => {
123 if (await handleKeypress()) return;
124
125 await vscode.commands.executeCommand('default:type', { text: '\n' });
126 };
127}
128
129export function parentModule(ctx: Ctx): Cmd {
130 return async () => {
131 const editor = ctx.activeRustEditor;
132 const client = ctx.client;
133 if (!editor || !client) return;
134
135 const response = await client.sendRequest(ra.parentModule, {
136 textDocument: { uri: editor.document.uri.toString() },
137 position: client.code2ProtocolConverter.asPosition(
138 editor.selection.active,
139 ),
140 });
141 const loc = response[0];
142 if (!loc) return;
143
144 const uri = client.protocol2CodeConverter.asUri(loc.targetUri);
145 const range = client.protocol2CodeConverter.asRange(loc.targetRange);
146
147 const doc = await vscode.workspace.openTextDocument(uri);
148 const e = await vscode.window.showTextDocument(doc);
149 e.selection = new vscode.Selection(range.start, range.start);
150 e.revealRange(range, vscode.TextEditorRevealType.InCenter);
151 };
152}
153
154export function ssr(ctx: Ctx): Cmd {
155 return async () => {
156 const client = ctx.client;
157 if (!client) return;
158
159 const options: vscode.InputBoxOptions = {
160 value: "() ==>> ()",
161 prompt: "Enter request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
162 validateInput: async (x: string) => {
163 try {
164 await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
165 } catch (e) {
166 return e.toString();
167 }
168 return null;
169 }
170 };
171 const request = await vscode.window.showInputBox(options);
172 if (!request) return;
173
174 const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
175
176 await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
177 };
178}
179
180export function serverVersion(ctx: Ctx): Cmd {
181 return async () => {
182 const { stdout } = spawnSync(ctx.serverPath, ["--version"], { encoding: "utf8" });
183 const commitHash = stdout.slice(`rust-analyzer `.length).trim();
184 const { releaseTag } = ctx.config.package;
185
186 void vscode.window.showInformationMessage(
187 `rust-analyzer version: ${releaseTag ?? "unreleased"} (${commitHash})`
188 );
189 };
190}
191
192export function toggleInlayHints(ctx: Ctx): Cmd {
193 return async () => {
194 await vscode
195 .workspace
196 .getConfiguration(`${ctx.config.rootSection}.inlayHints`)
197 .update('enable', !ctx.config.inlayHints.enable, vscode.ConfigurationTarget.Workspace);
198 };
199}
200
201// Opens the virtual file that will show the syntax tree
202//
203// The contents of the file come from the `TextDocumentContentProvider`
204export function syntaxTree(ctx: Ctx): Cmd {
205 const tdcp = new class implements vscode.TextDocumentContentProvider {
206 readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
207 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
208 constructor() {
209 vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
210 vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
211 }
212
213 private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
214 if (isRustDocument(event.document)) {
215 // We need to order this after language server updates, but there's no API for that.
216 // Hence, good old sleep().
217 void sleep(10).then(() => this.eventEmitter.fire(this.uri));
218 }
219 }
220 private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
221 if (editor && isRustEditor(editor)) {
222 this.eventEmitter.fire(this.uri);
223 }
224 }
225
226 provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
227 const rustEditor = ctx.activeRustEditor;
228 if (!rustEditor) return '';
229
230 // When the range based query is enabled we take the range of the selection
231 const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
232 ? ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
233 : null;
234
235 const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
236 return ctx.client.sendRequest(ra.syntaxTree, params, ct);
237 }
238
239 get onDidChange(): vscode.Event<vscode.Uri> {
240 return this.eventEmitter.event;
241 }
242 };
243
244 void new AstInspector(ctx);
245
246 ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider('rust-analyzer', tdcp));
247 ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
248 brackets: [["[", ")"]],
249 }));
250
251 return async () => {
252 const editor = vscode.window.activeTextEditor;
253 const rangeEnabled = !!editor && !editor.selection.isEmpty;
254
255 const uri = rangeEnabled
256 ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
257 : tdcp.uri;
258
259 const document = await vscode.workspace.openTextDocument(uri);
260
261 tdcp.eventEmitter.fire(uri);
262
263 void await vscode.window.showTextDocument(document, {
264 viewColumn: vscode.ViewColumn.Two,
265 preserveFocus: true
266 });
267 };
268}
269
270
271// Opens the virtual file that will show the syntax tree
272//
273// The contents of the file come from the `TextDocumentContentProvider`
274export function expandMacro(ctx: Ctx): Cmd {
275 function codeFormat(expanded: ra.ExpandedMacro): string {
276 let result = `// Recursive expansion of ${expanded.name}! macro\n`;
277 result += '// ' + '='.repeat(result.length - 3);
278 result += '\n\n';
279 result += expanded.expansion;
280
281 return result;
282 }
283
284 const tdcp = new class implements vscode.TextDocumentContentProvider {
285 uri = vscode.Uri.parse('rust-analyzer://expandMacro/[EXPANSION].rs');
286 eventEmitter = new vscode.EventEmitter<vscode.Uri>();
287 async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
288 const editor = vscode.window.activeTextEditor;
289 const client = ctx.client;
290 if (!editor || !client) return '';
291
292 const position = editor.selection.active;
293
294 const expanded = await client.sendRequest(ra.expandMacro, {
295 textDocument: { uri: editor.document.uri.toString() },
296 position,
297 });
298
299 if (expanded == null) return 'Not available';
300
301 return codeFormat(expanded);
302 }
303
304 get onDidChange(): vscode.Event<vscode.Uri> {
305 return this.eventEmitter.event;
306 }
307 }();
308
309 ctx.pushCleanup(
310 vscode.workspace.registerTextDocumentContentProvider(
311 'rust-analyzer',
312 tdcp,
313 ),
314 );
315
316 return async () => {
317 const document = await vscode.workspace.openTextDocument(tdcp.uri);
318 tdcp.eventEmitter.fire(tdcp.uri);
319 return vscode.window.showTextDocument(
320 document,
321 vscode.ViewColumn.Two,
322 true,
323 );
324 };
325}
326
327export function collectGarbage(ctx: Ctx): Cmd {
328 return async () => ctx.client.sendRequest(ra.collectGarbage, null);
329}
330
331export function showReferences(ctx: Ctx): Cmd {
332 return (uri: string, position: lc.Position, locations: lc.Location[]) => {
333 const client = ctx.client;
334 if (client) {
335 vscode.commands.executeCommand(
336 'editor.action.showReferences',
337 vscode.Uri.parse(uri),
338 client.protocol2CodeConverter.asPosition(position),
339 locations.map(client.protocol2CodeConverter.asLocation),
340 );
341 }
342 };
343}
344
345export function applyActionGroup(_ctx: Ctx): Cmd {
346 return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => {
347 const selectedAction = await vscode.window.showQuickPick(actions);
348 if (!selectedAction) return;
349 await applySnippetWorkspaceEdit(selectedAction.edit);
350 };
351}
352
353export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
354 return async (edit: vscode.WorkspaceEdit) => {
355 await applySnippetWorkspaceEdit(edit);
356 };
357}
358
359export function run(ctx: Ctx): Cmd {
360 let prevRunnable: RunnableQuickPick | undefined;
361
362 return async () => {
363 const item = await selectRunnable(ctx, prevRunnable);
364 if (!item) return;
365
366 item.detail = 'rerun';
367 prevRunnable = item;
368 const task = createTask(item.runnable);
369 return await vscode.tasks.executeTask(task);
370 };
371}
372
373export function runSingle(ctx: Ctx): Cmd {
374 return async (runnable: ra.Runnable) => {
375 const editor = ctx.activeRustEditor;
376 if (!editor) return;
377
378 const task = createTask(runnable);
379 task.group = vscode.TaskGroup.Build;
380 task.presentationOptions = {
381 reveal: vscode.TaskRevealKind.Always,
382 panel: vscode.TaskPanelKind.Dedicated,
383 clear: true,
384 };
385
386 return vscode.tasks.executeTask(task);
387 };
388}
389
390export function debug(ctx: Ctx): Cmd {
391 let prevDebuggee: RunnableQuickPick | undefined;
392
393 return async () => {
394 const item = await selectRunnable(ctx, prevDebuggee, true);
395 if (!item) return;
396
397 item.detail = 'restart';
398 prevDebuggee = item;
399 return await startDebugSession(ctx, item.runnable);
400 };
401}
402
403export function debugSingle(ctx: Ctx): Cmd {
404 return async (config: ra.Runnable) => {
405 await startDebugSession(ctx, config);
406 };
407}
408
409export function newDebugConfig(ctx: Ctx): Cmd {
410 return async () => {
411 const item = await selectRunnable(ctx, undefined, true, false);
412 if (!item) return;
413
414 await makeDebugConfig(ctx, item.runnable);
415 };
416}