1174: improve cargo watch r=matklad a=vemoo

- Add start and stop commands
- Cleanup trypescript code to avoid definite assignment assertions (`!` after possibly undefined value)
- Recover `rustc-watch` problem matcher because it's still useful, can be used with any command, for example `cargo test`

Co-authored-by: Bernardo <berublan@gmail.com>
This commit is contained in:
bors[bot] 2019-04-20 14:02:36 +00:00
commit 526a6aba10
6 changed files with 136 additions and 54 deletions

View file

@ -76,6 +76,14 @@ Shows internal statistic about memory usage of rust-analyzer
Manually triggers GC
#### Start Cargo Watch
Start `cargo watch` for live error highlighting. Will prompt to install if it's not already installed.
#### Stop Cargo Watch
Stop `cargo watch`
### Code Actions (Assists)
These are triggered in a particular context via light bulb. We use custom code on

View file

@ -119,6 +119,16 @@
"command": "rust-analyzer.reload",
"title": "Restart server",
"category": "Rust Analyzer"
},
{
"command": "rust-analyzer.startCargoWatch",
"title": "Start Cargo Watch",
"category": "Rust Analyzer"
},
{
"command": "rust-analyzer.stopCargoWatch",
"title": "Stop Cargo Watch",
"category": "Rust Analyzer"
}
],
"keybindings": [
@ -250,6 +260,18 @@
"${workspaceRoot}"
],
"pattern": "$rustc"
},
{
"name": "rustc-watch",
"fileLocation": [
"relative",
"${workspaceRoot}"
],
"background": {
"beginsPattern": "^\\[Running\\b",
"endsPattern": "^\\[Finished running\\b"
},
"pattern": "$rustc"
}
]
}

View file

@ -7,44 +7,55 @@ import { terminate } from '../utils/processes';
import { LineBuffer } from './line_buffer';
import { StatusDisplay } from './watch_status';
export class CargoWatchProvider {
private diagnosticCollection?: vscode.DiagnosticCollection;
private cargoProcess?: child_process.ChildProcess;
private outBuffer: string = '';
private statusDisplay?: StatusDisplay;
private outputChannel?: vscode.OutputChannel;
export function registerCargoWatchProvider(
subscriptions: vscode.Disposable[]
): CargoWatchProvider | undefined {
let cargoExists = false;
const cargoTomlFile = path.join(vscode.workspace.rootPath!, 'Cargo.toml');
// Check if the working directory is valid cargo root path
try {
if (fs.existsSync(cargoTomlFile)) {
cargoExists = true;
}
} catch (err) {
cargoExists = false;
}
public activate(subscriptions: vscode.Disposable[]) {
let cargoExists = false;
const cargoTomlFile = path.join(
vscode.workspace.rootPath!,
'Cargo.toml'
if (!cargoExists) {
vscode.window.showErrorMessage(
`Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}`
);
// Check if the working directory is valid cargo root path
try {
if (fs.existsSync(cargoTomlFile)) {
cargoExists = true;
}
} catch (err) {
cargoExists = false;
}
return;
}
if (!cargoExists) {
vscode.window.showErrorMessage(
`Couldn\'t find \'Cargo.toml\' in ${cargoTomlFile}`
);
return;
}
const provider = new CargoWatchProvider();
subscriptions.push(provider);
return provider;
}
subscriptions.push(this);
export class CargoWatchProvider implements vscode.Disposable {
private readonly diagnosticCollection: vscode.DiagnosticCollection;
private readonly statusDisplay: StatusDisplay;
private readonly outputChannel: vscode.OutputChannel;
private cargoProcess?: child_process.ChildProcess;
constructor() {
this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
'rustc'
);
this.statusDisplay = new StatusDisplay(subscriptions);
this.statusDisplay = new StatusDisplay();
this.outputChannel = vscode.window.createOutputChannel(
'Cargo Watch Trace'
);
}
public start() {
if (this.cargoProcess) {
vscode.window.showInformationMessage(
'Cargo Watch is already running'
);
return;
}
let args = 'check --message-format json';
if (Server.config.cargoWatchOptions.checkArguments.length > 0) {
@ -95,25 +106,28 @@ export class CargoWatchProvider {
this.logInfo('cargo-watch started.');
}
public dispose(): void {
if (this.diagnosticCollection) {
this.diagnosticCollection.clear();
this.diagnosticCollection.dispose();
}
public stop() {
if (this.cargoProcess) {
this.cargoProcess.kill();
terminate(this.cargoProcess);
this.cargoProcess = undefined;
} else {
vscode.window.showInformationMessage('Cargo Watch is not running');
}
}
if (this.outputChannel) {
this.outputChannel.dispose();
}
public dispose(): void {
this.stop();
this.diagnosticCollection.clear();
this.diagnosticCollection.dispose();
this.outputChannel.dispose();
this.statusDisplay.dispose();
}
private logInfo(line: string) {
if (Server.config.cargoWatchOptions.trace === 'verbose') {
this.outputChannel!.append(line);
this.outputChannel.append(line);
}
}
@ -122,18 +136,18 @@ export class CargoWatchProvider {
Server.config.cargoWatchOptions.trace === 'error' ||
Server.config.cargoWatchOptions.trace === 'verbose'
) {
this.outputChannel!.append(line);
this.outputChannel.append(line);
}
}
private parseLine(line: string) {
if (line.startsWith('[Running')) {
this.diagnosticCollection!.clear();
this.statusDisplay!.show();
this.diagnosticCollection.clear();
this.statusDisplay.show();
}
if (line.startsWith('[Finished running')) {
this.statusDisplay!.hide();
this.statusDisplay.hide();
}
function getLevel(s: string): vscode.DiagnosticSeverity {
@ -193,7 +207,7 @@ export class CargoWatchProvider {
// The format of the package_id is "{name} {version} ({source_id})",
// https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53
this.statusDisplay!.packageName = msg.package_id.split(' ')[0];
this.statusDisplay.packageName = msg.package_id.split(' ')[0];
} else if (data.reason === 'compiler-message') {
const msg = data.message as RustDiagnostic;

View file

@ -5,7 +5,7 @@ import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import { Server } from '../server';
import { CargoWatchProvider } from './cargo_watch';
import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch';
interface RunnablesParams {
textDocument: lc.TextDocumentIdentifier;
@ -137,7 +137,7 @@ export async function handleSingle(runnable: Runnable) {
*/
export async function interactivelyStartCargoWatch(
context: vscode.ExtensionContext
) {
): Promise<CargoWatchProvider | undefined> {
if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') {
return;
}
@ -153,6 +153,12 @@ export async function interactivelyStartCargoWatch(
}
}
return startCargoWatch(context);
}
export async function startCargoWatch(
context: vscode.ExtensionContext
): Promise<CargoWatchProvider | undefined> {
const execPromise = util.promisify(child_process.exec);
const { stderr } = await execPromise('cargo watch --version').catch(e => e);
@ -197,6 +203,9 @@ export async function interactivelyStartCargoWatch(
}
}
const validater = new CargoWatchProvider();
validater.activate(context.subscriptions);
const provider = await registerCargoWatchProvider(context.subscriptions);
if (provider) {
provider.start();
}
return provider;
}

View file

@ -2,19 +2,18 @@ import * as vscode from 'vscode';
const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
export class StatusDisplay {
export class StatusDisplay implements vscode.Disposable {
public packageName?: string;
private i = 0;
private statusBarItem: vscode.StatusBarItem;
private timer?: NodeJS.Timeout;
constructor(subscriptions: vscode.Disposable[]) {
constructor() {
this.statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left,
10
);
subscriptions.push(this.statusBarItem);
this.statusBarItem.hide();
}
@ -33,7 +32,7 @@ export class StatusDisplay {
}
}, 300);
this.statusBarItem!.show();
this.statusBarItem.show();
}
public hide() {
@ -42,7 +41,16 @@ export class StatusDisplay {
this.timer = undefined;
}
this.statusBarItem!.hide();
this.statusBarItem.hide();
}
public dispose() {
if (this.timer) {
clearInterval(this.timer);
this.timer = undefined;
}
this.statusBarItem.dispose();
}
private frame() {

View file

@ -2,7 +2,11 @@ import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as commands from './commands';
import { interactivelyStartCargoWatch } from './commands/runnables';
import { CargoWatchProvider } from './commands/cargo_watch';
import {
interactivelyStartCargoWatch,
startCargoWatch
} from './commands/runnables';
import { SyntaxTreeContentProvider } from './commands/syntaxTree';
import * as events from './events';
import * as notifications from './notifications';
@ -126,7 +130,24 @@ export function activate(context: vscode.ExtensionContext) {
vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
// Executing `cargo watch` provides us with inline diagnostics on save
interactivelyStartCargoWatch(context);
let provider: CargoWatchProvider | undefined;
interactivelyStartCargoWatch(context).then(p => {
provider = p;
});
registerCommand('rust-analyzer.startCargoWatch', () => {
if (provider) {
provider.start();
} else {
startCargoWatch(context).then(p => {
provider = p;
});
}
});
registerCommand('rust-analyzer.stopCargoWatch', () => {
if (provider) {
provider.stop();
}
});
// Start the language server, finally!
startServer();