Skip to content

Commit

Permalink
Merge pull request #620 from posit-dev/feature/variable-indexing
Browse files Browse the repository at this point in the history
Index all assigned variables
  • Loading branch information
lionel- authored Nov 5, 2024
2 parents 4b4ab09 + ac18348 commit f2800d1
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

## 2024-10

- Objects assigned at top level are now indexed, in addition to assigned functions. When a name is assigned multiple times, we now only index the first occurrence. This allows you to jump to the first "declaration" of the variable. In the future we'll improve this mechanism so that you can jump to the most recent assignment.

We also index `method(generic, class) <-` assignment to help with S7 development. This might be replaced by a "Find implementations" mechanism in the future.

- Results from completions have been improved with extra details.
Package functions now display the package name (posit-dev/positron#5225)
and namespace completions now display `::` to hint at what is being
Expand Down
8 changes: 8 additions & 0 deletions crates/ark/src/lsp/completions/completion_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,14 @@ pub(super) unsafe fn completion_item_from_object(
Ok(item)
}

pub(super) fn completion_item_from_variable(name: &str) -> anyhow::Result<CompletionItem> {
let mut item = completion_item(String::from(name), CompletionData::Object {
name: String::from(name),
})?;
item.kind = Some(CompletionItemKind::VALUE);
Ok(item)
}

pub(super) unsafe fn completion_item_from_promise(
name: &str,
object: SEXP,
Expand Down
1 change: 1 addition & 0 deletions crates/ark/src/lsp/completions/sources/composite/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ fn completions_from_workspace_arguments(
// Not a function
return Ok(None);
},
indexer::IndexEntryData::Variable { .. } => return Ok(None),
}

// Only 1 call worth of arguments are added to the completion set.
Expand Down
11 changes: 11 additions & 0 deletions crates/ark/src/lsp/completions/sources/composite/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use tower_lsp::lsp_types::MarkupContent;
use tower_lsp::lsp_types::MarkupKind;

use crate::lsp::completions::completion_item::completion_item_from_function;
use crate::lsp::completions::completion_item::completion_item_from_variable;
use crate::lsp::completions::sources::utils::filter_out_dot_prefixes;
use crate::lsp::document_context::DocumentContext;
use crate::lsp::indexer;
Expand Down Expand Up @@ -97,6 +98,16 @@ pub(super) fn completions_from_workspace(
},

indexer::IndexEntryData::Section { level: _, title: _ } => {},
indexer::IndexEntryData::Variable { name } => {
let completion = match completion_item_from_variable(name) {
Ok(item) => item,
Err(err) => {
log::error!("{err:?}");
return;
},
};
completions.push(completion);
},
}
});

Expand Down
50 changes: 49 additions & 1 deletion crates/ark/src/lsp/indexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ use crate::treesitter::NodeTypeExt;

#[derive(Clone, Debug)]
pub enum IndexEntryData {
Variable {
name: String,
},
Function {
name: String,
arguments: Vec<String>,
Expand Down Expand Up @@ -117,7 +120,13 @@ fn insert(path: &Path, entry: IndexEntry) -> anyhow::Result<()> {
let path = str_from_path(path)?;

let index = index.entry(path.to_string()).or_default();
index.insert(entry.key.clone(), entry);

// Retain the first occurrence in the index. In the future we'll track every occurrences and
// their scopes but for now we only track the first definition of an object (in a way, its
// declaration).
if !index.contains_key(&entry.key) {
index.insert(entry.key.clone(), entry);
}

Ok(())
}
Expand Down Expand Up @@ -205,6 +214,11 @@ fn index_node(path: &Path, contents: &Rope, node: &Node) -> anyhow::Result<Optio
return Ok(Some(entry));
}

// Should be after function indexing as this is a more general case
if let Ok(Some(entry)) = index_variable(path, contents, node) {
return Ok(Some(entry));
}

if let Ok(Some(entry)) = index_comment(path, contents, node) {
return Ok(Some(entry));
}
Expand Down Expand Up @@ -262,6 +276,40 @@ fn index_function(
}))
}

fn index_variable(
_path: &Path,
contents: &Rope,
node: &Node,
) -> anyhow::Result<Option<IndexEntry>> {
if !matches!(
node.node_type(),
NodeType::BinaryOperator(BinaryOperatorType::LeftAssignment) |
NodeType::BinaryOperator(BinaryOperatorType::EqualsAssignment)
) {
return Ok(None);
}

let Some(lhs) = node.child_by_field_name("lhs") else {
return Ok(None);
};

let lhs_text = contents.node_slice(&lhs)?.to_string();

// Super hacky but let's wait until the typed API to do better
if !lhs_text.starts_with("method(") && !lhs.is_identifier_or_string() {
return Ok(None);
}

let start = convert_point_to_position(contents, lhs.start_position());
let end = convert_point_to_position(contents, lhs.end_position());

Ok(Some(IndexEntry {
key: lhs_text.clone(),
range: Range { start, end },
data: IndexEntryData::Variable { name: lhs_text },
}))
}

fn index_comment(_path: &Path, contents: &Rope, node: &Node) -> anyhow::Result<Option<IndexEntry>> {
// check for comment
node.is_comment().into_result()?;
Expand Down
13 changes: 13 additions & 0 deletions crates/ark/src/lsp/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ pub fn symbols(params: &WorkspaceSymbolParams) -> anyhow::Result<Vec<SymbolInfor
container_name: None,
});
},
IndexEntryData::Variable { name } => {
info.push(SymbolInformation {
name: name.clone(),
kind: SymbolKind::VARIABLE,
location: Location {
uri: Url::from_file_path(path).unwrap(),
range: entry.range,
},
tags: None,
deprecated: None,
container_name: None,
});
},
};
});

Expand Down

0 comments on commit f2800d1

Please sign in to comment.