mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 14:13:58 +00:00
Merge #940
940: Show workspace loaded notification r=matklad a=vipentti This fixes #935 This adds support for more `InitializationOptions` which are provided by the client. Co-authored-by: Ville Penttinen <villem.penttinen@gmail.com>
This commit is contained in:
commit
efff774068
10 changed files with 110 additions and 54 deletions
39
crates/ra_lsp_server/src/init.rs
Normal file
39
crates/ra_lsp_server/src/init.rs
Normal file
|
@ -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<bool, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let opt = Option::deserialize(deserializer)?;
|
||||||
|
Ok(opt.unwrap_or(true))
|
||||||
|
}
|
|
@ -5,7 +5,8 @@ mod main_loop;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
mod project_model;
|
mod project_model;
|
||||||
pub mod req;
|
pub mod req;
|
||||||
|
pub mod init;
|
||||||
mod server_world;
|
mod server_world;
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
|
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
|
||||||
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};
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::Deserialize;
|
||||||
use flexi_logger::{Duplicate, Logger};
|
use flexi_logger::{Duplicate, Logger};
|
||||||
use gen_lsp_server::{run_server, stdio_transport};
|
use gen_lsp_server::{run_server, stdio_transport};
|
||||||
|
|
||||||
use ra_lsp_server::Result;
|
use ra_lsp_server::{Result, InitializationOptions};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
::std::env::set_var("RUST_BACKTRACE", "short");
|
::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<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main_inner() -> Result<()> {
|
fn main_inner() -> Result<()> {
|
||||||
let (receiver, sender, threads) = stdio_transport();
|
let (receiver, sender, threads) = stdio_transport();
|
||||||
let cwd = ::std::env::current_dir()?;
|
let cwd = ::std::env::current_dir()?;
|
||||||
run_server(ra_lsp_server::server_capabilities(), receiver, sender, |params, r, s| {
|
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 root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
|
||||||
let supports_decorations = params
|
|
||||||
|
let opts = params
|
||||||
.initialization_options
|
.initialization_options
|
||||||
.and_then(|v| InitializationOptions::deserialize(v).ok())
|
.and_then(|v| InitializationOptions::deserialize(v).ok())
|
||||||
.and_then(|it| it.publish_decorations)
|
.unwrap_or(InitializationOptions::default());
|
||||||
== Some(true);
|
|
||||||
ra_lsp_server::main_loop(false, root, supports_decorations, r, s)
|
ra_lsp_server::main_loop(root, opts, r, s)
|
||||||
})?;
|
})?;
|
||||||
log::info!("shutting down IO...");
|
log::info!("shutting down IO...");
|
||||||
threads.join()?;
|
threads.join()?;
|
||||||
|
|
|
@ -22,6 +22,7 @@ use crate::{
|
||||||
req,
|
req,
|
||||||
server_world::{ServerWorld, ServerWorldState},
|
server_world::{ServerWorld, ServerWorldState},
|
||||||
Result,
|
Result,
|
||||||
|
InitializationOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Fail)]
|
#[derive(Debug, Fail)]
|
||||||
|
@ -46,9 +47,8 @@ enum Task {
|
||||||
const THREADPOOL_SIZE: usize = 8;
|
const THREADPOOL_SIZE: usize = 8;
|
||||||
|
|
||||||
pub fn main_loop(
|
pub fn main_loop(
|
||||||
internal_mode: bool,
|
|
||||||
ws_root: PathBuf,
|
ws_root: PathBuf,
|
||||||
supports_decorations: bool,
|
options: InitializationOptions,
|
||||||
msg_receiver: &Receiver<RawMessage>,
|
msg_receiver: &Receiver<RawMessage>,
|
||||||
msg_sender: &Sender<RawMessage>,
|
msg_sender: &Sender<RawMessage>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
@ -63,11 +63,12 @@ pub fn main_loop(
|
||||||
Ok(ws) => vec![ws],
|
Ok(ws) => vec![ws],
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("loading workspace failed: {}", e);
|
log::error!("loading workspace failed: {}", e);
|
||||||
let msg = RawNotification::new::<req::ShowMessage>(&req::ShowMessageParams {
|
|
||||||
typ: req::MessageType::Error,
|
show_message(
|
||||||
message: format!("rust-analyzer failed to load workspace: {}", e),
|
req::MessageType::Error,
|
||||||
});
|
format!("rust-analyzer failed to load workspace: {}", e),
|
||||||
msg_sender.send(msg.into()).unwrap();
|
msg_sender,
|
||||||
|
);
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,8 +81,7 @@ pub fn main_loop(
|
||||||
let mut pending_requests = FxHashSet::default();
|
let mut pending_requests = FxHashSet::default();
|
||||||
let mut subs = Subscriptions::new();
|
let mut subs = Subscriptions::new();
|
||||||
let main_res = main_loop_inner(
|
let main_res = main_loop_inner(
|
||||||
internal_mode,
|
options,
|
||||||
supports_decorations,
|
|
||||||
&pool,
|
&pool,
|
||||||
msg_sender,
|
msg_sender,
|
||||||
msg_receiver,
|
msg_receiver,
|
||||||
|
@ -148,8 +148,7 @@ impl fmt::Debug for Event {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop_inner(
|
fn main_loop_inner(
|
||||||
internal_mode: bool,
|
options: InitializationOptions,
|
||||||
supports_decorations: bool,
|
|
||||||
pool: &ThreadPool,
|
pool: &ThreadPool,
|
||||||
msg_sender: &Sender<RawMessage>,
|
msg_sender: &Sender<RawMessage>,
|
||||||
msg_receiver: &Receiver<RawMessage>,
|
msg_receiver: &Receiver<RawMessage>,
|
||||||
|
@ -163,6 +162,7 @@ fn main_loop_inner(
|
||||||
// time to always have a thread ready to react to input.
|
// time to always have a thread ready to react to input.
|
||||||
let mut in_flight_libraries = 0;
|
let mut in_flight_libraries = 0;
|
||||||
let mut pending_libraries = Vec::new();
|
let mut pending_libraries = Vec::new();
|
||||||
|
let mut send_workspace_notification = true;
|
||||||
|
|
||||||
let (libdata_sender, libdata_receiver) = unbounded();
|
let (libdata_sender, libdata_receiver) = unbounded();
|
||||||
loop {
|
loop {
|
||||||
|
@ -190,7 +190,6 @@ fn main_loop_inner(
|
||||||
state_changed = true;
|
state_changed = true;
|
||||||
}
|
}
|
||||||
Event::Lib(lib) => {
|
Event::Lib(lib) => {
|
||||||
feedback(internal_mode, "library loaded", msg_sender);
|
|
||||||
state.add_lib(lib);
|
state.add_lib(lib);
|
||||||
in_flight_libraries -= 1;
|
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 {
|
if send_workspace_notification
|
||||||
feedback(internal_mode, "workspace loaded", msg_sender);
|
&& 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 {
|
if state_changed {
|
||||||
update_file_notifications_on_threadpool(
|
update_file_notifications_on_threadpool(
|
||||||
pool,
|
pool,
|
||||||
state.snapshot(),
|
state.snapshot(),
|
||||||
supports_decorations,
|
options.publish_decorations,
|
||||||
task_sender.clone(),
|
task_sender.clone(),
|
||||||
subs.subscriptions(),
|
subs.subscriptions(),
|
||||||
)
|
)
|
||||||
|
@ -501,11 +508,12 @@ fn update_file_notifications_on_threadpool(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn feedback(intrnal_mode: bool, msg: &str, sender: &Sender<RawMessage>) {
|
fn show_message<M: Into<String>>(typ: req::MessageType, msg: M, sender: &Sender<RawMessage>) {
|
||||||
if !intrnal_mode {
|
let not = RawNotification::new::<req::ShowMessage>(&req::ShowMessageParams {
|
||||||
return;
|
typ,
|
||||||
}
|
message: msg.into(),
|
||||||
let not = RawNotification::new::<req::InternalFeedback>(&msg.to_string());
|
});
|
||||||
|
|
||||||
sender.send(not.into()).unwrap();
|
sender.send(not.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,10 +172,3 @@ pub struct SourceChange {
|
||||||
pub workspace_edit: WorkspaceEdit,
|
pub workspace_edit: WorkspaceEdit,
|
||||||
pub cursor_position: Option<TextDocumentPositionParams>,
|
pub cursor_position: Option<TextDocumentPositionParams>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum InternalFeedback {}
|
|
||||||
|
|
||||||
impl Notification for InternalFeedback {
|
|
||||||
const METHOD: &'static str = "internalFeedback";
|
|
||||||
type Params = String;
|
|
||||||
}
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ version = "0.0.0"
|
||||||
use std::collections::Spam;
|
use std::collections::Spam;
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
server.wait_for_feedback("workspace loaded");
|
server.wait_for_message("workspace loaded");
|
||||||
eprintln!("loading took {:?}", project_start.elapsed());
|
eprintln!("loading took {:?}", project_start.elapsed());
|
||||||
let completion_start = Instant::now();
|
let completion_start = Instant::now();
|
||||||
let res = server.send_request::<Completion>(CompletionParams {
|
let res = server.send_request::<Completion>(CompletionParams {
|
||||||
|
@ -53,7 +53,7 @@ fn foo() {
|
||||||
}
|
}
|
||||||
",
|
",
|
||||||
);
|
);
|
||||||
server.wait_for_feedback("workspace loaded");
|
server.wait_for_message("workspace loaded");
|
||||||
server.request::<Runnables>(
|
server.request::<Runnables>(
|
||||||
RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
|
RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
|
||||||
json!([
|
json!([
|
||||||
|
@ -107,7 +107,7 @@ pub fn foo() {}
|
||||||
fn test_eggs() {}
|
fn test_eggs() {}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
server.wait_for_feedback("workspace loaded");
|
server.wait_for_message("workspace loaded");
|
||||||
server.request::<Runnables>(
|
server.request::<Runnables>(
|
||||||
RunnablesParams {
|
RunnablesParams {
|
||||||
text_document: server.doc_id("tests/spam.rs"),
|
text_document: server.doc_id("tests/spam.rs"),
|
||||||
|
@ -167,7 +167,7 @@ fn main() {
|
||||||
pub use std::collections::HashMap;
|
pub use std::collections::HashMap;
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
server.wait_for_feedback("workspace loaded");
|
server.wait_for_message("workspace loaded");
|
||||||
|
|
||||||
server.request::<Formatting>(
|
server.request::<Formatting>(
|
||||||
DocumentFormattingParams {
|
DocumentFormattingParams {
|
||||||
|
@ -216,7 +216,7 @@ mod bar;
|
||||||
fn main() {}
|
fn main() {}
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
server.wait_for_feedback("workspace loaded");
|
server.wait_for_message("workspace loaded");
|
||||||
let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
|
let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
|
||||||
server.request::<CodeActionRequest>(
|
server.request::<CodeActionRequest>(
|
||||||
CodeActionParams {
|
CodeActionParams {
|
||||||
|
|
|
@ -13,6 +13,7 @@ use lsp_types::{
|
||||||
notification::DidOpenTextDocument,
|
notification::DidOpenTextDocument,
|
||||||
request::{Request, Shutdown},
|
request::{Request, Shutdown},
|
||||||
DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url,
|
DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url,
|
||||||
|
notification::{Notification, ShowMessage},
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{to_string_pretty, Value};
|
use serde_json::{to_string_pretty, Value};
|
||||||
|
@ -22,6 +23,7 @@ use test_utils::{parse_fixture, find_mismatch};
|
||||||
|
|
||||||
use ra_lsp_server::{
|
use ra_lsp_server::{
|
||||||
main_loop, req,
|
main_loop, req,
|
||||||
|
InitializationOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn project(fixture: &str) -> Server {
|
pub fn project(fixture: &str) -> Server {
|
||||||
|
@ -56,7 +58,13 @@ impl Server {
|
||||||
"test server",
|
"test server",
|
||||||
128,
|
128,
|
||||||
move |mut msg_receiver, mut msg_sender| {
|
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 {
|
let res = Server {
|
||||||
|
@ -133,13 +141,14 @@ impl Server {
|
||||||
}
|
}
|
||||||
panic!("no response");
|
panic!("no response");
|
||||||
}
|
}
|
||||||
pub fn wait_for_feedback(&self, feedback: &str) {
|
pub fn wait_for_message(&self, message: &str) {
|
||||||
self.wait_for_feedback_n(feedback, 1)
|
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 {
|
let f = |msg: &RawMessage| match msg {
|
||||||
RawMessage::Notification(n) if n.method == "internalFeedback" => {
|
RawMessage::Notification(n) if n.method == ShowMessage::METHOD => {
|
||||||
return n.clone().cast::<req::InternalFeedback>().unwrap() == feedback;
|
let msg = n.clone().cast::<req::ShowMessage>().unwrap();
|
||||||
|
msg.message == message
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
|
@ -150,6 +150,11 @@
|
||||||
"default": false,
|
"default": false,
|
||||||
"description": "Highlight Rust code (overrides built-in syntax highlighting)"
|
"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": {
|
"rust-analyzer.enableEnhancedTyping": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
|
|
|
@ -8,6 +8,7 @@ export class Config {
|
||||||
public highlightingOn = true;
|
public highlightingOn = true;
|
||||||
public enableEnhancedTyping = true;
|
public enableEnhancedTyping = true;
|
||||||
public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
|
public raLspServerPath = RA_LSP_DEBUG || 'ra_lsp_server';
|
||||||
|
public showWorkspaceLoadedNotification = true;
|
||||||
|
|
||||||
private prevEnhancedTyping: null | boolean = null;
|
private prevEnhancedTyping: null | boolean = null;
|
||||||
|
|
||||||
|
@ -24,6 +25,12 @@ export class Config {
|
||||||
this.highlightingOn = config.get('highlightingOn') as boolean;
|
this.highlightingOn = config.get('highlightingOn') as boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.has('showWorkspaceLoadedNotification')) {
|
||||||
|
this.showWorkspaceLoadedNotification = config.get(
|
||||||
|
'showWorkspaceLoadedNotification'
|
||||||
|
) as boolean;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.highlightingOn && Server) {
|
if (!this.highlightingOn && Server) {
|
||||||
Server.highlighter.removeHighlights();
|
Server.highlighter.removeHighlights();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,9 @@ export class Server {
|
||||||
const clientOptions: lc.LanguageClientOptions = {
|
const clientOptions: lc.LanguageClientOptions = {
|
||||||
documentSelector: [{ scheme: 'file', language: 'rust' }],
|
documentSelector: [{ scheme: 'file', language: 'rust' }],
|
||||||
initializationOptions: {
|
initializationOptions: {
|
||||||
publishDecorations: true
|
publishDecorations: true,
|
||||||
|
showWorkspaceLoaded:
|
||||||
|
Server.config.showWorkspaceLoadedNotification
|
||||||
},
|
},
|
||||||
traceOutputChannel
|
traceOutputChannel
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue