Skip to content

Commit

Permalink
Extract out completions_from_string() (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
DavisVaughan authored Dec 14, 2023
1 parent 3d58017 commit d82f634
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 93 deletions.
7 changes: 4 additions & 3 deletions crates/ark/src/lsp/completions/sources/unique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ mod custom;
mod extractor;
mod file_path;
mod namespace;
mod string;

use anyhow::Result;
use colon::completions_from_single_colon;
use comment::completions_from_comment;
use custom::completions_from_custom_source;
use extractor::completions_from_at;
use extractor::completions_from_dollar;
use file_path::completions_from_file_path;
use namespace::completions_from_namespace;
use string::completions_from_string;
use tower_lsp::lsp_types::CompletionItem;

use crate::lsp::document_context::DocumentContext;
Expand All @@ -40,8 +41,8 @@ pub fn completions_from_unique_sources(
return Ok(Some(completions));
}

// Try file completions
if let Some(completions) = completions_from_file_path(context)? {
// Try string (like file path) completions
if let Some(completions) = completions_from_string(context)? {
return Ok(Some(completions));
}

Expand Down
93 changes: 3 additions & 90 deletions crates/ark/src/lsp/completions/sources/unique/file_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,31 +20,11 @@ use crate::lsp::completions::completion_item::completion_item_from_direntry;
use crate::lsp::completions::sources::utils::set_sort_text_by_words_first;
use crate::lsp::document_context::DocumentContext;

pub fn completions_from_file_path(
context: &DocumentContext,
) -> Result<Option<Vec<CompletionItem>>> {
pub(super) fn completions_from_file_path(context: &DocumentContext) -> Result<Vec<CompletionItem>> {
log::info!("completions_from_file_path()");

let node = context.node;

if node.kind() != "string" {
return Ok(None);
}

// Must actually be "inside" the string, so these places don't count, even
// though they are detected as part of the string nodes `|""|`
if node.start_position() == context.point || node.end_position() == context.point {
return Ok(None);
}

let mut completions: Vec<CompletionItem> = vec![];

// Return empty set if we are here due to a trigger character like `$`.
// See posit-dev/positron#1884.
if context.trigger.is_some() {
return Ok(Some(completions));
}

// Get the contents of the string token.
//
// NOTE: This includes the quotation characters on the string, and so
Expand Down Expand Up @@ -77,6 +57,7 @@ pub fn completions_from_file_path(
// look for files in this directory
log::info!("Reading directory: {}", path.display());
let entries = std::fs::read_dir(path)?;

for entry in entries.into_iter() {
let entry = unwrap!(entry, Err(error) => {
log::error!("{}", error);
Expand All @@ -95,73 +76,5 @@ pub fn completions_from_file_path(
// the sort list (like those starting with `.`)
set_sort_text_by_words_first(&mut completions);

Ok(Some(completions))
}

#[cfg(test)]
mod tests {
use harp::assert_match;
use tree_sitter::Point;

use crate::lsp::completions::sources::completions_from_unique_sources;
use crate::lsp::completions::sources::unique::file_path::completions_from_file_path;
use crate::lsp::document_context::DocumentContext;
use crate::lsp::documents::Document;
use crate::test::r_test;

#[test]
fn test_file_path_outside_quotes() {
r_test(|| {
// Before or after the `''`, i.e. `|''` or `''|`.
// Still considered part of the string node.
let point = Point { row: 0, column: 0 };
let document = Document::new("''");
let context = DocumentContext::new(&document, point, None);

assert_eq!(context.node.kind(), "string");
assert_eq!(completions_from_file_path(&context).unwrap(), None);
})
}

#[test]
fn test_file_path_not_string() {
r_test(|| {
let point = Point { row: 0, column: 0 };
let document = Document::new("foo");
let context = DocumentContext::new(&document, point, None);

assert_eq!(context.node.kind(), "identifier");
assert_eq!(completions_from_file_path(&context).unwrap(), None);
})
}

#[test]
fn test_file_path_trigger() {
r_test(|| {
// Before or after the `''`, i.e. `|''` or `''|`.
// Still considered part of the string node.
let point = Point { row: 0, column: 2 };

// Assume home directory is not empty
let document = Document::new("'~/'");

// `None` trigger -> Return file completions
let context = DocumentContext::new(&document, point, None);
assert_match!(
completions_from_file_path(&context).unwrap(),
Some(items) => {
assert!(items.len() > 0)
}
);

// `Some` trigger -> Should return empty completion set
let context = DocumentContext::new(&document, point, Some(String::from("$")));
let res = completions_from_file_path(&context).unwrap();
assert_match!(res, Some(items) => { assert!(items.len() == 0) });

// Check one level up too
let res = completions_from_unique_sources(&context).unwrap();
assert_match!(res, Some(items) => { assert!(items.len() == 0) });
})
}
Ok(completions)
}
112 changes: 112 additions & 0 deletions crates/ark/src/lsp/completions/sources/unique/string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// string.rs
//
// Copyright (C) 2023 Posit Software, PBC. All rights reserved.
//
//

use anyhow::Result;
use tower_lsp::lsp_types::CompletionItem;

use super::file_path::completions_from_file_path;
use crate::lsp::document_context::DocumentContext;

pub fn completions_from_string(context: &DocumentContext) -> Result<Option<Vec<CompletionItem>>> {
log::info!("completions_from_string()");

let node = context.node;

if node.kind() != "string" {
return Ok(None);
}

// Must actually be "inside" the string, so these places don't count, even
// though they are detected as part of the string nodes `|""|`
if node.start_position() == context.point || node.end_position() == context.point {
return Ok(None);
}

// Even if we don't find any completions, we were inside a string so we
// don't want to provide completions for anything else, so we always at
// least return an empty `completions` vector from here.
let mut completions: Vec<CompletionItem> = vec![];

// Return empty set if we are here due to a trigger character like `$`.
// See posit-dev/positron#1884.
if context.trigger.is_some() {
return Ok(Some(completions));
}

// Try file path completions
completions.append(&mut completions_from_file_path(context)?);

Ok(Some(completions))
}

#[cfg(test)]
mod tests {
use harp::assert_match;
use tree_sitter::Point;

use crate::lsp::completions::sources::completions_from_unique_sources;
use crate::lsp::completions::sources::unique::string::completions_from_string;
use crate::lsp::document_context::DocumentContext;
use crate::lsp::documents::Document;
use crate::test::r_test;

#[test]
fn test_outside_quotes() {
r_test(|| {
// Before or after the `''`, i.e. `|''` or `''|`.
// Still considered part of the string node.
let point = Point { row: 0, column: 0 };
let document = Document::new("''");
let context = DocumentContext::new(&document, point, None);

assert_eq!(context.node.kind(), "string");
assert_eq!(completions_from_string(&context).unwrap(), None);
})
}

#[test]
fn test_not_string() {
r_test(|| {
let point = Point { row: 0, column: 0 };
let document = Document::new("foo");
let context = DocumentContext::new(&document, point, None);

assert_eq!(context.node.kind(), "identifier");
assert_eq!(completions_from_string(&context).unwrap(), None);
})
}

#[test]
fn test_trigger() {
r_test(|| {
// Before or after the `''`, i.e. `|''` or `''|`.
// Still considered part of the string node.
let point = Point { row: 0, column: 2 };

// Assume home directory is not empty
let document = Document::new("'~/'");

// `None` trigger -> Return file completions
let context = DocumentContext::new(&document, point, None);
assert_match!(
completions_from_string(&context).unwrap(),
Some(items) => {
assert!(items.len() > 0)
}
);

// `Some` trigger -> Should return empty completion set
let context = DocumentContext::new(&document, point, Some(String::from("$")));
let res = completions_from_string(&context).unwrap();
assert_match!(res, Some(items) => { assert!(items.len() == 0) });

// Check one level up too
let res = completions_from_unique_sources(&context).unwrap();
assert_match!(res, Some(items) => { assert!(items.len() == 0) });
})
}
}

0 comments on commit d82f634

Please sign in to comment.