Skip to content

Commit 2749a7d

Browse files
authored
Improve data.table column names autocomplete. (#716)
1 parent 45eef9d commit 2749a7d

File tree

3 files changed

+120
-7
lines changed

3 files changed

+120
-7
lines changed

crates/ark/src/lsp/completions/sources/composite/subset.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,15 @@ pub(super) fn completions_from_subset(
6868

6969
let text = context.document.contents.node_slice(&child)?.to_string();
7070

71-
completions_from_evaluated_object_names(&text, ENQUOTE)
71+
completions_from_evaluated_object_names(&text, ENQUOTE, node.node_type())
7272
}
7373

7474
#[cfg(test)]
7575
mod tests {
7676
use harp::eval::RParseEvalOptions;
7777
use tree_sitter::Point;
7878

79+
use crate::fixtures::package_is_installed;
7980
use crate::lsp::completions::sources::composite::subset::completions_from_subset;
8081
use crate::lsp::document_context::DocumentContext;
8182
use crate::lsp::documents::Document;
@@ -126,4 +127,66 @@ mod tests {
126127
harp::parse_eval("remove(foo)", options.clone()).unwrap();
127128
})
128129
}
130+
131+
#[test]
132+
fn test_data_table_subset_completions() {
133+
r_task(|| {
134+
if !package_is_installed("data.table") {
135+
return;
136+
}
137+
138+
harp::parse_eval_global("x <- data.table::as.data.table(mtcars)").unwrap();
139+
140+
// Works for single comlumn name completion
141+
let point = Point { row: 0, column: 2 };
142+
let document = Document::new("x[]", None);
143+
let context = DocumentContext::new(&document, point, None);
144+
145+
let completions = completions_from_subset(&context).unwrap().unwrap();
146+
assert_eq!(completions.len(), 11);
147+
148+
let completion = completions.get(0).unwrap();
149+
assert_eq!(completion.label, "mpg".to_string());
150+
assert_eq!(completion.insert_text, None); // No enquote, means the label is used directly
151+
152+
// Works when completing inside a `c` call
153+
let point = Point { row: 0, column: 6 };
154+
let document = Document::new("x[, c()]", None);
155+
let context = DocumentContext::new(&document, point, None);
156+
157+
let completions = completions_from_subset(&context).unwrap().unwrap();
158+
assert_eq!(completions.len(), 11);
159+
160+
let completion = completions.get(0).unwrap();
161+
assert_eq!(completion.label, "mpg".to_string());
162+
assert_eq!(completion.insert_text, None); // No enquote, means the label is used directly
163+
164+
// Works when completing inside a `c` call with a collumn already selected
165+
let point = Point { row: 0, column: 10 };
166+
let document = Document::new("x[, c(mpg,)]", None);
167+
let context = DocumentContext::new(&document, point, None);
168+
169+
let completions = completions_from_subset(&context).unwrap().unwrap();
170+
assert_eq!(completions.len(), 11);
171+
172+
let completion = completions.get(0).unwrap();
173+
// TODO: ideally we could assert that mpg doesn't appear again, or appears at the end
174+
assert_eq!(completion.label, "mpg".to_string());
175+
assert_eq!(completion.insert_text, None); // No enquote, means the label is used directly
176+
177+
// Works completing subset2
178+
let point = Point { row: 0, column: 3 };
179+
let document = Document::new("x[[]]", None);
180+
let context = DocumentContext::new(&document, point, None);
181+
182+
let completions = completions_from_subset(&context).unwrap().unwrap();
183+
assert_eq!(completions.len(), 11);
184+
185+
let completion = completions.get(0).unwrap();
186+
assert_eq!(completion.label, "mpg".to_string());
187+
assert_eq!(completion.insert_text, Some("\"mpg\"".to_string())); // Enquoted result
188+
189+
harp::parse_eval_global("remove(x)").unwrap();
190+
})
191+
}
129192
}

crates/ark/src/lsp/completions/sources/unique/subset.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ pub(super) fn completions_from_string_subset(
5050

5151
let text = context.document.contents.node_slice(&node)?.to_string();
5252

53-
if let Some(mut candidates) = completions_from_evaluated_object_names(&text, ENQUOTE)? {
53+
if let Some(mut candidates) =
54+
completions_from_evaluated_object_names(&text, ENQUOTE, node.node_type())?
55+
{
5456
completions.append(&mut candidates);
5557
}
5658

crates/ark/src/lsp/completions/sources/utils.rs

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use harp::eval::RParseEvalOptions;
1111
use harp::exec::RFunction;
1212
use harp::exec::RFunctionExt;
1313
use harp::object::RObject;
14+
use harp::utils::r_inherits;
1415
use regex::Regex;
1516
use tower_lsp::lsp_types::CompletionItem;
1617
use tree_sitter::Node;
@@ -170,6 +171,7 @@ fn call_prev_leaf_position_type(node: &Node, allow_ambiguous: bool) -> CallNodeP
170171
pub(super) fn completions_from_evaluated_object_names(
171172
name: &str,
172173
enquote: bool,
174+
node_type: NodeType,
173175
) -> Result<Option<Vec<CompletionItem>>> {
174176
log::info!("completions_from_evaluated_object_names({name:?})");
175177

@@ -204,6 +206,15 @@ pub(super) fn completions_from_evaluated_object_names(
204206
let completions = if harp::utils::r_is_matrix(object.sexp) {
205207
// Special case just for 2D arrays
206208
completions_from_object_colnames(object, name, enquote)?
209+
} else if r_inherits(object.sexp, "data.table") {
210+
// The `[` method for data.table uses NSE so we don't enquote the names
211+
// https://github.com/posit-dev/positron/issues/3140
212+
let enquote = match node_type {
213+
NodeType::Subset => false,
214+
NodeType::Subset2 => true,
215+
_ => enquote,
216+
};
217+
completions_from_object_names(object, name, enquote)?
207218
} else {
208219
completions_from_object_names(object, name, enquote)?
209220
};
@@ -259,6 +270,7 @@ mod tests {
259270
use harp::eval::parse_eval_global;
260271
use tree_sitter::Point;
261272

273+
use crate::fixtures::package_is_installed;
262274
use crate::lsp::completions::sources::utils::call_node_position_type;
263275
use crate::lsp::completions::sources::utils::completions_from_evaluated_object_names;
264276
use crate::lsp::completions::sources::utils::CallNodePositionType;
@@ -426,7 +438,7 @@ mod tests {
426438
parse_eval_global("x <- 1:2").unwrap();
427439
parse_eval_global("names(x) <- c('a', 'b')").unwrap();
428440

429-
let completions = completions_from_evaluated_object_names("x", false)
441+
let completions = completions_from_evaluated_object_names("x", false, NodeType::Subset)
430442
.unwrap()
431443
.unwrap();
432444
assert_eq!(completions.len(), 2);
@@ -438,7 +450,7 @@ mod tests {
438450
// Data frame
439451
parse_eval_global("x <- data.frame(a = 1, b = 2, c = 3)").unwrap();
440452

441-
let completions = completions_from_evaluated_object_names("x", false)
453+
let completions = completions_from_evaluated_object_names("x", false, NodeType::Subset)
442454
.unwrap()
443455
.unwrap();
444456
assert_eq!(completions.len(), 3);
@@ -452,7 +464,7 @@ mod tests {
452464
parse_eval_global("x <- array(1:2)").unwrap();
453465
parse_eval_global("names(x) <- c('a', 'b')").unwrap();
454466

455-
let completions = completions_from_evaluated_object_names("x", false)
467+
let completions = completions_from_evaluated_object_names("x", false, NodeType::Subset)
456468
.unwrap()
457469
.unwrap();
458470
assert_eq!(completions.len(), 2);
@@ -466,7 +478,7 @@ mod tests {
466478
parse_eval_global("rownames(x) <- 'a'").unwrap();
467479
parse_eval_global("colnames(x) <- 'b'").unwrap();
468480

469-
let completions = completions_from_evaluated_object_names("x", false)
481+
let completions = completions_from_evaluated_object_names("x", false, NodeType::Subset)
470482
.unwrap()
471483
.unwrap();
472484
assert_eq!(completions.len(), 1);
@@ -482,12 +494,48 @@ mod tests {
482494
parse_eval_global("rownames(x) <- 'a'").unwrap();
483495
parse_eval_global("colnames(x) <- 'b'").unwrap();
484496

485-
let completions = completions_from_evaluated_object_names("x", false)
497+
let completions = completions_from_evaluated_object_names("x", false, NodeType::Subset)
486498
.unwrap()
487499
.unwrap();
488500
assert!(completions.is_empty());
489501

490502
parse_eval_global("remove(x)").unwrap();
491503
})
492504
}
505+
506+
#[test]
507+
fn test_data_table_completions() {
508+
r_task(|| {
509+
// Skip test if data.table is not installed
510+
if !package_is_installed("data.table") {
511+
return;
512+
}
513+
514+
parse_eval_global("x <- data.table::as.data.table(mtcars)").unwrap();
515+
516+
// Subset completions
517+
let completions = completions_from_evaluated_object_names("x", false, NodeType::Subset)
518+
.unwrap()
519+
.unwrap();
520+
521+
assert_eq!(completions.len(), 11);
522+
assert_eq!(completions.get(0).unwrap().label, String::from("mpg"));
523+
assert_eq!(completions.get(0).unwrap().insert_text, None);
524+
525+
// Subset2 completions
526+
let completions =
527+
completions_from_evaluated_object_names("x", false, NodeType::Subset2)
528+
.unwrap()
529+
.unwrap();
530+
531+
assert_eq!(completions.len(), 11);
532+
assert_eq!(completions.get(0).unwrap().label, String::from("mpg"));
533+
assert_eq!(
534+
completions.get(0).unwrap().insert_text,
535+
Some("\"mpg\"".to_string())
536+
);
537+
538+
parse_eval_global("remove(x)").unwrap();
539+
})
540+
}
493541
}

0 commit comments

Comments
 (0)