diff options
Diffstat (limited to 'support/texlab/src/feature.rs')
-rw-r--r-- | support/texlab/src/feature.rs | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/support/texlab/src/feature.rs b/support/texlab/src/feature.rs new file mode 100644 index 0000000000..2b32b64188 --- /dev/null +++ b/support/texlab/src/feature.rs @@ -0,0 +1,405 @@ +use crate::{ + components::{Component, COMPONENT_DATABASE}, + protocol::*, + tex::{Distribution, Language, UnknownDistribution}, + workspace::{Document, DocumentContent, DocumentParams, Snapshot}, +}; +use async_trait::async_trait; +use itertools::Itertools; +use std::{ + env, + path::{Path, PathBuf}, + sync::Arc, +}; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct DocumentView { + pub snapshot: Arc<Snapshot>, + pub current: Arc<Document>, + pub related: Vec<Arc<Document>>, +} + +impl DocumentView { + pub fn analyze( + snapshot: Arc<Snapshot>, + current: Arc<Document>, + options: &Options, + current_dir: &Path, + ) -> Self { + let related = snapshot.relations(¤t.uri, options, current_dir); + Self { + snapshot, + current, + related, + } + } + + pub fn components(&self) -> Vec<&'static Component> { + let mut start_components = vec![COMPONENT_DATABASE.kernel()]; + for doc in &self.related { + if let DocumentContent::Latex(table) = &doc.content { + table + .components + .iter() + .flat_map(|file| COMPONENT_DATABASE.find(file)) + .for_each(|component| start_components.push(component)) + } + } + + let mut all_components = Vec::new(); + for component in start_components { + all_components.push(component); + component + .references + .iter() + .flat_map(|file| COMPONENT_DATABASE.find(&file)) + .for_each(|component| all_components.push(component)) + } + + all_components + .into_iter() + .unique_by(|component| &component.file_names) + .collect() + } +} + +#[derive(Clone)] +pub struct FeatureRequest<P> { + pub params: P, + pub view: DocumentView, + pub distro: Arc<dyn Distribution>, + pub client_capabilities: Arc<ClientCapabilities>, + pub options: Options, + pub current_dir: Arc<PathBuf>, +} + +impl<P> FeatureRequest<P> { + pub fn snapshot(&self) -> &Snapshot { + &self.view.snapshot + } + + pub fn current(&self) -> &Document { + &self.view.current + } + + pub fn related(&self) -> &[Arc<Document>] { + &self.view.related + } +} + +#[async_trait] +pub trait FeatureProvider { + type Params; + type Output; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output; +} + +type ListProvider<P, O> = Box<dyn FeatureProvider<Params = P, Output = Vec<O>> + Send + Sync>; + +#[derive(Default)] +pub struct ConcatProvider<P, O> { + providers: Vec<ListProvider<P, O>>, +} + +impl<P, O> ConcatProvider<P, O> { + pub fn new(providers: Vec<ListProvider<P, O>>) -> Self { + Self { providers } + } +} + +#[async_trait] +impl<P, O> FeatureProvider for ConcatProvider<P, O> +where + P: Send + Sync, + O: Send + Sync, +{ + type Params = P; + type Output = Vec<O>; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<P>) -> Vec<O> { + let mut items = Vec::new(); + for provider in &self.providers { + items.append(&mut provider.execute(req).await); + } + items + } +} + +type OptionProvider<P, O> = Box<dyn FeatureProvider<Params = P, Output = Option<O>> + Send + Sync>; + +#[derive(Default)] +pub struct ChoiceProvider<P, O> { + providers: Vec<OptionProvider<P, O>>, +} + +impl<P, O> ChoiceProvider<P, O> { + pub fn new(providers: Vec<OptionProvider<P, O>>) -> Self { + Self { providers } + } +} + +#[async_trait] +impl<P, O> FeatureProvider for ChoiceProvider<P, O> +where + P: Send + Sync, + O: Send + Sync, +{ + type Params = P; + type Output = Option<O>; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<P>) -> Option<O> { + for provider in &self.providers { + let item = provider.execute(req).await; + if item.is_some() { + return item; + } + } + None + } +} + +#[derive(Debug)] +pub struct FeatureTester { + main: String, + files: Vec<(String, String)>, + distro: Arc<dyn Distribution>, + position: Position, + new_name: String, + include_declaration: bool, + client_capabilities: Arc<ClientCapabilities>, + current_dir: Arc<PathBuf>, + root_dir: Option<PathBuf>, + output_dir: Option<PathBuf>, +} + +impl Default for FeatureTester { + fn default() -> Self { + Self::new() + } +} + +impl FeatureTester { + pub fn new() -> Self { + Self { + main: String::new(), + files: Vec::new(), + distro: Arc::new(UnknownDistribution::default()), + position: Position::default(), + new_name: String::new(), + include_declaration: false, + client_capabilities: Arc::default(), + current_dir: Arc::new(env::temp_dir()), + root_dir: None, + output_dir: None, + } + } + + pub fn main<S: Into<String>>(&mut self, name: S) -> &mut Self { + self.main = name.into(); + self + } + + pub fn file<S, T>(&mut self, name: S, text: T) -> &mut Self + where + S: Into<String>, + T: Into<String>, + { + self.files.push((name.into(), text.into())); + self + } + + pub fn position(&mut self, line: u64, character: u64) -> &mut Self { + self.position = Position::new(line, character); + self + } + + pub fn new_name<S: Into<String>>(&mut self, value: S) -> &mut Self { + self.new_name = value.into(); + self + } + + pub fn include_declaration(&mut self) -> &mut Self { + self.include_declaration = true; + self + } + + pub fn root_directory<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self { + self.root_dir = Some(path.into()); + self + } + + pub fn output_directory<P: Into<PathBuf>>(&mut self, path: P) -> &mut Self { + self.output_dir = Some(path.into()); + self + } + + pub fn uri(name: &str) -> Uri { + let path = env::temp_dir().join(name); + Uri::from_file_path(path).unwrap() + } + + fn identifier(&self) -> TextDocumentIdentifier { + let uri = Self::uri(&self.main); + TextDocumentIdentifier::new(uri.into()) + } + + fn options(&self) -> Options { + Options { + latex: Some(LatexOptions { + build: Some(LatexBuildOptions { + output_directory: self.output_dir.clone(), + ..LatexBuildOptions::default() + }), + root_directory: self.root_dir.clone(), + ..LatexOptions::default() + }), + ..Options::default() + } + } + + async fn view(&self) -> DocumentView { + let mut snapshot = Snapshot::new(); + let resolver = self.distro.resolver().await; + let options = self.options(); + for (name, text) in &self.files { + let uri = Self::uri(name); + let path = uri.to_file_path().unwrap(); + let language = path + .extension() + .and_then(|ext| ext.to_str()) + .and_then(Language::by_extension) + .unwrap(); + let doc = Document::open(DocumentParams { + uri, + text: text.trim().into(), + language, + resolver: &resolver, + options: &options, + current_dir: &self.current_dir, + }); + snapshot.push(doc); + } + let current = snapshot.find(&Self::uri(&self.main)).unwrap(); + DocumentView::analyze(Arc::new(snapshot), current, &options, &self.current_dir) + } + + async fn request<P>(&self, params: P) -> FeatureRequest<P> { + FeatureRequest { + params, + view: self.view().await, + client_capabilities: Arc::clone(&self.client_capabilities), + distro: self.distro.clone(), + options: self.options(), + current_dir: Arc::clone(&self.current_dir), + } + } + + pub async fn test_position<F, O>(&self, provider: F) -> O + where + F: FeatureProvider<Params = TextDocumentPositionParams, Output = O>, + { + let text_document = self.identifier(); + let params = TextDocumentPositionParams::new(text_document, self.position); + let req = self.request(params).await; + provider.execute(&req).await + } + + pub async fn test_completion<F, O>(&self, provider: F) -> O + where + F: FeatureProvider<Params = CompletionParams, Output = O>, + { + let req = self.test_completion_request().await; + provider.execute(&req).await + } + + pub async fn test_completion_request(&self) -> FeatureRequest<CompletionParams> { + let params = CompletionParams { + text_document_position: TextDocumentPositionParams::new( + self.identifier(), + self.position, + ), + context: None, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + self.request(params).await + } + + pub async fn test_folding<F, O>(&self, provider: F) -> O + where + F: FeatureProvider<Params = FoldingRangeParams, Output = O>, + { + let text_document = self.identifier(); + let params = FoldingRangeParams { + text_document, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + let req = self.request(params).await; + provider.execute(&req).await + } + + pub async fn test_link<F, O>(&self, provider: F) -> O + where + F: FeatureProvider<Params = DocumentLinkParams, Output = O>, + { + let text_document = self.identifier(); + let params = DocumentLinkParams { + text_document, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + let req = self.request(params).await; + provider.execute(&req).await + } + + pub async fn test_reference<F, O>(&self, provider: F) -> O + where + F: FeatureProvider<Params = ReferenceParams, Output = O>, + { + let params = ReferenceParams { + text_document_position: TextDocumentPositionParams::new( + self.identifier(), + self.position, + ), + context: ReferenceContext { + include_declaration: self.include_declaration, + }, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + let req = self.request(params).await; + provider.execute(&req).await + } + + pub async fn test_rename<F, O>(&self, provider: F) -> O + where + F: FeatureProvider<Params = RenameParams, Output = O>, + { + let params = RenameParams { + text_document_position: TextDocumentPositionParams::new( + self.identifier(), + self.position, + ), + new_name: self.new_name.clone(), + work_done_progress_params: WorkDoneProgressParams::default(), + }; + let req = self.request(params).await; + provider.execute(&req).await + } + + pub async fn test_symbol<F, O>(&self, provider: F) -> O + where + F: FeatureProvider<Params = DocumentSymbolParams, Output = O>, + { + let text_document = self.identifier(); + let params = DocumentSymbolParams { + text_document, + work_done_progress_params: WorkDoneProgressParams::default(), + partial_result_params: PartialResultParams::default(), + }; + let req = self.request(params).await; + provider.execute(&req).await + } +} |