diff --git a/CHANGELOG.md b/CHANGELOG.md index 159b0b341..14f72f641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/crates/ark/src/lsp/completions/completion_item.rs b/crates/ark/src/lsp/completions/completion_item.rs index c0ee1aab4..6ebc15dd2 100644 --- a/crates/ark/src/lsp/completions/completion_item.rs +++ b/crates/ark/src/lsp/completions/completion_item.rs @@ -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 { + 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, diff --git a/crates/ark/src/lsp/completions/sources/composite/call.rs b/crates/ark/src/lsp/completions/sources/composite/call.rs index a31f083da..cf976edbe 100644 --- a/crates/ark/src/lsp/completions/sources/composite/call.rs +++ b/crates/ark/src/lsp/completions/sources/composite/call.rs @@ -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. diff --git a/crates/ark/src/lsp/completions/sources/composite/workspace.rs b/crates/ark/src/lsp/completions/sources/composite/workspace.rs index 29abfec75..432742d30 100644 --- a/crates/ark/src/lsp/completions/sources/composite/workspace.rs +++ b/crates/ark/src/lsp/completions/sources/composite/workspace.rs @@ -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; @@ -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); + }, } }); diff --git a/crates/ark/src/lsp/indexer.rs b/crates/ark/src/lsp/indexer.rs index e6b642a4e..34f018c7b 100644 --- a/crates/ark/src/lsp/indexer.rs +++ b/crates/ark/src/lsp/indexer.rs @@ -32,6 +32,9 @@ use crate::treesitter::NodeTypeExt; #[derive(Clone, Debug)] pub enum IndexEntryData { + Variable { + name: String, + }, Function { name: String, arguments: Vec, @@ -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(()) } @@ -205,6 +214,11 @@ fn index_node(path: &Path, contents: &Rope, node: &Node) -> anyhow::Result anyhow::Result> { + 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> { // check for comment node.is_comment().into_result()?; diff --git a/crates/ark/src/lsp/symbols.rs b/crates/ark/src/lsp/symbols.rs index c427930dd..5be47b267 100644 --- a/crates/ark/src/lsp/symbols.rs +++ b/crates/ark/src/lsp/symbols.rs @@ -95,6 +95,19 @@ pub fn symbols(params: &WorkspaceSymbolParams) -> anyhow::Result { + 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, + }); + }, }; });