Skip to content

Commit 8790f19

Browse files
committed
Be willing to complete in the face of emptiness
1 parent 4074f51 commit 8790f19

File tree

1 file changed

+30
-7
lines changed

1 file changed

+30
-7
lines changed

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use std::collections::HashMap;
1919
use stdext::*;
2020
use tower_lsp::lsp_types::CompletionItem;
2121
use tower_lsp::lsp_types::CompletionItemKind;
22-
use tree_sitter::Node;
2322

2423
use crate::lsp::completions::completion_context::CompletionContext;
2524
use crate::lsp::completions::sources::collect_completions;
@@ -57,7 +56,7 @@ pub(crate) fn get_completions(
5756

5857
// For the rest of the general completions, we require an identifier to
5958
// begin showing anything.
60-
if is_identifier_like(completion_context.document_context.node) {
59+
if is_identifier_like(completion_context) {
6160
push_completions(keyword::KeywordSource, completion_context, &mut completions)?;
6261

6362
push_completions(
@@ -172,8 +171,10 @@ fn sort_completions(completions: &mut Vec<CompletionItem>) {
172171
}
173172
}
174173

175-
fn is_identifier_like(x: Node) -> bool {
176-
if x.is_identifier() {
174+
fn is_identifier_like(completion_context: &CompletionContext) -> bool {
175+
let node = completion_context.document_context.node;
176+
177+
if node.is_identifier() {
177178
// Obvious case
178179
return true;
179180
}
@@ -185,12 +186,29 @@ fn is_identifier_like(x: Node) -> bool {
185186
// - `for<tab>` should provide completions for things like `forcats`
186187
// - `for<tab>` should provide snippet completions for the `for` snippet
187188
// The keywords here come from matching snippets in `r.code-snippets`.
188-
if matches!(x.node_type(), NodeType::Anonymous(kind) if matches!(kind.as_str(), "if" | "for" | "while"))
189+
if matches!(node.node_type(), NodeType::Anonymous(kind) if matches!(kind.as_str(), "if" | "for" | "while"))
189190
{
190191
return true;
191192
}
192193

193-
return false;
194+
// Consider when the user asks for completions with no existing
195+
// text-to-complete, such as at the R prompt in the Console or in an empty R
196+
// file.
197+
// Gesture-wise, a Positron user could do this with Ctrl + Space, which
198+
// invokes the command editor.action.triggerSuggest.
199+
// The nominal completion node in these cases is basically degenerate, i.e.
200+
// it's just the root node of the AST.
201+
// In this case, we should just provide "all" completions, for some
202+
// reasonable definition of "all".
203+
// TODO: Handle the related case of asking for completions on an empty line
204+
// of a non-empty R file. In this case, we will have latched on to some
205+
// neighboring node, but perhaps should not. The text extracted from this
206+
// node is usually not an identifier and this function will return false.
207+
if node.node_type() == NodeType::Program {
208+
return true;
209+
}
210+
211+
false
194212
}
195213

196214
#[cfg(test)]
@@ -206,6 +224,9 @@ mod tests {
206224

207225
#[test]
208226
fn test_completions_on_anonymous_node_keywords() {
227+
use crate::lsp::completions::completion_context::CompletionContext;
228+
use crate::lsp::state::WorldState;
229+
209230
r_task(|| {
210231
// `if`, `for`, and `while` in particular are both tree-sitter
211232
// anonymous nodes and snippet keywords, so they need to look like
@@ -214,7 +235,9 @@ mod tests {
214235
let point = Point { row: 0, column: 0 };
215236
let document = Document::new(keyword, None);
216237
let context = DocumentContext::new(&document, point, None);
217-
assert!(is_identifier_like(context.node));
238+
let state = WorldState::default();
239+
let completion_context = CompletionContext::new(&context, &state);
240+
assert!(is_identifier_like(&completion_context));
218241
assert_eq!(
219242
context.node.node_type(),
220243
NodeType::Anonymous(keyword.to_string())

0 commit comments

Comments
 (0)