diff --git a/crates/ra_lsp_server/src/init.rs b/crates/ra_lsp_server/src/init.rs new file mode 100644 index 000000000000..0b7a47a0b9ee --- /dev/null +++ b/crates/ra_lsp_server/src/init.rs @@ -0,0 +1,39 @@ +use serde::{Deserialize, Deserializer}; + +/// Client provided initialization options +#[derive(Deserialize, Clone, Copy, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InitializationOptions { + /// Whether the client supports our custom highlighting publishing decorations. + /// This is different to the highlightingOn setting, which is whether the user + /// wants our custom highlighting to be used. + /// + /// Defaults to `true` + #[serde(default = "bool_true", deserialize_with = "nullable_bool_true")] + pub publish_decorations: bool, + + /// Whether or not the workspace loaded notification should be sent + /// + /// Defaults to `true` + #[serde(default = "bool_true", deserialize_with = "nullable_bool_true")] + pub show_workspace_loaded: bool, +} + +impl Default for InitializationOptions { + fn default() -> InitializationOptions { + InitializationOptions { publish_decorations: true, show_workspace_loaded: true } + } +} + +fn bool_true() -> bool { + true +} + +/// Deserializes a null value to a bool true by default +fn nullable_bool_true<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or(true)) +} diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 5b5f3b948fdd..59e16a47c5e6 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -5,7 +5,8 @@ mod main_loop; mod markdown; mod project_model; pub mod req; +pub mod init; mod server_world; pub type Result = ::std::result::Result; -pub use crate::{caps::server_capabilities, main_loop::main_loop, main_loop::LspError}; +pub use crate::{caps::server_capabilities, main_loop::main_loop, main_loop::LspError, init::InitializationOptions}; diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs index 03f83c7bef14..5a29052071c3 100644 --- a/crates/ra_lsp_server/src/main.rs +++ b/crates/ra_lsp_server/src/main.rs @@ -2,7 +2,7 @@ use serde::Deserialize; use flexi_logger::{Duplicate, Logger}; use gen_lsp_server::{run_server, stdio_transport}; -use ra_lsp_server::Result; +use ra_lsp_server::{Result, InitializationOptions}; fn main() -> Result<()> { ::std::env::set_var("RUST_BACKTRACE", "short"); @@ -24,26 +24,18 @@ fn main() -> Result<()> { } } -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct InitializationOptions { - // Whether the client supports our custom highlighting publishing decorations. - // This is different to the highlightingOn setting, which is whether the user - // wants our custom highlighting to be used. - publish_decorations: Option, -} - fn main_inner() -> Result<()> { let (receiver, sender, threads) = stdio_transport(); let cwd = ::std::env::current_dir()?; run_server(ra_lsp_server::server_capabilities(), receiver, sender, |params, r, s| { let root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd); - let supports_decorations = params + + let opts = params .initialization_options .and_then(|v| InitializationOptions::deserialize(v).ok()) - .and_then(|it| it.publish_decorations) - == Some(true); - ra_lsp_server::main_loop(false, root, supports_decorations, r, s) + .unwrap_or(InitializationOptions::default()); + + ra_lsp_server::main_loop(root, opts, r, s) })?; log::info!("shutting down IO..."); threads.join()?; diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 0f8ef10b991b..d0c2a95ef70f 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -22,6 +22,7 @@ use crate::{ req, server_world::{ServerWorld, ServerWorldState}, Result, + InitializationOptions, }; #[derive(Debug, Fail)] @@ -46,9 +47,8 @@ enum Task { const THREADPOOL_SIZE: usize = 8; pub fn main_loop( - internal_mode: bool, ws_root: PathBuf, - supports_decorations: bool, + options: InitializationOptions, msg_receiver: &Receiver, msg_sender: &Sender, ) -> Result<()> { @@ -63,11 +63,12 @@ pub fn main_loop( Ok(ws) => vec![ws], Err(e) => { log::error!("loading workspace failed: {}", e); - let msg = RawNotification::new::(&req::ShowMessageParams { - typ: req::MessageType::Error, - message: format!("rust-analyzer failed to load workspace: {}", e), - }); - msg_sender.send(msg.into()).unwrap(); + + show_message( + req::MessageType::Error, + format!("rust-analyzer failed to load workspace: {}", e), + msg_sender, + ); Vec::new() } } @@ -80,8 +81,7 @@ pub fn main_loop( let mut pending_requests = FxHashSet::default(); let mut subs = Subscriptions::new(); let main_res = main_loop_inner( - internal_mode, - supports_decorations, + options, &pool, msg_sender, msg_receiver, @@ -148,8 +148,7 @@ impl fmt::Debug for Event { } fn main_loop_inner( - internal_mode: bool, - supports_decorations: bool, + options: InitializationOptions, pool: &ThreadPool, msg_sender: &Sender, msg_receiver: &Receiver, @@ -163,6 +162,7 @@ fn main_loop_inner( // time to always have a thread ready to react to input. let mut in_flight_libraries = 0; let mut pending_libraries = Vec::new(); + let mut send_workspace_notification = true; let (libdata_sender, libdata_receiver) = unbounded(); loop { @@ -190,7 +190,6 @@ fn main_loop_inner( state_changed = true; } Event::Lib(lib) => { - feedback(internal_mode, "library loaded", msg_sender); state.add_lib(lib); in_flight_libraries -= 1; } @@ -244,15 +243,23 @@ fn main_loop_inner( }); } - if state.roots_to_scan == 0 && pending_libraries.is_empty() && in_flight_libraries == 0 { - feedback(internal_mode, "workspace loaded", msg_sender); + if send_workspace_notification + && state.roots_to_scan == 0 + && pending_libraries.is_empty() + && in_flight_libraries == 0 + { + if options.show_workspace_loaded { + show_message(req::MessageType::Info, "workspace loaded", msg_sender); + } + // Only send the notification first time + send_workspace_notification = false; } if state_changed { update_file_notifications_on_threadpool( pool, state.snapshot(), - supports_decorations, + options.publish_decorations, task_sender.clone(), subs.subscriptions(), ) @@ -501,11 +508,12 @@ fn update_file_notifications_on_threadpool( }); } -fn feedback(intrnal_mode: bool, msg: &str, sender: &Sender) { - if !intrnal_mode { - return; - } - let not = RawNotification::new::(&msg.to_string()); +fn show_message>(typ: req::MessageType, msg: M, sender: &Sender) { + let not = RawNotification::new::(&req::ShowMessageParams { + typ, + message: msg.into(), + }); + sender.send(not.into()).unwrap(); } diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index 484fde7e500f..e0571fd78bf6 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs @@ -172,10 +172,3 @@ pub struct SourceChange { pub workspace_edit: WorkspaceEdit, pub cursor_position: Option, } - -pub enum InternalFeedback {} - -impl Notification for InternalFeedback { - const METHOD: &'static str = "internalFeedback"; - type Params = String; -} diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs index 996bf8e018ba..1c099a78f59f 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/main.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs @@ -31,7 +31,7 @@ version = "0.0.0" use std::collections::Spam; "#, ); - server.wait_for_feedback("workspace loaded"); + server.wait_for_message("workspace loaded"); eprintln!("loading took {:?}", project_start.elapsed()); let completion_start = Instant::now(); let res = server.send_request::(CompletionParams { @@ -53,7 +53,7 @@ fn foo() { } ", ); - server.wait_for_feedback("workspace loaded"); + server.wait_for_message("workspace loaded"); server.request::( RunnablesParams { text_document: server.doc_id("lib.rs"), position: None }, json!([ @@ -107,7 +107,7 @@ pub fn foo() {} fn test_eggs() {} "#, ); - server.wait_for_feedback("workspace loaded"); + server.wait_for_message("workspace loaded"); server.request::( RunnablesParams { text_document: server.doc_id("tests/spam.rs"), @@ -167,7 +167,7 @@ fn main() { pub use std::collections::HashMap; "#, ); - server.wait_for_feedback("workspace loaded"); + server.wait_for_message("workspace loaded"); server.request::( DocumentFormattingParams { @@ -216,7 +216,7 @@ mod bar; fn main() {} "#, ); - server.wait_for_feedback("workspace loaded"); + server.wait_for_message("workspace loaded"); let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None }; server.request::( CodeActionParams { diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs index f4e7eaf75bab..8bfc8d62206c 100644 --- a/crates/ra_lsp_server/tests/heavy_tests/support.rs +++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs @@ -13,6 +13,7 @@ use lsp_types::{ notification::DidOpenTextDocument, request::{Request, Shutdown}, DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, + notification::{Notification, ShowMessage}, }; use serde::Serialize; use serde_json::{to_string_pretty, Value}; @@ -22,6 +23,7 @@ use test_utils::{parse_fixture, find_mismatch}; use ra_lsp_server::{ main_loop, req, + InitializationOptions, }; pub fn project(fixture: &str) -> Server { @@ -56,7 +58,13 @@ impl Server { "test server", 128, move |mut msg_receiver, mut msg_sender| { - main_loop(true, path, true, &mut msg_receiver, &mut msg_sender).unwrap() + main_loop( + path, + InitializationOptions::default(), + &mut msg_receiver, + &mut msg_sender, + ) + .unwrap() }, ); let res = Server { @@ -133,13 +141,14 @@ impl Server { } panic!("no response"); } - pub fn wait_for_feedback(&self, feedback: &str) { - self.wait_for_feedback_n(feedback, 1) + pub fn wait_for_message(&self, message: &str) { + self.wait_for_message_n(message, 1) } - pub fn wait_for_feedback_n(&self, feedback: &str, n: usize) { + pub fn wait_for_message_n(&self, message: &str, n: usize) { let f = |msg: &RawMessage| match msg { - RawMessage::Notification(n) if n.method == "internalFeedback" => { - return n.clone().cast::().unwrap() == feedback; + RawMessage::Notification(n) if n.method == ShowMessage::METHOD => { + let msg = n.clone().cast::().unwrap(); + msg.message == message } _ => false, }; diff --git a/editors/code/package.json b/editors/code/package.json index fda411810a36..47eaac878a69 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -150,6 +150,11 @@ "default": false, "description": "Highlight Rust code (overrides built-in syntax highlighting)" }, + "rust-analyzer.showWorkspaceLoadedNotification": { + "type": "boolean", + "default": true, + "description": "Show notification when workspace was loaded" + }, "rust-analyzer.enableEnhancedTyping": { "type": "boolean", "default": true, diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index 4e353798cf97..afc5cc6aff68 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -8,6 +8,7 @@ export class Config { public highlightingOn = true; public enableEnhancedTyping = true; public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server'; + public showWorkspaceLoadedNotification = true; private prevEnhancedTyping: null | boolean = null; @@ -24,6 +25,12 @@ export class Config { this.highlightingOn = config.get('highlightingOn') as boolean; } + if (config.has('showWorkspaceLoadedNotification')) { + this.showWorkspaceLoadedNotification = config.get( + 'showWorkspaceLoadedNotification' + ) as boolean; + } + if (!this.highlightingOn && Server) { Server.highlighter.removeHighlights(); } diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts index 9ead87fae6db..50461b0c6eab 100644 --- a/editors/code/src/server.ts +++ b/editors/code/src/server.ts @@ -26,7 +26,9 @@ export class Server { const clientOptions: lc.LanguageClientOptions = { documentSelector: [{ scheme: 'file', language: 'rust' }], initializationOptions: { - publishDecorations: true + publishDecorations: true, + showWorkspaceLoaded: + Server.config.showWorkspaceLoadedNotification }, traceOutputChannel };