Skip to content

Use smallest spanning node as the "starter" node in completions #805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
65 changes: 50 additions & 15 deletions crates/ark/src/lsp/completions/sources/composite/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,8 @@ fn completions_from_workspace_arguments(
#[cfg(test)]
mod tests {
use harp::eval::RParseEvalOptions;
use tree_sitter::Point;

use crate::fixtures::point_from_cursor;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find tests that use this fixture much easier to read, so I've updated anything I touched to use this.

use crate::lsp::completions::sources::composite::call::completions_from_call;
use crate::lsp::document_context::DocumentContext;
use crate::lsp::documents::Document;
Expand All @@ -313,8 +313,8 @@ mod tests {
fn test_completions_after_user_types_part_of_an_argument_name() {
r_task(|| {
// Right after `tab`
let point = Point { row: 0, column: 9 };
let document = Document::new("match(tab)", None);
let (text, point) = point_from_cursor("match(tab@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap().unwrap();

Expand All @@ -324,8 +324,8 @@ mod tests {
assert_eq!(completions.get(1).unwrap().label, "table = ");

// Right after `tab`
let point = Point { row: 0, column: 12 };
let document = Document::new("match(1, tab)", None);
let (text, point) = point_from_cursor("match(1, tab@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap().unwrap();

Expand All @@ -342,8 +342,8 @@ mod tests {
// Can't find the function
r_task(|| {
// Place cursor between `()`
let point = Point { row: 0, column: 21 };
let document = Document::new("not_a_known_function()", None);
let (text, point) = point_from_cursor("not_a_known_function(@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap();
assert!(completions.is_none());
Expand All @@ -360,8 +360,8 @@ mod tests {
harp::parse_eval("my_fun <- function(y, x) x + y", options.clone()).unwrap();

// Place cursor between `()`
let point = Point { row: 0, column: 7 };
let document = Document::new("my_fun()", None);
let (text, point) = point_from_cursor("my_fun(@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap().unwrap();

Expand All @@ -375,15 +375,15 @@ mod tests {
assert_eq!(completion.label, "x = ");

// Place just before the `()`
let point = Point { row: 0, column: 6 };
let document = Document::new("my_fun()", None);
let (text, point) = point_from_cursor("my_fun@()");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap();
assert!(completions.is_none());

// Place just after the `()`
let point = Point { row: 0, column: 8 };
let document = Document::new("my_fun()", None);
let (text, point) = point_from_cursor("my_fun()@");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap();
assert!(completions.is_none());
Expand All @@ -403,8 +403,8 @@ mod tests {
harp::parse_eval("my_fun <- 1", options.clone()).unwrap();

// Place cursor between `()`
let point = Point { row: 0, column: 7 };
let document = Document::new("my_fun()", None);
let (text, point) = point_from_cursor("my_fun(@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap().unwrap();
assert_eq!(completions.len(), 0);
Expand All @@ -413,4 +413,39 @@ mod tests {
harp::parse_eval("remove(my_fun)", options.clone()).unwrap();
})
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here are some tests re: "the rlang::abort() case (using a base R function)".

#[test]
fn test_completions_multiline_call() {
r_task(|| {
// No arguments typed yet
let (text, point) = point_from_cursor("match(\n @\n)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap().unwrap();

assert_eq!(completions.len(), 4);
assert_eq!(completions.get(0).unwrap().label, "x = ");
assert_eq!(completions.get(1).unwrap().label, "table = ");

// Partially typed argument
let (text, point) = point_from_cursor("match(\n tab@\n)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap().unwrap();

assert_eq!(completions.len(), 4);
assert_eq!(completions.get(0).unwrap().label, "x = ");
assert_eq!(completions.get(1).unwrap().label, "table = ");

// Partially typed second argument
let (text, point) = point_from_cursor("match(\n 1,\n tab@\n)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
let completions = completions_from_call(&context, None).unwrap().unwrap();

assert_eq!(completions.len(), 4);
assert_eq!(completions.get(0).unwrap().label, "x = ");
assert_eq!(completions.get(1).unwrap().label, "table = ");
})
}
}
16 changes: 16 additions & 0 deletions crates/ark/src/lsp/completions/sources/unique/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,18 +302,34 @@ mod tests {
let (text, point) = point_from_cursor("Sys.getenv(@)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// Inside the parentheses, multiline
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I just interleaved the multiline scenarios inside the existing test. This is the Sys.getenv() custom completion case.

let (text, point) = point_from_cursor("Sys.getenv(\n @\n)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// Named argument
let (text, point) = point_from_cursor("Sys.getenv(x = @)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// Named argument, multiline
let (text, point) = point_from_cursor("Sys.getenv(\n x = @\n)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// Typed some and then requested completions
let (text, point) = point_from_cursor("Sys.getenv(ARK_@)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// Typed some and then requested completions, multiline
let (text, point) = point_from_cursor("Sys.getenv(\n ARK_@\n)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// After a named argument
let (text, point) = point_from_cursor("Sys.getenv(unset = '1', @)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// After a named argument, multiline
let (text, point) = point_from_cursor("Sys.getenv(\n unset = '1',\n @\n)");
assert_has_ark_test_envvar_completion(text.as_str(), point);

// Should not have it here
let (text, point) = point_from_cursor("Sys.getenv('foo', @)");
let document = Document::new(text.as_str(), None);
Expand Down
66 changes: 39 additions & 27 deletions crates/ark/src/lsp/completions/sources/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pub(super) enum CallNodePositionType {

pub(super) fn call_node_position_type(node: &Node, point: Point) -> CallNodePositionType {
match node.node_type() {
NodeType::Arguments => return CallNodePositionType::Name,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fixup for argument completions inside a call and custom completions such as Sys.getenv().

NodeType::Anonymous(kind) if kind == "(" => {
if point.is_before_or_equal(node.start_position()) {
// Before the `(`
Expand Down Expand Up @@ -267,9 +268,9 @@ fn completions_from_object_names_impl(
#[cfg(test)]
mod tests {
use harp::eval::parse_eval_global;
use tree_sitter::Point;

use crate::fixtures::package_is_installed;
use crate::fixtures::point_from_cursor;
use crate::lsp::completions::sources::utils::call_node_position_type;
use crate::lsp::completions::sources::utils::completions_from_evaluated_object_names;
use crate::lsp::completions::sources::utils::CallNodePositionType;
Expand All @@ -282,8 +283,8 @@ mod tests {
#[test]
fn test_call_node_position_type() {
// Before `(`, but on it
let point = Point { row: 0, column: 3 };
let document = Document::new("fn ()", None);
let (text, point) = point_from_cursor("fn @()");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
context.node.node_type(),
Expand All @@ -295,8 +296,8 @@ mod tests {
);

// After `)`, but on it
let point = Point { row: 0, column: 4 };
let document = Document::new("fn()", None);
let (text, point) = point_from_cursor("fn()@");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
context.node.node_type(),
Expand All @@ -308,8 +309,8 @@ mod tests {
);

// After `(`, but on it
let point = Point { row: 0, column: 3 };
let document = Document::new("fn()", None);
let (text, point) = point_from_cursor("fn(@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
context.node.node_type(),
Expand All @@ -321,26 +322,26 @@ mod tests {
);

// After `x`
let point = Point { row: 0, column: 4 };
let document = Document::new("fn(x)", None);
let (text, point) = point_from_cursor("fn(x@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
call_node_position_type(&context.node, context.point),
CallNodePositionType::Ambiguous
);

// After `x`
let point = Point { row: 0, column: 7 };
let document = Document::new("fn(1, x)", None);
let (text, point) = point_from_cursor("fn(1, x@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
call_node_position_type(&context.node, context.point),
CallNodePositionType::Ambiguous
);

// Directly after `,`
let point = Point { row: 0, column: 5 };
let document = Document::new("fn(x, )", None);
let (text, point) = point_from_cursor("fn(x,@ )");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(context.node.node_type(), NodeType::Comma);
assert_eq!(
Expand All @@ -349,8 +350,8 @@ mod tests {
);

// After `,`, but on `)`
let point = Point { row: 0, column: 6 };
let document = Document::new("fn(x, )", None);
let (text, point) = point_from_cursor("fn(x, @)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
context.node.node_type(),
Expand All @@ -362,8 +363,8 @@ mod tests {
);

// After `=`
let point = Point { row: 0, column: 6 };
let document = Document::new("fn(x =)", None);
let (text, point) = point_from_cursor("fn(x =@ )");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
context.node.node_type(),
Expand All @@ -375,17 +376,17 @@ mod tests {
);

// In an expression
let point = Point { row: 0, column: 4 };
let document = Document::new("fn(1 + 1)", None);
let (text, point) = point_from_cursor("fn(1@ + 1)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(context.node.node_type(), NodeType::Float);
assert_eq!(
call_node_position_type(&context.node, context.point),
CallNodePositionType::Value
);

let point = Point { row: 0, column: 8 };
let document = Document::new("fn(1 + 1)", None);
let (text, point) = point_from_cursor("fn(1 + 1@)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(context.node.node_type(), NodeType::Float);
assert_eq!(
Expand All @@ -395,8 +396,8 @@ mod tests {

// Right before an expression
// (special case where we still provide argument completions)
let point = Point { row: 0, column: 6 };
let document = Document::new("fn(1, 1 + 1)", None);
let (text, point) = point_from_cursor("fn(1, @1 + 1)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(context.node.node_type(), NodeType::Float);
assert_eq!(
Expand All @@ -406,8 +407,8 @@ mod tests {

// After an identifier, before the `)`, with whitespace between them,
// but on the `)`
let point = Point { row: 0, column: 5 };
let document = Document::new("fn(x )", None);
let (text, point) = point_from_cursor("fn(x @)");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert_eq!(
context.node.node_type(),
Expand All @@ -420,14 +421,25 @@ mod tests {

// After an identifier, before the `)`, with whitespace between them,
// but on the identifier
let point = Point { row: 0, column: 4 };
let document = Document::new("fn(x )", None);
let (text, point) = point_from_cursor("fn(x@ )");
let document = Document::new(text.as_str(), None);
let context = DocumentContext::new(&document, point, None);
assert!(context.node.is_identifier());
assert_eq!(
call_node_position_type(&context.node, context.point),
CallNodePositionType::Ambiguous
);

// After `(`, and on own line
let (text, point) = point_from_cursor("fn(\n @\n)");
let document = Document::new(&text, None);
let context = DocumentContext::new(&document, point, None);

assert_eq!(context.node.node_type(), NodeType::Arguments);
assert_eq!(
call_node_position_type(&context.node, context.point),
CallNodePositionType::Name
);
}

#[test]
Expand Down
Loading