Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement StatusBar #5188

Merged
merged 2 commits into from
Jul 2, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions crates/rust-analyzer/src/config.rs
Original file line number Diff line number Diff line change
@@ -130,6 +130,7 @@ pub struct ClientCapsConfig {
pub code_action_group: bool,
pub resolve_code_action: bool,
pub hover_actions: bool,
pub status_notification: bool,
}

impl Config {
@@ -365,6 +366,7 @@ impl Config {
self.client_caps.code_action_group = get_bool("codeActionGroup");
self.client_caps.resolve_code_action = get_bool("resolveCodeAction");
self.client_caps.hover_actions = get_bool("hoverActions");
self.client_caps.status_notification = get_bool("statusNotification");
}
}
}
2 changes: 2 additions & 0 deletions crates/rust-analyzer/src/global_state.rs
Original file line number Diff line number Diff line change
@@ -31,6 +31,8 @@ use crate::{
pub(crate) enum Status {
Loading,
Ready,
Invalid,
NeedsReload,
}

impl Default for Status {
18 changes: 17 additions & 1 deletion crates/rust-analyzer/src/lsp_ext.rs
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
use std::{collections::HashMap, path::PathBuf};

use lsp_types::request::Request;
use lsp_types::{Position, Range, TextDocumentIdentifier};
use lsp_types::{notification::Notification, Position, Range, TextDocumentIdentifier};
use serde::{Deserialize, Serialize};

pub enum AnalyzerStatus {}
@@ -208,6 +208,22 @@ pub struct SsrParams {
pub parse_only: bool,
}

pub enum StatusNotification {}

#[serde(rename_all = "camelCase")]
#[derive(Serialize, Deserialize)]
pub enum Status {
Loading,
Ready,
NeedsReload,
Invalid,
}

impl Notification for StatusNotification {
type Params = Status;
const METHOD: &'static str = "rust-analyzer/status";
}

pub enum CodeActionRequest {}

impl Request for CodeActionRequest {
56 changes: 52 additions & 4 deletions crates/rust-analyzer/src/main_loop.rs
Original file line number Diff line number Diff line change
@@ -111,6 +111,35 @@ impl GlobalState {
}

fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> {
let registration_options = lsp_types::TextDocumentRegistrationOptions {
document_selector: Some(vec![
lsp_types::DocumentFilter {
language: None,
scheme: None,
pattern: Some("**/*.rs".into()),
},
lsp_types::DocumentFilter {
language: None,
scheme: None,
pattern: Some("**/Cargo.toml".into()),
},
lsp_types::DocumentFilter {
language: None,
scheme: None,
pattern: Some("**/Cargo.lock".into()),
},
]),
};
let registration = lsp_types::Registration {
id: "textDocument/didSave".to_string(),
method: "textDocument/didSave".to_string(),
register_options: Some(serde_json::to_value(registration_options).unwrap()),
};
self.send_request::<lsp_types::request::RegisterCapability>(
lsp_types::RegistrationParams { registrations: vec![registration] },
|_, _| (),
);

self.reload();

while let Some(event) = self.next_event(&inbox) {
@@ -169,16 +198,16 @@ impl GlobalState {
}
vfs::loader::Message::Progress { n_total, n_done } => {
if n_total == 0 {
self.status = Status::Ready;
self.transition(Status::Invalid);
} else {
let state = if n_done == 0 {
self.status = Status::Loading;
self.transition(Status::Loading);
Progress::Begin
} else if n_done < n_total {
Progress::Report
} else {
assert_eq!(n_done, n_total);
self.status = Status::Ready;
self.transition(Status::Ready);
Progress::End
};
self.report_progress(
@@ -274,6 +303,19 @@ impl GlobalState {
Ok(())
}

fn transition(&mut self, new_status: Status) {
self.status = Status::Ready;
if self.config.client_caps.status_notification {
let lsp_status = match new_status {
Status::Loading => lsp_ext::Status::Loading,
Status::Ready => lsp_ext::Status::Ready,
Status::Invalid => lsp_ext::Status::Invalid,
Status::NeedsReload => lsp_ext::Status::NeedsReload,
};
self.send_notification::<lsp_ext::StatusNotification>(lsp_status);
}
}

fn on_request(&mut self, request_received: Instant, req: Request) -> Result<()> {
self.register_request(&req, request_received);

@@ -383,10 +425,16 @@ impl GlobalState {
);
Ok(())
})?
.on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| {
.on::<lsp_types::notification::DidSaveTextDocument>(|this, params| {
if let Some(flycheck) = &this.flycheck {
flycheck.handle.update();
}
let uri = params.text_document.uri.as_str();
if uri.ends_with("Cargo.toml") || uri.ends_with("Cargo.lock") {
if matches!(this.status, Status::Ready | Status::Invalid) {
this.transition(Status::NeedsReload);
}
}
Ok(())
})?
.on::<lsp_types::notification::DidChangeConfiguration>(|this, _params| {
2 changes: 1 addition & 1 deletion crates/rust-analyzer/src/reload.rs
Original file line number Diff line number Diff line change
@@ -78,7 +78,7 @@ impl GlobalState {
.collect(),
};
let registration = lsp_types::Registration {
id: "file-watcher".to_string(),
id: "workspace/didChangeWatchedFiles".to_string(),
method: "workspace/didChangeWatchedFiles".to_string(),
register_options: Some(serde_json::to_value(registration_options).unwrap()),
};
17 changes: 12 additions & 5 deletions crates/rust-analyzer/tests/heavy_tests/support.rs
Original file line number Diff line number Diff line change
@@ -176,12 +176,19 @@ impl Server {
while let Some(msg) = self.recv() {
match msg {
Message::Request(req) => {
if req.method != "window/workDoneProgress/create"
&& !(req.method == "client/registerCapability"
&& req.params.to_string().contains("workspace/didChangeWatchedFiles"))
{
panic!("unexpected request: {:?}", req)
if req.method == "window/workDoneProgress/create" {
continue;
}
if req.method == "client/registerCapability" {
let params = req.params.to_string();
if ["workspace/didChangeWatchedFiles", "textDocument/didSave"]
.iter()
.any(|&it| params.contains(it))
{
continue;
}
}
panic!("unexpected request: {:?}", req)
}
Message::Notification(_) => (),
Message::Response(res) => {
12 changes: 12 additions & 0 deletions docs/dev/lsp-extensions.md
Original file line number Diff line number Diff line change
@@ -399,6 +399,18 @@ Returns internal status message, mostly for debugging purposes.

Reloads project information (that is, re-executes `cargo metadata`).

## Status Notification

**Client Capability:** `{ "statusNotification": boolean }`

**Method:** `rust-analyzer/status`

**Notification:** `"loading" | "ready" | "invalid" | "needsReload"`

This notification is sent from server to client.
The client can use it to display persistent status to the user (in modline).
For `needsReload` state, the client can provide a context-menu action to run `rust-analyzer/reloadWorkspace` request.

## Syntax Tree

**Method:** `rust-analyzer/syntaxTree`
1 change: 1 addition & 0 deletions editors/code/src/client.ts
Original file line number Diff line number Diff line change
@@ -161,6 +161,7 @@ class ExperimentalFeatures implements lc.StaticFeature {
caps.codeActionGroup = true;
caps.resolveCodeAction = true;
caps.hoverActions = true;
caps.statusNotification = true;
capabilities.experimental = caps;
}
initialize(_capabilities: lc.ServerCapabilities<any>, _documentSelector: lc.DocumentSelector | undefined): void {
43 changes: 42 additions & 1 deletion editors/code/src/ctx.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import * as vscode from 'vscode';
import * as lc from 'vscode-languageclient';
import * as ra from './lsp_ext';

import { Config } from './config';
import { createClient } from './client';
import { isRustEditor, RustEditor } from './util';
import { Status } from './lsp_ext';

export class Ctx {
private constructor(
readonly config: Config,
private readonly extCtx: vscode.ExtensionContext,
readonly client: lc.LanguageClient,
readonly serverPath: string,
readonly statusBar: vscode.StatusBarItem,
) {

}
@@ -22,9 +25,18 @@ export class Ctx {
cwd: string,
): Promise<Ctx> {
const client = createClient(serverPath, cwd);
const res = new Ctx(config, extCtx, client, serverPath);

const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
extCtx.subscriptions.push(statusBar);
statusBar.text = "rust-analyzer";
statusBar.tooltip = "ready";
statusBar.show();

const res = new Ctx(config, extCtx, client, serverPath, statusBar);

res.pushCleanup(client.start());
await client.onReady();
client.onNotification(ra.status, (status) => res.setStatus(status));
return res;
}

@@ -54,6 +66,35 @@ export class Ctx {
return this.extCtx.subscriptions;
}

setStatus(status: Status) {
switch (status) {
case "loading":
this.statusBar.text = "$(sync~spin) rust-analyzer";
this.statusBar.tooltip = "Loading the project";
this.statusBar.command = undefined;
this.statusBar.color = undefined;
break;
case "ready":
this.statusBar.text = "rust-analyzer";
this.statusBar.tooltip = "Ready";
this.statusBar.command = undefined;
this.statusBar.color = undefined;
break;
case "invalid":
this.statusBar.text = "$(error) rust-analyzer";
this.statusBar.tooltip = "Failed to load the project";
this.statusBar.command = undefined;
this.statusBar.color = new vscode.ThemeColor("notificationsErrorIcon.foreground");
break;
case "needsReload":
this.statusBar.text = "$(warning) rust-analyzer";
this.statusBar.tooltip = "Click to reload";
this.statusBar.command = "rust-analyzer.reloadWorkspace";
this.statusBar.color = new vscode.ThemeColor("notificationsWarningIcon.foreground");
break;
}
}

pushCleanup(d: Disposable) {
this.extCtx.subscriptions.push(d);
}
3 changes: 3 additions & 0 deletions editors/code/src/lsp_ext.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,9 @@ import * as lc from "vscode-languageclient";

export const analyzerStatus = new lc.RequestType<null, string, void>("rust-analyzer/analyzerStatus");

export type Status = "loading" | "ready" | "invalid" | "needsReload";
export const status = new lc.NotificationType<Status>("rust-analyzer/status");

export const reloadWorkspace = new lc.RequestType<null, null, void>("rust-analyzer/reloadWorkspace");

export interface SyntaxTreeParams {