Skip to content

Commit

Permalink
Implement "string subset" completions (#493)
Browse files Browse the repository at this point in the history
* Add tests to show that we enquote in composite subset completions

* Extract out common subset helper

* Add a few more treesitter `Node` utilities

* Implement string subset completions as a special case of unique string completions

* Custom `r_is_matrix()` just to be sure about the definition

* Add support for extracting `colnames()` from a matrix

* Add test for `matrix[, "<tab>"]` extracting column names

* Adapt to post rebase `main`
  • Loading branch information
DavisVaughan authored Aug 30, 2024
1 parent c9377f8 commit 15ef29e
Show file tree
Hide file tree
Showing 11 changed files with 496 additions and 61 deletions.
1 change: 1 addition & 0 deletions crates/ark/src/lsp/completions/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//
//

mod common;
mod composite;
mod unique;
mod utils;
Expand Down
8 changes: 8 additions & 0 deletions crates/ark/src/lsp/completions/sources/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// common.rs
//
// Copyright (C) 2024 Posit Software, PBC. All rights reserved.
//
//

pub(crate) mod subset;
61 changes: 61 additions & 0 deletions crates/ark/src/lsp/completions/sources/common/subset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// subset.rs
//
// Copyright (C) 2024 Posit Software, PBC. All rights reserved.
//
//

use tree_sitter::Node;
use tree_sitter::Point;

use crate::lsp::traits::point::PointExt;
use crate::treesitter::NodeType;
use crate::treesitter::NodeTypeExt;

pub(crate) fn is_within_subset_delimiters(
x: &Point,
subset_node: &Node,
subset_type: &NodeType,
) -> bool {
let (open, close) = match subset_type {
NodeType::Subset => ("[", "]"),
NodeType::Subset2 => ("[[", "]]"),
_ => std::unreachable!(),
};

let Some(arguments) = subset_node.child_by_field_name("arguments") else {
return false;
};

let n_children = arguments.child_count();

if n_children < 2 {
return false;
}

let Some(open_node) = arguments.child(1 - 1) else {
return false;
};
let Some(close_node) = arguments.child(n_children - 1) else {
return false;
};

// Ensure open and closing nodes are the right type
if !matches!(
open_node.node_type(),
NodeType::Anonymous(kind) if kind == open
) {
return false;
}
if !matches!(
close_node.node_type(),
NodeType::Anonymous(kind) if kind == close
) {
return false;
}

let contains_start = x.is_after_or_equal(open_node.end_position());
let contains_end = x.is_before_or_equal(close_node.start_position());

contains_start && contains_end
}
50 changes: 3 additions & 47 deletions crates/ark/src/lsp/completions/sources/composite/subset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@

use anyhow::Result;
use tower_lsp::lsp_types::CompletionItem;
use tree_sitter::Node;
use tree_sitter::Point;

use crate::lsp::completions::sources::common::subset::is_within_subset_delimiters;
use crate::lsp::completions::sources::utils::completions_from_evaluated_object_names;
use crate::lsp::document_context::DocumentContext;
use crate::lsp::traits::point::PointExt;
use crate::lsp::traits::rope::RopeExt;
use crate::treesitter::NodeType;
use crate::treesitter::NodeTypeExt;
Expand Down Expand Up @@ -73,50 +71,6 @@ pub(super) fn completions_from_subset(
completions_from_evaluated_object_names(&text, ENQUOTE)
}

fn is_within_subset_delimiters(x: &Point, subset_node: &Node, subset_type: &NodeType) -> bool {
let (open, close) = match subset_type {
NodeType::Subset => ("[", "]"),
NodeType::Subset2 => ("[[", "]]"),
_ => std::unreachable!(),
};

let Some(arguments) = subset_node.child_by_field_name("arguments") else {
return false;
};

let n_children = arguments.child_count();

if n_children < 2 {
return false;
}

let Some(open_node) = arguments.child(1 - 1) else {
return false;
};
let Some(close_node) = arguments.child(n_children - 1) else {
return false;
};

// Ensure open and closing nodes are the right type
if !matches!(
open_node.node_type(),
NodeType::Anonymous(kind) if kind == open
) {
return false;
}
if !matches!(
close_node.node_type(),
NodeType::Anonymous(kind) if kind == close
) {
return false;
}

let contains_start = x.is_after_or_equal(open_node.end_position());
let contains_end = x.is_before_or_equal(close_node.start_position());

contains_start && contains_end
}

#[cfg(test)]
mod tests {
use harp::eval::RParseEvalOptions;
Expand Down Expand Up @@ -148,9 +102,11 @@ mod tests {

let completion = completions.get(0).unwrap();
assert_eq!(completion.label, "b".to_string());
assert_eq!(completion.insert_text, Some("\"b\"".to_string()));

let completion = completions.get(1).unwrap();
assert_eq!(completion.label, "a".to_string());
assert_eq!(completion.insert_text, Some("\"a\"".to_string()));

// Right before the `[`
let point = Point { row: 0, column: 3 };
Expand Down
1 change: 1 addition & 0 deletions crates/ark/src/lsp/completions/sources/unique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod extractor;
mod file_path;
mod namespace;
mod string;
mod subset;

use anyhow::Result;
use colon::completions_from_single_colon;
Expand Down
6 changes: 4 additions & 2 deletions crates/ark/src/lsp/completions/sources/unique/file_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ use crate::lsp::completions::sources::utils::set_sort_text_by_words_first;
use crate::lsp::document_context::DocumentContext;
use crate::lsp::traits::rope::RopeExt;

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

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

Expand Down
18 changes: 13 additions & 5 deletions crates/ark/src/lsp/completions/sources/unique/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
use anyhow::Result;
use tower_lsp::lsp_types::CompletionItem;

use super::file_path::completions_from_file_path;
use super::file_path::completions_from_string_file_path;
use crate::lsp::completions::sources::unique::subset::completions_from_string_subset;
use crate::lsp::document_context::DocumentContext;
use crate::treesitter::NodeTypeExt;

Expand All @@ -27,9 +28,9 @@ pub fn completions_from_string(context: &DocumentContext) -> Result<Option<Vec<C
return Ok(None);
}

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

// Return empty set if we are here due to a trigger character like `$`.
Expand All @@ -38,8 +39,15 @@ pub fn completions_from_string(context: &DocumentContext) -> Result<Option<Vec<C
return Ok(Some(completions));
}

// Try file path completions
completions.append(&mut completions_from_file_path(context)?);
// Check if we are doing string subsetting, like `x["<tab>"]`. This is a very unique
// case that takes priority over file path completions.
if let Some(mut candidates) = completions_from_string_subset(context)? {
completions.append(&mut candidates);
return Ok(Some(completions));
}

// If no special string cases are hit, we show file path completions
completions.append(&mut completions_from_string_file_path(context)?);

Ok(Some(completions))
}
Expand Down
Loading

0 comments on commit 15ef29e

Please sign in to comment.