From 36ccaf8ab3ea65260627d3056d288d9478d2fd11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20F=C3=B6rster?= Date: Fri, 6 Mar 2020 19:44:56 +0100 Subject: [PATCH] Refactor configuration management module --- Cargo.toml | 2 +- benches/benchmarks/open_latex.rs | 2 +- src/config.rs | 103 +++++++++++ src/lib.rs | 2 + src/main.rs | 41 ++++- src/server.rs | 301 +++++++++++++++++++++++++++++++ src/syntax/latex/analysis.rs | 10 +- src/syntax/latex/mod.rs | 14 +- src/workspace.rs | 51 ++++-- 9 files changed, 495 insertions(+), 31 deletions(-) create mode 100644 src/config.rs create mode 100644 src/server.rs diff --git a/Cargo.toml b/Cargo.toml index 8bcf116c..50ae1cd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ serde_json = "1.0" serde_repr = "0.1" stderrlog = "0.4" tempfile = "3.1" -tokio = { version = "0.2", features = ["fs", "macros", "process", "time"] } +tokio = { version = "0.2", features = ["fs", "io-std", "macros", "process", "time"] } tokio-util = { version = "0.2", features = ["codec"] } url = "2.1" diff --git a/benches/benchmarks/open_latex.rs b/benches/benchmarks/open_latex.rs index f558584e..fbfb88aa 100644 --- a/benches/benchmarks/open_latex.rs +++ b/benches/benchmarks/open_latex.rs @@ -28,7 +28,7 @@ async fn criterion_benchmark(criterion: &mut Criterion) { uri: &uri, resolver: &resolver, options: &options, - cwd: &cwd, + current_dir: &cwd, }; criterion.bench_function("symbols.tex", |b| b.iter(|| latex::open(params))); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..84de2268 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,103 @@ +use crate::protocol::*; +use futures::lock::Mutex; +use log::{error, warn}; +use serde::de::DeserializeOwned; +use std::sync::Arc; + +#[derive(Debug)] +pub struct ConfigManager { + client: Arc, + client_capabilities: Arc, + options: Mutex, +} + +impl ConfigManager { + pub fn new(client: Arc, client_capabilities: Arc) -> Self { + Self { + client, + client_capabilities, + options: Mutex::default(), + } + } + + pub async fn get(&self) -> Options { + self.options.lock().await.clone() + } + + pub async fn register(&self) { + if !self.client_capabilities.has_pull_configuration_support() + && self.client_capabilities.has_push_configuration_support() + { + let registration = Registration { + id: "pull-config".into(), + method: "workspace/didChangeConfiguration".into(), + register_options: None, + }; + let params = RegistrationParams { + registrations: vec![registration], + }; + + if let Err(why) = self.client.register_capability(params).await { + error!( + "Failed to register \"workspace/didChangeConfiguration\": {}", + why.message + ); + } + } + } + + pub async fn push(&self, options: serde_json::Value) { + match serde_json::from_value(options) { + Ok(options) => { + *self.options.lock().await = options; + } + Err(why) => { + error!("Invalid configuration: {}", why); + } + } + } + + pub async fn pull(&self) -> bool { + if self.client_capabilities.has_pull_configuration_support() { + let latex = self.pull_section("latex").await; + let bibtex = self.pull_section("bibtex").await; + + let new_options = Options { + latex: Some(latex), + bibtex: Some(bibtex), + }; + let mut old_options = self.options.lock().await; + let has_changed = *old_options != new_options; + *old_options = new_options; + has_changed + } else { + false + } + } + + async fn pull_section(&self, section: &str) -> T { + let params = ConfigurationParams { + items: vec![ConfigurationItem { + section: Some(section.into()), + scope_uri: None, + }], + }; + + match self.client.configuration(params).await { + Ok(json) => match serde_json::from_value::>(json) { + Ok(config) => config.into_iter().next().unwrap(), + Err(_) => { + warn!("Invalid configuration: {}", section); + T::default() + } + }, + Err(why) => { + error!( + "Retrieving configuration for {} failed: {}", + section, why.message + ); + T::default() + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index c27fac4a..e8ab0163 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ pub mod components; +pub mod config; pub mod jsonrpc; pub mod protocol; +pub mod server; pub mod syntax; pub mod tex; pub mod workspace; diff --git a/src/main.rs b/src/main.rs index ca2059d7..481070b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,17 @@ use clap::{app_from_crate, crate_authors, crate_description, crate_name, crate_version, Arg}; -use std::error; +use futures::{channel::mpsc, prelude::*}; +use std::{env, error, sync::Arc}; use stderrlog::{ColorChoice, Timestamp}; -use texlab::tex::Distribution; +use texlab::{ + jsonrpc::MessageHandler, + protocol::{LatexLspClient, LspCodec}, + server::LatexLspServer, + tex::Distribution, +}; +use tokio_util::codec::{FramedRead, FramedWrite}; #[tokio::main] async fn main() -> Result<(), Box> { - let _ = Distribution::detect().await; - let matches = app_from_crate!() .author("") .arg( @@ -32,5 +37,33 @@ async fn main() -> Result<(), Box> { .init() .expect("failed to initialize logger"); + let mut stdin = FramedRead::new(tokio::io::stdin(), LspCodec); + let (stdout_tx, mut stdout_rx) = mpsc::channel(0); + + let distro = Distribution::detect().await; + let client = Arc::new(LatexLspClient::new(stdout_tx.clone())); + let server = Arc::new(LatexLspServer::new( + Arc::new(distro), + Arc::clone(&client), + env::current_dir().expect("failed to get working directory"), + )); + let mut handler = MessageHandler { + server, + client, + output: stdout_tx, + }; + + tokio::spawn(async move { + let mut stdout = FramedWrite::new(tokio::io::stdout(), LspCodec); + loop { + let message = stdout_rx.next().await.unwrap(); + stdout.send(message).await.unwrap(); + } + }); + + while let Some(json) = stdin.next().await { + handler.handle(&json.unwrap()).await; + } + Ok(()) } diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 00000000..d1dbf2f0 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,301 @@ +use crate::{ + components::COMPONENT_DATABASE, + config::ConfigManager, + jsonrpc::{server::Result, Middleware}, + protocol::*, + tex::Distribution, + workspace::Workspace, +}; +use futures::lock::Mutex; +use futures_boxed::boxed; +use jsonrpc_derive::{jsonrpc_method, jsonrpc_server}; +use once_cell::sync::{Lazy, OnceCell}; +use std::{mem, path::PathBuf, sync::Arc}; + +pub struct LatexLspServer { + distro: Arc>, + client: Arc, + client_capabilities: OnceCell>, + config_manager: OnceCell>, + action_manager: ActionManager, + workspace: Workspace, +} + +#[jsonrpc_server] +impl LatexLspServer { + pub fn new( + distro: Arc>, + client: Arc, + current_dir: PathBuf, + ) -> Self { + let workspace = Workspace::new(Arc::clone(&distro), current_dir); + Self { + distro, + client, + client_capabilities: OnceCell::new(), + config_manager: OnceCell::new(), + action_manager: ActionManager::default(), + workspace, + } + } + + fn client_capabilities(&self) -> Arc { + Arc::clone( + self.client_capabilities + .get() + .expect("initialize has not been called"), + ) + } + + fn config_manager(&self) -> &ConfigManager { + self.config_manager + .get() + .expect("initialize has not been called") + } + + #[jsonrpc_method("initialize", kind = "request")] + pub async fn initialize(&self, params: InitializeParams) -> Result { + self.client_capabilities + .set(Arc::new(params.capabilities)) + .expect("initialize was called two times"); + + let _ = self.config_manager.set(ConfigManager::new( + Arc::clone(&self.client), + self.client_capabilities(), + )); + + let capabilities = ServerCapabilities { + text_document_sync: Some(TextDocumentSyncCapability::Options( + TextDocumentSyncOptions { + open_close: Some(true), + change: Some(TextDocumentSyncKind::Full), + will_save: None, + will_save_wait_until: None, + save: Some(SaveOptions { + include_text: Some(false), + }), + }, + )), + hover_provider: Some(true), + completion_provider: Some(CompletionOptions { + resolve_provider: Some(true), + trigger_characters: Some(vec![ + "\\".into(), + "{".into(), + "}".into(), + "@".into(), + "/".into(), + " ".into(), + ]), + }), + signature_help_provider: None, + definition_provider: Some(true), + type_definition_provider: None, + implementation_provider: None, + references_provider: Some(true), + document_highlight_provider: Some(true), + document_symbol_provider: Some(true), + workspace_symbol_provider: Some(true), + code_action_provider: None, + code_lens_provider: None, + document_formatting_provider: Some(true), + document_range_formatting_provider: None, + document_on_type_formatting_provider: None, + rename_provider: Some(RenameProviderCapability::Options(RenameOptions { + prepare_provider: Some(true), + })), + document_link_provider: Some(DocumentLinkOptions { + resolve_provider: Some(false), + }), + color_provider: None, + folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), + execute_command_provider: None, + workspace: None, + selection_range_provider: None, + }; + + Lazy::force(&COMPONENT_DATABASE); + Ok(InitializeResult { capabilities }) + } + + #[jsonrpc_method("initialized", kind = "notification")] + pub async fn initialized(&self, _params: InitializedParams) { + self.action_manager.push(Action::PullConfiguration).await; + self.action_manager.push(Action::RegisterCapabilities).await; + } + + #[jsonrpc_method("shutdown", kind = "request")] + pub async fn shutdown(&self, _params: ()) -> Result<()> { + Ok(()) + } + + #[jsonrpc_method("exit", kind = "notification")] + pub async fn exit(&self, _params: ()) {} + + #[jsonrpc_method("$/cancelRequest", kind = "notification")] + pub async fn cancel_request(&self, _params: CancelParams) {} + + #[jsonrpc_method("textDocument/didOpen", kind = "notification")] + pub async fn did_open(&self, params: DidOpenTextDocumentParams) {} + + #[jsonrpc_method("textDocument/didChange", kind = "notification")] + pub async fn did_change(&self, params: DidChangeTextDocumentParams) {} + + #[jsonrpc_method("textDocument/didSave", kind = "notification")] + pub async fn did_save(&self, params: DidSaveTextDocumentParams) {} + + #[jsonrpc_method("textDocument/didClose", kind = "notification")] + pub async fn did_close(&self, _params: DidCloseTextDocumentParams) {} + + #[jsonrpc_method("workspace/didChangeConfiguration", kind = "notification")] + pub async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { + self.config_manager().push(params.settings).await; + } + + #[jsonrpc_method("window/workDoneProgress/cancel", kind = "notification")] + pub async fn work_done_progress_cancel(&self, params: WorkDoneProgressCancelParams) {} + + #[jsonrpc_method("textDocument/completion", kind = "request")] + pub async fn completion(&self, params: CompletionParams) -> Result { + Ok(CompletionList { + is_incomplete: true, + items: Vec::new(), + }) + } + + #[jsonrpc_method("completionItem/resolve", kind = "request")] + pub async fn completion_resolve(&self, mut item: CompletionItem) -> Result { + Ok(item) + } + + #[jsonrpc_method("textDocument/hover", kind = "request")] + pub async fn hover(&self, params: TextDocumentPositionParams) -> Result> { + Ok(None) + } + + #[jsonrpc_method("textDocument/definition", kind = "request")] + pub async fn definition( + &self, + params: TextDocumentPositionParams, + ) -> Result { + Ok(DefinitionResponse::Locations(Vec::new())) + } + + #[jsonrpc_method("textDocument/references", kind = "request")] + pub async fn references(&self, params: ReferenceParams) -> Result> { + Ok(Vec::new()) + } + + #[jsonrpc_method("textDocument/documentHighlight", kind = "request")] + pub async fn document_highlight( + &self, + params: TextDocumentPositionParams, + ) -> Result> { + Ok(Vec::new()) + } + + #[jsonrpc_method("workspace/symbol", kind = "request")] + pub async fn workspace_symbol( + &self, + params: WorkspaceSymbolParams, + ) -> Result> { + Ok(Vec::new()) + } + + #[jsonrpc_method("textDocument/documentSymbol", kind = "request")] + pub async fn document_symbol( + &self, + params: DocumentSymbolParams, + ) -> Result { + Ok(DocumentSymbolResponse::Flat(Vec::new())) + } + + #[jsonrpc_method("textDocument/documentLink", kind = "request")] + pub async fn document_link(&self, params: DocumentLinkParams) -> Result> { + Ok(Vec::new()) + } + + #[jsonrpc_method("textDocument/formatting", kind = "request")] + pub async fn formatting(&self, params: DocumentFormattingParams) -> Result> { + Ok(Vec::new()) + } + + #[jsonrpc_method("textDocument/prepareRename", kind = "request")] + pub async fn prepare_rename( + &self, + params: TextDocumentPositionParams, + ) -> Result> { + Ok(None) + } + + #[jsonrpc_method("textDocument/rename", kind = "request")] + pub async fn rename(&self, params: RenameParams) -> Result> { + Ok(None) + } + + #[jsonrpc_method("textDocument/foldingRange", kind = "request")] + pub async fn folding_range(&self, params: FoldingRangeParams) -> Result> { + Ok(Vec::new()) + } + + #[jsonrpc_method("textDocument/build", kind = "request")] + pub async fn build(&self, params: BuildParams) -> Result { + Ok(BuildResult { + status: BuildStatus::Failure, + }) + } + + #[jsonrpc_method("textDocument/forwardSearch", kind = "request")] + pub async fn forward_search( + &self, + params: TextDocumentPositionParams, + ) -> Result { + Ok(ForwardSearchResult { + status: ForwardSearchStatus::Failure, + }) + } +} + +impl Middleware for LatexLspServer { + #[boxed] + async fn before_message(&self) {} + + #[boxed] + async fn after_message(&self) { + for action in self.action_manager.take().await { + match action { + Action::PullConfiguration => { + let config_manager = self.config_manager(); + if config_manager.pull().await { + let options = config_manager.get().await; + self.workspace.reparse(&options).await; + } + } + Action::RegisterCapabilities => self.config_manager().register().await, + }; + } + } +} + +#[derive(Debug, PartialEq, Clone)] +enum Action { + PullConfiguration, + RegisterCapabilities, +} + +#[derive(Debug, Default)] +struct ActionManager { + actions: Mutex>, +} + +impl ActionManager { + pub async fn push(&self, action: Action) { + let mut actions = self.actions.lock().await; + actions.push(action); + } + + pub async fn take(&self) -> Vec { + let mut actions = self.actions.lock().await; + mem::replace(&mut *actions, Vec::new()) + } +} diff --git a/src/syntax/latex/analysis.rs b/src/syntax/latex/analysis.rs index 8960397a..068f5be9 100644 --- a/src/syntax/latex/analysis.rs +++ b/src/syntax/latex/analysis.rs @@ -14,7 +14,7 @@ pub struct SymbolTableParams<'a> { pub uri: &'a Uri, pub resolver: &'a Resolver, pub options: &'a Options, - pub cwd: &'a Path, + pub current_dir: &'a Path, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -46,7 +46,7 @@ impl SymbolTable { uri, resolver, options, - cwd, + current_dir, } = params; let commands: Vec<_> = tree.commands().collect(); @@ -56,7 +56,7 @@ impl SymbolTable { uri, resolver, options, - cwd, + current_dir, }; let mut environments = None; @@ -134,7 +134,7 @@ pub struct SymbolContext<'a> { uri: &'a Uri, resolver: &'a Resolver, options: &'a Options, - cwd: &'a Path, + current_dir: &'a Path, } #[derive(Debug, Clone, Copy, Serialize, Deserialize)] @@ -298,7 +298,7 @@ impl Include { .and_then(|opts| opts.root_directory.as_ref()) { let file_name = ctx.uri.path_segments()?.last()?; - let path = ctx.cwd.join(root_directory).join(file_name); + let path = ctx.current_dir.join(root_directory).join(file_name); Uri::from_file_path(path).ok() } else { Some(ctx.uri.clone()) diff --git a/src/syntax/latex/mod.rs b/src/syntax/latex/mod.rs index 1a5c5ffc..299ef805 100644 --- a/src/syntax/latex/mod.rs +++ b/src/syntax/latex/mod.rs @@ -18,7 +18,7 @@ pub struct OpenParams<'a> { pub uri: &'a Uri, pub resolver: &'a Resolver, pub options: &'a Options, - pub cwd: &'a Path, + pub current_dir: &'a Path, } pub fn open(params: OpenParams) -> SymbolTable { @@ -27,7 +27,7 @@ pub fn open(params: OpenParams) -> SymbolTable { uri, resolver, options, - cwd, + current_dir, } = params; let lexer = Lexer::new(text); @@ -39,7 +39,7 @@ pub fn open(params: OpenParams) -> SymbolTable { uri, resolver, options, - cwd, + current_dir, }; SymbolTable::analyze(params) } @@ -62,7 +62,7 @@ mod tests { uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(), resolver: &Resolver::default(), options: &Options::default(), - cwd: &env::current_dir().unwrap(), + current_dir: &env::current_dir().unwrap(), }) } @@ -222,7 +222,7 @@ mod tests { uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(), resolver: &Resolver::default(), options: &Options::default(), - cwd: &env::current_dir().unwrap(), + current_dir: &env::current_dir().unwrap(), }); let actual_names: Vec<_> = table @@ -364,7 +364,7 @@ mod tests { uri: &Uri::parse("http://www.foo.com/dir1/dir2/foo.tex").unwrap(), resolver: &resolver, options: &Options::default(), - cwd: &env::current_dir().unwrap(), + current_dir: &env::current_dir().unwrap(), }); assert_eq!(table.includes.len(), 1); @@ -463,7 +463,7 @@ mod tests { uri: &Uri::parse("http://www.foo.com/bar.tex").unwrap(), resolver: &Resolver::default(), options: &Options::default(), - cwd: &env::current_dir().unwrap(), + current_dir: &env::current_dir().unwrap(), }); assert_eq!( table.components, diff --git a/src/workspace.rs b/src/workspace.rs index 658fb73b..4df1ff82 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -5,7 +5,7 @@ use crate::{ tex::{Distribution, Language, Resolver}, }; use futures::lock::Mutex; -use log::{error, warn}; +use log::{debug, error, warn}; use petgraph::{graph::Graph, visit::Dfs}; use serde::{Deserialize, Serialize}; use std::{ @@ -28,7 +28,7 @@ pub struct DocumentParams<'a> { language: Language, resolver: &'a Resolver, options: &'a Options, - cwd: &'a Path, + current_dir: &'a Path, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -57,7 +57,7 @@ impl Document { language, resolver, options, - cwd, + current_dir, } = params; let content = match language { @@ -67,7 +67,7 @@ impl Document { text: &text, resolver, options, - cwd, + current_dir, }); DocumentContent::Latex(Box::new(table)) } @@ -269,15 +269,15 @@ impl error::Error for WorkspaceLoadError { pub struct Workspace { distro: Arc>, - cwd: PathBuf, + current_dir: PathBuf, snapshot: Mutex>, } impl Workspace { - pub fn new(distro: Arc>, cwd: PathBuf) -> Self { + pub fn new(distro: Arc>, current_dir: PathBuf) -> Self { Self { distro, - cwd, + current_dir, snapshot: Mutex::default(), } } @@ -299,6 +299,7 @@ impl Workspace { } }; + debug!("Adding document: {}", document.uri); let mut snapshot = self.snapshot.lock().await; *snapshot = self .add_or_update( @@ -340,6 +341,7 @@ impl Workspace { } }; + debug!("Loading document: {}", uri); let mut snapshot = self.snapshot.lock().await; *snapshot = self .add_or_update(&snapshot, uri, text, language, options) @@ -363,11 +365,34 @@ impl Workspace { DocumentContent::Bibtex(_) => Language::Bibtex, }; + debug!("Updating document: {}", uri); *snapshot = self .add_or_update(&snapshot, uri, text, language, options) .await; } + pub async fn reparse(&self, options: &Options) { + let snapshot = self.get().await; + for doc in &snapshot.0 { + let language = match doc.content { + DocumentContent::Latex(_) => Language::Latex, + DocumentContent::Bibtex(_) => Language::Bibtex, + }; + + let mut snapshot = self.snapshot.lock().await; + debug!("Reparsing document: {}", doc.uri); + *snapshot = self + .add_or_update( + &snapshot, + doc.uri.clone(), + doc.text.clone(), + language, + options, + ) + .await; + } + } + async fn add_or_update( &self, snapshot: &Snapshot, @@ -383,7 +408,7 @@ impl Workspace { language, resolver: &resolver, options, - cwd: &self.cwd, + current_dir: &self.current_dir, }); let mut documents: Vec> = snapshot @@ -412,7 +437,7 @@ mod tests { language, resolver: &Resolver::default(), options: &Options::default(), - cwd: &env::current_dir().unwrap(), + current_dir: &env::current_dir().unwrap(), })) } @@ -586,7 +611,7 @@ mod tests { language: Language::Latex, resolver: &Resolver::default(), options: &options, - cwd: &cwd, + current_dir: &cwd, })), Arc::new(Document::open(DocumentParams { uri: uri2.clone(), @@ -594,7 +619,7 @@ mod tests { language: Language::Latex, resolver: &Resolver::default(), options: &options, - cwd: &cwd, + current_dir: &cwd, })), ]; let actual_uris: Vec<_> = snapshot @@ -627,7 +652,7 @@ mod tests { language: Language::Latex, resolver: &Resolver::default(), options: &options, - cwd: &cwd, + current_dir: &cwd, })), Arc::new(Document::open(DocumentParams { uri: uri2.clone(), @@ -635,7 +660,7 @@ mod tests { language: Language::Latex, resolver: &Resolver::default(), options: &options, - cwd: &cwd, + current_dir: &cwd, })), ]; let actual_uris: Vec<_> = snapshot