diff --git a/crates/ark/src/lsp/diagnostics.rs b/crates/ark/src/lsp/diagnostics.rs index 16719d2f5..3776490dd 100644 --- a/crates/ark/src/lsp/diagnostics.rs +++ b/crates/ark/src/lsp/diagnostics.rs @@ -601,151 +601,6 @@ fn recurse_call_arguments_default( ().ok() } -// This is commented out because: -// -// - The package not installed lint is a bit too distracting. Should it become -// an assist? -// https://github.com/posit-dev/positron/issues/2672 -// - We'd like to avoid running R code during diagnostics -// https://github.com/posit-dev/positron/issues/2321 -// - The diagnostic meshes R and tree-sitter objects in a way that's not -// perfectly safe and we have a known crash logged: -// https://github.com/posit-dev/positron/issues/2630. This diagnostic uses R for -// argument matching but since we prefer to avoid running `r_task()` in LSP code -// we should just reimplement argument matching on the Rust side. - -// struct TreeSitterCall<'a> { -// // A call of the form (list(0L, ), foo = list(1L, )) -// pub call: RObject, -// node_phantom: PhantomData<&'a Node<'a>>, -// } -// -// impl<'a> TreeSitterCall<'a> { -// pub unsafe fn new( -// node: Node<'a>, -// function: &str, -// context: &mut DiagnosticContext, -// ) -> Result { -// // start with a call to the function: () -// let sym = r_symbol!(function); -// let call = RObject::new(Rf_lang1(sym)); -// -// // then augment it with arguments -// let mut tail = *call; -// -// if let Some(arguments) = node.child_by_field_name("arguments") { -// let mut cursor = arguments.walk(); -// let children = arguments.children_by_field_name("argument", &mut cursor); -// let mut i = 0; -// for child in children { -// let arg_list = RObject::from(Rf_allocVector(VECSXP, 2)); -// -// // set the argument to a list<2>, with its first element: a scalar integer -// // that corresponds to its O-based position. The position is used below to -// // map back to the Node -// SET_VECTOR_ELT(*arg_list, 0, Rf_ScalarInteger(i as i32)); -// -// // Set the second element of the list to an external pointer -// // to the child node. -// if let Some(value) = child.child_by_field_name("value") { -// // TODO: Wrap this in a nice constructor -// let node_size = std::mem::size_of::(); -// let node_storage = Rf_allocVector(RAWSXP, node_size as isize); -// SET_VECTOR_ELT(*arg_list, 1, node_storage); -// -// let p_node_storage: *mut Node<'a> = RAW(node_storage) as *mut Node<'a>; -// std::ptr::copy_nonoverlapping(&value, p_node_storage, 1); -// } -// -// SETCDR(tail, Rf_cons(*arg_list, R_NilValue)); -// tail = CDR(tail); -// -// // potentially add the argument name -// if let Some(name) = child.child_by_field_name("name") { -// let name = context.contents.node_slice(&name)?.to_string(); -// let sym_name = r_symbol!(name); -// SET_TAG(tail, sym_name); -// } -// -// i = i + 1; -// } -// } -// -// Ok(Self { -// call, -// node_phantom: PhantomData, -// }) -// } -// } -// -// fn recurse_call_arguments_custom( -// node: Node, -// context: &mut DiagnosticContext, -// diagnostics: &mut Vec, -// function: &str, -// diagnostic_function: &str, -// ) -> Result<()> { -// r_task(|| unsafe { -// // Build a call that mixes treesitter nodes (as external pointers) -// // library(foo, pos = 2 + 2) -// // -> -// // library([0, ], pos = [1, ]) -// // where: -// // - node0 is an external pointer to a treesitter Node for the identifier `foo` -// // - node1 is an external pointer to a treesitter Node for the call `2 + 2` -// // -// // The TreeSitterCall object holds on to the nodes, so that they can be -// // safely passed down to the R side as external pointers -// let call = TreeSitterCall::new(node, function, context)?; -// -// let custom_diagnostics = RFunction::from(diagnostic_function) -// .add(r_expr_quote(call.call)) -// .add(ExternalPointer::new(context.contents)) -// .call()?; -// -// if !r_is_null(*custom_diagnostics) { -// let n = Rf_xlength(*custom_diagnostics); -// for i in 0..n { -// // diag is a list with: -// // - The kind of diagnostic: skip, default, simple -// // - The node external pointer, i.e. the ones made in TreeSitterCall::new -// // - The message, when kind is "simple" -// let diag = VECTOR_ELT(*custom_diagnostics, i); -// -// let kind: String = RObject::view(VECTOR_ELT(diag, 0)).try_into()?; -// -// if kind == "skip" { -// // skip the diagnostic entirely, e.g. -// // library(foo) -// // ^^^ -// continue; -// } -// -// let ptr = VECTOR_ELT(diag, 1); -// let value: Node<'static> = *(RAW(ptr) as *mut Node<'static>); -// -// if kind == "default" { -// // the R side gives up, so proceed as normal, e.g. -// // library(foo, pos = ...) -// // ^^^ -// recurse(value, context, diagnostics)?; -// } else if kind == "simple" { -// // Simple diagnostic from R, e.g. -// // library("ggplot3") -// // ^^^^^^^ Package 'ggplot3' is not installed -// let message: String = RObject::view(VECTOR_ELT(diag, 2)).try_into()?; -// let range = value.range(); -// let range = convert_tree_sitter_range_to_lsp_range(context.contents, range); -// let diagnostic = Diagnostic::new_simple(range, message.into()); -// diagnostics.push(diagnostic); -// } -// } -// } -// -// ().ok() -// }) -// } - fn recurse_call( node: Node, context: &mut DiagnosticContext, diff --git a/crates/ark/src/lsp/mod.rs b/crates/ark/src/lsp/mod.rs index dcb4a0af7..1adf7bfa2 100644 --- a/crates/ark/src/lsp/mod.rs +++ b/crates/ark/src/lsp/mod.rs @@ -27,5 +27,4 @@ pub mod state; pub mod statement_range; pub mod symbols; pub mod traits; -pub mod treesitter; pub mod util; diff --git a/crates/ark/src/lsp/treesitter.rs b/crates/ark/src/lsp/treesitter.rs deleted file mode 100644 index 122e86638..000000000 --- a/crates/ark/src/lsp/treesitter.rs +++ /dev/null @@ -1,38 +0,0 @@ -// -// treesitter.rs -// -// Copyright (C) 2023 Posit Software, PBC. All rights reserved. -// -// - -use harp::external_ptr::ExternalPointer; -use harp::object::RObject; -use libr::RAW; -use libr::SEXP; -use ropey::Rope; -use tree_sitter::Node; - -use crate::lsp::traits::rope::RopeExt; - -#[harp::register] -pub unsafe extern "C" fn ps_treesitter_node_text( - ffi_node: SEXP, - ffi_contents: SEXP, -) -> anyhow::Result { - let node: Node<'static> = *(RAW(ffi_node) as *mut Node<'static>); - let contents = ExternalPointer::::reference(ffi_contents); - - let text = contents - .node_slice(&node) - .map(|slice| slice.to_string()) - .unwrap_or(String::from("")); - - Ok(*RObject::from(text)) -} - -#[harp::register] -pub unsafe extern "C" fn ps_treesitter_node_kind(ffi_node: SEXP) -> anyhow::Result { - let node: Node<'static> = *(RAW(ffi_node) as *mut Node<'static>); - - Ok(*RObject::from(node.kind())) -} diff --git a/crates/ark/src/modules/positron/diagnostics.R b/crates/ark/src/modules/positron/diagnostics.R deleted file mode 100644 index a429011e7..000000000 --- a/crates/ark/src/modules/positron/diagnostics.R +++ /dev/null @@ -1,106 +0,0 @@ -# -# completions.R -# -# Copyright (C) 2022-2024 Posit Software, PBC. All rights reserved. -# -# - -#' @export -.ps.diagnostics.diagnostic <- function(kind = "skip", node = NULL, message = NULL) { - list(kind, node, message) -} - -#' @export -.ps.diagnostics.custom.library <- function(call, contents, fun = base::library) { - # TODO: it could be interesting to have a diagnostic - # when this will fail on wrong argument, e.g. - # library(pkg = ggplot2) - # ^^^ : unused argument (pkg) - matched_call <- match.call(fun, call) - - # here we get a call where arguments are named, e.g. - # library(package = ) where is a list of 2 things: - # - a 0-based integer position for this argument. This is not - # currently used - # - an external pointer to a treesitter Node, which can be queried - # with .ps.treesitter.node.text() and .ps.treesitter.node.kind() - # - # We might simplify and only pass around the external pointer if - # we realize the position isn't useful. - - # identify if character.only was set, so that we can - # adapt the diagnostic appropriately - is_character_only <- function(matched_call, contents) { - character_only <- matched_call[["character.only"]] - - if (is.null(character_only)) { - FALSE - } else { - ptr <- character_only[[2L]] - text <- .ps.treesitter.node.text(ptr, contents) - !identical(text, "FALSE") - } - } - - # deal with arguments `package` and `help` which use - # non standard evaluation, e.g. library(ggplot2) - diagnostic_package <- function(arg, contents, character_only) { - index <- arg[[1L]] - node <- arg[[2L]] - - kind <- .ps.treesitter.node.kind(node) - - # library(foo, character.only = TRUE) - if (kind == "identifier" && character_only) { - return(.ps.diagnostics.diagnostic("default", node)) - } - - if (kind %in% c("string", "identifier")) { - # library("foo") or library(foo) - pkg <- .ps.treesitter.node.text(node, contents) - - if (kind == "string") { - pkg <- gsub("^(['\"])(.*)\\1$", "\\2", pkg) - } - - # The package is installed, just skip the diagnostic - if (pkg %in% base::.packages(all.available = TRUE)) { - return(.ps.diagnostics.diagnostic("skip")) - } - - msg <- sprintf("Package '%s' is not installed", pkg) - return(.ps.diagnostics.diagnostic("simple", node, message = msg)) - } - - .ps.diagnostics.diagnostic("default", node) - } - - # Before scanning all arguments, we need to check if - # character.only is set, so that we can adapt how the - # package and help arguments are handled - character_only <- is_character_only(matched_call, contents) - - # Scan the given arguments and make diagnostics for each - n <- length(matched_call) - out <- vector("list", n - 1L) - names <- names(matched_call) - for (i in seq_len(n - 1L)) { - arg <- matched_call[[i + 1L]] - name <- names[[i + 1L]] - - diagnostic <- if (name %in% c("package", "help")) { - diagnostic_package(arg, contents, character_only) - } else { - .ps.diagnostics.diagnostic("default", node = arg[[2L]]) - } - - out[[i]] <- diagnostic - } - - out -} - -#' @export -.ps.diagnostics.custom.require <- function(call, contents, fun = base::require) { - .ps.diagnostics.custom.library(call, contents, fun) -} diff --git a/crates/ark/src/modules/positron/treesitter.R b/crates/ark/src/modules/positron/treesitter.R deleted file mode 100644 index 0df65b88f..000000000 --- a/crates/ark/src/modules/positron/treesitter.R +++ /dev/null @@ -1,16 +0,0 @@ -# -# treesitter.R -# -# Copyright (C) 2023-2024 Posit Software, PBC. All rights reserved. -# -# - -#' @export -.ps.treesitter.node.text <- function(node, contents) { - .ps.Call("ps_treesitter_node_text", node, contents) -} - -#' @export -.ps.treesitter.node.kind <- function(node) { - .ps.Call("ps_treesitter_node_kind", node) -}