]> git.lizzy.rs Git - rust.git/blob - editors/code/src/commands/runnables.ts
Handle errors when `cargo watch` fails
[rust.git] / editors / code / src / commands / runnables.ts
1 import * as child_process from 'child_process';
2
3 import * as util from 'util';
4 import * as vscode from 'vscode';
5 import * as lc from 'vscode-languageclient';
6
7 import { Server } from '../server';
8 import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch';
9
10 interface RunnablesParams {
11     textDocument: lc.TextDocumentIdentifier;
12     position?: lc.Position;
13 }
14
15 interface Runnable {
16     label: string;
17     bin: string;
18     args: string[];
19     env: { [index: string]: string };
20     cwd?: string;
21 }
22
23 class RunnableQuickPick implements vscode.QuickPickItem {
24     public label: string;
25     public description?: string | undefined;
26     public detail?: string | undefined;
27     public picked?: boolean | undefined;
28
29     constructor(public runnable: Runnable) {
30         this.label = runnable.label;
31     }
32 }
33
34 interface CargoTaskDefinition extends vscode.TaskDefinition {
35     type: 'cargo';
36     label: string;
37     command: string;
38     args: string[];
39     env?: { [key: string]: string };
40 }
41
42 function createTask(spec: Runnable): vscode.Task {
43     const TASK_SOURCE = 'Rust';
44     const definition: CargoTaskDefinition = {
45         type: 'cargo',
46         label: spec.label,
47         command: spec.bin,
48         args: spec.args,
49         env: spec.env
50     };
51
52     const execOption: vscode.ShellExecutionOptions = {
53         cwd: spec.cwd || '.',
54         env: definition.env
55     };
56     const exec = new vscode.ShellExecution(
57         definition.command,
58         definition.args,
59         execOption
60     );
61
62     const f = vscode.workspace.workspaceFolders![0];
63     const t = new vscode.Task(
64         definition,
65         f,
66         definition.label,
67         TASK_SOURCE,
68         exec,
69         ['$rustc']
70     );
71     t.presentationOptions.clear = true;
72     return t;
73 }
74
75 let prevRunnable: RunnableQuickPick | undefined;
76 export async function handle() {
77     const editor = vscode.window.activeTextEditor;
78     if (editor == null || editor.document.languageId !== 'rust') {
79         return;
80     }
81     const textDocument: lc.TextDocumentIdentifier = {
82         uri: editor.document.uri.toString()
83     };
84     const params: RunnablesParams = {
85         textDocument,
86         position: Server.client.code2ProtocolConverter.asPosition(
87             editor.selection.active
88         )
89     };
90     const runnables = await Server.client.sendRequest<Runnable[]>(
91         'rust-analyzer/runnables',
92         params
93     );
94     const items: RunnableQuickPick[] = [];
95     if (prevRunnable) {
96         items.push(prevRunnable);
97     }
98     for (const r of runnables) {
99         if (
100             prevRunnable &&
101             JSON.stringify(prevRunnable.runnable) === JSON.stringify(r)
102         ) {
103             continue;
104         }
105         items.push(new RunnableQuickPick(r));
106     }
107     const item = await vscode.window.showQuickPick(items);
108     if (item) {
109         item.detail = 'rerun';
110         prevRunnable = item;
111         const task = createTask(item.runnable);
112         return await vscode.tasks.executeTask(task);
113     }
114 }
115
116 export async function handleSingle(runnable: Runnable) {
117     const editor = vscode.window.activeTextEditor;
118     if (editor == null || editor.document.languageId !== 'rust') {
119         return;
120     }
121
122     const task = createTask(runnable);
123     task.group = vscode.TaskGroup.Build;
124     task.presentationOptions = {
125         reveal: vscode.TaskRevealKind.Always,
126         panel: vscode.TaskPanelKind.Dedicated,
127         clear: true
128     };
129
130     return vscode.tasks.executeTask(task);
131 }
132
133 /**
134  * Interactively asks the user whether we should run `cargo check` in order to
135  * provide inline diagnostics; the user is met with a series of dialog boxes
136  * that, when accepted, allow us to `cargo install cargo-watch` and then run it.
137  */
138 export async function interactivelyStartCargoWatch(
139     context: vscode.ExtensionContext
140 ): Promise<CargoWatchProvider | undefined> {
141     if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') {
142         return;
143     }
144
145     if (Server.config.cargoWatchOptions.enableOnStartup === 'ask') {
146         const watch = await vscode.window.showInformationMessage(
147             'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)',
148             'yes',
149             'no'
150         );
151         if (watch !== 'yes') {
152             return;
153         }
154     }
155
156     return startCargoWatch(context);
157 }
158
159 export async function startCargoWatch(
160     context: vscode.ExtensionContext
161 ): Promise<CargoWatchProvider | undefined> {
162     const execPromise = util.promisify(child_process.exec);
163
164     const { stderr } = await execPromise('cargo watch --version').catch(e => e);
165
166     if (stderr.includes('no such subcommand: `watch`')) {
167         const msg =
168             'The `cargo-watch` subcommand is not installed. Install? (takes ~1-2 minutes)';
169         const install = await vscode.window.showInformationMessage(
170             msg,
171             'yes',
172             'no'
173         );
174         if (install !== 'yes') {
175             return;
176         }
177
178         const label = 'install-cargo-watch';
179         const taskFinished = new Promise((resolve, reject) => {
180             const disposable = vscode.tasks.onDidEndTask(({ execution }) => {
181                 if (execution.task.name === label) {
182                     disposable.dispose();
183                     resolve();
184                 }
185             });
186         });
187
188         vscode.tasks.executeTask(
189             createTask({
190                 label,
191                 bin: 'cargo',
192                 args: ['install', 'cargo-watch'],
193                 env: {}
194             })
195         );
196         await taskFinished;
197         const output = await execPromise('cargo watch --version').catch(e => e);
198         if (output.stderr !== '') {
199             vscode.window.showErrorMessage(
200                 `Couldn't install \`cargo-\`watch: ${output.stderr}`
201             );
202             return;
203         }
204     } else if (stderr !== '') {
205         vscode.window.showErrorMessage(
206             `Couldn't run \`cargo watch\`: ${stderr}`
207         );
208         return;
209     }
210
211     const provider = await registerCargoWatchProvider(context.subscriptions);
212     if (provider) {
213         provider.start();
214     }
215     return provider;
216 }