-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Protocol for extending the variables pane (#560)
Mechanism to extend Positron's variable pane --------- Co-authored-by: Tomasz Kalinowski <[email protected]> Co-authored-by: Lionel Henry <[email protected]> Co-authored-by: Davis Vaughan <[email protected]>
- Loading branch information
1 parent
e07efa9
commit 4bb6ca9
Showing
8 changed files
with
447 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// | ||
// methods.rs | ||
// | ||
// Copyright (C) 2024 by Posit Software, PBC | ||
// | ||
// | ||
|
||
use anyhow::anyhow; | ||
use harp::call::RArgument; | ||
use harp::exec::RFunction; | ||
use harp::exec::RFunctionExt; | ||
use harp::r_null; | ||
use harp::utils::r_is_object; | ||
use harp::RObject; | ||
use libr::SEXP; | ||
use strum_macros::Display; | ||
use strum_macros::EnumIter; | ||
use strum_macros::EnumString; | ||
use strum_macros::IntoStaticStr; | ||
|
||
use crate::modules::ARK_ENVS; | ||
|
||
#[derive(Debug, PartialEq, EnumString, EnumIter, IntoStaticStr, Display, Eq, Hash, Clone)] | ||
pub enum ArkGenerics { | ||
#[strum(serialize = "ark_positron_variable_display_value")] | ||
VariableDisplayValue, | ||
|
||
#[strum(serialize = "ark_positron_variable_display_type")] | ||
VariableDisplayType, | ||
|
||
#[strum(serialize = "ark_positron_variable_has_children")] | ||
VariableHasChildren, | ||
|
||
#[strum(serialize = "ark_positron_variable_kind")] | ||
VariableKind, | ||
} | ||
|
||
impl ArkGenerics { | ||
// Dispatches the method on `x` | ||
// Returns | ||
// - `None` if no method was found, | ||
// - `Err` if method was found and errored | ||
// - `Err` if the method result could not be coerced to `T` | ||
// - T, if method was found and was successfully executed | ||
pub fn try_dispatch<T>(&self, x: SEXP, args: Vec<RArgument>) -> anyhow::Result<Option<T>> | ||
where | ||
// Making this a generic allows us to handle the conversion to the expected output | ||
// type within the dispatch, which is much more ergonomic. | ||
T: TryFrom<RObject>, | ||
<T as TryFrom<RObject>>::Error: std::fmt::Debug, | ||
{ | ||
if !r_is_object(x) { | ||
return Ok(None); | ||
} | ||
|
||
let generic: &str = self.into(); | ||
let mut call = RFunction::new("", "call_ark_method"); | ||
|
||
call.add(generic); | ||
call.add(x); | ||
|
||
for RArgument { name, value } in args.into_iter() { | ||
call.param(name.as_str(), value); | ||
} | ||
|
||
let result = call.call_in(ARK_ENVS.positron_ns)?; | ||
|
||
// No method for that object | ||
if result.sexp == r_null() { | ||
return Ok(None); | ||
} | ||
|
||
// Convert the result to the expected return type | ||
match result.try_into() { | ||
Ok(value) => Ok(Some(value)), | ||
Err(err) => Err(anyhow!("Conversion failed: {err:?}")), | ||
} | ||
} | ||
|
||
pub fn register_method(&self, class: &str, method: RObject) -> anyhow::Result<()> { | ||
let generic_name: &str = self.into(); | ||
RFunction::new("", ".ark.register_method") | ||
.add(RObject::try_from(generic_name)?) | ||
.add(RObject::try_from(class)?) | ||
.add(method) | ||
.call_in(ARK_ENVS.positron_ns)?; | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
# | ||
# methods.R | ||
# | ||
# Copyright (C) 2024 Posit Software, PBC. All rights reserved. | ||
# | ||
# | ||
|
||
ark_methods_table <- new.env(parent = emptyenv()) | ||
ark_methods_table$ark_positron_variable_display_value <- new.env(parent = emptyenv()) | ||
ark_methods_table$ark_positron_variable_display_type <- new.env(parent = emptyenv()) | ||
ark_methods_table$ark_positron_variable_has_children <- new.env(parent = emptyenv()) | ||
ark_methods_table$ark_positron_variable_kind <- new.env(parent = emptyenv()) | ||
lockEnvironment(ark_methods_table, TRUE) | ||
|
||
ark_methods_allowed_packages <- c("torch", "reticulate") | ||
|
||
#' Register the methods with the Positron runtime | ||
#' | ||
#' @param generic Generic function name as a character to register | ||
#' @param class Class name as a character | ||
#' @param method A method to be registered. Should be a call object. | ||
#' @export | ||
.ark.register_method <- function(generic, class, method) { | ||
|
||
# Check if the caller is an allowed package | ||
if (!in_ark_tests()) { | ||
calling_env <- .ps.env_name(topenv(parent.frame())) | ||
if (!(calling_env %in% paste0("namespace:", ark_methods_allowed_packages))) { | ||
stop("Only allowed packages can register methods. Called from ", calling_env) | ||
} | ||
} | ||
|
||
stopifnot( | ||
is_string(generic), | ||
generic %in% names(ark_methods_table), | ||
typeof(class) == "character" | ||
) | ||
for (cls in class) { | ||
assign(cls, method, envir = ark_methods_table[[generic]]) | ||
} | ||
invisible() | ||
} | ||
|
||
call_ark_method <- function(generic, object, ...) { | ||
methods_table <- ark_methods_table[[generic]] | ||
|
||
if (is.null(methods_table)) { | ||
return(NULL) | ||
} | ||
|
||
for (cls in class(object)) { | ||
if (!is.null(method <- get0(cls, envir = methods_table))) { | ||
return(eval( | ||
as.call(list(method, object, ...)), | ||
envir = globalenv() | ||
)) | ||
} | ||
} | ||
|
||
NULL | ||
} |
Oops, something went wrong.