diff options
Diffstat (limited to 'support/texlab/src/completion/bibtex')
-rw-r--r-- | support/texlab/src/completion/bibtex/command.rs | 112 | ||||
-rw-r--r-- | support/texlab/src/completion/bibtex/entry_type.rs | 155 | ||||
-rw-r--r-- | support/texlab/src/completion/bibtex/field_name.rs | 176 | ||||
-rw-r--r-- | support/texlab/src/completion/bibtex/mod.rs | 3 |
4 files changed, 446 insertions, 0 deletions
diff --git a/support/texlab/src/completion/bibtex/command.rs b/support/texlab/src/completion/bibtex/command.rs new file mode 100644 index 0000000000..5d61a8e568 --- /dev/null +++ b/support/texlab/src/completion/bibtex/command.rs @@ -0,0 +1,112 @@ +use crate::completion::factory::{self, LatexComponentId}; +use crate::completion::DATABASE; +use crate::range::RangeExt; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::*; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct BibtexCommandCompletionProvider; + +impl FeatureProvider for BibtexCommandCompletionProvider { + type Params = CompletionParams; + type Output = Vec<CompletionItem>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + let mut items = Vec::new(); + if let SyntaxTree::Bibtex(tree) = &request.document().tree { + let position = request.params.text_document_position.position; + if let Some(BibtexNode::Command(command)) = tree.find(position).last() { + if command.token.range().contains(position) + && command.token.start().character != position.character + { + let mut range = command.range(); + range.start.character += 1; + + let component = LatexComponentId::kernel(); + for command in &DATABASE.kernel().commands { + let text_edit = TextEdit::new(range, (&command.name).into()); + let item = factory::command( + request, + (&command.name).into(), + command.image.as_ref().map(AsRef::as_ref), + command.glyph.as_ref().map(AsRef::as_ref), + text_edit, + &component, + ); + items.push(item); + } + } + } + } + items + } +} + +#[cfg(test)] +mod tests { + use super::*; + use lsp_types::{Position, Range}; + + #[test] + fn test_inside_command() { + let items = test_feature( + BibtexCommandCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo, bar=\n\\}")], + main_file: "foo.bib", + position: Position::new(1, 1), + ..FeatureSpec::default() + }, + ); + assert!(!items.is_empty()); + assert_eq!( + items[0].text_edit.as_ref().map(|edit| edit.range), + Some(Range::new_simple(1, 1, 1, 2)) + ); + } + + #[test] + fn test_start_of_command() { + let items = test_feature( + BibtexCommandCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo, bar=\n\\}")], + main_file: "foo.bib", + position: Position::new(1, 0), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } + + #[test] + fn test_inside_text() { + let items = test_feature( + BibtexCommandCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo, bar=\n}")], + main_file: "foo.bib", + position: Position::new(1, 0), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } + + #[test] + fn test_latex() { + let items = test_feature( + BibtexCommandCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.tex", "\\")], + main_file: "foo.tex", + position: Position::new(0, 1), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } +} diff --git a/support/texlab/src/completion/bibtex/entry_type.rs b/support/texlab/src/completion/bibtex/entry_type.rs new file mode 100644 index 0000000000..8deabdb33e --- /dev/null +++ b/support/texlab/src/completion/bibtex/entry_type.rs @@ -0,0 +1,155 @@ +use crate::completion::factory; +use crate::range::RangeExt; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::*; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct BibtexEntryTypeCompletionProvider; + +impl FeatureProvider for BibtexEntryTypeCompletionProvider { + type Params = CompletionParams; + type Output = Vec<CompletionItem>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + if let SyntaxTree::Bibtex(tree) = &request.document().tree { + let position = request.params.text_document_position.position; + for declaration in &tree.root.children { + match declaration { + BibtexDeclaration::Preamble(preamble) => { + if contains(&preamble.ty, position) { + return make_items(request, preamble.ty.range()); + } + } + BibtexDeclaration::String(string) => { + if contains(&string.ty, position) { + return make_items(request, string.ty.range()); + } + } + BibtexDeclaration::Entry(entry) => { + if contains(&entry.ty, position) { + return make_items(request, entry.ty.range()); + } + } + BibtexDeclaration::Comment(_) => {} + } + } + } + Vec::new() + } +} + +fn contains(ty: &BibtexToken, position: Position) -> bool { + ty.range().contains(position) && ty.start().character != position.character +} + +fn make_items(request: &FeatureRequest<CompletionParams>, mut range: Range) -> Vec<CompletionItem> { + range.start.character += 1; + let mut items = Vec::new(); + for ty in &LANGUAGE_DATA.entry_types { + let text_edit = TextEdit::new(range, (&ty.name).into()); + let item = factory::entry_type(request, ty, text_edit); + items.push(item); + } + items +} + +#[cfg(test)] +mod tests { + use super::*; + use lsp_types::Position; + + #[test] + fn test_before_at_sign() { + let items = test_feature( + BibtexEntryTypeCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@")], + main_file: "foo.bib", + position: Position::new(0, 0), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } + + #[test] + fn test_after_at_sign() { + let items = test_feature( + BibtexEntryTypeCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@")], + main_file: "foo.bib", + position: Position::new(0, 1), + ..FeatureSpec::default() + }, + ); + assert!(!items.is_empty()); + assert_eq!( + items[0].text_edit.as_ref().map(|edit| edit.range), + Some(Range::new_simple(0, 1, 0, 1)) + ); + } + + #[test] + fn test_inside_entry_type() { + let items = test_feature( + BibtexEntryTypeCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@foo")], + main_file: "foo.bib", + position: Position::new(0, 2), + ..FeatureSpec::default() + }, + ); + assert!(!items.is_empty()); + assert_eq!( + items[0].text_edit.as_ref().map(|edit| edit.range), + Some(Range::new_simple(0, 1, 0, 4)) + ); + } + + #[test] + fn test_inside_entry_key() { + let items = test_feature( + BibtexEntryTypeCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo,}")], + main_file: "foo.bib", + position: Position::new(0, 11), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } + + #[test] + fn test_inside_comments() { + let items = test_feature( + BibtexEntryTypeCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "foo")], + main_file: "foo.bib", + position: Position::new(0, 2), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } + + #[test] + fn test_latex() { + let items = test_feature( + BibtexEntryTypeCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.tex", "@")], + main_file: "foo.tex", + position: Position::new(0, 1), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } +} diff --git a/support/texlab/src/completion/bibtex/field_name.rs b/support/texlab/src/completion/bibtex/field_name.rs new file mode 100644 index 0000000000..abddad4f5a --- /dev/null +++ b/support/texlab/src/completion/bibtex/field_name.rs @@ -0,0 +1,176 @@ +use crate::completion::factory; +use crate::range::RangeExt; +use crate::syntax::*; +use crate::workspace::*; +use futures_boxed::boxed; +use lsp_types::{CompletionItem, CompletionParams, Range, TextEdit}; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct BibtexFieldNameCompletionProvider; + +impl FeatureProvider for BibtexFieldNameCompletionProvider { + type Params = CompletionParams; + type Output = Vec<CompletionItem>; + + #[boxed] + async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output { + if let SyntaxTree::Bibtex(tree) = &request.document().tree { + let position = request.params.text_document_position.position; + match tree.find(position).last() { + Some(BibtexNode::Field(field)) => { + if field.name.range().contains(position) { + return make_items(request, field.name.range()); + } + } + Some(BibtexNode::Entry(entry)) => { + if !entry.is_comment() && !entry.ty.range().contains(position) { + let edit_range = Range::new(position, position); + if let Some(key) = &entry.key { + if !key.range().contains(position) { + return make_items(request, edit_range); + } + } else { + return make_items(request, edit_range); + } + } + } + _ => {} + } + } + Vec::new() + } +} + +fn make_items( + request: &FeatureRequest<CompletionParams>, + edit_range: Range, +) -> Vec<CompletionItem> { + let mut items = Vec::new(); + for field in &LANGUAGE_DATA.fields { + let text_edit = TextEdit::new(edit_range, (&field.name).into()); + let item = factory::field_name(request, field, text_edit); + items.push(item); + } + items +} + +#[cfg(test)] +mod tests { + use super::*; + use lsp_types::Position; + + #[test] + fn test_inside_first_field() { + let items = test_feature( + BibtexFieldNameCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo,\nbar}")], + main_file: "foo.bib", + position: Position::new(1, 1), + ..FeatureSpec::default() + }, + ); + assert!(!items.is_empty()); + assert_eq!( + items[0].text_edit.as_ref().map(|edit| edit.range), + Some(Range::new_simple(1, 0, 1, 3)) + ); + } + + #[test] + fn test_inside_second_field() { + let items = test_feature( + BibtexFieldNameCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file( + "foo.bib", + "@article{foo, bar = {baz}, qux}", + )], + main_file: "foo.bib", + position: Position::new(0, 27), + ..FeatureSpec::default() + }, + ); + assert!(!items.is_empty()); + assert_eq!( + items[0].text_edit.as_ref().map(|edit| edit.range), + Some(Range::new_simple(0, 27, 0, 30)) + ); + } + + #[test] + fn test_inside_entry() { + let items = test_feature( + BibtexFieldNameCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo, \n}")], + main_file: "foo.bib", + position: Position::new(1, 0), + ..FeatureSpec::default() + }, + ); + assert!(!items.is_empty()); + assert_eq!( + items[0].text_edit.as_ref().map(|edit| edit.range), + Some(Range::new_simple(1, 0, 1, 0)) + ); + } + + #[test] + fn test_inside_content() { + let items = test_feature( + BibtexFieldNameCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo,\nbar = {baz}}")], + main_file: "foo.bib", + position: Position::new(1, 7), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } + + #[test] + fn test_inside_entry_type() { + let items = test_feature( + BibtexFieldNameCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.bib", "@article{foo,}")], + main_file: "foo.bib", + position: Position::new(0, 3), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } + + // TODO: Improve behavior of this provider + // + // #[test] + // fn test_after_equals_sign() { + // let items = test_feature( + // BibtexFieldNameCompletionProvider, + // FeatureSpec { + // files: vec![FeatureSpec::file("foo.bib", "@article{foo, bar = \n}")], + // main_file: "foo.bib", + // position: Position::new(1, 0), + // ..FeatureSpec::default() + // }, + // ); + // assert!(items.is_empty()); + // } + + #[test] + fn test_inside_latex() { + let items = test_feature( + BibtexFieldNameCompletionProvider, + FeatureSpec { + files: vec![FeatureSpec::file("foo.tex", "@article{foo,}")], + main_file: "foo.tex", + position: Position::new(0, 3), + ..FeatureSpec::default() + }, + ); + assert!(items.is_empty()); + } +} diff --git a/support/texlab/src/completion/bibtex/mod.rs b/support/texlab/src/completion/bibtex/mod.rs new file mode 100644 index 0000000000..9117d7f266 --- /dev/null +++ b/support/texlab/src/completion/bibtex/mod.rs @@ -0,0 +1,3 @@ +pub mod command; +pub mod entry_type; +pub mod field_name; |