From aa60015a17841dc6e61f4cf4b4e17df795618906 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Mon, 1 Apr 2024 13:29:16 -0600 Subject: [PATCH 1/3] Add `execute_code` OpenRPC method --- crates/amalthea/src/comm/ui_comm.rs | 26 +++++++++++++++++++ .../src/modules/positron/frontend-methods.R | 5 ++++ crates/ark/src/modules/rstudio/stubs.R | 9 +++++++ crates/ark/src/ui/methods.rs | 15 +++++++++++ 4 files changed, 55 insertions(+) diff --git a/crates/amalthea/src/comm/ui_comm.rs b/crates/amalthea/src/comm/ui_comm.rs index f5f7a5f6b..74bda4145 100644 --- a/crates/amalthea/src/comm/ui_comm.rs +++ b/crates/amalthea/src/comm/ui_comm.rs @@ -191,6 +191,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 { @@ -276,6 +292,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 @@ -313,6 +335,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), @@ -390,6 +415,7 @@ pub fn ui_frontend_reply_from_value( 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..a814532c7 100644 --- a/crates/ark/src/modules/positron/frontend-methods.R +++ b/crates/ark/src/modules/positron/frontend-methods.R @@ -40,6 +40,11 @@ .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/stubs.R b/crates/ark/src/modules/rstudio/stubs.R index 696af1e69..05a9887af 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(lapply(code, function(x) .ps.ui.executeCode(x, 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..27738a5c3 100644 --- a/crates/ark/src/ui/methods.rs +++ b/crates/ark/src/ui/methods.rs @@ -6,6 +6,7 @@ // use amalthea::comm::ui_comm::DebugSleepParams; +use amalthea::comm::ui_comm::ExecuteCodeParams; use amalthea::comm::ui_comm::ModifyEditorSelectionsParams; use amalthea::comm::ui_comm::ShowDialogParams; use amalthea::comm::ui_comm::ShowQuestionParams; @@ -89,6 +90,20 @@ pub unsafe extern "C" fn ps_ui_show_question( 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 { From 329decd0fd5d2283f2f3b753b4a2e29cd8ec9f0e Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Mon, 1 Apr 2024 16:15:21 -0600 Subject: [PATCH 2/3] Add `new_document` OpenRPC method --- crates/amalthea/src/comm/ui_comm.rs | 21 +++++++++++++++++++ .../src/modules/positron/frontend-methods.R | 5 +++++ crates/ark/src/modules/rstudio/document-api.R | 12 ++++------- crates/ark/src/modules/rstudio/stubs.R | 2 +- crates/ark/src/ui/methods.rs | 18 ++++++++++++++++ 5 files changed, 49 insertions(+), 9 deletions(-) diff --git a/crates/amalthea/src/comm/ui_comm.rs b/crates/amalthea/src/comm/ui_comm.rs index 74bda4145..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 { @@ -274,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 @@ -326,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), @@ -412,6 +432,7 @@ 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()), diff --git a/crates/ark/src/modules/positron/frontend-methods.R b/crates/ark/src/modules/positron/frontend-methods.R index a814532c7..dc5f6cd19 100644 --- a/crates/ark/src/modules/positron/frontend-methods.R +++ b/crates/ark/src/modules/positron/frontend-methods.R @@ -35,6 +35,11 @@ .ps.Call("ps_ui_navigate_to_file", file, line, column) } +#' @export +.ps.ui.newDocument <- function(contents, languageId, line, column) { + .ps.Call("ps_ui_new_document", contents, languageId, line, column) +} + #' @export .ps.ui.executeCommand <- function(command) { .ps.Call("ps_ui_execute_command", command) diff --git a/crates/ark/src/modules/rstudio/document-api.R b/crates/ark/src/modules/rstudio/document-api.R index 8c68a8142..88638ff38 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 + stopifnot(!execute) languageId <- if (type == "rmarkdown") "rmd" else type - invisible(.ps.ui.documentNew(text, languageId)) + invisible(.ps.ui.newDocument(paste(code, collapse = "\n"), languageId, row, column)) } #' @export diff --git a/crates/ark/src/modules/rstudio/stubs.R b/crates/ark/src/modules/rstudio/stubs.R index 05a9887af..d323dad25 100644 --- a/crates/ark/src/modules/rstudio/stubs.R +++ b/crates/ark/src/modules/rstudio/stubs.R @@ -21,7 +21,7 @@ stopifnot(echo && execute && !animate) # If we add new args later, remember to put them **after** the existing args - invisible(lapply(code, function(x) .ps.ui.executeCode(x, focus))) + invisible(.ps.ui.executeCode(paste(code, collapse = "\n"), focus)) } #' @export diff --git a/crates/ark/src/ui/methods.rs b/crates/ark/src/ui/methods.rs index 27738a5c3..f6b2b4dba 100644 --- a/crates/ark/src/ui/methods.rs +++ b/crates/ark/src/ui/methods.rs @@ -8,6 +8,7 @@ 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; @@ -90,6 +91,23 @@ 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, + _line: SEXP, + _column: 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 { From 9b4663a42aade079f208d7b0a1eba62d370ec7d2 Mon Sep 17 00:00:00 2001 From: Julia Silge Date: Wed, 3 Apr 2024 10:24:06 -0600 Subject: [PATCH 3/3] Clarify how we do not yet support row/column --- crates/ark/src/modules/positron/frontend-methods.R | 4 ++-- crates/ark/src/modules/rstudio/document-api.R | 6 +++--- crates/ark/src/ui/methods.rs | 2 -- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/ark/src/modules/positron/frontend-methods.R b/crates/ark/src/modules/positron/frontend-methods.R index dc5f6cd19..a38bddcee 100644 --- a/crates/ark/src/modules/positron/frontend-methods.R +++ b/crates/ark/src/modules/positron/frontend-methods.R @@ -36,8 +36,8 @@ } #' @export -.ps.ui.newDocument <- function(contents, languageId, line, column) { - .ps.Call("ps_ui_new_document", contents, languageId, line, column) +.ps.ui.newDocument <- function(contents, languageId) { + .ps.Call("ps_ui_new_document", contents, languageId) } #' @export diff --git a/crates/ark/src/modules/rstudio/document-api.R b/crates/ark/src/modules/rstudio/document-api.R index 88638ff38..bcaa03c10 100644 --- a/crates/ark/src/modules/rstudio/document-api.R +++ b/crates/ark/src/modules/rstudio/document-api.R @@ -95,11 +95,11 @@ selection_as_range <- function(ps_sel) { #' @export .rs.api.documentNew <- function(type, code, row = 0, column = 0, execute = FALSE) { - # TODO: Support execute - stopifnot(!execute) + # TODO: Support execute and row/column + stopifnot(!execute && !row && !column) languageId <- if (type == "rmarkdown") "rmd" else type - invisible(.ps.ui.newDocument(paste(code, collapse = "\n"), languageId, row, column)) + invisible(.ps.ui.newDocument(paste(code, collapse = "\n"), languageId)) } #' @export diff --git a/crates/ark/src/ui/methods.rs b/crates/ark/src/ui/methods.rs index f6b2b4dba..b5f7b5b93 100644 --- a/crates/ark/src/ui/methods.rs +++ b/crates/ark/src/ui/methods.rs @@ -95,8 +95,6 @@ pub unsafe extern "C" fn ps_ui_show_question( pub unsafe extern "C" fn ps_ui_new_document( contents: SEXP, language_id: SEXP, - _line: SEXP, - _column: SEXP, ) -> anyhow::Result { let params = NewDocumentParams { contents: RObject::view(contents).try_into()?,