diff options
author | Norbert Preining <norbert@preining.info> | 2020-05-28 03:03:21 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2020-05-28 03:03:21 +0000 |
commit | 745892fbddea56040139108277e728b53fd8fc11 (patch) | |
tree | 15e55e299b38fccdabce8364f1fbf4f02ef37994 /support/texlab/src/test/mod.rs | |
parent | 195ca7e0c377d83455867bdd8e409d4e1cf024ea (diff) |
CTAN sync 202005280303
Diffstat (limited to 'support/texlab/src/test/mod.rs')
-rw-r--r-- | support/texlab/src/test/mod.rs | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/support/texlab/src/test/mod.rs b/support/texlab/src/test/mod.rs new file mode 100644 index 0000000000..5f26fb0dcc --- /dev/null +++ b/support/texlab/src/test/mod.rs @@ -0,0 +1,632 @@ +#![allow(deprecated)] + +mod client; +mod server; + +pub use self::{ + client::{TestLatexLspClient, TestLspClient}, + server::TestLatexLspServer, +}; + +use crate::{ + protocol::*, + server::LatexLspServer, + tex::{Distribution, UnknownDistribution}, +}; +use futures::{ + channel::mpsc, + future::{join, AbortHandle, Abortable}, + lock::Mutex, + prelude::*, +}; +use jsonrpc::MessageHandler; +use once_cell::sync::Lazy; +use std::{path::PathBuf, sync::Arc}; +use tempfile::{tempdir, TempDir}; +use tokio::fs; + +struct GlobalDistribution { + distro: Mutex<Option<Arc<dyn Distribution>>>, +} + +impl GlobalDistribution { + fn new() -> Self { + Self { + distro: Mutex::new(None), + } + } + + async fn get(&self, use_distro: bool) -> Arc<dyn Distribution> { + if use_distro { + let mut distro_lock = self.distro.lock().await; + match &*distro_lock { + Some(distro) => distro.clone(), + None => { + let distro = Distribution::detect().await; + *distro_lock = Some(distro.clone()); + distro + } + } + } else { + Arc::new(UnknownDistribution::default()) + } + } +} + +static DISTRO: Lazy<GlobalDistribution> = Lazy::new(GlobalDistribution::new); + +#[derive(Debug)] +struct Endpoint<S, C> { + receiver: mpsc::Receiver<String>, + client: Arc<C>, + server: Arc<S>, + handler: MessageHandler<S, C>, +} + +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct TestBedBuilder { + files: Vec<(PathBuf, String)>, + use_distro: bool, + root_dir: Option<PathBuf>, + latex_build: Option<LatexBuildOptions>, + latex_lint: Option<LatexLintOptions>, + latex_forward_search: Option<LatexForwardSearchOptions>, + bibtex_formatting: Option<BibtexFormattingOptions>, +} + +impl TestBedBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn file<P: Into<PathBuf>>(&mut self, path: P, text: &str) -> &mut Self { + self.files.push((path.into(), text.trim().into())); + self + } + + pub fn use_distro(&mut self) -> &mut Self { + self.use_distro = true; + self + } + + pub fn root_dir<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self { + self.root_dir = Some(path.into()); + self + } + + pub fn latex_build(&mut self, options: LatexBuildOptions) -> &mut Self { + self.latex_build = Some(options); + self + } + + pub fn latex_lint(&mut self, options: LatexLintOptions) -> &mut Self { + self.latex_lint = Some(options); + self + } + + pub fn latex_forward_search(&mut self, options: LatexForwardSearchOptions) -> &mut Self { + self.latex_forward_search = Some(options); + self + } + + pub fn bibtex_formatting(&mut self, options: BibtexFormattingOptions) -> &mut Self { + self.bibtex_formatting = Some(options); + self + } + + pub async fn build(&self) -> TestBed { + let dir = tempdir().expect("failed to create temporary directory"); + for (path, text) in &self.files { + let full_path = dir.path().join(path); + fs::create_dir_all(full_path.parent().unwrap()) + .await + .unwrap(); + fs::write(&full_path, text).await.unwrap(); + } + + let (tx1, rx1) = mpsc::channel(0); + let (tx2, rx2) = mpsc::channel(0); + + let endpoint1 = self.build_endpoint1(&dir, tx2, rx1).await; + let endpoint2 = self.build_endpoint2(&dir, tx1, rx2).await; + let server = Arc::clone(&endpoint2.server); + let client = Arc::clone(&endpoint2.client); + TestBed { + dir, + server, + client, + endpoint1: Some(endpoint1), + endpoint2: Some(endpoint2), + handle: None, + } + } + + async fn build_endpoint1( + &self, + dir: &TempDir, + tx2: mpsc::Sender<String>, + rx1: mpsc::Receiver<String>, + ) -> Endpoint<LatexLspServer<LatexLspClient>, LatexLspClient> { + let client = Arc::new(LatexLspClient::new(tx2.clone())); + let server = Arc::new(LatexLspServer::new( + DISTRO.get(self.use_distro).await, + Arc::clone(&client), + Arc::new(dir.path().to_path_buf()), + )); + + Endpoint { + receiver: rx1, + client: Arc::clone(&client), + server: Arc::clone(&server), + handler: MessageHandler { + client, + server, + output: tx2.clone(), + }, + } + } + + async fn build_endpoint2( + &self, + dir: &TempDir, + tx1: mpsc::Sender<String>, + rx2: mpsc::Receiver<String>, + ) -> Endpoint<TestLatexLspServer, TestLatexLspClient> { + let options = Options { + latex: Some(LatexOptions { + root_directory: self.root_dir.as_ref().map(|path| dir.path().join(path)), + build: self.latex_build.clone(), + forward_search: self.latex_forward_search.clone(), + lint: self.latex_lint.clone(), + }), + bibtex: Some(BibtexOptions { + formatting: self.bibtex_formatting.clone(), + }), + }; + + let test_client = Arc::new(TestLatexLspClient::new(tx1.clone())); + let test_server = Arc::new(TestLatexLspServer::new(options)); + + Endpoint { + receiver: rx2, + client: Arc::clone(&test_client), + server: Arc::clone(&test_server), + handler: MessageHandler { + client: test_client, + server: test_server, + output: tx1, + }, + } + } +} + +pub struct TestBed { + pub dir: TempDir, + pub server: Arc<TestLatexLspServer>, + pub client: Arc<TestLatexLspClient>, + endpoint1: Option<Endpoint<LatexLspServer<LatexLspClient>, LatexLspClient>>, + endpoint2: Option<Endpoint<TestLatexLspServer, TestLatexLspClient>>, + handle: Option<AbortHandle>, +} + +impl TestBed { + pub fn spawn(&mut self) { + let (handle, reg) = AbortHandle::new_pair(); + + let endpoint1 = self.endpoint1.take().unwrap(); + let endpoint2 = self.endpoint2.take().unwrap(); + let mut rx1 = endpoint1.receiver; + let mut rx2 = endpoint2.receiver; + let mut handler1 = endpoint1.handler; + let mut handler2 = endpoint2.handler; + + tokio::spawn(Abortable::new( + async move { + let task1 = async move { + while let Some(json) = rx2.next().await { + handler2.handle(&json).await; + } + }; + + let task2 = async move { + while let Some(json) = rx1.next().await { + handler1.handle(&json).await; + } + }; + + join(task1, task2).await; + }, + reg, + )); + self.handle = Some(handle); + } + + pub async fn initialize(&self, capabilities: ClientCapabilities) { + self.client + .initialize(InitializeParams { + capabilities, + initialization_options: None, + process_id: None, + root_path: None, + root_uri: None, + trace: None, + workspace_folders: None, + client_info: None, + }) + .await + .unwrap(); + self.client.initialized(InitializedParams {}).await; + } + + pub fn path(&self, relative_path: &str) -> PathBuf { + self.dir.path().join(relative_path) + } + + pub fn uri(&self, relative_path: &str) -> Uri { + Uri::from_file_path(self.path(relative_path)).unwrap() + } + + pub fn identifier(&self, relative_path: &str) -> TextDocumentIdentifier { + TextDocumentIdentifier::new(self.uri(relative_path).into()) + } + + pub async fn open(&self, relative_path: &str) { + let full_path = self.path(relative_path); + let params = DidOpenTextDocumentParams { + text_document: TextDocumentItem { + language_id: if relative_path.ends_with("bib") { + "bibtex".into() + } else { + "latex".into() + }, + uri: Uri::from_file_path(&full_path).unwrap().into(), + version: 0, + text: fs::read_to_string(&full_path).await.unwrap(), + }, + }; + self.client.did_open(params).await; + } + + pub async fn edit<S: Into<String>>(&self, relative_path: &str, text: S) { + let uri = self.uri(relative_path).into(); + let params = DidChangeTextDocumentParams { + text_document: VersionedTextDocumentIdentifier::new(uri, 0), + content_changes: vec![TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: text.into(), + }], + }; + self.client.did_change(params).await; + } + + pub async fn push_options(&self) { + let options = self.server.options.lock().await.clone(); + let params = DidChangeConfigurationParams { + settings: serde_json::to_value::<Options>(options).unwrap(), + }; + self.client.did_change_configuration(params).await + } + + pub async fn definition_link( + &self, + relative_path: &str, + line: u64, + character: u64, + ) -> Option<Vec<LocationLink>> { + let params = TextDocumentPositionParams { + text_document: self.identifier(relative_path), + position: Position::new(line, character), + }; + let response = self.client.definition(params).await.ok()?; + match response { + DefinitionResponse::LocationLinks(links) => Some(links), + DefinitionResponse::Locations(_) => unreachable!(), + } + } + + pub async fn definition_location( + &self, + relative_path: &str, + line: u64, + character: u64, + ) -> Option<Vec<Location>> { + let params = TextDocumentPositionParams { + text_document: self.identifier(relative_path), + position: Position::new(line, character), + }; + let response = self.client.definition(params).await.ok()?; + match response { + DefinitionResponse::LocationLinks(_) => unreachable!(), + DefinitionResponse::Locations(locations) => Some(locations), + } + } + + pub async fn completion( + &self, + relative_path: &str, + line: u64, + character: u64, + ) -> Option<Vec<CompletionItem>> { + let params = CompletionParams { + text_document_position: TextDocumentPositionParams { + text_document: self.identifier(relative_path), + position: Position::new(line, character), + }, + context: None, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + self.client + .completion(params) + .await + .ok() + .map(|list| list.items) + } + + pub async fn folding_range(&self, relative_path: &str) -> Option<Vec<FoldingRange>> { + let params = FoldingRangeParams { + text_document: self.identifier(relative_path), + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + self.client.folding_range(params).await.ok() + } + + pub async fn document_highlight( + &self, + relative_path: &str, + line: u64, + character: u64, + ) -> Option<Vec<DocumentHighlight>> { + let params = TextDocumentPositionParams { + text_document: self.identifier(relative_path), + position: Position::new(line, character), + }; + self.client.document_highlight(params).await.ok() + } + + pub async fn document_link(&self, relative_path: &str) -> Option<Vec<DocumentLink>> { + let params = DocumentLinkParams { + text_document: self.identifier(relative_path), + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + self.client.document_link(params).await.ok() + } + + pub async fn references( + &self, + relative_path: &str, + line: u64, + character: u64, + include_declaration: bool, + ) -> Option<Vec<Location>> { + let params = ReferenceParams { + text_document_position: TextDocumentPositionParams::new( + self.identifier(relative_path), + Position::new(line, character), + ), + context: ReferenceContext { + include_declaration, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + self.client.references(params).await.ok() + } + + pub async fn prepare_rename( + &self, + relative_path: &str, + line: u64, + character: u64, + ) -> Option<Option<Range>> { + let pos = Position::new(line, character); + let params = TextDocumentPositionParams::new(self.identifier(relative_path), pos); + self.client.prepare_rename(params).await.ok() + } + + pub async fn rename<S: Into<String>>( + &self, + relative_path: &str, + line: u64, + character: u64, + new_name: S, + ) -> Option<Option<WorkspaceEdit>> { + let params = RenameParams { + text_document_position: TextDocumentPositionParams::new( + self.identifier(relative_path), + Position::new(line, character), + ), + new_name: new_name.into(), + work_done_progress_params: WorkDoneProgressParams::default(), + }; + self.client.rename(params).await.ok() + } + + pub async fn document_symbol_flat( + &self, + relative_path: &str, + ) -> Option<Vec<SymbolInformation>> { + let params = DocumentSymbolParams { + text_document: self.identifier(relative_path), + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + match self.client.document_symbol(params).await.ok()? { + DocumentSymbolResponse::Flat(symbols) => Some(symbols), + DocumentSymbolResponse::Nested(_) => unreachable!(), + } + } + + pub async fn document_symbol_nested(&self, relative_path: &str) -> Option<Vec<DocumentSymbol>> { + let params = DocumentSymbolParams { + text_document: self.identifier(relative_path), + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + match self.client.document_symbol(params).await.ok()? { + DocumentSymbolResponse::Flat(_) => unreachable!(), + DocumentSymbolResponse::Nested(symbols) => Some(symbols), + } + } + + pub async fn hover( + &self, + relative_path: &str, + line: u64, + character: u64, + ) -> Option<Option<Hover>> { + let params = TextDocumentPositionParams { + text_document: self.identifier(relative_path), + position: Position::new(line, character), + }; + self.client.hover(params).await.ok() + } + + pub async fn detect_root(&self, relative_path: &str) { + self.client + .detect_root(self.identifier(relative_path)) + .await + .unwrap(); + } + + pub async fn shutdown(&self) { + self.client.shutdown(()).await.unwrap(); + self.client.exit(()).await; + } +} + +impl Drop for TestBed { + fn drop(&mut self) { + if let Some(handle) = self.handle.as_ref() { + handle.abort(); + } + } +} + +pub static PULL_CAPABILITIES: ClientCapabilities = { + ClientCapabilities { + experimental: None, + text_document: None, + window: None, + workspace: Some(WorkspaceClientCapabilities { + apply_edit: None, + configuration: Some(true), + did_change_configuration: None, + did_change_watched_files: None, + execute_command: None, + symbol: None, + workspace_edit: None, + workspace_folders: None, + }), + } +}; + +pub static PUSH_CAPABILITIES: ClientCapabilities = { + ClientCapabilities { + experimental: None, + text_document: None, + window: None, + workspace: Some(WorkspaceClientCapabilities { + apply_edit: None, + configuration: None, + did_change_configuration: Some(GenericCapability { + dynamic_registration: Some(true), + }), + did_change_watched_files: None, + execute_command: None, + symbol: None, + workspace_edit: None, + workspace_folders: None, + }), + } +}; + +pub static NESTED_SYMBOL_CAPABILITIES: ClientCapabilities = { + ClientCapabilities { + experimental: None, + text_document: Some(TextDocumentClientCapabilities { + code_action: None, + code_lens: None, + color_provider: None, + completion: None, + declaration: None, + definition: None, + document_highlight: None, + document_link: None, + document_symbol: Some(DocumentSymbolCapability { + hierarchical_document_symbol_support: Some(true), + dynamic_registration: None, + symbol_kind: None, + }), + folding_range: None, + formatting: None, + hover: None, + implementation: None, + on_type_formatting: None, + publish_diagnostics: None, + range_formatting: None, + references: None, + rename: None, + signature_help: None, + synchronization: None, + type_definition: None, + }), + window: None, + workspace: Some(WorkspaceClientCapabilities { + apply_edit: None, + configuration: Some(true), + did_change_configuration: None, + did_change_watched_files: None, + execute_command: None, + symbol: None, + workspace_edit: None, + workspace_folders: None, + }), + } +}; + +pub static LOCATION_LINK_CAPABILITIES: ClientCapabilities = { + ClientCapabilities { + experimental: None, + text_document: Some(TextDocumentClientCapabilities { + code_action: None, + code_lens: None, + color_provider: None, + completion: None, + declaration: None, + definition: Some(GotoCapability { + dynamic_registration: None, + link_support: Some(true), + }), + document_highlight: None, + document_link: None, + document_symbol: None, + folding_range: None, + formatting: None, + hover: None, + implementation: None, + on_type_formatting: None, + publish_diagnostics: None, + range_formatting: None, + references: None, + rename: None, + signature_help: None, + synchronization: None, + type_definition: None, + }), + window: None, + workspace: Some(WorkspaceClientCapabilities { + apply_edit: None, + configuration: Some(true), + did_change_configuration: None, + did_change_watched_files: None, + execute_command: None, + symbol: None, + workspace_edit: None, + workspace_folders: None, + }), + } +}; |