summaryrefslogtreecommitdiff
path: root/support/texlab/src/features/completion/field.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src/features/completion/field.rs')
-rw-r--r--support/texlab/src/features/completion/field.rs200
1 files changed, 200 insertions, 0 deletions
diff --git a/support/texlab/src/features/completion/field.rs b/support/texlab/src/features/completion/field.rs
new file mode 100644
index 0000000000..d7297b481b
--- /dev/null
+++ b/support/texlab/src/features/completion/field.rs
@@ -0,0 +1,200 @@
+use cancellation::CancellationToken;
+use cstree::TextRange;
+use lsp_types::CompletionParams;
+
+use crate::{
+ features::cursor::CursorContext,
+ syntax::{bibtex, CstNode},
+ LANGUAGE_DATA,
+};
+
+use super::types::{InternalCompletionItem, InternalCompletionItemData};
+
+pub fn complete_fields<'a>(
+ context: &'a CursorContext<CompletionParams>,
+ items: &mut Vec<InternalCompletionItem<'a>>,
+ cancellation_token: &CancellationToken,
+) -> Option<()> {
+ cancellation_token.result().ok()?;
+
+ let token = context.cursor.as_bibtex()?;
+ let range = if token.kind() == bibtex::WORD {
+ token.text_range()
+ } else {
+ TextRange::empty(context.offset)
+ };
+
+ let parent = token.parent();
+ if let Some(entry) = bibtex::Entry::cast(parent) {
+ if entry.key()?.text_range() == token.text_range() {
+ return None;
+ }
+ } else {
+ bibtex::Field::cast(parent)?;
+ }
+
+ for field in &LANGUAGE_DATA.fields {
+ let data = InternalCompletionItemData::Field { field };
+ let item = InternalCompletionItem::new(range, data);
+ items.push(item);
+ }
+ Some(())
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::features::testing::FeatureTester;
+
+ use super::*;
+
+ #[test]
+ fn test_empty_latex_document() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.tex", "")])
+ .main("main.tex")
+ .line(0)
+ .character(0)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[test]
+ fn test_empty_bibtex_document() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "")])
+ .main("main.bib")
+ .line(0)
+ .character(0)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[test]
+ fn test_empty_entry_open() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "@article{foo,")])
+ .main("main.bib")
+ .line(0)
+ .character(13)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(13.into(), 13.into()));
+ }
+ }
+
+ #[test]
+ fn test_empty_entry_closed() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "@article{foo,}")])
+ .main("main.bib")
+ .line(0)
+ .character(13)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(13.into(), 13.into()));
+ }
+ }
+
+ #[test]
+ fn test_entry_field_name() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "@article{foo, a")])
+ .main("main.bib")
+ .line(0)
+ .character(15)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(14.into(), 15.into()));
+ }
+ }
+
+ #[test]
+ fn test_entry_field_value() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "@article{foo, author = bar}")])
+ .main("main.bib")
+ .line(0)
+ .character(24)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(actual_items.is_empty());
+ }
+
+ #[test]
+ fn test_entry_two_fields_name_closed() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "@article{foo, author = bar, baz}")])
+ .main("main.bib")
+ .line(0)
+ .character(29)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(28.into(), 31.into()));
+ }
+ }
+
+ #[test]
+ fn test_entry_two_fields_name_open() {
+ let request = FeatureTester::builder()
+ .files(vec![("main.bib", "@article{foo, author = bar, baz")])
+ .main("main.bib")
+ .line(0)
+ .character(29)
+ .build()
+ .completion();
+
+ let context = CursorContext::new(request);
+ let mut actual_items = Vec::new();
+ complete_fields(&context, &mut actual_items, CancellationToken::none());
+
+ assert!(!actual_items.is_empty());
+ for item in actual_items {
+ assert_eq!(item.range, TextRange::new(28.into(), 31.into()));
+ }
+ }
+}