Refactor configuration management module

This commit is contained in:
Patrick Förster 2020-03-06 19:44:56 +01:00
parent 09e6050f37
commit 36ccaf8ab3
9 changed files with 495 additions and 31 deletions

View file

@ -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"

View file

@ -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)));

103
src/config.rs Normal file
View file

@ -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<C> {
client: Arc<C>,
client_capabilities: Arc<ClientCapabilities>,
options: Mutex<Options>,
}
impl<C: LspClient + Send + Sync + 'static> ConfigManager<C> {
pub fn new(client: Arc<C>, client_capabilities: Arc<ClientCapabilities>) -> 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<T: DeserializeOwned + Default>(&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::<Vec<T>>(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()
}
}
}
}

View file

@ -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;

View file

@ -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<dyn error::Error>> {
let _ = Distribution::detect().await;
let matches = app_from_crate!()
.author("")
.arg(
@ -32,5 +37,33 @@ async fn main() -> Result<(), Box<dyn error::Error>> {
.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(())
}

301
src/server.rs Normal file
View file

@ -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<C> {
distro: Arc<Box<dyn Distribution + Send + Sync>>,
client: Arc<C>,
client_capabilities: OnceCell<Arc<ClientCapabilities>>,
config_manager: OnceCell<ConfigManager<C>>,
action_manager: ActionManager,
workspace: Workspace,
}
#[jsonrpc_server]
impl<C: LspClient + Send + Sync + 'static> LatexLspServer<C> {
pub fn new(
distro: Arc<Box<dyn Distribution + Send + Sync>>,
client: Arc<C>,
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<ClientCapabilities> {
Arc::clone(
self.client_capabilities
.get()
.expect("initialize has not been called"),
)
}
fn config_manager(&self) -> &ConfigManager<C> {
self.config_manager
.get()
.expect("initialize has not been called")
}
#[jsonrpc_method("initialize", kind = "request")]
pub async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
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<CompletionList> {
Ok(CompletionList {
is_incomplete: true,
items: Vec::new(),
})
}
#[jsonrpc_method("completionItem/resolve", kind = "request")]
pub async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
Ok(item)
}
#[jsonrpc_method("textDocument/hover", kind = "request")]
pub async fn hover(&self, params: TextDocumentPositionParams) -> Result<Option<Hover>> {
Ok(None)
}
#[jsonrpc_method("textDocument/definition", kind = "request")]
pub async fn definition(
&self,
params: TextDocumentPositionParams,
) -> Result<DefinitionResponse> {
Ok(DefinitionResponse::Locations(Vec::new()))
}
#[jsonrpc_method("textDocument/references", kind = "request")]
pub async fn references(&self, params: ReferenceParams) -> Result<Vec<Location>> {
Ok(Vec::new())
}
#[jsonrpc_method("textDocument/documentHighlight", kind = "request")]
pub async fn document_highlight(
&self,
params: TextDocumentPositionParams,
) -> Result<Vec<DocumentHighlight>> {
Ok(Vec::new())
}
#[jsonrpc_method("workspace/symbol", kind = "request")]
pub async fn workspace_symbol(
&self,
params: WorkspaceSymbolParams,
) -> Result<Vec<SymbolInformation>> {
Ok(Vec::new())
}
#[jsonrpc_method("textDocument/documentSymbol", kind = "request")]
pub async fn document_symbol(
&self,
params: DocumentSymbolParams,
) -> Result<DocumentSymbolResponse> {
Ok(DocumentSymbolResponse::Flat(Vec::new()))
}
#[jsonrpc_method("textDocument/documentLink", kind = "request")]
pub async fn document_link(&self, params: DocumentLinkParams) -> Result<Vec<DocumentLink>> {
Ok(Vec::new())
}
#[jsonrpc_method("textDocument/formatting", kind = "request")]
pub async fn formatting(&self, params: DocumentFormattingParams) -> Result<Vec<TextEdit>> {
Ok(Vec::new())
}
#[jsonrpc_method("textDocument/prepareRename", kind = "request")]
pub async fn prepare_rename(
&self,
params: TextDocumentPositionParams,
) -> Result<Option<Range>> {
Ok(None)
}
#[jsonrpc_method("textDocument/rename", kind = "request")]
pub async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
Ok(None)
}
#[jsonrpc_method("textDocument/foldingRange", kind = "request")]
pub async fn folding_range(&self, params: FoldingRangeParams) -> Result<Vec<FoldingRange>> {
Ok(Vec::new())
}
#[jsonrpc_method("textDocument/build", kind = "request")]
pub async fn build(&self, params: BuildParams) -> Result<BuildResult> {
Ok(BuildResult {
status: BuildStatus::Failure,
})
}
#[jsonrpc_method("textDocument/forwardSearch", kind = "request")]
pub async fn forward_search(
&self,
params: TextDocumentPositionParams,
) -> Result<ForwardSearchResult> {
Ok(ForwardSearchResult {
status: ForwardSearchStatus::Failure,
})
}
}
impl<C: LspClient + Send + Sync + 'static> Middleware for LatexLspServer<C> {
#[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<Vec<Action>>,
}
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<Action> {
let mut actions = self.actions.lock().await;
mem::replace(&mut *actions, Vec::new())
}
}

View file

@ -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())

View file

@ -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,

View file

@ -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<Box<dyn Distribution + Send + Sync>>,
cwd: PathBuf,
current_dir: PathBuf,
snapshot: Mutex<Arc<Snapshot>>,
}
impl Workspace {
pub fn new(distro: Arc<Box<dyn Distribution + Send + Sync>>, cwd: PathBuf) -> Self {
pub fn new(distro: Arc<Box<dyn Distribution + Send + Sync>>, 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<Arc<Document>> = 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