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/index.ts56
-rw-r--r--editors/code/src/commands/join_lines.ts12
-rw-r--r--editors/code/src/commands/on_enter.ts5
-rw-r--r--editors/code/src/commands/runnables.ts197
-rw-r--r--editors/code/src/commands/ssr.ts2
-rw-r--r--editors/code/src/commands/syntax_tree.ts4
6 files changed, 185 insertions, 91 deletions
diff --git a/editors/code/src/commands/index.ts b/editors/code/src/commands/index.ts
index bdb7fc3b0..e5ed77e32 100644
--- a/editors/code/src/commands/index.ts
+++ b/editors/code/src/commands/index.ts
@@ -4,6 +4,7 @@ import * as ra from '../rust-analyzer-api';
4 4
5import { Ctx, Cmd } from '../ctx'; 5import { Ctx, Cmd } from '../ctx';
6import * as sourceChange from '../source_change'; 6import * as sourceChange from '../source_change';
7import { assert } from '../util';
7 8
8export * from './analyzer_status'; 9export * from './analyzer_status';
9export * from './matching_brace'; 10export * from './matching_brace';
@@ -51,3 +52,58 @@ export function selectAndApplySourceChange(ctx: Ctx): Cmd {
51 } 52 }
52 }; 53 };
53} 54}
55
56export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd {
57 return async (edit: vscode.WorkspaceEdit) => {
58 await applySnippetWorkspaceEdit(edit);
59 };
60}
61
62export async function applySnippetWorkspaceEdit(edit: vscode.WorkspaceEdit) {
63 assert(edit.entries().length === 1, `bad ws edit: ${JSON.stringify(edit)}`);
64 const [uri, edits] = edit.entries()[0];
65
66 const editor = vscode.window.visibleTextEditors.find((it) => it.document.uri.toString() === uri.toString());
67 if (!editor) return;
68
69 let selection: vscode.Selection | undefined = undefined;
70 let lineDelta = 0;
71 await editor.edit((builder) => {
72 for (const indel of edits) {
73 const parsed = parseSnippet(indel.newText);
74 if (parsed) {
75 const [newText, [placeholderStart, placeholderLength]] = parsed;
76 const prefix = newText.substr(0, placeholderStart);
77 const lastNewline = prefix.lastIndexOf('\n');
78
79 const startLine = indel.range.start.line + lineDelta + countLines(prefix);
80 const startColumn = lastNewline === -1 ?
81 indel.range.start.character + placeholderStart
82 : prefix.length - lastNewline - 1;
83 const endColumn = startColumn + placeholderLength;
84 selection = new vscode.Selection(
85 new vscode.Position(startLine, startColumn),
86 new vscode.Position(startLine, endColumn),
87 );
88 builder.replace(indel.range, newText);
89 } else {
90 lineDelta = countLines(indel.newText) - (indel.range.end.line - indel.range.start.line);
91 builder.replace(indel.range, indel.newText);
92 }
93 }
94 });
95 if (selection) editor.selection = selection;
96}
97
98function parseSnippet(snip: string): [string, [number, number]] | undefined {
99 const m = snip.match(/\$(0|\{0:([^}]*)\})/);
100 if (!m) return undefined;
101 const placeholder = m[2] ?? "";
102 const range: [number, number] = [m.index!!, placeholder.length];
103 const insert = snip.replace(m[0], placeholder);
104 return [insert, range];
105}
106
107function countLines(text: string): number {
108 return (text.match(/\n/g) || []).length;
109}
diff --git a/editors/code/src/commands/join_lines.ts b/editors/code/src/commands/join_lines.ts
index de0614653..0bf1ee6e6 100644
--- a/editors/code/src/commands/join_lines.ts
+++ b/editors/code/src/commands/join_lines.ts
@@ -1,7 +1,7 @@
1import * as ra from '../rust-analyzer-api'; 1import * as ra from '../rust-analyzer-api';
2import * as lc from 'vscode-languageclient';
2 3
3import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd } from '../ctx';
4import { applySourceChange } from '../source_change';
5 5
6export function joinLines(ctx: Ctx): Cmd { 6export function joinLines(ctx: Ctx): Cmd {
7 return async () => { 7 return async () => {
@@ -9,10 +9,14 @@ export function joinLines(ctx: Ctx): Cmd {
9 const client = ctx.client; 9 const client = ctx.client;
10 if (!editor || !client) return; 10 if (!editor || !client) return;
11 11
12 const change = await client.sendRequest(ra.joinLines, { 12 const items: lc.TextEdit[] = await client.sendRequest(ra.joinLines, {
13 range: client.code2ProtocolConverter.asRange(editor.selection), 13 ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)),
14 textDocument: { uri: editor.document.uri.toString() }, 14 textDocument: { uri: editor.document.uri.toString() },
15 }); 15 });
16 await applySourceChange(ctx, change); 16 editor.edit((builder) => {
17 client.protocol2CodeConverter.asTextEdits(items).forEach((edit) => {
18 builder.replace(edit.range, edit.newText);
19 });
20 });
17 }; 21 };
18} 22}
diff --git a/editors/code/src/commands/on_enter.ts b/editors/code/src/commands/on_enter.ts
index 285849db7..a7871c31e 100644
--- a/editors/code/src/commands/on_enter.ts
+++ b/editors/code/src/commands/on_enter.ts
@@ -1,8 +1,8 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api'; 2import * as ra from '../rust-analyzer-api';
3 3
4import { applySourceChange } from '../source_change';
5import { Cmd, Ctx } from '../ctx'; 4import { Cmd, Ctx } from '../ctx';
5import { applySnippetWorkspaceEdit } from '.';
6 6
7async function handleKeypress(ctx: Ctx) { 7async function handleKeypress(ctx: Ctx) {
8 const editor = ctx.activeRustEditor; 8 const editor = ctx.activeRustEditor;
@@ -21,7 +21,8 @@ async function handleKeypress(ctx: Ctx) {
21 }); 21 });
22 if (!change) return false; 22 if (!change) return false;
23 23
24 await applySourceChange(ctx, change); 24 const workspaceEdit = client.protocol2CodeConverter.asWorkspaceEdit(change);
25 await applySnippetWorkspaceEdit(workspaceEdit);
25 return true; 26 return true;
26} 27}
27 28
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index d77e8188c..0bd30fb07 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -1,43 +1,93 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3import * as ra from '../rust-analyzer-api'; 3import * as ra from '../rust-analyzer-api';
4import * as os from "os";
5 4
6import { Ctx, Cmd } from '../ctx'; 5import { Ctx, Cmd } from '../ctx';
7import { Cargo } from '../cargo'; 6import { startDebugSession, getDebugConfiguration } from '../debug';
8 7
9export function run(ctx: Ctx): Cmd { 8const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }];
10 let prevRunnable: RunnableQuickPick | undefined;
11 9
12 return async () => { 10async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise<RunnableQuickPick | undefined> {
13 const editor = ctx.activeRustEditor; 11 const editor = ctx.activeRustEditor;
14 const client = ctx.client; 12 const client = ctx.client;
15 if (!editor || !client) return; 13 if (!editor || !client) return;
16 14
17 const textDocument: lc.TextDocumentIdentifier = { 15 const textDocument: lc.TextDocumentIdentifier = {
18 uri: editor.document.uri.toString(), 16 uri: editor.document.uri.toString(),
19 }; 17 };
20 18
21 const runnables = await client.sendRequest(ra.runnables, { 19 const runnables = await client.sendRequest(ra.runnables, {
22 textDocument, 20 textDocument,
23 position: client.code2ProtocolConverter.asPosition( 21 position: client.code2ProtocolConverter.asPosition(
24 editor.selection.active, 22 editor.selection.active,
25 ), 23 ),
26 }); 24 });
27 const items: RunnableQuickPick[] = []; 25 const items: RunnableQuickPick[] = [];
28 if (prevRunnable) { 26 if (prevRunnable) {
29 items.push(prevRunnable); 27 items.push(prevRunnable);
28 }
29 for (const r of runnables) {
30 if (
31 prevRunnable &&
32 JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
33 ) {
34 continue;
30 } 35 }
31 for (const r of runnables) { 36
32 if ( 37 if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) {
33 prevRunnable && 38 continue;
34 JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
35 ) {
36 continue;
37 }
38 items.push(new RunnableQuickPick(r));
39 } 39 }
40 const item = await vscode.window.showQuickPick(items); 40 items.push(new RunnableQuickPick(r));
41 }
42
43 if (items.length === 0) {
44 // it is the debug case, run always has at least 'cargo check ...'
45 // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables
46 vscode.window.showErrorMessage("There's no debug target!");
47 return;
48 }
49
50 return await new Promise((resolve) => {
51 const disposables: vscode.Disposable[] = [];
52 const close = (result?: RunnableQuickPick) => {
53 resolve(result);
54 disposables.forEach(d => d.dispose());
55 };
56
57 const quickPick = vscode.window.createQuickPick<RunnableQuickPick>();
58 quickPick.items = items;
59 quickPick.title = "Select Runnable";
60 if (showButtons) {
61 quickPick.buttons = quickPickButtons;
62 }
63 disposables.push(
64 quickPick.onDidHide(() => close()),
65 quickPick.onDidAccept(() => close(quickPick.selectedItems[0])),
66 quickPick.onDidTriggerButton((_button) => {
67 (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))();
68 close();
69 }),
70 quickPick.onDidChangeActive((active) => {
71 if (showButtons && active.length > 0) {
72 if (active[0].label.startsWith('cargo')) {
73 // save button makes no sense for `cargo test` or `cargo check`
74 quickPick.buttons = [];
75 } else if (quickPick.buttons.length === 0) {
76 quickPick.buttons = quickPickButtons;
77 }
78 }
79 }),
80 quickPick
81 );
82 quickPick.show();
83 });
84}
85
86export function run(ctx: Ctx): Cmd {
87 let prevRunnable: RunnableQuickPick | undefined;
88
89 return async () => {
90 const item = await selectRunnable(ctx, prevRunnable);
41 if (!item) return; 91 if (!item) return;
42 92
43 item.detail = 'rerun'; 93 item.detail = 'rerun';
@@ -64,71 +114,54 @@ export function runSingle(ctx: Ctx): Cmd {
64 }; 114 };
65} 115}
66 116
67function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): vscode.DebugConfiguration { 117export function debug(ctx: Ctx): Cmd {
68 return { 118 let prevDebuggee: RunnableQuickPick | undefined;
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 119
83async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): Promise<vscode.DebugConfiguration> { 120 return async () => {
84 debugOutput.clear(); 121 const item = await selectRunnable(ctx, prevDebuggee, true);
85 122 if (!item) return;
86 const cargo = new Cargo(config.cwd || '.', debugOutput);
87 const executable = await cargo.executableFromArgs(config.args);
88 123
89 // if we are here, there were no compilation errors. 124 item.detail = 'restart';
90 return { 125 prevDebuggee = item;
91 type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg', 126 return await startDebugSession(ctx, item.runnable);
92 request: "launch",
93 name: config.label,
94 program: executable,
95 args: config.extraArgs,
96 cwd: config.cwd,
97 sourceFileMap: sourceFileMap,
98 }; 127 };
99} 128}
100 129
101export function debugSingle(ctx: Ctx): Cmd { 130export function debugSingle(ctx: Ctx): Cmd {
102 return async (config: ra.Runnable) => { 131 return async (config: ra.Runnable) => {
103 const editor = ctx.activeRustEditor; 132 await startDebugSession(ctx, config);
104 if (!editor) return; 133 };
134}
105 135
106 const lldbId = "vadimcn.vscode-lldb"; 136async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> {
107 const cpptoolsId = "ms-vscode.cpptools"; 137 const scope = ctx.activeRustEditor?.document.uri;
138 if (!scope) return;
108 139
109 const debugEngineId = ctx.config.debug.engine; 140 const debugConfig = await getDebugConfiguration(ctx, item.runnable);
110 let debugEngine = null; 141 if (!debugConfig) return;
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 142
121 if (!debugEngine) { 143 const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope);
122 vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=${lldbId})` 144 const configurations = wsLaunchSection.get<any[]>("configurations") || [];
123 + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=${cpptoolsId}) extension for debugging.`); 145
124 return; 146 const index = configurations.findIndex(c => c.name === debugConfig.name);
125 } 147 if (index !== -1) {
148 const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update');
149 if (answer === "Cancel") return;
150
151 configurations[index] = debugConfig;
152 } else {
153 configurations.push(debugConfig);
154 }
155
156 await wsLaunchSection.update("configurations", configurations);
157}
126 158
127 const debugConfig = lldbId === debugEngine.id 159export function newDebugConfig(ctx: Ctx): Cmd {
128 ? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap) 160 return async () => {
129 : await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap); 161 const item = await selectRunnable(ctx, undefined, true, false);
162 if (!item) return;
130 163
131 return vscode.debug.startDebugging(undefined, debugConfig); 164 await makeDebugConfig(ctx, item);
132 }; 165 };
133} 166}
134 167
diff --git a/editors/code/src/commands/ssr.ts b/editors/code/src/commands/ssr.ts
index 6fee051fd..4ef8cdf04 100644
--- a/editors/code/src/commands/ssr.ts
+++ b/editors/code/src/commands/ssr.ts
@@ -11,7 +11,7 @@ export function ssr(ctx: Ctx): Cmd {
11 11
12 const options: vscode.InputBoxOptions = { 12 const options: vscode.InputBoxOptions = {
13 value: "() ==>> ()", 13 value: "() ==>> ()",
14 prompt: "EnteR request, for example 'Foo($a:expr) ==> Foo::new($a)' ", 14 prompt: "Enter request, for example 'Foo($a:expr) ==> Foo::new($a)' ",
15 validateInput: async (x: string) => { 15 validateInput: async (x: string) => {
16 try { 16 try {
17 await client.sendRequest(ra.ssr, { query: x, parseOnly: true }); 17 await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts
index cfcf47b2f..a5446c327 100644
--- a/editors/code/src/commands/syntax_tree.ts
+++ b/editors/code/src/commands/syntax_tree.ts
@@ -206,7 +206,7 @@ class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, D
206 } 206 }
207 207
208 private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range { 208 private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
209 const parsedRange = /\[(\d+); (\d+)\)/.exec(astLine); 209 const parsedRange = /(\d+)\.\.(\d+)/.exec(astLine);
210 if (!parsedRange) return; 210 if (!parsedRange) return;
211 211
212 const [begin, end] = parsedRange 212 const [begin, end] = parsedRange
@@ -225,7 +225,7 @@ class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, D
225 return doc.positionAt(targetOffset); 225 return doc.positionAt(targetOffset);
226 } 226 }
227 227
228 // Shitty workaround for crlf line endings 228 // Dirty workaround for crlf line endings
229 // We are still in this prehistoric era of carriage returns here... 229 // We are still in this prehistoric era of carriage returns here...
230 230
231 let line = 0; 231 let line = 0;