From fad5048567f52fab6b9f9715ec1b042c15e7aef0 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Wed, 3 Jun 2026 00:13:34 +0200 Subject: [PATCH 1/3] Reshape `DocumentContext` and migrate hover, signature help, and completion to `ArkFile` --- crates/ark/src/fixtures/utils.rs | 11 ++ .../src/lsp/completions/completion_item.rs | 8 +- .../src/lsp/completions/function_context.rs | 20 +--- crates/ark/src/lsp/completions/provide.rs | 2 +- .../src/lsp/completions/sources/composite.rs | 16 +-- .../lsp/completions/sources/composite/call.rs | 60 ++++++---- .../completions/sources/composite/document.rs | 4 +- .../lsp/completions/sources/composite/pipe.rs | 24 ++-- .../completions/sources/composite/subset.rs | 48 +++++--- .../sources/composite/workspace.rs | 2 +- .../lsp/completions/sources/unique/colon.rs | 2 +- .../lsp/completions/sources/unique/comment.rs | 19 ++-- .../lsp/completions/sources/unique/custom.rs | 21 ++-- .../completions/sources/unique/extractor.rs | 48 ++++---- .../completions/sources/unique/file_path.rs | 8 +- .../completions/sources/unique/namespace.rs | 8 +- .../lsp/completions/sources/unique/string.rs | 24 ++-- .../lsp/completions/sources/unique/subset.rs | 45 ++++---- .../ark/src/lsp/completions/sources/utils.rs | 73 +++++++----- crates/ark/src/lsp/completions/tests/utils.rs | 6 +- crates/ark/src/lsp/document_context.rs | 107 ++++++++++-------- crates/ark/src/lsp/handlers.rs | 48 +++++--- crates/ark/src/lsp/hover.rs | 6 +- crates/ark/src/lsp/signature_help.rs | 35 +++--- 24 files changed, 376 insertions(+), 269 deletions(-) diff --git a/crates/ark/src/fixtures/utils.rs b/crates/ark/src/fixtures/utils.rs index 67cfd5fc2a..30ec079006 100644 --- a/crates/ark/src/fixtures/utils.rs +++ b/crates/ark/src/fixtures/utils.rs @@ -29,6 +29,17 @@ pub fn r_test_init() { Console::test_init(); } +pub const TEST_ENCODING: aether_lsp_utils::proto::PositionEncoding = + aether_lsp_utils::proto::PositionEncoding::Wide(biome_line_index::WideEncoding::Utf16); + +pub fn tree_sitter_parse(code: &str) -> tree_sitter::Tree { + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(&tree_sitter_r::LANGUAGE.into()) + .unwrap(); + parser.parse(code, None).unwrap() +} + pub fn point_from_cursor(x: &str) -> (String, Point) { // i.e. looking for `@` in something like `fn(x = @1, y = 2)`, and it treats the // `@` as the cursor position diff --git a/crates/ark/src/lsp/completions/completion_item.rs b/crates/ark/src/lsp/completions/completion_item.rs index 8c16b3cf7a..50fc5a77ef 100644 --- a/crates/ark/src/lsp/completions/completion_item.rs +++ b/crates/ark/src/lsp/completions/completion_item.rs @@ -100,7 +100,7 @@ pub(super) fn completion_item_from_assignment( let lhs = node.child_by_field_name("lhs").into_result()?; let rhs = node.child_by_field_name("rhs").into_result()?; - let label = lhs.node_as_str(&context.document.contents)?.to_string(); + let label = lhs.node_as_str(context.contents)?.to_string(); // TODO: Resolve functions that exist in-document here. let mut item = completion_item(label.clone(), CompletionData::ScopeVariable { @@ -123,7 +123,7 @@ pub(super) fn completion_item_from_assignment( // benefit from the logic in completion_item_from_function() :( if rhs.node_type() == NodeType::FunctionDefinition { if let Some(parameters) = rhs.child_by_field_name("parameters") { - let parameters = parameters.node_as_str(&context.document.contents)?; + let parameters = parameters.node_as_str(context.contents)?; item.detail = Some(join!(label, parameters)); } @@ -644,9 +644,7 @@ fn completion_item_from_dot_dot_dot( item.kind = Some(CompletionItemKind::FIELD); - let position = context - .document - .lsp_position_from_tree_sitter_point(context.point)?; + let position = context.lsp_position_from_tree_sitter_point(context.point)?; let range = Range { start: position, diff --git a/crates/ark/src/lsp/completions/function_context.rs b/crates/ark/src/lsp/completions/function_context.rs index c594848bfb..d89d774f9c 100644 --- a/crates/ark/src/lsp/completions/function_context.rs +++ b/crates/ark/src/lsp/completions/function_context.rs @@ -62,7 +62,6 @@ impl FunctionContext { // function-flavored CompletionItem in this degenerate case, but we // return a dummy FunctionContext just to be safe. let node_end = document_context - .document .lsp_position_from_tree_sitter_point(completion_node.range().end_point)?; return Ok(Self { @@ -74,10 +73,7 @@ impl FunctionContext { }); }; - let usage = determine_function_usage( - &effective_function_node, - &document_context.document.contents, - ); + let usage = determine_function_usage(&effective_function_node, document_context.contents); let function_name_node = if effective_function_node.is_namespace_operator() { // Note: this could be 'None', in the case of, e.g., `dplyr::@` @@ -93,7 +89,7 @@ impl FunctionContext { let name = match function_name_node { Some(node) => node - .node_to_string(&document_context.document.contents) + .node_to_string(document_context.contents) .unwrap_or_default(), None => String::new(), }; @@ -111,16 +107,12 @@ impl FunctionContext { Ok(Self { name, range: match function_name_node { - Some(node) => document_context - .document - .lsp_range_from_tree_sitter_range(node.range())?, + Some(node) => document_context.lsp_range_from_tree_sitter_range(node.range())?, None => { // Create a zero-width range at the end of the effective_function_node - let node_end = document_context - .document - .lsp_position_from_tree_sitter_point( - effective_function_node.range().end_point, - )?; + let node_end = document_context.lsp_position_from_tree_sitter_point( + effective_function_node.range().end_point, + )?; lsp_types::Range::new(node_end, node_end) }, }, diff --git a/crates/ark/src/lsp/completions/provide.rs b/crates/ark/src/lsp/completions/provide.rs index 44e6e64f73..6006d1efad 100644 --- a/crates/ark/src/lsp/completions/provide.rs +++ b/crates/ark/src/lsp/completions/provide.rs @@ -25,7 +25,7 @@ pub(crate) fn provide_completions( "provide_completions() - Completion node text: '{node_text}', Node type: '{node_type:?}'", node_text = document_context .node - .node_as_str(&document_context.document.contents) + .node_as_str(document_context.contents) .unwrap_or_default(), node_type = document_context.node.node_type() ); diff --git a/crates/ark/src/lsp/completions/sources/composite.rs b/crates/ark/src/lsp/completions/sources/composite.rs index 369bc88317..b43e73db9d 100644 --- a/crates/ark/src/lsp/completions/sources/composite.rs +++ b/crates/ark/src/lsp/completions/sources/composite.rs @@ -220,7 +220,6 @@ mod tests { use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::composite::get_completions; use crate::lsp::completions::sources::composite::is_identifier_like; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::lsp::state::WorldState; use crate::r_task; @@ -235,8 +234,9 @@ mod tests { // identifiers that we provide completions for for keyword in ["if", "for", "while"] { let (text, point) = point_from_cursor(&format!("{keyword}@")); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert!(is_identifier_like(context.node)); assert_eq!( @@ -251,8 +251,9 @@ mod tests { fn test_get_completions_on_empty_document() { r_task(|| { let (text, point) = point_from_cursor("@"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); @@ -269,8 +270,9 @@ mod tests { r_task(|| { let code = "x <- 1:3\n@\nrnorm(3)"; let (text, point) = point_from_cursor(code); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); diff --git a/crates/ark/src/lsp/completions/sources/composite/call.rs b/crates/ark/src/lsp/completions/sources/composite/call.rs index bbec784cd9..91d442c611 100644 --- a/crates/ark/src/lsp/completions/sources/composite/call.rs +++ b/crates/ark/src/lsp/completions/sources/composite/call.rs @@ -76,7 +76,7 @@ fn completions_from_call( return Ok(None); }; - let callee = callee.node_as_str(&document_context.document.contents)?; + let callee = callee.node_as_str(document_context.contents)?; // - Prefer `root` as the first argument if it exists // - Then fall back to looking it up, if possible @@ -122,7 +122,7 @@ fn get_first_argument(context: &DocumentContext, node: &Node) -> anyhow::Result< return Ok(None); }; - let text = value.node_as_str(&context.document.contents)?; + let text = value.node_as_str(context.contents)?; let options = RParseEvalOptions { forbid_function_calls: true, @@ -280,7 +280,6 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::composite::call::completions_from_call; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::lsp::state::WorldState; use crate::r_task; @@ -290,8 +289,9 @@ mod tests { r_task(|| { // Right after `tab` let (text, point) = point_from_cursor("match(tab@)"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -303,8 +303,9 @@ mod tests { // Right after `tab` let (text, point) = point_from_cursor("match(1, tab@)"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -323,8 +324,9 @@ mod tests { r_task(|| { // Place cursor between `()` let (text, point) = point_from_cursor("not_a_known_function(@)"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); @@ -343,8 +345,9 @@ mod tests { // Place cursor between `()` let (text, point) = point_from_cursor("my_fun(@)"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -360,8 +363,9 @@ mod tests { // Place just before the `()` let (text, point) = point_from_cursor("my_fun@()"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); @@ -369,8 +373,9 @@ mod tests { // Place just after the `()` let (text, point) = point_from_cursor("my_fun()@"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); @@ -392,8 +397,9 @@ mod tests { // Place cursor between `()` let (text, point) = point_from_cursor("my_fun(@)"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -409,8 +415,9 @@ mod tests { r_task(|| { // No arguments typed yet let (text, point) = point_from_cursor("match(\n @\n)"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -421,8 +428,9 @@ mod tests { // Partially typed argument let (text, point) = point_from_cursor("match(\n tab@\n)"); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -433,8 +441,9 @@ mod tests { // 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 document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -450,8 +459,9 @@ mod tests { r_task(|| { fn assert_no_call_completions(code_with_cursor: &str) { let (text, point) = point_from_cursor(code_with_cursor); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/composite/document.rs b/crates/ark/src/lsp/completions/sources/composite/document.rs index 94309912e9..a101af48ca 100644 --- a/crates/ark/src/lsp/completions/sources/composite/document.rs +++ b/crates/ark/src/lsp/completions/sources/composite/document.rs @@ -168,7 +168,7 @@ fn completions_from_document_function_arguments( continue; } - let parameter = node.node_as_str(&context.document.contents)?.to_string(); + let parameter = node.node_as_str(context.contents)?.to_string(); match completion_item_from_scope_parameter(parameter.as_str(), context) { Ok(item) => completions.push(item), Err(err) => log::error!("{err:?}"), @@ -185,7 +185,7 @@ fn call_uses_nse(node: &Node, context: &DocumentContext) -> bool { lhs.is_identifier_or_string().into_result()?; let value = lhs - .node_as_str(&context.document.contents)? + .node_as_str(context.contents)? .to_string(); matches!(value.as_str(), "expression" | "local" | "quote" | "enquote" | "substitute" | "with" | "within").into_result()?; diff --git a/crates/ark/src/lsp/completions/sources/composite/pipe.rs b/crates/ark/src/lsp/completions/sources/composite/pipe.rs index efe3449cda..e39970b9c4 100644 --- a/crates/ark/src/lsp/completions/sources/composite/pipe.rs +++ b/crates/ark/src/lsp/completions/sources/composite/pipe.rs @@ -121,7 +121,7 @@ fn find_pipe_root_name(context: &DocumentContext, node: &Node) -> anyhow::Result let Some(root) = find_pipe_root_node(context, *node)? else { return Ok(None); }; - if !root.is_pipe_operator(&context.document.contents)? { + if !root.is_pipe_operator(context.contents)? { return Ok(None); } @@ -130,7 +130,7 @@ fn find_pipe_root_name(context: &DocumentContext, node: &Node) -> anyhow::Result return Ok(None); }; - while lhs.is_pipe_operator(&context.document.contents)? { + while lhs.is_pipe_operator(context.contents)? { lhs = match lhs.child_by_field_name("lhs") { Some(lhs) => lhs, None => return Ok(None), @@ -138,7 +138,7 @@ fn find_pipe_root_name(context: &DocumentContext, node: &Node) -> anyhow::Result } // Try to evaluate the left-hand side - let root = lhs.node_as_str(&context.document.contents)?.to_string(); + let root = lhs.node_as_str(context.contents)?.to_string(); Ok(Some(root)) } @@ -150,7 +150,7 @@ fn find_pipe_root_node<'a>( let mut root = None; loop { - if node.is_pipe_operator(&context.document.contents)? { + if node.is_pipe_operator(context.contents)? { root = Some(node); } @@ -167,7 +167,6 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::composite::pipe::find_pipe_root; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::r_task; use crate::treesitter::node_find_containing_call; @@ -177,8 +176,9 @@ mod tests { r_task(|| { // Place cursor between `()` of `bar()` let (text, point) = point_from_cursor("x |> foo() %>% bar(@)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let call_node = node_find_containing_call(context.node); let root = find_pipe_root(&context, call_node).unwrap().unwrap(); @@ -190,8 +190,9 @@ mod tests { // `%||%` is not a pipe! // Place cursor between `()` of `bar()` let (text, point) = point_from_cursor("x |> foo() %||% bar(@)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let call_node = node_find_containing_call(context.node); let root = find_pipe_root(&context, call_node).unwrap(); @@ -209,8 +210,9 @@ mod tests { // Place cursor between `()` let (text, point) = point_from_cursor("x %>% foo(@)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let call_node = node_find_containing_call(context.node); let root = find_pipe_root(&context, call_node).unwrap().unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/composite/subset.rs b/crates/ark/src/lsp/completions/sources/composite/subset.rs index 0f9840012f..d3e77e082b 100644 --- a/crates/ark/src/lsp/completions/sources/composite/subset.rs +++ b/crates/ark/src/lsp/completions/sources/composite/subset.rs @@ -80,7 +80,7 @@ pub(crate) fn completions_from_subset( return Ok(Some(vec![])); }; - let text = child.node_as_str(&context.document.contents)?.to_string(); + let text = child.node_as_str(context.contents)?.to_string(); completions_from_evaluated_object_names(&text, ENQUOTE, node.node_type()) } @@ -92,7 +92,6 @@ mod tests { use crate::fixtures::package_is_installed; use crate::lsp::completions::sources::composite::subset::completions_from_subset; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::r_task; @@ -109,8 +108,9 @@ mod tests { // Right after the `[` let point = Point { row: 0, column: 4 }; - let document = Document::new("foo[]", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("foo[]"); + let context = + DocumentContext::new(&tree, "foo[]", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 2); @@ -125,15 +125,17 @@ mod tests { // Right before the `[` let point = Point { row: 0, column: 3 }; - let document = Document::new("foo[]", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("foo[]"); + let context = + DocumentContext::new(&tree, "foo[]", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_subset(&context).unwrap(); assert!(completions.is_none()); // Right after the `]` let point = Point { row: 0, column: 5 }; - let document = Document::new("foo[]", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("foo[]"); + let context = + DocumentContext::new(&tree, "foo[]", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_subset(&context).unwrap(); assert!(completions.is_none()); @@ -153,8 +155,9 @@ mod tests { // Works for single comlumn name completion let point = Point { row: 0, column: 2 }; - let document = Document::new("x[]", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("x[]"); + let context = + DocumentContext::new(&tree, "x[]", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); @@ -165,8 +168,14 @@ mod tests { // Works when completing inside a `c` call let point = Point { row: 0, column: 6 }; - let document = Document::new("x[, c()]", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("x[, c()]"); + let context = DocumentContext::new( + &tree, + "x[, c()]", + crate::fixtures::TEST_ENCODING, + point, + None, + ); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); @@ -177,8 +186,14 @@ mod tests { // Works when completing inside a `c` call with a collumn already selected let point = Point { row: 0, column: 10 }; - let document = Document::new("x[, c(mpg,)]", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("x[, c(mpg,)]"); + let context = DocumentContext::new( + &tree, + "x[, c(mpg,)]", + crate::fixtures::TEST_ENCODING, + point, + None, + ); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); @@ -190,8 +205,9 @@ mod tests { // Works completing subset2 let point = Point { row: 0, column: 3 }; - let document = Document::new("x[[]]", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("x[[]]"); + let context = + DocumentContext::new(&tree, "x[[]]", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); diff --git a/crates/ark/src/lsp/completions/sources/composite/workspace.rs b/crates/ark/src/lsp/completions/sources/composite/workspace.rs index 3d677da47f..0c63740bc0 100644 --- a/crates/ark/src/lsp/completions/sources/composite/workspace.rs +++ b/crates/ark/src/lsp/completions/sources/composite/workspace.rs @@ -62,7 +62,7 @@ fn completions_from_workspace( let mut completions = vec![]; let token = if node.is_identifier() { - node.node_as_str(&context.document.contents)?.to_string() + node.node_as_str(context.contents)?.to_string() } else { "".to_string() }; diff --git a/crates/ark/src/lsp/completions/sources/unique/colon.rs b/crates/ark/src/lsp/completions/sources/unique/colon.rs index 5cf7e67948..8db3316a99 100644 --- a/crates/ark/src/lsp/completions/sources/unique/colon.rs +++ b/crates/ark/src/lsp/completions/sources/unique/colon.rs @@ -43,7 +43,7 @@ fn completions_from_single_colon( } fn is_single_colon(context: &DocumentContext) -> bool { - let Ok(text) = context.node.node_as_str(&context.document.contents) else { + let Ok(text) = context.node.node_as_str(context.contents) else { return false; }; text.eq(":") diff --git a/crates/ark/src/lsp/completions/sources/unique/comment.rs b/crates/ark/src/lsp/completions/sources/unique/comment.rs index 6437c97925..d1415802c0 100644 --- a/crates/ark/src/lsp/completions/sources/unique/comment.rs +++ b/crates/ark/src/lsp/completions/sources/unique/comment.rs @@ -51,7 +51,7 @@ fn completions_from_comment( let pattern = Regex::new(r"^.*\s")?; - let contents = node.node_as_str(&context.document.contents)?; + let contents = node.node_as_str(context.contents)?; let token = pattern.replace(contents, ""); let mut completions: Vec = vec![]; @@ -142,21 +142,22 @@ fn inject_roxygen_comment_after_newline(x: &str) -> String { fn test_comment() { use tree_sitter::Point; - use crate::lsp::document::Document; use crate::r_task; r_task(|| { // If not in a comment, return `None` let point = Point { row: 0, column: 1 }; - let document = Document::new("mean()", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("mean()"); + let context = + DocumentContext::new(&tree, "mean()", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_comment(&context).unwrap(); assert!(completions.is_none()); // If in a comment, return empty vector let point = Point { row: 0, column: 1 }; - let document = Document::new("# mean", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("# mean"); + let context = + DocumentContext::new(&tree, "# mean", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_comment(&context).unwrap().unwrap(); assert!(completions.is_empty()); }); @@ -167,7 +168,6 @@ fn test_roxygen_comment() { use libr::LOGICAL_ELT; use tree_sitter::Point; - use crate::lsp::document::Document; use crate::r_task; r_task(|| unsafe { @@ -183,8 +183,9 @@ fn test_roxygen_comment() { } let point = Point { row: 0, column: 4 }; - let document = Document::new("#' @", None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse("#' @"); + let context = + DocumentContext::new(&tree, "#' @", crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_comment(&context).unwrap().unwrap(); // Make sure we find it diff --git a/crates/ark/src/lsp/completions/sources/unique/custom.rs b/crates/ark/src/lsp/completions/sources/unique/custom.rs index 1d1df66e43..d6a1b491e8 100644 --- a/crates/ark/src/lsp/completions/sources/unique/custom.rs +++ b/crates/ark/src/lsp/completions/sources/unique/custom.rs @@ -210,7 +210,6 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::unique::custom::completions_from_custom_source; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::lsp::state::WorldState; use crate::r_task; @@ -219,8 +218,9 @@ mod tests { fn assert_has_completion(code_with_cursor: &str, name: &str, expected_insert_text: &str) { let (text, point) = point_from_cursor(code_with_cursor); let state = WorldState::default(); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_custom_source(&context).unwrap().unwrap(); @@ -237,8 +237,9 @@ mod tests { fn assert_no_completions(code_with_cursor: &str) { let (text, point) = point_from_cursor(code_with_cursor); let state = WorldState::default(); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_custom_source(&context).unwrap(); @@ -256,8 +257,9 @@ mod tests { let (text, point) = point_from_cursor("library(@)"); let state = WorldState::default(); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let context = CompletionContext::new(&document_context, &state); let n_compls = completions_from_custom_source(&context) @@ -270,8 +272,9 @@ mod tests { let (text, point) = point_from_cursor("library(uti@)"); let state = WorldState::default(); - let document = Document::new(text.as_str(), None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let context = CompletionContext::new(&document_context, &state); let compls = completions_from_custom_source(&context).unwrap().unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/unique/extractor.rs b/crates/ark/src/lsp/completions/sources/unique/extractor.rs index c9682b4b75..0a7d5cb84c 100644 --- a/crates/ark/src/lsp/completions/sources/unique/extractor.rs +++ b/crates/ark/src/lsp/completions/sources/unique/extractor.rs @@ -100,7 +100,7 @@ fn completions_from_extractor( }; // Extract out its name from the document - let text = node.node_as_str(&context.document.contents)?; + let text = node.node_as_str(context.contents)?; completions.append(&mut completions_from_extractor_object(text, fun)?); @@ -208,7 +208,6 @@ mod tests { use crate::fixtures::package_is_installed; use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::unique::extractor::completions_from_dollar; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::r_task; @@ -224,8 +223,9 @@ mod tests { harp::parse_eval("foo <- list(b = 1, a = 2)", options.clone()).unwrap(); let (text, point) = point_from_cursor("foo$@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_dollar(&context).unwrap().unwrap(); assert_eq!(completions.len(), 2); @@ -238,8 +238,9 @@ mod tests { assert_eq!(completion.label, "a".to_string()); let (text, point) = point_from_cursor("foo@$"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let completions = completions_from_dollar(&context).unwrap(); assert!(completions.is_none()); @@ -261,8 +262,9 @@ mod tests { assert_eq!(r_lgl_get(exists.sexp, 0), 0); let (text, point) = point_from_cursor("foo$@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // No error and empty completions list // (If the user is typing pseudocode, we want to respect that and say that we @@ -272,8 +274,9 @@ mod tests { assert_eq!(completions.len(), 0); let (text, point) = point_from_cursor("foo$mat@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // Same as above let completions = completions_from_dollar(&context).unwrap().unwrap(); @@ -285,8 +288,9 @@ mod tests { fn test_dollar_completions_on_complex_lhs() { r_task(|| { let (text, point) = point_from_cursor("list(a = 1, b = 2)$@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // No error and empty completions list // We know we are on the RHS of a `$`, but `r_parse_eval()` will fail on the @@ -310,8 +314,9 @@ mod tests { harp::parse_eval("foo <- list(b = 1, a = 2)", options.clone()).unwrap(); let (text, point) = point_from_cursor("foo@$"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // `None` because we have no completions to provide, and we do want other // completion sources to get a chance to run, as you can put arbitrary @@ -336,8 +341,9 @@ mod tests { harp::parse_eval("foo <- list(abcd = 1, wxyz = 2)", options.clone()).unwrap(); let (text, point) = point_from_cursor("foo$abc@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // All names of `foo`, the frontend filters them let completions = completions_from_dollar(&context).unwrap().unwrap(); @@ -346,8 +352,9 @@ mod tests { assert_eq!(completions.get(1).unwrap().label, String::from("wxyz")); let (text, point) = point_from_cursor("foo$a@bc"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // Same as above let completions = completions_from_dollar(&context).unwrap().unwrap(); @@ -383,8 +390,9 @@ foo <- Foo$new() .unwrap(); let (text, point) = point_from_cursor("foo$@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // We get some default R6 methods back, but we are looking for `abc` let completions = completions_from_dollar(&context).unwrap().unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/unique/file_path.rs b/crates/ark/src/lsp/completions/sources/unique/file_path.rs index 60a9a1b473..14f6b6a245 100644 --- a/crates/ark/src/lsp/completions/sources/unique/file_path.rs +++ b/crates/ark/src/lsp/completions/sources/unique/file_path.rs @@ -32,7 +32,7 @@ pub(super) fn completions_from_string_file_path( // NOTE: This includes the quotation characters on the string, and so // also includes any internal escapes! We need to decode the R string // by parsing it before searching the path entries. - let token = node.node_as_str(&context.document.contents)?; + let token = node.node_as_str(context.contents)?; // It's entirely possible that we can fail to parse the string, `R_ParseVector()` // can fail in various ways. We silently swallow these because they are unlikely @@ -96,7 +96,6 @@ pub(super) fn completions_from_string_file_path( mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::unique::file_path::completions_from_string_file_path; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::r_task; use crate::treesitter::node_find_string; @@ -107,8 +106,9 @@ mod tests { r_task(|| { // "\R" is an unrecognized escape character and `R_ParseVector()` errors on it let (text, point) = point_from_cursor(r#" ".\R\utils.R@" "#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_file_path(&node, &context).unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/unique/namespace.rs b/crates/ark/src/lsp/completions/sources/unique/namespace.rs index c23e48d305..a5d3c63e41 100644 --- a/crates/ark/src/lsp/completions/sources/unique/namespace.rs +++ b/crates/ark/src/lsp/completions/sources/unique/namespace.rs @@ -76,7 +76,7 @@ fn completions_from_namespace( return Ok(Some(completions)); }; - let package = package.node_as_str(&context.document.contents)?; + let package = package.node_as_str(context.contents)?; // Get the package namespace let Ok(namespace) = RFunction::new("base", "getNamespace").add(package).call() else { @@ -246,7 +246,6 @@ mod tests { use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::unique::namespace::completions_from_namespace; use crate::lsp::completions::tests::utils::find_completion_by_label; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::lsp::state::WorldState; use crate::r_task; @@ -255,8 +254,9 @@ mod tests { cursor_text: &str, ) -> anyhow::Result>> { let (text, point) = point_from_cursor(cursor_text); - let document = Document::new(&text, None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); diff --git a/crates/ark/src/lsp/completions/sources/unique/string.rs b/crates/ark/src/lsp/completions/sources/unique/string.rs index 1e0244dd40..f77056afd1 100644 --- a/crates/ark/src/lsp/completions/sources/unique/string.rs +++ b/crates/ark/src/lsp/completions/sources/unique/string.rs @@ -77,7 +77,6 @@ mod tests { use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::unique; use crate::lsp::completions::sources::unique::string::completions_from_string; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::lsp::state::WorldState; use crate::r_task; @@ -90,8 +89,9 @@ mod tests { // Before or after the `''`, i.e. `|''` or `''|`. // Still considered part of the string node. let (text, point) = point_from_cursor("@''"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert!(node_find_string(&context.node).is_some()); assert_eq!(completions_from_string(&context).unwrap(), None); @@ -102,8 +102,9 @@ mod tests { fn test_not_string() { r_task(|| { let (text, point) = point_from_cursor("@foo"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert!(context.node.is_identifier()); assert_eq!(completions_from_string(&context).unwrap(), None); @@ -116,10 +117,11 @@ mod tests { let (text, point) = point_from_cursor("'~/@'"); // Assume home directory is not empty - let document = Document::new(text.as_str(), None); + let tree = crate::fixtures::tree_sitter_parse(&text); // `None` trigger -> Return file completions - let context = DocumentContext::new(&document, point, None); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_match!( completions_from_string(&context).unwrap(), Some(items) => { @@ -128,7 +130,13 @@ mod tests { ); // `Some` trigger -> Should return empty completion set - let context = DocumentContext::new(&document, point, Some(String::from("$"))); + let context = DocumentContext::new( + &tree, + &text, + crate::fixtures::TEST_ENCODING, + point, + Some(String::from("$")), + ); let res = completions_from_string(&context).unwrap(); assert_match!(res, Some(items) => { assert!(items.is_empty()) }); diff --git a/crates/ark/src/lsp/completions/sources/unique/subset.rs b/crates/ark/src/lsp/completions/sources/unique/subset.rs index be83a25c78..1315f6d4da 100644 --- a/crates/ark/src/lsp/completions/sources/unique/subset.rs +++ b/crates/ark/src/lsp/completions/sources/unique/subset.rs @@ -46,7 +46,7 @@ pub(super) fn completions_from_string_subset( // completion sources from running. let mut completions: Vec = vec![]; - let text = node.node_as_str(&context.document.contents)?; + let text = node.node_as_str(context.contents)?; if let Some(mut candidates) = completions_from_evaluated_object_names(text, ENQUOTE, node.node_type())? @@ -68,7 +68,7 @@ fn node_find_object_for_string_subset<'tree>( let mut node = node_find_parent_call(node)?; if node.is_call() { - if !node_is_c_call(&node, &context.document.contents) { + if !node_is_c_call(&node, context.contents) { // Inside a call that isn't `c()` return None; } @@ -124,7 +124,6 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::unique::subset::completions_from_string_subset; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::r_task; use crate::treesitter::node_find_string; @@ -137,8 +136,9 @@ mod tests { // Inside top level `""` let (text, point) = point_from_cursor(r#"foo["@"]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) @@ -158,8 +158,9 @@ mod tests { // Inside `""` in `[[` let (text, point) = point_from_cursor(r#"foo[["@"]]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -168,8 +169,9 @@ mod tests { // Inside `""` as second argument let (text, point) = point_from_cursor(r#"foo[, "@"]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -178,8 +180,9 @@ mod tests { // Inside `""` inside `c()` let (text, point) = point_from_cursor(r#"foo[c("@")]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -188,8 +191,9 @@ mod tests { // Inside `""` inside `c()` with another string already specified let (text, point) = point_from_cursor(r#"foo[c("a", "@")]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -200,8 +204,9 @@ mod tests { // Instead file path completions should kick in, because this is an arbitrary // function call so subset completions don't make sense, but file ones might. let (text, point) = point_from_cursor(r#"foo[fn("@")]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context).unwrap(); assert!(completions.is_none()); @@ -209,8 +214,9 @@ mod tests { // A fake object that we can't get object names for. // It _looks_ like we want string completions though, so we return an empty set. let (text, point) = point_from_cursor(r#"not_real["@"]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -230,8 +236,9 @@ mod tests { parse_eval_global("colnames(foo) <- c('a', 'b')").unwrap(); let (text, point) = point_from_cursor(r#"foo[, "@"]"#); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) diff --git a/crates/ark/src/lsp/completions/sources/utils.rs b/crates/ark/src/lsp/completions/sources/utils.rs index ed2ab79198..334a850b8b 100644 --- a/crates/ark/src/lsp/completions/sources/utils.rs +++ b/crates/ark/src/lsp/completions/sources/utils.rs @@ -100,7 +100,7 @@ pub(super) fn filter_out_dot_prefixes( // Remove completions that start with `.` unless the user explicitly requested them let user_requested_dot = context .node - .node_as_str(&context.document.contents) + .node_as_str(context.contents) .map(|x| x.starts_with(".")) .unwrap_or(false); @@ -293,7 +293,6 @@ mod tests { 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; - use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::r_task; use crate::treesitter::NodeType; @@ -303,8 +302,9 @@ mod tests { fn test_call_node_position_type() { // Before `(`, but on it let (text, point) = point_from_cursor("fn @()"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from("(")) @@ -316,8 +316,9 @@ mod tests { // After `)`, but on it let (text, point) = point_from_cursor("fn()@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from(")")) @@ -329,8 +330,9 @@ mod tests { // After `(`, but on it let (text, point) = point_from_cursor("fn(@)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from("(")) @@ -342,8 +344,9 @@ mod tests { // After `x` let (text, point) = point_from_cursor("fn(x@)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( call_node_position_type(&context.node, context.point), CallNodePositionType::Ambiguous @@ -351,8 +354,9 @@ mod tests { // After `x` let (text, point) = point_from_cursor("fn(1, x@)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( call_node_position_type(&context.node, context.point), CallNodePositionType::Ambiguous @@ -360,8 +364,9 @@ mod tests { // Directly after `,` let (text, point) = point_from_cursor("fn(x,@ )"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!(context.node.node_type(), NodeType::Comma); assert_eq!( call_node_position_type(&context.node, context.point), @@ -370,8 +375,9 @@ mod tests { // After `,`, but on `)` let (text, point) = point_from_cursor("fn(x, @)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from(")")) @@ -383,8 +389,9 @@ mod tests { // After `=` let (text, point) = point_from_cursor("fn(x =@ )"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from("=")) @@ -396,8 +403,9 @@ mod tests { // In an expression let (text, point) = point_from_cursor("fn(1@ + 1)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!(context.node.node_type(), NodeType::Float); assert_eq!( call_node_position_type(&context.node, context.point), @@ -405,8 +413,9 @@ mod tests { ); let (text, point) = point_from_cursor("fn(1 + 1@)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!(context.node.node_type(), NodeType::Float); assert_eq!( call_node_position_type(&context.node, context.point), @@ -416,8 +425,9 @@ mod tests { // Right before an expression // (special case where we still provide argument completions) 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); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!(context.node.node_type(), NodeType::Float); assert_eq!( call_node_position_type(&context.node, context.point), @@ -427,8 +437,9 @@ mod tests { // After an identifier, before the `)`, with whitespace between them, // but on the `)` let (text, point) = point_from_cursor("fn(x @)"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from(")")) @@ -441,8 +452,9 @@ mod tests { // After an identifier, before the `)`, with whitespace between them, // but on the identifier let (text, point) = point_from_cursor("fn(x@ )"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert!(context.node.is_identifier()); assert_eq!( call_node_position_type(&context.node, context.point), @@ -451,8 +463,9 @@ mod tests { // 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); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!(context.node.node_type(), NodeType::Arguments); assert_eq!( diff --git a/crates/ark/src/lsp/completions/tests/utils.rs b/crates/ark/src/lsp/completions/tests/utils.rs index e9ffa26b48..4ead0532f9 100644 --- a/crates/ark/src/lsp/completions/tests/utils.rs +++ b/crates/ark/src/lsp/completions/tests/utils.rs @@ -11,14 +11,14 @@ use tower_lsp::lsp_types::CompletionTextEdit; use crate::fixtures::point_from_cursor; use crate::lsp::completions::provide_completions; use crate::lsp::completions::sources::utils::has_priority_prefix; -use crate::lsp::document::Document; use crate::lsp::document_context::DocumentContext; use crate::lsp::state::WorldState; pub(crate) fn get_completions_at_cursor(cursor_text: &str) -> anyhow::Result> { let (text, point) = point_from_cursor(cursor_text); - let document = Document::new(&text, None); - let document_context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let document_context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); let state = WorldState::default(); match provide_completions(&document_context, &state) { diff --git a/crates/ark/src/lsp/document_context.rs b/crates/ark/src/lsp/document_context.rs index 3e622cd911..010e2d08d7 100644 --- a/crates/ark/src/lsp/document_context.rs +++ b/crates/ark/src/lsp/document_context.rs @@ -5,17 +5,24 @@ // // +use aether_lsp_utils::proto::to_proto; +use aether_lsp_utils::proto::PositionEncoding; +use tower_lsp::lsp_types; use tree_sitter::Node; use tree_sitter::Point; -use crate::lsp::document::Document; use crate::lsp::traits::node::NodeExt; use crate::treesitter::NodeType; use crate::treesitter::NodeTypeExt; #[derive(Debug)] pub struct DocumentContext<'a> { - pub document: &'a Document, + /// We store extracted components of `&ArkFile` + `&dyn ArkDb` here because + /// the latter can't be sent over an `r_task()`. + pub tree: &'a tree_sitter::Tree, + pub contents: &'a str, + pub line_index: biome_line_index::LineIndex, + pub encoding: PositionEncoding, pub node: Node<'a>, /// Formerly known just as "node". This renaming unblocks completion /// improvements, where we really do want to focus on the smallest node @@ -28,22 +35,25 @@ pub struct DocumentContext<'a> { } impl<'a> DocumentContext<'a> { - pub fn new(document: &'a Document, point: Point, trigger: Option) -> Self { - // get reference to AST - let ast = &document.ast; + pub fn new( + tree: &'a tree_sitter::Tree, + contents: &'a str, + encoding: PositionEncoding, + point: Point, + trigger: Option, + ) -> Self { + let line_index = biome_line_index::LineIndex::new(contents); - let Some(node) = ast.root_node().find_smallest_spanning_node(point) else { - let contents = document.contents.to_string(); + let Some(node) = tree.root_node().find_smallest_spanning_node(point) else { panic!( "Failed to find spanning node containing point: {point} with contents '{contents}'" ); }; // find closest node at point - let Some(closest_node) = ast.root_node().find_closest_node_to_point(point) else { + let Some(closest_node) = tree.root_node().find_closest_node_to_point(point) else { // TODO: We really want to track this down and figure out what's happening // and fix it in `find_closest_node_to_point()`. - let contents = document.contents.to_string(); panic!("Failed to find closest node to point: {point} with contents '{contents}'"); }; @@ -85,13 +95,36 @@ impl<'a> DocumentContext<'a> { }; DocumentContext { - document, + tree, + contents, + line_index, + encoding, node, closest_node, point, trigger, } } + + pub fn lsp_position_from_tree_sitter_point( + &self, + point: tree_sitter::Point, + ) -> anyhow::Result { + let line_col = biome_line_index::LineCol { + line: point.row as u32, + col: point.column as u32, + }; + to_proto::position_from_line_col(line_col, &self.line_index, self.encoding) + } + + pub fn lsp_range_from_tree_sitter_range( + &self, + range: tree_sitter::Range, + ) -> anyhow::Result { + let start = self.lsp_position_from_tree_sitter_point(range.start_point)?; + let end = self.lsp_position_from_tree_sitter_point(range.end_point)?; + Ok(lsp_types::Range::new(start, end)) + } } #[cfg(test)] @@ -105,27 +138,17 @@ mod tests { fn test_document_context_start_of_document() { // Empty document let (text, point) = point_from_cursor("@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); - assert_eq!( - context - .node - .node_as_str(&context.document.contents) - .unwrap(), - "" - ); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + assert_eq!(context.node.node_as_str(context.contents).unwrap(), ""); // Start of document with text let (text, point) = point_from_cursor("@1 + 1"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); - assert_eq!( - context - .node - .node_as_str(&context.document.contents) - .unwrap(), - "1" - ); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + assert_eq!(context.node.node_as_str(context.contents).unwrap(), "1"); } #[test] @@ -133,31 +156,24 @@ mod tests { // Cursor at end of identifier "lib" at position (0, 3) // This reproduced a panic where find_smallest_spanning_node returned None let (text, point) = point_from_cursor("lib@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); // The node should be the identifier "lib" - assert_eq!( - context - .node - .node_as_str(&context.document.contents) - .unwrap(), - "lib" - ); + assert_eq!(context.node.node_as_str(context.contents).unwrap(), "lib"); } #[test] fn test_document_context_cursor_on_empty_line() { // as if we're about to type on the second line let (text, point) = point_from_cursor("toupper(letters)\n@"); - let document = Document::new(text.as_str(), None); - let context = DocumentContext::new(&document, point, None); + let tree = crate::fixtures::tree_sitter_parse(&text); + let context = + DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); assert_eq!(context.node.node_type(), NodeType::Program); assert_eq!( - context - .node - .node_as_str(&context.document.contents) - .unwrap(), + context.node.node_as_str(context.contents).unwrap(), "toupper(letters)\n" ); @@ -166,10 +182,7 @@ mod tests { NodeType::Anonymous(String::from(")")) ); assert_eq!( - context - .closest_node - .node_as_str(&context.document.contents) - .unwrap(), + context.closest_node.node_as_str(context.contents).unwrap(), ")" ); } diff --git a/crates/ark/src/lsp/handlers.rs b/crates/ark/src/lsp/handlers.rs index 470180a006..758692d150 100644 --- a/crates/ark/src/lsp/handlers.rs +++ b/crates/ark/src/lsp/handlers.rs @@ -5,7 +5,6 @@ // // -use aether_path::FilePath; use anyhow::anyhow; use serde_json::Value; use stdext::result::ResultExt; @@ -210,17 +209,23 @@ pub(crate) fn handle_completion( params: CompletionParams, state: &WorldState, ) -> LspResult> { - // Get reference to document. let uri = params.text_document_position.text_document.uri; - let document = state.get_document(&FilePath::from_url(&uri))?; + let file = state.ark_file(&uri)?; + let db = &state.db; + let encoding = state.config.position_encoding; let position = params.text_document_position.position; - let point = document.tree_sitter_point_from_lsp_position(position)?; + let point = file.tree_sitter_point_from_lsp_position(db, position)?; let trigger = params.context.and_then(|ctxt| ctxt.trigger_character); - // Build the document context. - let context = DocumentContext::new(document, point, trigger); + let context = DocumentContext::new( + file.tree_sitter(db), + file.contents(db), + encoding, + point, + trigger, + ); lsp::log_info!("Completion context: {:#?}", context); // TODO(oak/completions): Clone so the closure captures by value. `r_task()` @@ -245,13 +250,20 @@ pub(crate) fn handle_completion_resolve(mut item: CompletionItem) -> LspResult LspResult> { let uri = params.text_document_position_params.text_document.uri; - let document = state.get_document(&FilePath::from_url(&uri))?; + let file = state.ark_file(&uri)?; + let db = &state.db; + let encoding = state.config.position_encoding; let position = params.text_document_position_params.position; - let point = document.tree_sitter_point_from_lsp_position(position)?; + let point = file.tree_sitter_point_from_lsp_position(db, position)?; - // build document context - let context = DocumentContext::new(document, point, None); + let context = DocumentContext::new( + file.tree_sitter(db), + file.contents(db), + encoding, + point, + None, + ); // request hover information let result = r_task(|| r_hover(&context)); @@ -280,12 +292,20 @@ pub(crate) fn handle_signature_help( state: &WorldState, ) -> LspResult> { let uri = params.text_document_position_params.text_document.uri; - let document = state.get_document(&FilePath::from_url(&uri))?; + let file = state.ark_file(&uri)?; + let db = &state.db; + let encoding = state.config.position_encoding; let position = params.text_document_position_params.position; - let point = document.tree_sitter_point_from_lsp_position(position)?; - - let context = DocumentContext::new(document, point, None); + let point = file.tree_sitter_point_from_lsp_position(db, position)?; + + let context = DocumentContext::new( + file.tree_sitter(db), + file.contents(db), + encoding, + point, + None, + ); // request signature help let result = r_task(|| r_signature_help(&context)); diff --git a/crates/ark/src/lsp/hover.rs b/crates/ark/src/lsp/hover.rs index 5dadb1f29e..15ee8181b5 100644 --- a/crates/ark/src/lsp/hover.rs +++ b/crates/ark/src/lsp/hover.rs @@ -43,8 +43,8 @@ fn hover_context(node: Node, context: &DocumentContext) -> Result Result anyhow::Result> { // Get document AST + completion position. - let ast = &context.document.ast; + let ast = &context.tree; // Find the node closest to the completion point. let node = ast.root_node(); @@ -106,7 +106,7 @@ pub(crate) fn r_signature_help(context: &DocumentContext) -> anyhow::Result anyhow::Result anyhow::Result anyhow::Result Date: Tue, 16 Jun 2026 18:30:06 +0200 Subject: [PATCH 2/3] Add `TestDocument` helper --- crates/ark/src/fixtures/utils.rs | 11 --- .../src/lsp/completions/sources/composite.rs | 17 ++--- .../lsp/completions/sources/composite/call.rs | 57 ++++++--------- .../lsp/completions/sources/composite/pipe.rs | 17 ++--- .../completions/sources/composite/subset.rs | 47 ++++-------- .../lsp/completions/sources/unique/comment.rs | 17 +++-- .../lsp/completions/sources/unique/custom.rs | 22 +++--- .../completions/sources/unique/extractor.rs | 47 +++++------- .../completions/sources/unique/file_path.rs | 7 +- .../completions/sources/unique/namespace.rs | 7 +- .../lsp/completions/sources/unique/string.rs | 25 +++---- .../lsp/completions/sources/unique/subset.rs | 42 +++++------ .../ark/src/lsp/completions/sources/utils.rs | 72 ++++++++----------- crates/ark/src/lsp/completions/tests/utils.rs | 7 +- crates/ark/src/lsp/document_context.rs | 66 ++++++++++++----- crates/ark/src/lsp/handlers.rs | 3 + crates/ark/src/lsp/signature_help.rs | 22 +++--- 17 files changed, 213 insertions(+), 273 deletions(-) diff --git a/crates/ark/src/fixtures/utils.rs b/crates/ark/src/fixtures/utils.rs index 30ec079006..67cfd5fc2a 100644 --- a/crates/ark/src/fixtures/utils.rs +++ b/crates/ark/src/fixtures/utils.rs @@ -29,17 +29,6 @@ pub fn r_test_init() { Console::test_init(); } -pub const TEST_ENCODING: aether_lsp_utils::proto::PositionEncoding = - aether_lsp_utils::proto::PositionEncoding::Wide(biome_line_index::WideEncoding::Utf16); - -pub fn tree_sitter_parse(code: &str) -> tree_sitter::Tree { - let mut parser = tree_sitter::Parser::new(); - parser - .set_language(&tree_sitter_r::LANGUAGE.into()) - .unwrap(); - parser.parse(code, None).unwrap() -} - pub fn point_from_cursor(x: &str) -> (String, Point) { // i.e. looking for `@` in something like `fn(x = @1, y = 2)`, and it treats the // `@` as the cursor position diff --git a/crates/ark/src/lsp/completions/sources/composite.rs b/crates/ark/src/lsp/completions/sources/composite.rs index b43e73db9d..016e39965f 100644 --- a/crates/ark/src/lsp/completions/sources/composite.rs +++ b/crates/ark/src/lsp/completions/sources/composite.rs @@ -220,7 +220,7 @@ mod tests { use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::composite::get_completions; use crate::lsp::completions::sources::composite::is_identifier_like; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::lsp::state::WorldState; use crate::r_task; use crate::treesitter::NodeType; @@ -234,9 +234,8 @@ mod tests { // identifiers that we provide completions for for keyword in ["if", "for", "while"] { let (text, point) = point_from_cursor(&format!("{keyword}@")); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert!(is_identifier_like(context.node)); assert_eq!( @@ -251,9 +250,8 @@ mod tests { fn test_get_completions_on_empty_document() { r_task(|| { let (text, point) = point_from_cursor("@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); @@ -270,9 +268,8 @@ mod tests { r_task(|| { let code = "x <- 1:3\n@\nrnorm(3)"; let (text, point) = point_from_cursor(code); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); diff --git a/crates/ark/src/lsp/completions/sources/composite/call.rs b/crates/ark/src/lsp/completions/sources/composite/call.rs index 91d442c611..2f355cd81b 100644 --- a/crates/ark/src/lsp/completions/sources/composite/call.rs +++ b/crates/ark/src/lsp/completions/sources/composite/call.rs @@ -280,7 +280,7 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::composite::call::completions_from_call; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::lsp::state::WorldState; use crate::r_task; @@ -289,9 +289,8 @@ mod tests { r_task(|| { // Right after `tab` let (text, point) = point_from_cursor("match(tab@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -303,9 +302,8 @@ mod tests { // Right after `tab` let (text, point) = point_from_cursor("match(1, tab@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -324,9 +322,8 @@ mod tests { r_task(|| { // Place cursor between `()` let (text, point) = point_from_cursor("not_a_known_function(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); @@ -345,9 +342,8 @@ mod tests { // Place cursor between `()` let (text, point) = point_from_cursor("my_fun(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -363,9 +359,8 @@ mod tests { // Place just before the `()` let (text, point) = point_from_cursor("my_fun@()"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); @@ -373,9 +368,8 @@ mod tests { // Place just after the `()` let (text, point) = point_from_cursor("my_fun()@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); @@ -397,9 +391,8 @@ mod tests { // Place cursor between `()` let (text, point) = point_from_cursor("my_fun(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -415,9 +408,8 @@ mod tests { r_task(|| { // No arguments typed yet let (text, point) = point_from_cursor("match(\n @\n)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -428,9 +420,8 @@ mod tests { // Partially typed argument let (text, point) = point_from_cursor("match(\n tab@\n)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -441,9 +432,8 @@ mod tests { // Partially typed second argument let (text, point) = point_from_cursor("match(\n 1,\n tab@\n)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap().unwrap(); @@ -459,9 +449,8 @@ mod tests { r_task(|| { fn assert_no_call_completions(code_with_cursor: &str) { let (text, point) = point_from_cursor(code_with_cursor); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_call(&context).unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/composite/pipe.rs b/crates/ark/src/lsp/completions/sources/composite/pipe.rs index e39970b9c4..15b0d1b0d2 100644 --- a/crates/ark/src/lsp/completions/sources/composite/pipe.rs +++ b/crates/ark/src/lsp/completions/sources/composite/pipe.rs @@ -167,7 +167,7 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::composite::pipe::find_pipe_root; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::r_task; use crate::treesitter::node_find_containing_call; @@ -176,9 +176,8 @@ mod tests { r_task(|| { // Place cursor between `()` of `bar()` let (text, point) = point_from_cursor("x |> foo() %>% bar(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let call_node = node_find_containing_call(context.node); let root = find_pipe_root(&context, call_node).unwrap().unwrap(); @@ -190,9 +189,8 @@ mod tests { // `%||%` is not a pipe! // Place cursor between `()` of `bar()` let (text, point) = point_from_cursor("x |> foo() %||% bar(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let call_node = node_find_containing_call(context.node); let root = find_pipe_root(&context, call_node).unwrap(); @@ -210,9 +208,8 @@ mod tests { // Place cursor between `()` let (text, point) = point_from_cursor("x %>% foo(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let call_node = node_find_containing_call(context.node); let root = find_pipe_root(&context, call_node).unwrap().unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/composite/subset.rs b/crates/ark/src/lsp/completions/sources/composite/subset.rs index d3e77e082b..bfe9ad5939 100644 --- a/crates/ark/src/lsp/completions/sources/composite/subset.rs +++ b/crates/ark/src/lsp/completions/sources/composite/subset.rs @@ -92,7 +92,7 @@ mod tests { use crate::fixtures::package_is_installed; use crate::lsp::completions::sources::composite::subset::completions_from_subset; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::r_task; #[test] @@ -108,9 +108,8 @@ mod tests { // Right after the `[` let point = Point { row: 0, column: 4 }; - let tree = crate::fixtures::tree_sitter_parse("foo[]"); - let context = - DocumentContext::new(&tree, "foo[]", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("foo[]"); + let context = doc.context(point); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 2); @@ -125,17 +124,15 @@ mod tests { // Right before the `[` let point = Point { row: 0, column: 3 }; - let tree = crate::fixtures::tree_sitter_parse("foo[]"); - let context = - DocumentContext::new(&tree, "foo[]", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("foo[]"); + let context = doc.context(point); let completions = completions_from_subset(&context).unwrap(); assert!(completions.is_none()); // Right after the `]` let point = Point { row: 0, column: 5 }; - let tree = crate::fixtures::tree_sitter_parse("foo[]"); - let context = - DocumentContext::new(&tree, "foo[]", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("foo[]"); + let context = doc.context(point); let completions = completions_from_subset(&context).unwrap(); assert!(completions.is_none()); @@ -155,9 +152,8 @@ mod tests { // Works for single comlumn name completion let point = Point { row: 0, column: 2 }; - let tree = crate::fixtures::tree_sitter_parse("x[]"); - let context = - DocumentContext::new(&tree, "x[]", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("x[]"); + let context = doc.context(point); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); @@ -168,14 +164,8 @@ mod tests { // Works when completing inside a `c` call let point = Point { row: 0, column: 6 }; - let tree = crate::fixtures::tree_sitter_parse("x[, c()]"); - let context = DocumentContext::new( - &tree, - "x[, c()]", - crate::fixtures::TEST_ENCODING, - point, - None, - ); + let doc = TestDocument::new("x[, c()]"); + let context = doc.context(point); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); @@ -186,14 +176,8 @@ mod tests { // Works when completing inside a `c` call with a collumn already selected let point = Point { row: 0, column: 10 }; - let tree = crate::fixtures::tree_sitter_parse("x[, c(mpg,)]"); - let context = DocumentContext::new( - &tree, - "x[, c(mpg,)]", - crate::fixtures::TEST_ENCODING, - point, - None, - ); + let doc = TestDocument::new("x[, c(mpg,)]"); + let context = doc.context(point); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); @@ -205,9 +189,8 @@ mod tests { // Works completing subset2 let point = Point { row: 0, column: 3 }; - let tree = crate::fixtures::tree_sitter_parse("x[[]]"); - let context = - DocumentContext::new(&tree, "x[[]]", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("x[[]]"); + let context = doc.context(point); let completions = completions_from_subset(&context).unwrap().unwrap(); assert_eq!(completions.len(), 11); diff --git a/crates/ark/src/lsp/completions/sources/unique/comment.rs b/crates/ark/src/lsp/completions/sources/unique/comment.rs index d1415802c0..4db1915789 100644 --- a/crates/ark/src/lsp/completions/sources/unique/comment.rs +++ b/crates/ark/src/lsp/completions/sources/unique/comment.rs @@ -22,6 +22,8 @@ use crate::lsp::completions::completion_item::completion_item; use crate::lsp::completions::sources::CompletionSource; use crate::lsp::completions::types::CompletionData; use crate::lsp::document_context::DocumentContext; +#[cfg(test)] +use crate::lsp::document_context::TestDocument; use crate::lsp::traits::node::NodeExt; use crate::treesitter::NodeTypeExt; @@ -147,17 +149,15 @@ fn test_comment() { r_task(|| { // If not in a comment, return `None` let point = Point { row: 0, column: 1 }; - let tree = crate::fixtures::tree_sitter_parse("mean()"); - let context = - DocumentContext::new(&tree, "mean()", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("mean()"); + let context = doc.context(point); let completions = completions_from_comment(&context).unwrap(); assert!(completions.is_none()); // If in a comment, return empty vector let point = Point { row: 0, column: 1 }; - let tree = crate::fixtures::tree_sitter_parse("# mean"); - let context = - DocumentContext::new(&tree, "# mean", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("# mean"); + let context = doc.context(point); let completions = completions_from_comment(&context).unwrap().unwrap(); assert!(completions.is_empty()); }); @@ -183,9 +183,8 @@ fn test_roxygen_comment() { } let point = Point { row: 0, column: 4 }; - let tree = crate::fixtures::tree_sitter_parse("#' @"); - let context = - DocumentContext::new(&tree, "#' @", crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new("#' @"); + let context = doc.context(point); let completions = completions_from_comment(&context).unwrap().unwrap(); // Make sure we find it diff --git a/crates/ark/src/lsp/completions/sources/unique/custom.rs b/crates/ark/src/lsp/completions/sources/unique/custom.rs index d6a1b491e8..4bb2bde44e 100644 --- a/crates/ark/src/lsp/completions/sources/unique/custom.rs +++ b/crates/ark/src/lsp/completions/sources/unique/custom.rs @@ -210,7 +210,7 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::unique::custom::completions_from_custom_source; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::lsp::state::WorldState; use crate::r_task; @@ -218,9 +218,8 @@ mod tests { fn assert_has_completion(code_with_cursor: &str, name: &str, expected_insert_text: &str) { let (text, point) = point_from_cursor(code_with_cursor); let state = WorldState::default(); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_custom_source(&context).unwrap().unwrap(); @@ -237,9 +236,8 @@ mod tests { fn assert_no_completions(code_with_cursor: &str) { let (text, point) = point_from_cursor(code_with_cursor); let state = WorldState::default(); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let context = CompletionContext::new(&document_context, &state); let completions = completions_from_custom_source(&context).unwrap(); @@ -257,9 +255,8 @@ mod tests { let (text, point) = point_from_cursor("library(@)"); let state = WorldState::default(); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let context = CompletionContext::new(&document_context, &state); let n_compls = completions_from_custom_source(&context) @@ -272,9 +269,8 @@ mod tests { let (text, point) = point_from_cursor("library(uti@)"); let state = WorldState::default(); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let context = CompletionContext::new(&document_context, &state); let compls = completions_from_custom_source(&context).unwrap().unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/unique/extractor.rs b/crates/ark/src/lsp/completions/sources/unique/extractor.rs index 0a7d5cb84c..c6cfa74ff0 100644 --- a/crates/ark/src/lsp/completions/sources/unique/extractor.rs +++ b/crates/ark/src/lsp/completions/sources/unique/extractor.rs @@ -208,7 +208,7 @@ mod tests { use crate::fixtures::package_is_installed; use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::unique::extractor::completions_from_dollar; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::r_task; #[test] @@ -223,9 +223,8 @@ mod tests { harp::parse_eval("foo <- list(b = 1, a = 2)", options.clone()).unwrap(); let (text, point) = point_from_cursor("foo$@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let completions = completions_from_dollar(&context).unwrap().unwrap(); assert_eq!(completions.len(), 2); @@ -238,9 +237,8 @@ mod tests { assert_eq!(completion.label, "a".to_string()); let (text, point) = point_from_cursor("foo@$"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let completions = completions_from_dollar(&context).unwrap(); assert!(completions.is_none()); @@ -262,9 +260,8 @@ mod tests { assert_eq!(r_lgl_get(exists.sexp, 0), 0); let (text, point) = point_from_cursor("foo$@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // No error and empty completions list // (If the user is typing pseudocode, we want to respect that and say that we @@ -274,9 +271,8 @@ mod tests { assert_eq!(completions.len(), 0); let (text, point) = point_from_cursor("foo$mat@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // Same as above let completions = completions_from_dollar(&context).unwrap().unwrap(); @@ -288,9 +284,8 @@ mod tests { fn test_dollar_completions_on_complex_lhs() { r_task(|| { let (text, point) = point_from_cursor("list(a = 1, b = 2)$@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // No error and empty completions list // We know we are on the RHS of a `$`, but `r_parse_eval()` will fail on the @@ -314,9 +309,8 @@ mod tests { harp::parse_eval("foo <- list(b = 1, a = 2)", options.clone()).unwrap(); let (text, point) = point_from_cursor("foo@$"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // `None` because we have no completions to provide, and we do want other // completion sources to get a chance to run, as you can put arbitrary @@ -341,9 +335,8 @@ mod tests { harp::parse_eval("foo <- list(abcd = 1, wxyz = 2)", options.clone()).unwrap(); let (text, point) = point_from_cursor("foo$abc@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // All names of `foo`, the frontend filters them let completions = completions_from_dollar(&context).unwrap().unwrap(); @@ -352,9 +345,8 @@ mod tests { assert_eq!(completions.get(1).unwrap().label, String::from("wxyz")); let (text, point) = point_from_cursor("foo$a@bc"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // Same as above let completions = completions_from_dollar(&context).unwrap().unwrap(); @@ -390,9 +382,8 @@ foo <- Foo$new() .unwrap(); let (text, point) = point_from_cursor("foo$@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // We get some default R6 methods back, but we are looking for `abc` let completions = completions_from_dollar(&context).unwrap().unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/unique/file_path.rs b/crates/ark/src/lsp/completions/sources/unique/file_path.rs index 14f6b6a245..fc6f250c74 100644 --- a/crates/ark/src/lsp/completions/sources/unique/file_path.rs +++ b/crates/ark/src/lsp/completions/sources/unique/file_path.rs @@ -96,7 +96,7 @@ pub(super) fn completions_from_string_file_path( mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::unique::file_path::completions_from_string_file_path; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::r_task; use crate::treesitter::node_find_string; @@ -106,9 +106,8 @@ mod tests { r_task(|| { // "\R" is an unrecognized escape character and `R_ParseVector()` errors on it let (text, point) = point_from_cursor(r#" ".\R\utils.R@" "#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_file_path(&node, &context).unwrap(); diff --git a/crates/ark/src/lsp/completions/sources/unique/namespace.rs b/crates/ark/src/lsp/completions/sources/unique/namespace.rs index a5d3c63e41..f89f0ff54b 100644 --- a/crates/ark/src/lsp/completions/sources/unique/namespace.rs +++ b/crates/ark/src/lsp/completions/sources/unique/namespace.rs @@ -246,7 +246,7 @@ mod tests { use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::unique::namespace::completions_from_namespace; use crate::lsp::completions::tests::utils::find_completion_by_label; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::lsp::state::WorldState; use crate::r_task; @@ -254,9 +254,8 @@ mod tests { cursor_text: &str, ) -> anyhow::Result>> { let (text, point) = point_from_cursor(cursor_text); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); let context = CompletionContext::new(&document_context, &state); diff --git a/crates/ark/src/lsp/completions/sources/unique/string.rs b/crates/ark/src/lsp/completions/sources/unique/string.rs index f77056afd1..9f19bd2c32 100644 --- a/crates/ark/src/lsp/completions/sources/unique/string.rs +++ b/crates/ark/src/lsp/completions/sources/unique/string.rs @@ -77,7 +77,7 @@ mod tests { use crate::lsp::completions::completion_context::CompletionContext; use crate::lsp::completions::sources::unique; use crate::lsp::completions::sources::unique::string::completions_from_string; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::lsp::state::WorldState; use crate::r_task; use crate::treesitter::node_find_string; @@ -89,9 +89,8 @@ mod tests { // Before or after the `''`, i.e. `|''` or `''|`. // Still considered part of the string node. let (text, point) = point_from_cursor("@''"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert!(node_find_string(&context.node).is_some()); assert_eq!(completions_from_string(&context).unwrap(), None); @@ -102,9 +101,8 @@ mod tests { fn test_not_string() { r_task(|| { let (text, point) = point_from_cursor("@foo"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert!(context.node.is_identifier()); assert_eq!(completions_from_string(&context).unwrap(), None); @@ -117,11 +115,10 @@ mod tests { let (text, point) = point_from_cursor("'~/@'"); // Assume home directory is not empty - let tree = crate::fixtures::tree_sitter_parse(&text); + let doc = TestDocument::new(&text); // `None` trigger -> Return file completions - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let context = doc.context(point); assert_match!( completions_from_string(&context).unwrap(), Some(items) => { @@ -130,13 +127,7 @@ mod tests { ); // `Some` trigger -> Should return empty completion set - let context = DocumentContext::new( - &tree, - &text, - crate::fixtures::TEST_ENCODING, - point, - Some(String::from("$")), - ); + let context = doc.context_with_trigger(point, Some(String::from("$"))); let res = completions_from_string(&context).unwrap(); assert_match!(res, Some(items) => { assert!(items.is_empty()) }); diff --git a/crates/ark/src/lsp/completions/sources/unique/subset.rs b/crates/ark/src/lsp/completions/sources/unique/subset.rs index 1315f6d4da..921749bc33 100644 --- a/crates/ark/src/lsp/completions/sources/unique/subset.rs +++ b/crates/ark/src/lsp/completions/sources/unique/subset.rs @@ -124,7 +124,7 @@ mod tests { use crate::fixtures::point_from_cursor; use crate::lsp::completions::sources::unique::subset::completions_from_string_subset; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::r_task; use crate::treesitter::node_find_string; @@ -136,9 +136,8 @@ mod tests { // Inside top level `""` let (text, point) = point_from_cursor(r#"foo["@"]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) @@ -158,9 +157,8 @@ mod tests { // Inside `""` in `[[` let (text, point) = point_from_cursor(r#"foo[["@"]]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -169,9 +167,8 @@ mod tests { // Inside `""` as second argument let (text, point) = point_from_cursor(r#"foo[, "@"]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -180,9 +177,8 @@ mod tests { // Inside `""` inside `c()` let (text, point) = point_from_cursor(r#"foo[c("@")]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -191,9 +187,8 @@ mod tests { // Inside `""` inside `c()` with another string already specified let (text, point) = point_from_cursor(r#"foo[c("a", "@")]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -204,9 +199,8 @@ mod tests { // Instead file path completions should kick in, because this is an arbitrary // function call so subset completions don't make sense, but file ones might. let (text, point) = point_from_cursor(r#"foo[fn("@")]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context).unwrap(); assert!(completions.is_none()); @@ -214,9 +208,8 @@ mod tests { // A fake object that we can't get object names for. // It _looks_ like we want string completions though, so we return an empty set. let (text, point) = point_from_cursor(r#"not_real["@"]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) .unwrap() @@ -236,9 +229,8 @@ mod tests { parse_eval_global("colnames(foo) <- c('a', 'b')").unwrap(); let (text, point) = point_from_cursor(r#"foo[, "@"]"#); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let node = node_find_string(&context.node).unwrap(); let completions = completions_from_string_subset(&node, &context) diff --git a/crates/ark/src/lsp/completions/sources/utils.rs b/crates/ark/src/lsp/completions/sources/utils.rs index 334a850b8b..2be22ed892 100644 --- a/crates/ark/src/lsp/completions/sources/utils.rs +++ b/crates/ark/src/lsp/completions/sources/utils.rs @@ -293,7 +293,7 @@ mod tests { 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; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::r_task; use crate::treesitter::NodeType; use crate::treesitter::NodeTypeExt; @@ -302,9 +302,8 @@ mod tests { fn test_call_node_position_type() { // Before `(`, but on it let (text, point) = point_from_cursor("fn @()"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from("(")) @@ -316,9 +315,8 @@ mod tests { // After `)`, but on it let (text, point) = point_from_cursor("fn()@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from(")")) @@ -330,9 +328,8 @@ mod tests { // After `(`, but on it let (text, point) = point_from_cursor("fn(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from("(")) @@ -344,9 +341,8 @@ mod tests { // After `x` let (text, point) = point_from_cursor("fn(x@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( call_node_position_type(&context.node, context.point), CallNodePositionType::Ambiguous @@ -354,9 +350,8 @@ mod tests { // After `x` let (text, point) = point_from_cursor("fn(1, x@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( call_node_position_type(&context.node, context.point), CallNodePositionType::Ambiguous @@ -364,9 +359,8 @@ mod tests { // Directly after `,` let (text, point) = point_from_cursor("fn(x,@ )"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_type(), NodeType::Comma); assert_eq!( call_node_position_type(&context.node, context.point), @@ -375,9 +369,8 @@ mod tests { // After `,`, but on `)` let (text, point) = point_from_cursor("fn(x, @)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from(")")) @@ -389,9 +382,8 @@ mod tests { // After `=` let (text, point) = point_from_cursor("fn(x =@ )"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from("=")) @@ -403,9 +395,8 @@ mod tests { // In an expression let (text, point) = point_from_cursor("fn(1@ + 1)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_type(), NodeType::Float); assert_eq!( call_node_position_type(&context.node, context.point), @@ -413,9 +404,8 @@ mod tests { ); let (text, point) = point_from_cursor("fn(1 + 1@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_type(), NodeType::Float); assert_eq!( call_node_position_type(&context.node, context.point), @@ -425,9 +415,8 @@ mod tests { // Right before an expression // (special case where we still provide argument completions) let (text, point) = point_from_cursor("fn(1, @1 + 1)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_type(), NodeType::Float); assert_eq!( call_node_position_type(&context.node, context.point), @@ -437,9 +426,8 @@ mod tests { // After an identifier, before the `)`, with whitespace between them, // but on the `)` let (text, point) = point_from_cursor("fn(x @)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!( context.node.node_type(), NodeType::Anonymous(String::from(")")) @@ -452,9 +440,8 @@ mod tests { // After an identifier, before the `)`, with whitespace between them, // but on the identifier let (text, point) = point_from_cursor("fn(x@ )"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert!(context.node.is_identifier()); assert_eq!( call_node_position_type(&context.node, context.point), @@ -463,9 +450,8 @@ mod tests { // After `(`, and on own line let (text, point) = point_from_cursor("fn(\n @\n)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_type(), NodeType::Arguments); assert_eq!( diff --git a/crates/ark/src/lsp/completions/tests/utils.rs b/crates/ark/src/lsp/completions/tests/utils.rs index 4ead0532f9..b09a12de1f 100644 --- a/crates/ark/src/lsp/completions/tests/utils.rs +++ b/crates/ark/src/lsp/completions/tests/utils.rs @@ -11,14 +11,13 @@ use tower_lsp::lsp_types::CompletionTextEdit; use crate::fixtures::point_from_cursor; use crate::lsp::completions::provide_completions; use crate::lsp::completions::sources::utils::has_priority_prefix; -use crate::lsp::document_context::DocumentContext; +use crate::lsp::document_context::TestDocument; use crate::lsp::state::WorldState; pub(crate) fn get_completions_at_cursor(cursor_text: &str) -> anyhow::Result> { let (text, point) = point_from_cursor(cursor_text); - let tree = crate::fixtures::tree_sitter_parse(&text); - let document_context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let document_context = doc.context(point); let state = WorldState::default(); match provide_completions(&document_context, &state) { diff --git a/crates/ark/src/lsp/document_context.rs b/crates/ark/src/lsp/document_context.rs index 010e2d08d7..02319414f3 100644 --- a/crates/ark/src/lsp/document_context.rs +++ b/crates/ark/src/lsp/document_context.rs @@ -11,6 +11,10 @@ use tower_lsp::lsp_types; use tree_sitter::Node; use tree_sitter::Point; +#[cfg(test)] +use crate::lsp::ark_file::test_ark_file; +#[cfg(test)] +use crate::lsp::ark_file::ArkFile; use crate::lsp::traits::node::NodeExt; use crate::treesitter::NodeType; use crate::treesitter::NodeTypeExt; @@ -21,7 +25,7 @@ pub struct DocumentContext<'a> { /// the latter can't be sent over an `r_task()`. pub tree: &'a tree_sitter::Tree, pub contents: &'a str, - pub line_index: biome_line_index::LineIndex, + pub line_index: &'a biome_line_index::LineIndex, pub encoding: PositionEncoding, pub node: Node<'a>, /// Formerly known just as "node". This renaming unblocks completion @@ -38,12 +42,11 @@ impl<'a> DocumentContext<'a> { pub fn new( tree: &'a tree_sitter::Tree, contents: &'a str, + line_index: &'a biome_line_index::LineIndex, encoding: PositionEncoding, point: Point, trigger: Option, ) -> Self { - let line_index = biome_line_index::LineIndex::new(contents); - let Some(node) = tree.root_node().find_smallest_spanning_node(point) else { panic!( "Failed to find spanning node containing point: {point} with contents '{contents}'" @@ -114,7 +117,7 @@ impl<'a> DocumentContext<'a> { line: point.row as u32, col: point.column as u32, }; - to_proto::position_from_line_col(line_col, &self.line_index, self.encoding) + to_proto::position_from_line_col(line_col, self.line_index, self.encoding) } pub fn lsp_range_from_tree_sitter_range( @@ -127,6 +130,41 @@ impl<'a> DocumentContext<'a> { } } +/// Owns a `db` + `ArkFile` so unit tests can build a `DocumentContext` the same +/// way handlers do, borrowing the cached tree and line index from the database. +#[cfg(test)] +pub(crate) struct TestDocument { + db: oak_db::OakDatabase, + file: ArkFile, +} + +#[cfg(test)] +impl TestDocument { + pub(crate) fn new(contents: &str) -> Self { + let (db, file) = test_ark_file(contents); + Self { db, file } + } + + pub(crate) fn context(&self, point: Point) -> DocumentContext<'_> { + self.context_with_trigger(point, None) + } + + pub(crate) fn context_with_trigger( + &self, + point: Point, + trigger: Option, + ) -> DocumentContext<'_> { + DocumentContext::new( + self.file.tree_sitter(&self.db), + self.file.contents(&self.db), + self.file.line_index(&self.db), + self.file.encoding, + point, + trigger, + ) + } +} + #[cfg(test)] mod tests { use super::*; @@ -138,16 +176,14 @@ mod tests { fn test_document_context_start_of_document() { // Empty document let (text, point) = point_from_cursor("@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_as_str(context.contents).unwrap(), ""); // Start of document with text let (text, point) = point_from_cursor("@1 + 1"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_as_str(context.contents).unwrap(), "1"); } @@ -156,9 +192,8 @@ mod tests { // Cursor at end of identifier "lib" at position (0, 3) // This reproduced a panic where find_smallest_spanning_node returned None let (text, point) = point_from_cursor("lib@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); // The node should be the identifier "lib" assert_eq!(context.node.node_as_str(context.contents).unwrap(), "lib"); } @@ -167,9 +202,8 @@ mod tests { fn test_document_context_cursor_on_empty_line() { // as if we're about to type on the second line let (text, point) = point_from_cursor("toupper(letters)\n@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); assert_eq!(context.node.node_type(), NodeType::Program); assert_eq!( diff --git a/crates/ark/src/lsp/handlers.rs b/crates/ark/src/lsp/handlers.rs index 758692d150..a79178341f 100644 --- a/crates/ark/src/lsp/handlers.rs +++ b/crates/ark/src/lsp/handlers.rs @@ -222,6 +222,7 @@ pub(crate) fn handle_completion( let context = DocumentContext::new( file.tree_sitter(db), file.contents(db), + file.line_index(db), encoding, point, trigger, @@ -260,6 +261,7 @@ pub(crate) fn handle_hover(params: HoverParams, state: &WorldState) -> LspResult let context = DocumentContext::new( file.tree_sitter(db), file.contents(db), + file.line_index(db), encoding, point, None, @@ -302,6 +304,7 @@ pub(crate) fn handle_signature_help( let context = DocumentContext::new( file.tree_sitter(db), file.contents(db), + file.line_index(db), encoding, point, None, diff --git a/crates/ark/src/lsp/signature_help.rs b/crates/ark/src/lsp/signature_help.rs index fcb071a7ce..691c80a7e2 100644 --- a/crates/ark/src/lsp/signature_help.rs +++ b/crates/ark/src/lsp/signature_help.rs @@ -509,7 +509,7 @@ mod tests { use tower_lsp::lsp_types::ParameterLabel; use crate::fixtures::point_from_cursor; - use crate::lsp::document_context::DocumentContext; + use crate::lsp::document_context::TestDocument; use crate::lsp::signature_help::argument_label; use crate::lsp::signature_help::r_signature_help; @@ -517,9 +517,8 @@ mod tests { fn test_basic_signature_help() { crate::r_task(|| { let (text, point) = point_from_cursor("library(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let help = r_signature_help(&context); let help = help.unwrap().unwrap(); @@ -542,17 +541,15 @@ mod tests { fn test_no_signature_help_outside_parentheses() { crate::r_task(|| { let (text, point) = point_from_cursor("library@()"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let help = r_signature_help(&context); let help = help.unwrap(); assert!(help.is_none()); let (text, point) = point_from_cursor("library()@"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let help = r_signature_help(&context); let help = help.unwrap(); assert!(help.is_none()); @@ -579,9 +576,8 @@ fn <- function( harp::parse_eval_global(fun).unwrap(); let (text, point) = point_from_cursor("fn(@)"); - let tree = crate::fixtures::tree_sitter_parse(&text); - let context = - DocumentContext::new(&tree, &text, crate::fixtures::TEST_ENCODING, point, None); + let doc = TestDocument::new(&text); + let context = doc.context(point); let help = r_signature_help(&context); let help = help.unwrap().unwrap(); From 112e465047a73197f376068874bf90b9b818b6f1 Mon Sep 17 00:00:00 2001 From: Lionel Henry Date: Tue, 16 Jun 2026 18:40:05 +0200 Subject: [PATCH 3/3] Address code review --- crates/ark/src/lsp/ark_file.rs | 35 ++++++++++++++----- .../src/lsp/completions/completion_item.rs | 4 ++- .../src/lsp/completions/function_context.rs | 19 +++++++--- crates/ark/src/lsp/document_context.rs | 22 ------------ 4 files changed, 45 insertions(+), 35 deletions(-) diff --git a/crates/ark/src/lsp/ark_file.rs b/crates/ark/src/lsp/ark_file.rs index 8c4245ecd3..d55c35e332 100644 --- a/crates/ark/src/lsp/ark_file.rs +++ b/crates/ark/src/lsp/ark_file.rs @@ -90,11 +90,7 @@ impl ArkFile { db: &dyn ArkDb, point: tree_sitter::Point, ) -> anyhow::Result { - let line_col = biome_line_index::LineCol { - line: point.row as u32, - col: point.column as u32, - }; - to_proto::position_from_line_col(line_col, self.line_index(db), self.encoding) + lsp_position_from_tree_sitter_point(point, self.line_index(db), self.encoding) } pub(crate) fn lsp_range_from_tree_sitter_range( @@ -102,9 +98,7 @@ impl ArkFile { db: &dyn ArkDb, range: tree_sitter::Range, ) -> anyhow::Result { - let start = self.lsp_position_from_tree_sitter_point(db, range.start_point)?; - let end = self.lsp_position_from_tree_sitter_point(db, range.end_point)?; - Ok(lsp_types::Range::new(start, end)) + lsp_range_from_tree_sitter_range(range, self.line_index(db), self.encoding) } pub(crate) fn tree_sitter_range_from_lsp_range( @@ -126,6 +120,31 @@ impl ArkFile { } } +/// Free functions over `LineIndex` + `PositionEncoding`, so anything holding +/// those two (an `ArkFile` plus its `db`, or a `DocumentContext`) can convert +/// without each carrying its own copy of the logic. +pub(crate) fn lsp_position_from_tree_sitter_point( + point: tree_sitter::Point, + line_index: &biome_line_index::LineIndex, + encoding: PositionEncoding, +) -> anyhow::Result { + let line_col = biome_line_index::LineCol { + line: point.row as u32, + col: point.column as u32, + }; + to_proto::position_from_line_col(line_col, line_index, encoding) +} + +pub(crate) fn lsp_range_from_tree_sitter_range( + range: tree_sitter::Range, + line_index: &biome_line_index::LineIndex, + encoding: PositionEncoding, +) -> anyhow::Result { + let start = lsp_position_from_tree_sitter_point(range.start_point, line_index, encoding)?; + let end = lsp_position_from_tree_sitter_point(range.end_point, line_index, encoding)?; + Ok(lsp_types::Range::new(start, end)) +} + #[cfg(test)] pub(crate) fn test_ark_file(code: &str) -> (oak_db::OakDatabase, ArkFile) { use aether_path::FilePath; diff --git a/crates/ark/src/lsp/completions/completion_item.rs b/crates/ark/src/lsp/completions/completion_item.rs index 50fc5a77ef..e266f57d5f 100644 --- a/crates/ark/src/lsp/completions/completion_item.rs +++ b/crates/ark/src/lsp/completions/completion_item.rs @@ -36,6 +36,7 @@ use tower_lsp::lsp_types::Range; use tower_lsp::lsp_types::TextEdit; use tree_sitter::Node; +use crate::lsp::ark_file::lsp_position_from_tree_sitter_point; use crate::lsp::completions::function_context::ArgumentsStatus; use crate::lsp::completions::function_context::FunctionContext; use crate::lsp::completions::function_context::FunctionRefUsage; @@ -644,7 +645,8 @@ fn completion_item_from_dot_dot_dot( item.kind = Some(CompletionItemKind::FIELD); - let position = context.lsp_position_from_tree_sitter_point(context.point)?; + let position = + lsp_position_from_tree_sitter_point(context.point, context.line_index, context.encoding)?; let range = Range { start: position, diff --git a/crates/ark/src/lsp/completions/function_context.rs b/crates/ark/src/lsp/completions/function_context.rs index d89d774f9c..0d6cbceaf7 100644 --- a/crates/ark/src/lsp/completions/function_context.rs +++ b/crates/ark/src/lsp/completions/function_context.rs @@ -10,6 +10,8 @@ use tower_lsp::lsp_types; use tower_lsp::lsp_types::Range; use tree_sitter::Node; +use crate::lsp::ark_file::lsp_position_from_tree_sitter_point; +use crate::lsp::ark_file::lsp_range_from_tree_sitter_range; use crate::lsp::document_context::DocumentContext; use crate::lsp::traits::node::NodeExt; use crate::treesitter::node_find_parent_call; @@ -61,8 +63,11 @@ impl FunctionContext { // We shouldn't ever attempt to instantiate a FunctionContext or // function-flavored CompletionItem in this degenerate case, but we // return a dummy FunctionContext just to be safe. - let node_end = document_context - .lsp_position_from_tree_sitter_point(completion_node.range().end_point)?; + let node_end = lsp_position_from_tree_sitter_point( + completion_node.range().end_point, + document_context.line_index, + document_context.encoding, + )?; return Ok(Self { name: String::new(), @@ -107,11 +112,17 @@ impl FunctionContext { Ok(Self { name, range: match function_name_node { - Some(node) => document_context.lsp_range_from_tree_sitter_range(node.range())?, + Some(node) => lsp_range_from_tree_sitter_range( + node.range(), + document_context.line_index, + document_context.encoding, + )?, None => { // Create a zero-width range at the end of the effective_function_node - let node_end = document_context.lsp_position_from_tree_sitter_point( + let node_end = lsp_position_from_tree_sitter_point( effective_function_node.range().end_point, + document_context.line_index, + document_context.encoding, )?; lsp_types::Range::new(node_end, node_end) }, diff --git a/crates/ark/src/lsp/document_context.rs b/crates/ark/src/lsp/document_context.rs index 02319414f3..e6d3e39001 100644 --- a/crates/ark/src/lsp/document_context.rs +++ b/crates/ark/src/lsp/document_context.rs @@ -5,9 +5,7 @@ // // -use aether_lsp_utils::proto::to_proto; use aether_lsp_utils::proto::PositionEncoding; -use tower_lsp::lsp_types; use tree_sitter::Node; use tree_sitter::Point; @@ -108,26 +106,6 @@ impl<'a> DocumentContext<'a> { trigger, } } - - pub fn lsp_position_from_tree_sitter_point( - &self, - point: tree_sitter::Point, - ) -> anyhow::Result { - let line_col = biome_line_index::LineCol { - line: point.row as u32, - col: point.column as u32, - }; - to_proto::position_from_line_col(line_col, self.line_index, self.encoding) - } - - pub fn lsp_range_from_tree_sitter_range( - &self, - range: tree_sitter::Range, - ) -> anyhow::Result { - let start = self.lsp_position_from_tree_sitter_point(range.start_point)?; - let end = self.lsp_position_from_tree_sitter_point(range.end_point)?; - Ok(lsp_types::Range::new(start, end)) - } } /// Owns a `db` + `ArkFile` so unit tests can build a `DocumentContext` the same