diff options
Diffstat (limited to 'support/texlab/src/rename')
-rw-r--r-- | support/texlab/src/rename/bibtex_entry.rs | 272 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_cmd.rs | 128 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_command.rs | 114 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_env.rs | 157 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_environment.rs | 137 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_label.rs | 181 | ||||
-rw-r--r-- | support/texlab/src/rename/mod.rs | 30 |
7 files changed, 545 insertions, 474 deletions
diff --git a/support/texlab/src/rename/bibtex_entry.rs b/support/texlab/src/rename/bibtex_entry.rs index 46484c7f23..97eaa8fd9d 100644 --- a/support/texlab/src/rename/bibtex_entry.rs +++ b/support/texlab/src/rename/bibtex_entry.rs @@ -1,172 +1,192 @@ -use crate::range::RangeExt; -use crate::syntax::*; -use crate::workspace::*; -use futures_boxed::boxed; -use lsp_types::*; +use crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{ + Position, Range, RangeExt, RenameParams, TextDocumentPositionParams, TextEdit, + WorkspaceEdit, + }, + syntax::{Span, SyntaxNode}, + workspace::DocumentContent, +}; +use async_trait::async_trait; use std::collections::HashMap; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct BibtexEntryPrepareRenameProvider; +#[async_trait] impl FeatureProvider for BibtexEntryPrepareRenameProvider { type Params = TextDocumentPositionParams; type Output = Option<Range>; - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<TextDocumentPositionParams>, - ) -> Option<Range> { - find_key(&request.document().tree, request.params.position).map(Span::range) + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + find_key(&req.current().content, req.params.position).map(Span::range) } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct BibtexEntryRenameProvider; +#[async_trait] impl FeatureProvider for BibtexEntryRenameProvider { type Params = RenameParams; type Output = Option<WorkspaceEdit>; - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<RenameParams>, - ) -> Option<WorkspaceEdit> { + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { let key_name = find_key( - &request.document().tree, - request.params.text_document_position.position, + &req.current().content, + req.params.text_document_position.position, )?; let mut changes = HashMap::new(); - for document in request.related_documents() { - let mut edits = Vec::new(); - match &document.tree { - SyntaxTree::Latex(tree) => { - tree.citations - .iter() - .flat_map(LatexCitation::keys) - .filter(|citation| citation.text() == key_name.text) - .map(|citation| { - TextEdit::new(citation.range(), request.params.new_name.clone()) - }) - .for_each(|edit| edits.push(edit)); - } - SyntaxTree::Bibtex(tree) => { - for entry in tree.entries() { - if let Some(key) = &entry.key { - if key.text() == key_name.text { - edits.push(TextEdit::new( - key.range(), - request.params.new_name.clone(), - )); - } - } - } - } + for doc in req.related() { + let edits = match &doc.content { + DocumentContent::Latex(table) => table + .citations + .iter() + .flat_map(|citation| citation.keys(&table)) + .filter(|citation| citation.text() == key_name.text) + .map(|citation| TextEdit::new(citation.range(), req.params.new_name.clone())) + .collect(), + DocumentContent::Bibtex(tree) => tree + .children(tree.root) + .filter_map(|node| tree.as_entry(node)) + .filter_map(|entry| entry.key.as_ref()) + .filter(|entry_key| entry_key.text() == key_name.text) + .map(|entry_key| TextEdit::new(entry_key.range(), req.params.new_name.clone())) + .collect(), }; - changes.insert(document.uri.clone().into(), edits); + changes.insert(doc.uri.clone().into(), edits); } Some(WorkspaceEdit::new(changes)) } } -fn find_key(tree: &SyntaxTree, position: Position) -> Option<&Span> { - match tree { - SyntaxTree::Latex(tree) => { - for citation in &tree.citations { - let keys = citation.keys(); - for key in keys { - if key.range().contains(position) { - return Some(&key.span); - } - } - } - None - } - SyntaxTree::Bibtex(tree) => { - for entry in tree.entries() { - if let Some(key) = &entry.key { - if key.range().contains(position) { - return Some(&key.span); - } - } - } - None - } +fn find_key(content: &DocumentContent, pos: Position) -> Option<&Span> { + match content { + DocumentContent::Latex(table) => table + .citations + .iter() + .flat_map(|citation| citation.keys(&table)) + .find(|key| key.range().contains(pos)) + .map(|key| &key.span), + DocumentContent::Bibtex(tree) => tree + .children(tree.root) + .filter_map(|node| tree.as_entry(node)) + .filter_map(|entry| entry.key.as_ref()) + .find(|key| key.range().contains(pos)) + .map(|key| &key.span), } } #[cfg(test)] mod tests { use super::*; - use lsp_types::Position; - - #[test] - fn test_entry() { - let edit = test_feature( - BibtexEntryRenameProvider, - FeatureSpec { - files: vec![ - FeatureSpec::file("foo.bib", "@article{foo, bar = baz}"), - FeatureSpec::file("bar.tex", "\\addbibresource{foo.bib}\n\\cite{foo}"), - ], - main_file: "foo.bib", - position: Position::new(0, 9), - new_name: "qux", - ..FeatureSpec::default() - }, - ); - let mut changes = HashMap::new(); - changes.insert( - FeatureSpec::uri("foo.bib"), + use crate::{feature::FeatureTester, protocol::RangeExt}; + use indoc::indoc; + + #[tokio::test] + async fn entry() { + let actual_edit = FeatureTester::new() + .file("main.bib", r#"@article{foo, bar = baz}"#) + .file( + "main.tex", + indoc!( + r#" + \addbibresource{main.bib} + \cite{foo} + "# + ), + ) + .main("main.bib") + .position(0, 9) + .new_name("qux") + .test_rename(BibtexEntryRenameProvider) + .await + .unwrap(); + + let mut expected_changes = HashMap::new(); + expected_changes.insert( + FeatureTester::uri("main.bib").into(), vec![TextEdit::new(Range::new_simple(0, 9, 0, 12), "qux".into())], ); - changes.insert( - FeatureSpec::uri("bar.tex"), + expected_changes.insert( + FeatureTester::uri("main.tex").into(), vec![TextEdit::new(Range::new_simple(1, 6, 1, 9), "qux".into())], ); - assert_eq!(edit, Some(WorkspaceEdit::new(changes))); + let expected_edit = WorkspaceEdit::new(expected_changes); + + assert_eq!(actual_edit, expected_edit); } - #[test] - fn test_citation() { - let edit = test_feature( - BibtexEntryRenameProvider, - FeatureSpec { - files: vec![ - FeatureSpec::file("foo.bib", "@article{foo, bar = baz}"), - FeatureSpec::file("bar.tex", "\\addbibresource{foo.bib}\n\\cite{foo}"), - ], - main_file: "bar.tex", - position: Position::new(1, 6), - new_name: "qux", - ..FeatureSpec::default() - }, - ); - let mut changes = HashMap::new(); - changes.insert( - FeatureSpec::uri("foo.bib"), + #[tokio::test] + async fn citation() { + let actual_edit = FeatureTester::new() + .file("main.bib", r#"@article{foo, bar = baz}"#) + .file( + "main.tex", + indoc!( + r#" + \addbibresource{main.bib} + \cite{foo} + "# + ), + ) + .main("main.tex") + .position(1, 6) + .new_name("qux") + .test_rename(BibtexEntryRenameProvider) + .await + .unwrap(); + + let mut expected_changes = HashMap::new(); + expected_changes.insert( + FeatureTester::uri("main.bib").into(), vec![TextEdit::new(Range::new_simple(0, 9, 0, 12), "qux".into())], ); - changes.insert( - FeatureSpec::uri("bar.tex"), + expected_changes.insert( + FeatureTester::uri("main.tex").into(), vec![TextEdit::new(Range::new_simple(1, 6, 1, 9), "qux".into())], ); - assert_eq!(edit, Some(WorkspaceEdit::new(changes))); + let expected_edit = WorkspaceEdit::new(expected_changes); + + assert_eq!(actual_edit, expected_edit); } - #[test] - fn test_field_name() { - let edit = test_feature( - BibtexEntryRenameProvider, - FeatureSpec { - files: vec![FeatureSpec::file("foo.bib", "@article{foo, bar = baz}")], - main_file: "foo.bib", - position: Position::new(0, 14), - new_name: "qux", - ..FeatureSpec::default() - }, - ); - assert_eq!(edit, None); + #[tokio::test] + async fn field_name() { + let actual_edit = FeatureTester::new() + .file("main.bib", r#"@article{foo, bar = baz}"#) + .main("main.bib") + .position(0, 14) + .new_name("qux") + .test_rename(BibtexEntryRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } + + #[tokio::test] + async fn empty_latex_document() { + let actual_edit = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .new_name("") + .test_rename(BibtexEntryRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } + + #[tokio::test] + async fn empty_bibtex_document() { + let actual_edit = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .new_name("") + .test_rename(BibtexEntryRenameProvider) + .await; + + assert_eq!(actual_edit, None); } } diff --git a/support/texlab/src/rename/latex_cmd.rs b/support/texlab/src/rename/latex_cmd.rs new file mode 100644 index 0000000000..4770c4c485 --- /dev/null +++ b/support/texlab/src/rename/latex_cmd.rs @@ -0,0 +1,128 @@ +use crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{ + Position, Range, RenameParams, TextDocumentPositionParams, TextEdit, WorkspaceEdit, + }, + syntax::{latex, SyntaxNode}, + workspace::DocumentContent, +}; +use async_trait::async_trait; +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct LatexCommandPrepareRenameProvider; + +#[async_trait] +impl FeatureProvider for LatexCommandPrepareRenameProvider { + type Params = TextDocumentPositionParams; + type Output = Option<Range>; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + let pos = req.params.position; + find_command(&req.current().content, pos).map(SyntaxNode::range) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct LatexCommandRenameProvider; + +#[async_trait] +impl FeatureProvider for LatexCommandRenameProvider { + type Params = RenameParams; + type Output = Option<WorkspaceEdit>; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + let pos = req.params.text_document_position.position; + let cmd_name = find_command(&req.current().content, pos)?.name.text(); + let mut changes = HashMap::new(); + for doc in req.related() { + if let DocumentContent::Latex(table) = &doc.content { + let edits = table + .commands + .iter() + .filter_map(|node| table.as_command(*node)) + .filter(|cmd| cmd.name.text() == cmd_name) + .map(|cmd| { + TextEdit::new(cmd.name.range(), format!("\\{}", req.params.new_name)) + }) + .collect(); + changes.insert(doc.uri.clone().into(), edits); + } + } + Some(WorkspaceEdit::new(changes)) + } +} + +fn find_command(content: &DocumentContent, pos: Position) -> Option<&latex::Command> { + if let DocumentContent::Latex(table) = &content { + table.as_command(table.find_command_by_short_name_range(pos)?) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{feature::FeatureTester, protocol::RangeExt}; + use indoc::indoc; + + #[tokio::test] + async fn command() { + let actual_edit = FeatureTester::new() + .file( + "foo.tex", + indoc!( + r#" + \include{bar.tex} + \baz + "# + ), + ) + .file("bar.tex", r#"\baz"#) + .main("foo.tex") + .position(1, 2) + .new_name("qux") + .test_rename(LatexCommandRenameProvider) + .await + .unwrap(); + + let mut expected_changes = HashMap::new(); + expected_changes.insert( + FeatureTester::uri("foo.tex").into(), + vec![TextEdit::new(Range::new_simple(1, 0, 1, 4), "\\qux".into())], + ); + expected_changes.insert( + FeatureTester::uri("bar.tex").into(), + vec![TextEdit::new(Range::new_simple(0, 0, 0, 4), "\\qux".into())], + ); + + assert_eq!(actual_edit, WorkspaceEdit::new(expected_changes)); + } + + #[tokio::test] + async fn empty_latex_document() { + let actual_edit = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .new_name("") + .test_rename(LatexCommandRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } + + #[tokio::test] + async fn empty_bibtex_document() { + let actual_edit = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .new_name("") + .test_rename(LatexCommandRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } +} diff --git a/support/texlab/src/rename/latex_command.rs b/support/texlab/src/rename/latex_command.rs deleted file mode 100644 index 52bac7a8c9..0000000000 --- a/support/texlab/src/rename/latex_command.rs +++ /dev/null @@ -1,114 +0,0 @@ -use crate::syntax::*; -use crate::workspace::*; -use futures_boxed::boxed; -use lsp_types::*; -use std::collections::HashMap; -use std::sync::Arc; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct LatexCommandPrepareRenameProvider; - -impl FeatureProvider for LatexCommandPrepareRenameProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Range>; - - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<TextDocumentPositionParams>, - ) -> Option<Range> { - let position = request.params.position; - find_command(&request.document().tree, position).map(|cmd| cmd.range()) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct LatexCommandRenameProvider; - -impl FeatureProvider for LatexCommandRenameProvider { - type Params = RenameParams; - type Output = Option<WorkspaceEdit>; - - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<RenameParams>, - ) -> Option<WorkspaceEdit> { - let command = find_command( - &request.document().tree, - request.params.text_document_position.position, - )?; - let mut changes = HashMap::new(); - for document in request.related_documents() { - if let SyntaxTree::Latex(tree) = &document.tree { - let edits: Vec<TextEdit> = tree - .commands - .iter() - .filter(|cmd| cmd.name.text() == command.name.text()) - .map(|cmd| { - TextEdit::new(cmd.name.range(), format!("\\{}", request.params.new_name)) - }) - .collect(); - changes.insert(document.uri.clone().into(), edits); - } - } - Some(WorkspaceEdit::new(changes)) - } -} - -fn find_command(tree: &SyntaxTree, position: Position) -> Option<Arc<LatexCommand>> { - if let SyntaxTree::Latex(tree) = tree { - tree.find_command_by_name(position) - } else { - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::range::RangeExt; - use lsp_types::{Position, Range}; - - #[test] - fn test() { - let edit = test_feature( - LatexCommandRenameProvider, - FeatureSpec { - files: vec![ - FeatureSpec::file("foo.tex", "\\include{bar.tex}\n\\baz"), - FeatureSpec::file("bar.tex", "\\baz"), - ], - main_file: "foo.tex", - position: Position::new(1, 2), - new_name: "qux", - ..FeatureSpec::default() - }, - ); - let mut changes = HashMap::new(); - changes.insert( - FeatureSpec::uri("foo.tex"), - vec![TextEdit::new(Range::new_simple(1, 0, 1, 4), "\\qux".into())], - ); - changes.insert( - FeatureSpec::uri("bar.tex"), - vec![TextEdit::new(Range::new_simple(0, 0, 0, 4), "\\qux".into())], - ); - assert_eq!(edit, Some(WorkspaceEdit::new(changes))); - } - - #[test] - fn test_bibtex() { - let edit = test_feature( - LatexCommandRenameProvider, - FeatureSpec { - files: vec![FeatureSpec::file("foo.bib", "@article{foo, bar = baz}")], - main_file: "foo.bib", - position: Position::new(0, 14), - new_name: "qux", - ..FeatureSpec::default() - }, - ); - assert_eq!(edit, None); - } -} diff --git a/support/texlab/src/rename/latex_env.rs b/support/texlab/src/rename/latex_env.rs new file mode 100644 index 0000000000..6e302ef8b6 --- /dev/null +++ b/support/texlab/src/rename/latex_env.rs @@ -0,0 +1,157 @@ +use crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{ + Position, Range, RangeExt, RenameParams, TextDocumentPositionParams, TextEdit, + WorkspaceEdit, + }, + syntax::{latex, SyntaxNode}, + workspace::DocumentContent, +}; +use async_trait::async_trait; +use std::collections::HashMap; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct LatexEnvironmentPrepareRenameProvider; + +#[async_trait] +impl FeatureProvider for LatexEnvironmentPrepareRenameProvider { + type Params = TextDocumentPositionParams; + type Output = Option<Range>; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + let pos = req.params.position; + let (left_name, right_name) = find_environment(&req.current().content, pos)?; + let range = if left_name.range().contains(pos) { + left_name.range() + } else { + right_name.range() + }; + Some(range) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] +pub struct LatexEnvironmentRenameProvider; + +#[async_trait] +impl FeatureProvider for LatexEnvironmentRenameProvider { + type Params = RenameParams; + type Output = Option<WorkspaceEdit>; + + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + let (left_name, right_name) = find_environment( + &req.current().content, + req.params.text_document_position.position, + )?; + let edits = vec![ + TextEdit::new(left_name.range(), req.params.new_name.clone()), + TextEdit::new(right_name.range(), req.params.new_name.clone()), + ]; + let mut changes = HashMap::new(); + changes.insert(req.current().uri.clone().into(), edits); + Some(WorkspaceEdit::new(changes)) + } +} + +fn find_environment( + content: &DocumentContent, + pos: Position, +) -> Option<(&latex::Token, &latex::Token)> { + if let DocumentContent::Latex(table) = content { + for env in &table.environments { + if let Some(left_name) = env.left.name(&table) { + if let Some(right_name) = env.right.name(&table) { + if left_name.range().contains(pos) || right_name.range().contains(pos) { + return Some((left_name, right_name)); + } + } + } + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::feature::FeatureTester; + use indoc::indoc; + + #[tokio::test] + async fn environment() { + let actual_edit = FeatureTester::new() + .file( + "main.tex", + indoc!( + r#" + \begin{foo} + \end{bar} + "# + ), + ) + .main("main.tex") + .position(0, 8) + .new_name("baz") + .test_rename(LatexEnvironmentRenameProvider) + .await + .unwrap(); + + let mut expected_changes = HashMap::new(); + expected_changes.insert( + FeatureTester::uri("main.tex").into(), + vec![ + TextEdit::new(Range::new_simple(0, 7, 0, 10), "baz".into()), + TextEdit::new(Range::new_simple(1, 5, 1, 8), "baz".into()), + ], + ); + + assert_eq!(actual_edit, WorkspaceEdit::new(expected_changes)); + } + + #[tokio::test] + async fn command() { + let actual_edit = FeatureTester::new() + .file( + "main.tex", + indoc!( + r#" + \begin{foo} + \end{bar} + "# + ), + ) + .main("main.tex") + .position(0, 5) + .new_name("baz") + .test_rename(LatexEnvironmentRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } + + #[tokio::test] + async fn empty_latex_document() { + let actual_edit = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .new_name("") + .test_rename(LatexEnvironmentRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } + + #[tokio::test] + async fn empty_bibtex_document() { + let actual_edit = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .new_name("") + .test_rename(LatexEnvironmentRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } +} diff --git a/support/texlab/src/rename/latex_environment.rs b/support/texlab/src/rename/latex_environment.rs deleted file mode 100644 index 28d6450356..0000000000 --- a/support/texlab/src/rename/latex_environment.rs +++ /dev/null @@ -1,137 +0,0 @@ -use crate::range::RangeExt; -use crate::syntax::*; -use crate::workspace::*; -use futures_boxed::boxed; -use lsp_types::*; -use std::collections::HashMap; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct LatexEnvironmentPrepareRenameProvider; - -impl FeatureProvider for LatexEnvironmentPrepareRenameProvider { - type Params = TextDocumentPositionParams; - type Output = Option<Range>; - - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<TextDocumentPositionParams>, - ) -> Option<Range> { - let position = request.params.position; - let environment = find_environment(&request.document().tree, position)?; - let left_range = environment.left.name().unwrap().range(); - let right_range = environment.right.name().unwrap().range(); - if left_range.contains(position) { - Some(left_range) - } else { - Some(right_range) - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct LatexEnvironmentRenameProvider; - -impl FeatureProvider for LatexEnvironmentRenameProvider { - type Params = RenameParams; - type Output = Option<WorkspaceEdit>; - - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<RenameParams>, - ) -> Option<WorkspaceEdit> { - let environment = find_environment( - &request.document().tree, - request.params.text_document_position.position, - )?; - let edits = vec![ - TextEdit::new( - environment.left.name().unwrap().range(), - request.params.new_name.clone(), - ), - TextEdit::new( - environment.right.name().unwrap().range(), - request.params.new_name.clone(), - ), - ]; - let mut changes = HashMap::new(); - changes.insert(request.document().uri.clone().into(), edits); - Some(WorkspaceEdit::new(changes)) - } -} - -fn find_environment(tree: &SyntaxTree, position: Position) -> Option<&LatexEnvironment> { - if let SyntaxTree::Latex(tree) = &tree { - for environment in &tree.env.environments { - if let Some(left_name) = environment.left.name() { - if let Some(right_name) = environment.right.name() { - if left_name.range().contains(position) || right_name.range().contains(position) - { - return Some(environment); - } - } - } - } - } - None -} - -#[cfg(test)] -mod tests { - use super::*; - use lsp_types::{Position, Range}; - - #[test] - fn test_environment() { - let edit = test_feature( - LatexEnvironmentRenameProvider, - FeatureSpec { - files: vec![FeatureSpec::file("foo.tex", "\\begin{foo}\n\\end{bar}")], - main_file: "foo.tex", - position: Position::new(0, 8), - new_name: "baz", - ..FeatureSpec::default() - }, - ); - let mut changes = HashMap::new(); - changes.insert( - FeatureSpec::uri("foo.tex"), - vec![ - TextEdit::new(Range::new_simple(0, 7, 0, 10), "baz".into()), - TextEdit::new(Range::new_simple(1, 5, 1, 8), "baz".into()), - ], - ); - assert_eq!(edit, Some(WorkspaceEdit::new(changes))); - } - - #[test] - fn test_command() { - let edit = test_feature( - LatexEnvironmentRenameProvider, - FeatureSpec { - files: vec![FeatureSpec::file("foo.tex", "\\begin{foo}\n\\end{bar}")], - main_file: "foo.tex", - position: Position::new(0, 5), - new_name: "baz", - ..FeatureSpec::default() - }, - ); - assert_eq!(edit, None); - } - - #[test] - fn test_bibtex() { - let edit = test_feature( - LatexEnvironmentRenameProvider, - FeatureSpec { - files: vec![FeatureSpec::file("foo.bib", "")], - main_file: "foo.bib", - position: Position::new(0, 0), - new_name: "baz", - ..FeatureSpec::default() - }, - ); - assert_eq!(edit, None); - } -} diff --git a/support/texlab/src/rename/latex_label.rs b/support/texlab/src/rename/latex_label.rs index 1cf5e3ea9d..27e0f9b224 100644 --- a/support/texlab/src/rename/latex_label.rs +++ b/support/texlab/src/rename/latex_label.rs @@ -1,67 +1,64 @@ -use crate::range::RangeExt; -use crate::syntax::*; -use crate::workspace::*; -use futures_boxed::boxed; -use lsp_types::*; +use crate::{ + feature::{FeatureProvider, FeatureRequest}, + protocol::{ + Position, Range, RangeExt, RenameParams, TextDocumentPositionParams, TextEdit, + WorkspaceEdit, + }, + syntax::{Span, SyntaxNode}, + workspace::DocumentContent, +}; +use async_trait::async_trait; use std::collections::HashMap; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct LatexLabelPrepareRenameProvider; +#[async_trait] impl FeatureProvider for LatexLabelPrepareRenameProvider { type Params = TextDocumentPositionParams; type Output = Option<Range>; - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<TextDocumentPositionParams>, - ) -> Option<Range> { - find_label(&request.document().tree, request.params.position).map(Span::range) + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + let pos = req.params.position; + find_label(&req.current().content, pos).map(Span::range) } } -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct LatexLabelRenameProvider; +#[async_trait] impl FeatureProvider for LatexLabelRenameProvider { type Params = RenameParams; type Output = Option<WorkspaceEdit>; - #[boxed] - async fn execute<'a>( - &'a self, - request: &'a FeatureRequest<RenameParams>, - ) -> Option<WorkspaceEdit> { - let name = find_label( - &request.document().tree, - request.params.text_document_position.position, - )?; + async fn execute<'a>(&'a self, req: &'a FeatureRequest<Self::Params>) -> Self::Output { + let pos = req.params.text_document_position.position; + let name = find_label(&req.current().content, pos)?; let mut changes = HashMap::new(); - for document in request.related_documents() { - if let SyntaxTree::Latex(tree) = &document.tree { - let edits = tree - .structure + for doc in req.related() { + if let DocumentContent::Latex(table) = &doc.content { + let edits = table .labels .iter() - .flat_map(LatexLabel::names) + .flat_map(|label| label.names(&table)) .filter(|label| label.text() == name.text) - .map(|label| TextEdit::new(label.range(), request.params.new_name.clone())) + .map(|label| TextEdit::new(label.range(), req.params.new_name.clone())) .collect(); - changes.insert(document.uri.clone().into(), edits); + changes.insert(doc.uri.clone().into(), edits); } } Some(WorkspaceEdit::new(changes)) } } -fn find_label(tree: &SyntaxTree, position: Position) -> Option<&Span> { - if let SyntaxTree::Latex(tree) = tree { - tree.structure +fn find_label(content: &DocumentContent, pos: Position) -> Option<&Span> { + if let DocumentContent::Latex(table) = content { + table .labels .iter() - .flat_map(LatexLabel::names) - .find(|label| label.range().contains(position)) + .flat_map(|label| label.names(&table)) + .find(|label| label.range().contains(pos)) .map(|label| &label.span) } else { None @@ -71,63 +68,79 @@ fn find_label(tree: &SyntaxTree, position: Position) -> Option<&Span> { #[cfg(test)] mod tests { use super::*; - use lsp_types::{Position, Range}; - - #[test] - fn test_label() { - let edit = test_feature( - LatexLabelRenameProvider, - FeatureSpec { - files: vec![ - FeatureSpec::file("foo.tex", "\\label{foo}\n\\include{bar}"), - FeatureSpec::file("bar.tex", "\\ref{foo}"), - FeatureSpec::file("baz.tex", "\\ref{foo}"), - ], - main_file: "foo.tex", - position: Position::new(0, 7), - new_name: "bar", - ..FeatureSpec::default() - }, - ); - let mut changes = HashMap::new(); - changes.insert( - FeatureSpec::uri("foo.tex"), + use crate::feature::FeatureTester; + use indoc::indoc; + + #[tokio::test] + async fn label() { + let actual_edit = FeatureTester::new() + .file( + "foo.tex", + indoc!( + r#" + \label{foo} + \include{bar} + "# + ), + ) + .file("bar.tex", r#"\ref{foo}"#) + .file("baz.tex", r#"\ref{foo}"#) + .main("foo.tex") + .position(0, 7) + .new_name("bar") + .test_rename(LatexLabelRenameProvider) + .await + .unwrap(); + + let mut expected_changes = HashMap::new(); + expected_changes.insert( + FeatureTester::uri("foo.tex").into(), vec![TextEdit::new(Range::new_simple(0, 7, 0, 10), "bar".into())], ); - changes.insert( - FeatureSpec::uri("bar.tex"), + expected_changes.insert( + FeatureTester::uri("bar.tex").into(), vec![TextEdit::new(Range::new_simple(0, 5, 0, 8), "bar".into())], ); - assert_eq!(edit, Some(WorkspaceEdit::new(changes))); + + assert_eq!(actual_edit, WorkspaceEdit::new(expected_changes)); } - #[test] - fn test_command_args() { - let edit = test_feature( - LatexLabelRenameProvider, - FeatureSpec { - files: vec![FeatureSpec::file("foo.tex", "\\foo{bar}")], - main_file: "foo.tex", - position: Position::new(0, 5), - new_name: "baz", - ..FeatureSpec::default() - }, - ); - assert_eq!(edit, None); + #[tokio::test] + async fn command_args() { + let actual_edit = FeatureTester::new() + .file("main.tex", r#"\foo{bar}"#) + .main("main.tex") + .position(0, 5) + .new_name("baz") + .test_rename(LatexLabelRenameProvider) + .await; + + assert_eq!(actual_edit, None); } - #[test] - fn test_bibtex() { - let edit = test_feature( - LatexLabelRenameProvider, - FeatureSpec { - files: vec![FeatureSpec::file("foo.bib", "")], - main_file: "foo.bib", - position: Position::new(0, 0), - new_name: "baz", - ..FeatureSpec::default() - }, - ); - assert_eq!(edit, None); + #[tokio::test] + async fn empty_latex_document() { + let actual_edit = FeatureTester::new() + .file("main.tex", "") + .main("main.tex") + .position(0, 0) + .new_name("") + .test_rename(LatexLabelRenameProvider) + .await; + + assert_eq!(actual_edit, None); + } + + #[tokio::test] + async fn empty_bibtex_document() { + let actual_edit = FeatureTester::new() + .file("main.bib", "") + .main("main.bib") + .position(0, 0) + .new_name("") + .test_rename(LatexLabelRenameProvider) + .await; + + assert_eq!(actual_edit, None); } } diff --git a/support/texlab/src/rename/mod.rs b/support/texlab/src/rename/mod.rs index e8816d1e95..40bf8ce1e1 100644 --- a/support/texlab/src/rename/mod.rs +++ b/support/texlab/src/rename/mod.rs @@ -1,15 +1,19 @@ mod bibtex_entry; -mod latex_command; -mod latex_environment; +mod latex_cmd; +mod latex_env; mod latex_label; -use self::bibtex_entry::*; -use self::latex_command::*; -use self::latex_environment::*; -use self::latex_label::*; -use crate::workspace::*; -use futures_boxed::boxed; -use lsp_types::*; +use self::{ + bibtex_entry::{BibtexEntryPrepareRenameProvider, BibtexEntryRenameProvider}, + latex_cmd::{LatexCommandPrepareRenameProvider, LatexCommandRenameProvider}, + latex_env::{LatexEnvironmentPrepareRenameProvider, LatexEnvironmentRenameProvider}, + latex_label::{LatexLabelPrepareRenameProvider, LatexLabelRenameProvider}, +}; +use crate::{ + feature::{ChoiceProvider, FeatureProvider, FeatureRequest}, + protocol::{Range, RenameParams, TextDocumentPositionParams, WorkspaceEdit}, +}; +use async_trait::async_trait; pub struct PrepareRenameProvider { provider: ChoiceProvider<TextDocumentPositionParams, Range>, @@ -34,16 +38,16 @@ impl Default for PrepareRenameProvider { } } +#[async_trait] impl FeatureProvider for PrepareRenameProvider { type Params = TextDocumentPositionParams; type Output = Option<Range>; - #[boxed] async fn execute<'a>( &'a self, - request: &'a FeatureRequest<TextDocumentPositionParams>, + req: &'a FeatureRequest<TextDocumentPositionParams>, ) -> Option<Range> { - self.provider.execute(request).await + self.provider.execute(req).await } } @@ -70,11 +74,11 @@ impl Default for RenameProvider { } } +#[async_trait] impl FeatureProvider for RenameProvider { type Params = RenameParams; type Output = Option<WorkspaceEdit>; - #[boxed] async fn execute<'a>( &'a self, request: &'a FeatureRequest<RenameParams>, |