Skip to content

Commit fefe0fb

Browse files
zacdav-dbZac Davies
andauthored
db_repl behaviour changes (#95)
* Changed REPL behaviour to handle multi-line expressions in R. Removed RStudio addins as they don't work properly and are no longer required with mulit-line expression support. * Updating NEWS.md --------- Co-authored-by: Zac Davies <[email protected]>
1 parent 130b2db commit fefe0fb

File tree

5 files changed

+88
-104
lines changed

5 files changed

+88
-104
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# brickster 0.2.8
2+
3+
* Adjusted `db_repl` to handle mulit-line expressions (R only)
4+
* Removed RStudio Addins to send lines/selection/files to console
5+
* Moved arrow to Suggests
6+
17
# brickster 0.2.7
28

39
* Exporting UC table functions (`db_uc_table*`) (#72)

R/remote-repl.R

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@ db_context_manager <- R6::R6Class(
2828
),
2929

3030
public = list(
31-
3231
#' @description Create a new context manager object.
3332
#' @param cluster_id The ID of the cluster to execute command on.
3433
#' @param language One of `r`, `py`, `scala`, `sql`, or `sh`.
3534
#' @param host Databricks workspace URL, defaults to calling [db_host()].
3635
#' @param token Databricks workspace token, defaults to calling [db_token()].
3736
#' @return A new `databricks_context_manager` object.
38-
initialize = function(cluster_id, language = c("r", "py", "scala", "sql", "sh"),
39-
host = db_host(), token = db_token()) {
37+
initialize = function(
38+
cluster_id,
39+
language = c("r", "py", "scala", "sql", "sh"),
40+
host = db_host(),
41+
token = db_token()
42+
) {
4043
language <- match.arg(language)
4144
private$cluster_id <- cluster_id
4245
private$host <- host
@@ -52,7 +55,7 @@ db_context_manager <- R6::R6Class(
5255
)
5356
ctx <- db_context_create(
5457
cluster_id = private$cluster_id,
55-
language = language,
58+
language = language,
5659
host = host,
5760
token = token
5861
)
@@ -75,7 +78,7 @@ db_context_manager <- R6::R6Class(
7578
#' @param cmd code to execute against Databricks cluster
7679
#' @param language One of `r`, `py`, `scala`, `sql`, or `sh`.
7780
#' @return Command results
78-
cmd_run = function(cmd, language = c("r", "py", "scala", "sql", "sh")) {
81+
cmd_run = function(cmd, language = c("r", "py", "scala", "sql", "sh")) {
7982
language <- match.arg(language)
8083
code = paste(cmd, collapse = "\n")
8184

@@ -95,16 +98,11 @@ db_context_manager <- R6::R6Class(
9598
)
9699

97100
cmd
98-
99101
}
100102
)
101103
)
102104
# nocov end
103105

104-
105-
106-
107-
108106
repl_prompt <- function(language) {
109107
glue::glue("[Databricks][{lang(language)}]> ")
110108
}
@@ -123,10 +121,13 @@ repl_prompt <- function(language) {
123121
#' @inheritParams auth_params
124122
#'
125123
#' @export
126-
db_repl <- function(cluster_id, language = c("r", "py", "scala", "sql", "sh"),
127-
host = db_host(), token = db_token()) {
128-
129-
if(!interactive()) {
124+
db_repl <- function(
125+
cluster_id,
126+
language = c("r", "py", "scala", "sql", "sh"),
127+
host = db_host(),
128+
token = db_token()
129+
) {
130+
if (!interactive()) {
130131
cli::cli_abort("{.fn db_repl} can only be called in an interactive context")
131132
}
132133

@@ -140,18 +141,64 @@ db_repl <- function(cluster_id, language = c("r", "py", "scala", "sql", "sh"),
140141
on.exit(manager$close())
141142
prompt <- repl_prompt(language)
142143

143-
while (TRUE) {
144-
cmd <- readline(prompt)
145-
# change language when command is `:{language}` (e.g. :py)
146-
if (cmd %in% c(":r", ":py", ":scala", ":sql", ":sh")) {
147-
new_lang <- substr(cmd, 2, 99)
148-
language <- new_lang
149-
prompt <- repl_prompt(language)
150-
} else if (cmd != "") {
151-
result <- manager$cmd_run(cmd, language)
152-
clean_result <- trimws(result)
153-
if (length(clean_result) > 0 && clean_result != "") {
154-
cat(clean_result, "\n")
144+
manager <- db_context_manager$new(
145+
cluster_id,
146+
if (language == "sh") "py" else language,
147+
host = host,
148+
token = token
149+
)
150+
on.exit(manager$close())
151+
152+
# prompts for current language
153+
prompt_main <- repl_prompt(language)
154+
prompt_cont <- sub("([> ]+)$", "+ ", prompt_main)
155+
buffer <- character()
156+
157+
repeat {
158+
# choose prompt based on R-buffer state
159+
prompt <- if (language == "r" && length(buffer) > 0) prompt_cont else
160+
prompt_main
161+
line <- readline(prompt)
162+
163+
# language-switch command always applies at top-level (empty R buffer)
164+
if (
165+
length(buffer) == 0 && line %in% c(":r", ":py", ":scala", ":sql", ":sh")
166+
) {
167+
language <- sub("^:", "", line)
168+
prompt_main <- repl_prompt(language)
169+
prompt_cont <- sub("([> ]+)$", "+ ", prompt_main)
170+
buffer <- character() # drop any partial R buffer
171+
next
172+
}
173+
174+
if (language == "r") {
175+
# buffer every R line
176+
buffer <- c(buffer, line)
177+
parsed <- try(parse(text = paste(buffer, collapse = "\n")), silent = TRUE)
178+
179+
if (inherits(parsed, "try-error")) {
180+
# incomplete R block? keep reading
181+
if (grepl("unexpected end of input", parsed[1])) {
182+
next
183+
}
184+
# real syntax error: show it, reset buffer
185+
cat("Syntax error:", parsed[1], "\n")
186+
buffer <- character()
187+
next
188+
}
189+
190+
# complete R expression(s) - send them as one chunk
191+
code <- paste(buffer, collapse = "\n")
192+
res <- manager$cmd_run(code, "r")
193+
out <- trimws(res)
194+
if (length(out) > 0 && nzchar(out)) cat(out, "\n")
195+
buffer <- character()
196+
} else {
197+
# non-R: single-line send
198+
if (nzchar(line)) {
199+
res <- manager$cmd_run(line, language)
200+
out <- trimws(res)
201+
if (length(out) > 0 && nzchar(out)) cat(out, "\n")
155202
}
156203
}
157204
}

R/rstudio-integrations.R

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +0,0 @@
1-
# nocov start
2-
rstudio_editor_contents <- function(only_selected = FALSE) {
3-
if (rstudioapi::isAvailable()) {
4-
context <- rstudioapi::getSourceEditorContext()
5-
if (only_selected) {
6-
text <- context$selection[[1]]$text
7-
} else {
8-
text <- context$contents
9-
}
10-
}
11-
}
12-
13-
prepare_code_for_repl <- function(text) {
14-
exprs <- gsub("\\\n", "", as.character(parse(text = text)))
15-
paste(exprs, collapse = "\n")
16-
}
17-
18-
send_selection <- function() {
19-
code <- rstudio_editor_contents(only_selected = TRUE)
20-
code <- prepare_code_for_repl(code)
21-
rstudioapi::sendToConsole(code = code, execute = TRUE)
22-
}
23-
24-
send_document <- function() {
25-
code <- rstudio_editor_contents(only_selected = FALSE)
26-
code <- prepare_code_for_repl(code)
27-
rstudioapi::sendToConsole(code = code, execute = TRUE)
28-
}
29-
30-
send_file <- function() {
31-
script <- rstudioapi::selectFile(filter = "*.R", existing = TRUE)
32-
if (!is.null(script) || script != "") {
33-
code <- prepare_code_for_repl(readLines(script))
34-
rstudioapi::sendToConsole(code = code, execute = TRUE)
35-
}
36-
}
37-
# nocov end

inst/rstudio/addins.dcf

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +0,0 @@
1-
Name: [brickster] Send selection to console
2-
Description: Send selected code to console, is compatible with remote REPL
3-
Binding: send_selection
4-
Interactive: false
5-
6-
Name: [brickster] Send current document to console
7-
Description: Send selected document to console, is compatible with remote REPL
8-
Binding: send_document
9-
Interactive: false
10-
11-
Name: [brickster] Send R script to console
12-
Description: Send selected script to console, is compatible with remote REPL
13-
Binding: send_file
14-
Interactive: false

vignettes/remote-repl.Rmd

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -62,36 +62,16 @@ After successfully connecting to the cluster you can run commands against the re
6262

6363
The REPL has a shortcut you can enter `:<language>` to change the active language. You can change between the following languages:
6464

65-
+---------------------------------+-----------------------------------+
66-
| Language | Shortcut |
67-
+=================================+===================================+
68-
| R | `:r` |
69-
+---------------------------------+-----------------------------------+
70-
| Python | `:py` |
71-
+---------------------------------+-----------------------------------+
72-
| SQL | `:sql` |
73-
+---------------------------------+-----------------------------------+
74-
| Scala | `:scala` |
75-
+---------------------------------+-----------------------------------+
76-
| Shell | `:sh` |
77-
+---------------------------------+-----------------------------------+
65+
| Language | Shortcut |
66+
|----------|----------|
67+
| R | `:r` |
68+
| Python | `:py` |
69+
| SQL | `:sql` |
70+
| Scala | `:scala` |
71+
| Shell | `:sh` |
7872

7973
When you change between languages all variables should persist unless REPL is exited.
8074

81-
## RStudio Addins
82-
83-
There are three addins included with `{brickster}` for use with the REPL:
84-
85-
1. Send selection to console
86-
87-
2. Send current document to console
88-
89-
3. Send R script to console
90-
91-
It's recommended that (1) and (2) be bound to a keyboard shortcut if using the REPL frequently ('Tools' \> 'Modify Keyboard Shortcuts...' \> 'Search for `brickster`').
92-
93-
These addins behave slightly different to the standard shortcuts as they ensure that commands that span multiple lines are handled correctly via the REPL.
94-
9575
## Limitations
9676

9777
- Development environments (e.g. RStudio, Positron) won't display variables from the remote contexts in the environment pane
@@ -101,3 +81,5 @@ These addins behave slightly different to the standard shortcuts as they ensure
10181
- Not designed to work with interactive serverless compute
10282

10383
- Cannot persist or recover sessions
84+
85+
- Multi-line expressions are only supported for R. Python, Scala, and SQL are limited to single line expressions.

0 commit comments

Comments
 (0)