diff options
Diffstat (limited to 'support/texlab/src/rename')
-rw-r--r-- | support/texlab/src/rename/bibtex_entry.rs | 172 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_command.rs | 114 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_environment.rs | 137 | ||||
-rw-r--r-- | support/texlab/src/rename/latex_label.rs | 133 | ||||
-rw-r--r-- | support/texlab/src/rename/mod.rs | 84 |
5 files changed, 640 insertions, 0 deletions
diff --git a/support/texlab/src/rename/bibtex_entry.rs b/support/texlab/src/rename/bibtex_entry.rs new file mode 100644 index 0000000000..46484c7f23 --- /dev/null +++ b/support/texlab/src/rename/bibtex_entry.rs @@ -0,0 +1,172 @@ +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 BibtexEntryPrepareRenameProvider; + +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) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct BibtexEntryRenameProvider; + +impl FeatureProvider for BibtexEntryRenameProvider { + type Params = RenameParams; + type Output = Option<WorkspaceEdit>; + + #[boxed] + async fn execute<'a>( + &'a self, + request: &'a FeatureRequest<RenameParams>, + ) -> Option<WorkspaceEdit> { + let key_name = find_key( + &request.document().tree, + request.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(), + )); + } + } + } + } + }; + changes.insert(document.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 + } + } +} + +#[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"), + vec![TextEdit::new(Range::new_simple(0, 9, 0, 12), "qux".into())], + ); + changes.insert( + FeatureSpec::uri("bar.tex"), + vec![TextEdit::new(Range::new_simple(1, 6, 1, 9), "qux".into())], + ); + assert_eq!(edit, Some(WorkspaceEdit::new(changes))); + } + + #[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"), + vec![TextEdit::new(Range::new_simple(0, 9, 0, 12), "qux".into())], + ); + changes.insert( + FeatureSpec::uri("bar.tex"), + vec![TextEdit::new(Range::new_simple(1, 6, 1, 9), "qux".into())], + ); + assert_eq!(edit, Some(WorkspaceEdit::new(changes))); + } + + #[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); + } +} diff --git a/support/texlab/src/rename/latex_command.rs b/support/texlab/src/rename/latex_command.rs new file mode 100644 index 0000000000..52bac7a8c9 --- /dev/null +++ b/support/texlab/src/rename/latex_command.rs @@ -0,0 +1,114 @@ +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_environment.rs b/support/texlab/src/rename/latex_environment.rs new file mode 100644 index 0000000000..28d6450356 --- /dev/null +++ b/support/texlab/src/rename/latex_environment.rs @@ -0,0 +1,137 @@ +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 new file mode 100644 index 0000000000..1cf5e3ea9d --- /dev/null +++ b/support/texlab/src/rename/latex_label.rs @@ -0,0 +1,133 @@ +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 LatexLabelPrepareRenameProvider; + +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) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct LatexLabelRenameProvider; + +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, + )?; + let mut changes = HashMap::new(); + for document in request.related_documents() { + if let SyntaxTree::Latex(tree) = &document.tree { + let edits = tree + .structure + .labels + .iter() + .flat_map(LatexLabel::names) + .filter(|label| label.text() == name.text) + .map(|label| TextEdit::new(label.range(), request.params.new_name.clone())) + .collect(); + changes.insert(document.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 + .labels + .iter() + .flat_map(LatexLabel::names) + .find(|label| label.range().contains(position)) + .map(|label| &label.span) + } else { + None + } +} + +#[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"), + vec![TextEdit::new(Range::new_simple(0, 7, 0, 10), "bar".into())], + ); + changes.insert( + FeatureSpec::uri("bar.tex"), + vec![TextEdit::new(Range::new_simple(0, 5, 0, 8), "bar".into())], + ); + assert_eq!(edit, Some(WorkspaceEdit::new(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); + } + + #[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); + } +} diff --git a/support/texlab/src/rename/mod.rs b/support/texlab/src/rename/mod.rs new file mode 100644 index 0000000000..e8816d1e95 --- /dev/null +++ b/support/texlab/src/rename/mod.rs @@ -0,0 +1,84 @@ +mod bibtex_entry; +mod latex_command; +mod latex_environment; +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::*; + +pub struct PrepareRenameProvider { + provider: ChoiceProvider<TextDocumentPositionParams, Range>, +} + +impl PrepareRenameProvider { + pub fn new() -> Self { + Self { + provider: ChoiceProvider::new(vec![ + Box::new(BibtexEntryPrepareRenameProvider), + Box::new(LatexCommandPrepareRenameProvider), + Box::new(LatexEnvironmentPrepareRenameProvider), + Box::new(LatexLabelPrepareRenameProvider), + ]), + } + } +} + +impl Default for PrepareRenameProvider { + fn default() -> Self { + Self::new() + } +} + +impl FeatureProvider for PrepareRenameProvider { + type Params = TextDocumentPositionParams; + type Output = Option<Range>; + + #[boxed] + async fn execute<'a>( + &'a self, + request: &'a FeatureRequest<TextDocumentPositionParams>, + ) -> Option<Range> { + self.provider.execute(request).await + } +} + +pub struct RenameProvider { + provider: ChoiceProvider<RenameParams, WorkspaceEdit>, +} + +impl RenameProvider { + pub fn new() -> Self { + Self { + provider: ChoiceProvider::new(vec![ + Box::new(BibtexEntryRenameProvider), + Box::new(LatexCommandRenameProvider), + Box::new(LatexEnvironmentRenameProvider), + Box::new(LatexLabelRenameProvider), + ]), + } + } +} + +impl Default for RenameProvider { + fn default() -> Self { + Self::new() + } +} + +impl FeatureProvider for RenameProvider { + type Params = RenameParams; + type Output = Option<WorkspaceEdit>; + + #[boxed] + async fn execute<'a>( + &'a self, + request: &'a FeatureRequest<RenameParams>, + ) -> Option<WorkspaceEdit> { + self.provider.execute(request).await + } +} |