Skip to content
19 changes: 19 additions & 0 deletions crates/ark/src/connections/r_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,22 @@ pub unsafe extern "C-unwind" fn ps_connection_updated(id: SEXP) -> Result<SEXP,

Ok(R_NilValue)
}

#[harp::register]
pub unsafe extern "C-unwind" fn ps_connection_focus(id: SEXP) -> Result<SEXP, anyhow::Error> {
if !RMain::is_initialized() {
return Ok(R_NilValue);
}

let main = RMain::get();
let comm_id: String = RObject::view(id).to::<String>()?;

let event = ConnectionsFrontendEvent::Focus;

main.get_comm_manager_tx().send(CommManagerEvent::Message(
comm_id,
CommMsg::Data(serde_json::to_value(event)?),
))?;

Ok(R_NilValue)
}
3 changes: 3 additions & 0 deletions crates/ark/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub enum ArkGenerics {

#[strum(serialize = "ark_positron_help_get_handler")]
HelpGetHandler,

#[strum(serialize = "ark_positron_variable_view")]
VariableView,
}

impl ArkGenerics {
Expand Down
139 changes: 138 additions & 1 deletion crates/ark/src/modules/positron/connection.R
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
.ps.Call("ps_connection_updated", id)
}

connection_focus <- function(id) {
.ps.Call("ps_connection_focus", id)
}

#' @export
.ps.connection_observer <- function() {
connections <- new.env(parent = emptyenv())
Expand All @@ -45,6 +49,7 @@
for (id in ls(envir = connections)) {
con <- get(id, envir = connections)
if (identical(con$host, host) && identical(con$type, type)) {
connection_focus(id)
return(invisible(id))
}
}
Expand All @@ -67,6 +72,7 @@
# until the end of the connection.
objectTypes = connection_flatten_object_types(listObjectTypes())
)
connection_focus(id)
invisible(id)
}

Expand Down Expand Up @@ -170,6 +176,36 @@ connection_flatten_object_types <- function(object_tree) {
return(TRUE)
}

# Helper to reconstruct ODBC connection code from connection info
odbc_connection_code <- function(info) {
# Try to reconstruct a reasonable connection string
# Priority: DSN > connection string parameters
if (!is.null(info$sourcename) && nzchar(info$sourcename)) {
# DSN-based connection
sprintf('odbc::dbConnect(odbc::odbc(), dsn = "%s")', info$sourcename)
} else {
# Build connection string from available parameters
params <- character()

if (!is.null(info$servername) && nzchar(info$servername)) {
params <- c(params, sprintf('server = "%s"', info$servername))
}
if (!is.null(info$dbname) && nzchar(info$dbname)) {
params <- c(params, sprintf('database = "%s"', info$dbname))
}

if (length(params) > 0) {
sprintf(
"odbc::dbConnect(odbc::odbc(), %s)",
paste(params, collapse = ", ")
)
} else {
# Fallback if we can't determine connection parameters
"# Connection opened from Variables Pane"
}
}
}

#' @export
.ps.connection_icon <- function(id, ...) {
con <- get(id, getOption("connectionObserver")$.connections)
Expand Down Expand Up @@ -283,6 +319,107 @@ connection_flatten_object_types <- function(object_tree) {
actions = NULL
)

if (is.null(id)) return("hello")
if (is.null(id)) {
return("hello")
}
id
}

# ODBC Connection Support
# These methods enable viewing ODBC connections in the Connections Pane
# from the Variables Pane

# Register ODBC connection methods when the odbc package is loaded
setHook(
packageEvent("odbc", "onLoad"),
function(...) {
# Mark ODBC connections as "connection" kind
.ark.register_method(
"ark_positron_variable_kind",
"OdbcConnection",
function(x) "connection"
)

# Enable viewer for valid ODBC connections
.ark.register_method(
"ark_positron_variable_has_viewer",
"OdbcConnection",
function(x) {
odbc::dbIsValid(x)
}
)

# View an ODBC connection in the Connections Pane
.ark.register_method(
"ark_positron_variable_view",
"OdbcConnection",
function(x) {
# Reconstruct the connection code from the connection info
info <- x@info
code <- odbc_connection_code(info)

# Use odbc's built-in connection observer integration
odbc:::on_connection_opened(x, code = code)
invisible(TRUE)
}
)
}
)

# BigQuery Connection Support
# These methods enable viewing BigQuery connections in the Connections Pane
# from the Variables Pane

# Register BigQuery connection methods when the bigrquery package is loaded
setHook(
packageEvent("bigrquery", "onLoad"),
function(...) {
# Mark BigQuery connections as "connection" kind
.ark.register_method(
"ark_positron_variable_kind",
"BigQueryConnection",
function(x) "connection"
)

# Enable viewer for valid BigQuery connections
.ark.register_method(
"ark_positron_variable_has_viewer",
"BigQueryConnection",
function(x) {
DBI::dbIsValid(x)
}
)

# View a BigQuery connection in the Connections Pane
.ark.register_method(
"ark_positron_variable_view",
"BigQueryConnection",
function(x) {
# Reconstruct the connection code
code <- bigrquery_connection_code(x)

# Use bigrquery's built-in connection observer integration
bigrquery:::on_connection_opened(x, code = code)
invisible(TRUE)
}
)
}
)

# Helper to reconstruct BigQuery connection code
bigrquery_connection_code <- function(con) {
project <- con@project
dataset <- con@dataset
billing <- con@billing

params <- c(sprintf('project = "%s"', project))

if (!is.null(dataset) && nzchar(dataset)) {
params <- c(params, sprintf('dataset = "%s"', dataset))
}
if (!is.null(billing) && nzchar(billing) && billing != project) {
params <- c(params, sprintf('billing = "%s"', billing))
}

sprintf("DBI::dbConnect(bigrquery::bigquery(), %s)", paste(params, collapse = ", "))
}
19 changes: 18 additions & 1 deletion crates/ark/src/modules/positron/methods.R
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ ark_methods_table$ark_positron_variable_get_children <- new.env(
ark_methods_table$ark_positron_help_get_handler <- new.env(
parent = emptyenv()
)

#' Custom view action for objects in Variables Pane
#'
#' @param x Object to view
#' @param ... Additional arguments (unused)
#' @return Logical value: TRUE on success, FALSE otherwise
ark_methods_table$ark_positron_variable_view <- new.env(
parent = emptyenv()
)

lockEnvironment(ark_methods_table, TRUE)

ark_methods_allowed_packages <- c("torch", "reticulate", "duckplyr")
Expand Down Expand Up @@ -169,7 +179,14 @@ call_ark_method <- function(generic, object, ...) {
return(NULL)
}

for (cls in class(object)) {
# Get all classes to check, including S4 superclasses
classes <- class(object)
if (isS4(object)) {
# For S4 objects, get the full inheritance hierarchy
classes <- methods::extends(class(object))
}

for (cls in classes) {
if (!is.null(method <- get0(cls, envir = methods_table))) {
return(eval(
as.call(list(method, object, ...)),
Expand Down
6 changes: 6 additions & 0 deletions crates/ark/src/variables/r_variables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ use crate::data_explorer::summary_stats::summary_stats;
use crate::lsp::events::EVENTS;
use crate::r_task;
use crate::thread::RThreadSafe;
use crate::variables::variable::try_dispatch_view;
use crate::variables::variable::PositronVariable;
use crate::view::view;

Expand Down Expand Up @@ -381,6 +382,11 @@ impl RVariables {
let env = self.env.get().clone();
let obj = PositronVariable::resolve_data_object(env.clone(), &path)?;

// Try custom view method first (e.g., for connections)
if try_dispatch_view(obj.sexp).map_err(harp::Error::Anyhow)? {
return Ok(None);
}

if r_is_function(obj.sexp) {
harp::as_result(view(&obj, &path, &env))?;
return Ok(None);
Expand Down
35 changes: 25 additions & 10 deletions crates/ark/src/variables/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,11 +594,7 @@ fn has_viewer(value: SEXP) -> bool {
return true;
}

if !(r_is_data_frame(value) || r_is_matrix(value)) {
return false;
}

// We have a data.frame or matrix. Dispatch to the has_viewer method
// Dispatch to the has_viewer method for any object
match ArkGenerics::VariableHasViewer.try_dispatch::<bool>(value, vec![]) {
Err(err) => {
log::error!(
Expand All @@ -608,10 +604,30 @@ fn has_viewer(value: SEXP) -> bool {
// The viewer method exists, but failed
false
},
// A matching viewer method was not found
Ok(None) => true,
// The viewer method was found, use its result
Ok(Some(val)) => val,
// No method found, fall back to default logic for data frames/matrices
Ok(None) => r_is_data_frame(value) || r_is_matrix(value),
}
}

/// Try to view an object using a custom view method.
/// This dispatches to the `ark_positron_variable_view` method.
/// Returns Ok(true) if a custom view was handled, Ok(false) if no method found,
/// or Err if the method failed.
pub fn try_dispatch_view(value: SEXP) -> anyhow::Result<bool> {
match ArkGenerics::VariableView.try_dispatch::<bool>(value, vec![]) {
Err(err) => {
return Err(anyhow!("Error in custom view: {err}"));
},
Ok(None) => {
// No custom view method found
Ok(false)
},
Ok(Some(false)) => {
return Err(anyhow!("Custom view method failed"));
},
Ok(Some(true)) => Ok(true),
}
}

Expand Down Expand Up @@ -1780,9 +1796,8 @@ mod tests {

assert_eq!(variable.kind, VariableKind::Other);

// Even though the viewer method returns TRUE, the object is not a data.frame
// or matrix, so it doesn't have a viewer.
assert_eq!(variable.has_viewer, false);
// The has_viewer method returns TRUE, so the object has a viewer
assert_eq!(variable.has_viewer, true);

// Now inspect `x`
let path = vec![String::from("x")];
Expand Down