From 47d52d516b4c1a9ab52c28f1b47fb39cb6818608 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Wed, 3 Apr 2024 10:32:10 -0600 Subject: [PATCH] Add a few more OpenRPC methods for rstudioapi shims (#286) * Add `execute_code` OpenRPC method * Add `new_document` OpenRPC method * Clarify how we do not yet support row/column --- crates/amalthea/src/comm/ui_comm.rs | 47 +++++++++++++++++++ .../src/modules/positron/frontend-methods.R | 10 ++++ crates/ark/src/modules/rstudio/document-api.R | 12 ++--- crates/ark/src/modules/rstudio/stubs.R | 9 ++++ crates/ark/src/ui/methods.rs | 31 ++++++++++++ 5 files changed, 101 insertions(+), 8 deletions(-) diff --git a/crates/amalthea/src/comm/ui_comm.rs b/crates/amalthea/src/comm/ui_comm.rs index f5f7a5f6b..cc0d22d45 100644 --- a/crates/amalthea/src/comm/ui_comm.rs +++ b/crates/amalthea/src/comm/ui_comm.rs @@ -127,6 +127,16 @@ pub struct OpenEditorParams { pub column: i64, } +/// Parameters for the NewDocument method. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct NewDocumentParams { + /// Document contents + pub contents: String, + + /// Language identifier + pub language_id: String, +} + /// Parameters for the ShowMessage method. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct ShowMessageParams { @@ -191,6 +201,22 @@ pub struct ExecuteCommandParams { pub command: String, } +/// Parameters for the ExecuteCode method. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ExecuteCodeParams { + /// The language ID of the code to execute + pub language_id: String, + + /// The code to execute + pub code: String, + + /// Whether to focus the runtime's console + pub focus: bool, + + /// Whether to bypass runtime code completeness checks + pub allow_incomplete: bool, +} + /// Parameters for the OpenWorkspace method. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] pub struct OpenWorkspaceParams { @@ -258,6 +284,13 @@ pub enum UiBackendReply { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(tag = "method", content = "params")] pub enum UiFrontendRequest { + /// Create a new document with text contents + /// + /// Use this to create a new document with the given language ID and text + /// contents + #[serde(rename = "new_document")] + NewDocument(NewDocumentParams), + /// Show a question /// /// Use this for a modal dialog that the user can accept or cancel @@ -276,6 +309,12 @@ pub enum UiFrontendRequest { #[serde(rename = "debug_sleep")] DebugSleep(DebugSleepParams), + /// Execute code in a Positron runtime + /// + /// Use this to execute code in a Positron runtime + #[serde(rename = "execute_code")] + ExecuteCode(ExecuteCodeParams), + /// Path to the workspace folder /// /// Returns the path to the workspace folder, or first folder if there are @@ -304,6 +343,9 @@ pub enum UiFrontendRequest { #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[serde(tag = "method", content = "result")] pub enum UiFrontendReply { + /// Reply for the new_document method (no result) + NewDocumentReply(), + /// Whether the user accepted or rejected the dialog. ShowQuestionReply(bool), @@ -313,6 +355,9 @@ pub enum UiFrontendReply { /// Reply for the debug_sleep method (no result) DebugSleepReply(), + /// Reply for the execute_code method (no result) + ExecuteCodeReply(), + /// The path to the workspace folder WorkspaceFolderReply(Option), @@ -387,9 +432,11 @@ pub fn ui_frontend_reply_from_value( request: &UiFrontendRequest, ) -> anyhow::Result { match request { + UiFrontendRequest::NewDocument(_) => Ok(UiFrontendReply::NewDocumentReply()), UiFrontendRequest::ShowQuestion(_) => Ok(UiFrontendReply::ShowQuestionReply(serde_json::from_value(reply)?)), UiFrontendRequest::ShowDialog(_) => Ok(UiFrontendReply::ShowDialogReply()), UiFrontendRequest::DebugSleep(_) => Ok(UiFrontendReply::DebugSleepReply()), + UiFrontendRequest::ExecuteCode(_) => Ok(UiFrontendReply::ExecuteCodeReply()), UiFrontendRequest::WorkspaceFolder => Ok(UiFrontendReply::WorkspaceFolderReply(serde_json::from_value(reply)?)), UiFrontendRequest::ModifyEditorSelections(_) => Ok(UiFrontendReply::ModifyEditorSelectionsReply()), UiFrontendRequest::LastActiveEditorContext => Ok(UiFrontendReply::LastActiveEditorContextReply(serde_json::from_value(reply)?)), diff --git a/crates/ark/src/modules/positron/frontend-methods.R b/crates/ark/src/modules/positron/frontend-methods.R index 6f61e57ed..a38bddcee 100644 --- a/crates/ark/src/modules/positron/frontend-methods.R +++ b/crates/ark/src/modules/positron/frontend-methods.R @@ -35,11 +35,21 @@ .ps.Call("ps_ui_navigate_to_file", file, line, column) } +#' @export +.ps.ui.newDocument <- function(contents, languageId) { + .ps.Call("ps_ui_new_document", contents, languageId) +} + #' @export .ps.ui.executeCommand <- function(command) { .ps.Call("ps_ui_execute_command", command) } +#' @export +.ps.ui.executeCode <- function(code, focus) { + .ps.Call("ps_ui_execute_code", code, focus) +} + #' @export .ps.ui.showMessage <- function(message) { .ps.Call("ps_ui_show_message", message) diff --git a/crates/ark/src/modules/rstudio/document-api.R b/crates/ark/src/modules/rstudio/document-api.R index 8c68a8142..bcaa03c10 100644 --- a/crates/ark/src/modules/rstudio/document-api.R +++ b/crates/ark/src/modules/rstudio/document-api.R @@ -94,16 +94,12 @@ selection_as_range <- function(ps_sel) { } #' @export -.rs.api.documentNew <- function(text, - type = c("r", "rmarkdown", "sql"), - position = rstudioapi::document_position(0, 0), - execute = FALSE) { - type <- match.arg(type) - # TODO: Support execute & position - stopifnot(!execute && position != rstudioapi::document_position(0, 0)) +.rs.api.documentNew <- function(type, code, row = 0, column = 0, execute = FALSE) { + # TODO: Support execute and row/column + stopifnot(!execute && !row && !column) languageId <- if (type == "rmarkdown") "rmd" else type - invisible(.ps.ui.documentNew(text, languageId)) + invisible(.ps.ui.newDocument(paste(code, collapse = "\n"), languageId)) } #' @export diff --git a/crates/ark/src/modules/rstudio/stubs.R b/crates/ark/src/modules/rstudio/stubs.R index 696af1e69..d323dad25 100644 --- a/crates/ark/src/modules/rstudio/stubs.R +++ b/crates/ark/src/modules/rstudio/stubs.R @@ -15,6 +15,15 @@ invisible(.ps.ui.navigateToFile(file, line, column)) } +#' @export +.rs.api.sendToConsole <- function(code, echo = TRUE, execute = TRUE, focus = TRUE, animate = FALSE) { + # TODO: support other args + stopifnot(echo && execute && !animate) + + # If we add new args later, remember to put them **after** the existing args + invisible(.ps.ui.executeCode(paste(code, collapse = "\n"), focus)) +} + #' @export .rs.api.restartSession <- function(command = "") { # TODO: support followup `command` argument diff --git a/crates/ark/src/ui/methods.rs b/crates/ark/src/ui/methods.rs index 60fbf2e7f..b5f7b5b93 100644 --- a/crates/ark/src/ui/methods.rs +++ b/crates/ark/src/ui/methods.rs @@ -6,7 +6,9 @@ // use amalthea::comm::ui_comm::DebugSleepParams; +use amalthea::comm::ui_comm::ExecuteCodeParams; use amalthea::comm::ui_comm::ModifyEditorSelectionsParams; +use amalthea::comm::ui_comm::NewDocumentParams; use amalthea::comm::ui_comm::ShowDialogParams; use amalthea::comm::ui_comm::ShowQuestionParams; use amalthea::comm::ui_comm::UiFrontendRequest; @@ -89,6 +91,35 @@ pub unsafe extern "C" fn ps_ui_show_question( Ok(out.sexp) } +#[harp::register] +pub unsafe extern "C" fn ps_ui_new_document( + contents: SEXP, + language_id: SEXP, +) -> anyhow::Result { + let params = NewDocumentParams { + contents: RObject::view(contents).try_into()?, + language_id: RObject::view(language_id).try_into()?, + }; + + let main = RMain::get(); + let out = main.call_frontend_method(UiFrontendRequest::NewDocument(params))?; + Ok(out.sexp) +} + +#[harp::register] +pub unsafe extern "C" fn ps_ui_execute_code(code: SEXP, focus: SEXP) -> anyhow::Result { + let params = ExecuteCodeParams { + language_id: String::from("r"), + code: RObject::view(code).try_into()?, + focus: RObject::view(focus).try_into()?, + allow_incomplete: false, + }; + + let main = RMain::get(); + let out = main.call_frontend_method(UiFrontendRequest::ExecuteCode(params))?; + Ok(out.sexp) +} + #[harp::register] pub unsafe extern "C" fn ps_ui_debug_sleep(ms: SEXP) -> anyhow::Result { let params = DebugSleepParams {