diff --git a/Cargo.lock b/Cargo.lock index d1aa91d..99d2f8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "async-trait" version = "0.1.74" @@ -1172,6 +1178,7 @@ dependencies = [ name = "stef-lsp" version = "0.1.0" dependencies = [ + "anyhow", "clap", "directories", "ouroboros", diff --git a/crates/stef-lsp/Cargo.toml b/crates/stef-lsp/Cargo.toml index 1de9057..14c2ebb 100644 --- a/crates/stef-lsp/Cargo.toml +++ b/crates/stef-lsp/Cargo.toml @@ -10,6 +10,7 @@ repository.workspace = true license.workspace = true [dependencies] +anyhow = "1.0.75" clap.workspace = true directories = "5.0.1" ouroboros = "0.18.0" diff --git a/crates/stef-lsp/src/config.rs b/crates/stef-lsp/src/config.rs index d5c473c..937064c 100644 --- a/crates/stef-lsp/src/config.rs +++ b/crates/stef-lsp/src/config.rs @@ -5,3 +5,11 @@ use serde::Deserialize; pub struct Global { pub max_number_of_problems: u32, } + +impl Default for Global { + fn default() -> Self { + Self { + max_number_of_problems: 100, + } + } +} diff --git a/crates/stef-lsp/src/main.rs b/crates/stef-lsp/src/main.rs index 845c28e..fc5c8c0 100644 --- a/crates/stef-lsp/src/main.rs +++ b/crates/stef-lsp/src/main.rs @@ -1,14 +1,16 @@ +#![warn(clippy::expect_used, clippy::unwrap_used)] #![allow(missing_docs)] use std::collections::HashMap; +use anyhow::{ensure, Context, Result}; use directories::ProjectDirs; use ouroboros::self_referencing; use stef_parser::Schema; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, RwLock}; use tower_lsp::{ async_trait, - jsonrpc::Result, + jsonrpc::Result as LspResult, lsp_types::{ ConfigurationItem, Diagnostic, DidChangeConfigurationParams, DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams, InitializeParams, InitializeResult, @@ -20,7 +22,7 @@ use tower_lsp::{ }, Client, LanguageServer, LspService, Server, }; -use tracing::{debug, Level}; +use tracing::{debug, error, Level}; use tracing_subscriber::{filter::Targets, fmt::MakeWriter, prelude::*}; use self::cli::Cli; @@ -34,6 +36,7 @@ mod utf16; struct Backend { client: Client, files: Mutex>, + settings: RwLock, } #[self_referencing] @@ -42,12 +45,39 @@ struct File { content: String, #[borrows(content)] #[covariant] - schema: std::result::Result, Diagnostic>, + schema: Result, Diagnostic>, +} + +impl Backend { + async fn reload_settings(&self) -> Result<()> { + let mut settings = self + .client + .configuration(vec![ConfigurationItem { + scope_uri: None, + section: Some("stef".to_owned()), + }]) + .await + .context("failed getting configuration from client")?; + + ensure!( + settings.len() == 1, + "configuration contains not exactly one object" + ); + + let settings = serde_json::from_value(settings.remove(0)) + .context("failed to parse raw configuration")?; + + debug!("configuration loaded: {settings:#?}"); + + *self.settings.write().await = settings; + + Ok(()) + } } #[async_trait] impl LanguageServer for Backend { - async fn initialize(&self, _params: InitializeParams) -> Result { + async fn initialize(&self, _params: InitializeParams) -> LspResult { Ok(InitializeResult { server_info: Some(ServerInfo { name: env!("CARGO_CRATE_NAME").to_owned(), @@ -114,38 +144,26 @@ impl LanguageServer for Backend { } async fn initialized(&self, _params: InitializedParams) { - let settings = self - .client - .configuration(vec![ConfigurationItem { - scope_uri: None, - section: Some("stef".to_owned()), - }]) - .await - .unwrap() - .remove(0); - - let settings = serde_json::from_value::(settings).unwrap(); - - self.client - .log_message( - MessageType::INFO, - format!("current settings: {settings:#?}"), - ) - .await; + if let Err(e) = self.reload_settings().await { + error!(error = ?e, "failed loading initial settings"); + } - self.client + if let Err(e) = self + .client .register_capability(vec![Registration { id: "1".to_owned(), method: "workspace/didChangeConfiguration".to_owned(), register_options: None, }]) .await - .unwrap(); + { + error!(error = ?e, "failed registering for configuration changes"); + } debug!("initialized"); } - async fn shutdown(&self) -> Result<()> { + async fn shutdown(&self) -> LspResult<()> { Ok(()) } @@ -211,38 +229,26 @@ impl LanguageServer for Backend { async fn semantic_tokens_full( &self, params: SemanticTokensParams, - ) -> Result> { + ) -> LspResult> { debug!(uri = %params.text_document.uri, "requested semantic tokens"); Ok(None) } async fn did_change_configuration(&self, _: DidChangeConfigurationParams) { - let settings = self - .client - .configuration(vec![ConfigurationItem { - scope_uri: None, - section: Some("stef".to_owned()), - }]) - .await - .unwrap() - .remove(0); + debug!("configuration changed"); - let settings = serde_json::from_value::(settings).unwrap(); - - self.client - .log_message( - MessageType::INFO, - format!("updated settings: {settings:#?}"), - ) - .await; + if let Err(e) = self.reload_settings().await { + error!(error = ?e, "failed loading changed settings"); + } } } #[tokio::main] -async fn main() { +async fn main() -> Result<()> { let cli = Cli::parse(); - let dirs = ProjectDirs::from("rocks", "dnaka91", env!("CARGO_PKG_NAME")).unwrap(); + let dirs = ProjectDirs::from("rocks", "dnaka91", env!("CARGO_PKG_NAME")) + .context("failed locating project directories")?; let file_appender = tracing_appender::rolling::daily(dirs.cache_dir(), "log"); let (file_appender, _guard) = tracing_appender::non_blocking(file_appender); @@ -266,6 +272,7 @@ async fn main() { Backend { client, files: Mutex::default(), + settings: RwLock::default(), } }); @@ -278,12 +285,15 @@ async fn main() { .write(true) .open(file) .await - .expect("failed to open provided pipe/socket"); + .context("failed to open provided pipe/socket")?; + let (read, write) = tokio::io::split(file); Server::new(read, write, socket).serve(service).await; } else if let Some(port) = cli.socket { unimplemented!("open TCP connection on port {port}"); } + + Ok(()) } struct ClientLogWriter {