diff options
Diffstat (limited to 'editors/code/src/run.ts')
-rw-r--r-- | editors/code/src/run.ts | 204 |
1 files changed, 204 insertions, 0 deletions
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts new file mode 100644 index 000000000..2a7a429cf --- /dev/null +++ b/editors/code/src/run.ts | |||
@@ -0,0 +1,204 @@ | |||
1 | import * as vscode from 'vscode'; | ||
2 | import * as lc from 'vscode-languageclient'; | ||
3 | import * as ra from './lsp_ext'; | ||
4 | |||
5 | import { Ctx, Cmd } from './ctx'; | ||
6 | import { startDebugSession, getDebugConfiguration } from './debug'; | ||
7 | |||
8 | const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; | ||
9 | |||
10 | export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, debuggeeOnly = false, showButtons: boolean = true): Promise<RunnableQuickPick | undefined> { | ||
11 | const editor = ctx.activeRustEditor; | ||
12 | const client = ctx.client; | ||
13 | if (!editor || !client) return; | ||
14 | |||
15 | const textDocument: lc.TextDocumentIdentifier = { | ||
16 | uri: editor.document.uri.toString(), | ||
17 | }; | ||
18 | |||
19 | const runnables = await client.sendRequest(ra.runnables, { | ||
20 | textDocument, | ||
21 | position: client.code2ProtocolConverter.asPosition( | ||
22 | editor.selection.active, | ||
23 | ), | ||
24 | }); | ||
25 | const items: RunnableQuickPick[] = []; | ||
26 | if (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; | ||
35 | } | ||
36 | |||
37 | if (debuggeeOnly && (r.label.startsWith('doctest') || r.label.startsWith('cargo'))) { | ||
38 | continue; | ||
39 | } | ||
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 | |||
86 | export function runSingle(ctx: Ctx): Cmd { | ||
87 | return async (runnable: ra.Runnable) => { | ||
88 | const editor = ctx.activeRustEditor; | ||
89 | if (!editor) return; | ||
90 | |||
91 | const task = createTask(runnable); | ||
92 | task.group = vscode.TaskGroup.Build; | ||
93 | task.presentationOptions = { | ||
94 | reveal: vscode.TaskRevealKind.Always, | ||
95 | panel: vscode.TaskPanelKind.Dedicated, | ||
96 | clear: true, | ||
97 | }; | ||
98 | |||
99 | return vscode.tasks.executeTask(task); | ||
100 | }; | ||
101 | } | ||
102 | |||
103 | export function debug(ctx: Ctx): Cmd { | ||
104 | let prevDebuggee: RunnableQuickPick | undefined; | ||
105 | |||
106 | return async () => { | ||
107 | const item = await selectRunnable(ctx, prevDebuggee, true); | ||
108 | if (!item) return; | ||
109 | |||
110 | item.detail = 'restart'; | ||
111 | prevDebuggee = item; | ||
112 | return await startDebugSession(ctx, item.runnable); | ||
113 | }; | ||
114 | } | ||
115 | |||
116 | export function debugSingle(ctx: Ctx): Cmd { | ||
117 | return async (config: ra.Runnable) => { | ||
118 | await startDebugSession(ctx, config); | ||
119 | }; | ||
120 | } | ||
121 | |||
122 | async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise<void> { | ||
123 | const scope = ctx.activeRustEditor?.document.uri; | ||
124 | if (!scope) return; | ||
125 | |||
126 | const debugConfig = await getDebugConfiguration(ctx, item.runnable); | ||
127 | if (!debugConfig) return; | ||
128 | |||
129 | const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); | ||
130 | const configurations = wsLaunchSection.get<any[]>("configurations") || []; | ||
131 | |||
132 | const index = configurations.findIndex(c => c.name === debugConfig.name); | ||
133 | if (index !== -1) { | ||
134 | const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); | ||
135 | if (answer === "Cancel") return; | ||
136 | |||
137 | configurations[index] = debugConfig; | ||
138 | } else { | ||
139 | configurations.push(debugConfig); | ||
140 | } | ||
141 | |||
142 | await wsLaunchSection.update("configurations", configurations); | ||
143 | } | ||
144 | |||
145 | export function newDebugConfig(ctx: Ctx): Cmd { | ||
146 | return async () => { | ||
147 | const item = await selectRunnable(ctx, undefined, true, false); | ||
148 | if (!item) return; | ||
149 | |||
150 | await makeDebugConfig(ctx, item); | ||
151 | }; | ||
152 | } | ||
153 | |||
154 | export class RunnableQuickPick implements vscode.QuickPickItem { | ||
155 | public label: string; | ||
156 | public description?: string | undefined; | ||
157 | public detail?: string | undefined; | ||
158 | public picked?: boolean | undefined; | ||
159 | |||
160 | constructor(public runnable: ra.Runnable) { | ||
161 | this.label = runnable.label; | ||
162 | } | ||
163 | } | ||
164 | |||
165 | interface CargoTaskDefinition extends vscode.TaskDefinition { | ||
166 | type: 'cargo'; | ||
167 | label: string; | ||
168 | command: string; | ||
169 | args: string[]; | ||
170 | env?: { [key: string]: string }; | ||
171 | } | ||
172 | |||
173 | export function createTask(spec: ra.Runnable): vscode.Task { | ||
174 | const TASK_SOURCE = 'Rust'; | ||
175 | const definition: CargoTaskDefinition = { | ||
176 | type: 'cargo', | ||
177 | label: spec.label, | ||
178 | command: spec.bin, | ||
179 | args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, | ||
180 | env: spec.env, | ||
181 | }; | ||
182 | |||
183 | const execOption: vscode.ShellExecutionOptions = { | ||
184 | cwd: spec.cwd || '.', | ||
185 | env: definition.env, | ||
186 | }; | ||
187 | const exec = new vscode.ShellExecution( | ||
188 | definition.command, | ||
189 | definition.args, | ||
190 | execOption, | ||
191 | ); | ||
192 | |||
193 | const f = vscode.workspace.workspaceFolders![0]; | ||
194 | const t = new vscode.Task( | ||
195 | definition, | ||
196 | f, | ||
197 | definition.label, | ||
198 | TASK_SOURCE, | ||
199 | exec, | ||
200 | ['$rustc'], | ||
201 | ); | ||
202 | t.presentationOptions.clear = true; | ||
203 | return t; | ||
204 | } | ||