@damurka/jovian 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +175 -0
  3. package/docs/api/README.md +55 -0
  4. package/docs/api/session.md +143 -0
  5. package/docs/api/types.md +120 -0
  6. package/docs/architecture/overview.md +218 -0
  7. package/docs/cpp-usage.md +60 -0
  8. package/docs/development.md +105 -0
  9. package/docs/getting-started.md +127 -0
  10. package/docs/guides/comms.md +70 -0
  11. package/docs/guides/environments.md +80 -0
  12. package/docs/guides/history.md +44 -0
  13. package/docs/guides/interactive-input.md +48 -0
  14. package/docs/guides/interrupting.md +45 -0
  15. package/docs/guides/playground.md +33 -0
  16. package/docs/guides/sessions-lifecycle.md +70 -0
  17. package/docs/kernels.md +117 -0
  18. package/docs/protocol.md +135 -0
  19. package/docs/releasing.md +73 -0
  20. package/docs/troubleshooting.md +110 -0
  21. package/lib/execution/execution-queue.d.ts +20 -0
  22. package/lib/execution/execution-queue.js +256 -0
  23. package/lib/handlers/display-handler.d.ts +7 -0
  24. package/lib/handlers/display-handler.js +10 -0
  25. package/lib/handlers/error-handler.d.ts +7 -0
  26. package/lib/handlers/error-handler.js +8 -0
  27. package/lib/handlers/result-handler.d.ts +7 -0
  28. package/lib/handlers/result-handler.js +10 -0
  29. package/lib/handlers/stream-handler.d.ts +7 -0
  30. package/lib/handlers/stream-handler.js +8 -0
  31. package/lib/index.d.ts +7 -0
  32. package/lib/index.js +5 -0
  33. package/lib/messaging/message-parser.d.ts +6 -0
  34. package/lib/messaging/message-parser.js +33 -0
  35. package/lib/messaging/message-router.d.ts +14 -0
  36. package/lib/messaging/message-router.js +41 -0
  37. package/lib/middleware/index.d.ts +5 -0
  38. package/lib/middleware/index.js +5 -0
  39. package/lib/middleware/middleware-chain.d.ts +7 -0
  40. package/lib/middleware/middleware-chain.js +14 -0
  41. package/lib/middleware/middleware.d.ts +5 -0
  42. package/lib/middleware/middleware.js +2 -0
  43. package/lib/middleware/plugins/logging-plugin.d.ts +6 -0
  44. package/lib/middleware/plugins/logging-plugin.js +9 -0
  45. package/lib/middleware/plugins/metrics-plugin.d.ts +8 -0
  46. package/lib/middleware/plugins/metrics-plugin.js +13 -0
  47. package/lib/session/comm.d.ts +39 -0
  48. package/lib/session/comm.js +58 -0
  49. package/lib/session/native-paths.d.ts +48 -0
  50. package/lib/session/native-paths.js +108 -0
  51. package/lib/session/session-manager.d.ts +229 -0
  52. package/lib/session/session-manager.js +842 -0
  53. package/lib/session/supervisor-client.d.ts +36 -0
  54. package/lib/session/supervisor-client.js +147 -0
  55. package/lib/types/engine.d.ts +269 -0
  56. package/lib/types/engine.js +2 -0
  57. package/lib/types/index.d.ts +3 -0
  58. package/lib/types/index.js +3 -0
  59. package/lib/types/messages.d.ts +68 -0
  60. package/lib/types/messages.js +2 -0
  61. package/lib/utils/logger.d.ts +12 -0
  62. package/lib/utils/logger.js +58 -0
  63. package/lib/utils/network.d.ts +11 -0
  64. package/lib/utils/network.js +50 -0
  65. package/package.json +57 -0
  66. package/packages/hera/DESCRIPTION +29 -0
  67. package/packages/hera/LICENSE +2 -0
  68. package/packages/hera/LICENSE.md +21 -0
  69. package/packages/hera/NAMESPACE +32 -0
  70. package/packages/hera/NEWS.md +7 -0
  71. package/packages/hera/R/cell_options.R +13 -0
  72. package/packages/hera/R/comm.R +228 -0
  73. package/packages/hera/R/completion.R +54 -0
  74. package/packages/hera/R/execute.R +199 -0
  75. package/packages/hera/R/inspect.R +73 -0
  76. package/packages/hera/R/log.R +14 -0
  77. package/packages/hera/R/mime_bundle.R +65 -0
  78. package/packages/hera/R/routines.R +86 -0
  79. package/packages/hera/R/utils.R +32 -0
  80. package/packages/hera/R/zzz.R +128 -0
  81. package/packages/hera/man/Comm.Rd +179 -0
  82. package/packages/hera/man/CommManager.Rd +215 -0
  83. package/packages/hera/man/View.Rd +22 -0
  84. package/packages/hera/man/cell_options.Rd +20 -0
  85. package/packages/hera/man/clear_output.Rd +23 -0
  86. package/packages/hera/man/complete.Rd +23 -0
  87. package/packages/hera/man/display_data.Rd +22 -0
  88. package/packages/hera/man/is_elara.Rd +18 -0
  89. package/packages/hera/man/mime_bundle.Rd +25 -0
  90. package/packages/hera/man/mime_types.Rd +22 -0
  91. package/packages/hera/man/reexports.Rd +16 -0
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@damurka/jovian",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "author": "David Kariuki",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/damurka/jovian.git"
9
+ },
10
+ "homepage": "https://github.com/damurka/jovian#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/damurka/jovian/issues"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "provenance": true
17
+ },
18
+ "description": "Supervised R and Python Jupyter kernels for Node.js and Electron, backed by native C++ processes",
19
+ "keywords": [
20
+ "r",
21
+ "python",
22
+ "jupyter",
23
+ "kernel",
24
+ "repl",
25
+ "data-science",
26
+ "electron"
27
+ ],
28
+ "type": "module",
29
+ "main": "./lib/index.js",
30
+ "types": "./lib/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./lib/index.d.ts",
34
+ "import": "./lib/index.js"
35
+ },
36
+ "./types": {
37
+ "types": "./lib/types/index.d.ts",
38
+ "import": "./lib/types/index.js"
39
+ },
40
+ "./package.json": "./package.json"
41
+ },
42
+ "engines": {
43
+ "node": ">=22.4.0"
44
+ },
45
+ "files": [
46
+ "lib",
47
+ "packages/hera",
48
+ "docs",
49
+ "README.md",
50
+ "LICENSE"
51
+ ],
52
+ "optionalDependencies": {
53
+ "@damurka/jovian-win32-x64": "0.1.0",
54
+ "@damurka/jovian-linux-x64": "0.1.0",
55
+ "@damurka/jovian-darwin-arm64": "0.1.0"
56
+ }
57
+ }
@@ -0,0 +1,29 @@
1
+ Package: hera
2
+ Title: Companion to the 'Elara' 'jupyter' Kernel
3
+ Version: 0.6.0.9001
4
+ Authors@R: c(
5
+ person("Romain", "François", email = "romain@tada.science", role = c("aut", "cre")),
6
+ person("QuantStack", role = c("cph"))
7
+ )
8
+ Description: Set of R functions to be coupled with the 'Elara' 'jupyter' kernel
9
+ in order to drive execution of code in notebook input cells, how R objects
10
+ are to be displayed in output cells, and handle two way communication
11
+ with the front end through comms.
12
+ License: MIT + file LICENSE
13
+ Encoding: UTF-8
14
+ Roxygen: list(markdown = TRUE)
15
+ Imports:
16
+ cli,
17
+ evaluate,
18
+ glue,
19
+ IRdisplay,
20
+ jsonlite,
21
+ R6,
22
+ repr,
23
+ rlang,
24
+ tools,
25
+ utils
26
+ URL: https://github.com/damurka/jovian
27
+ BugReports: https://github.com/damurka/jovian/issues
28
+ Config/roxygen2/version: 8.0.0
29
+ Config/jovian/release: 0.1.0
@@ -0,0 +1,2 @@
1
+ YEAR: 2025
2
+ COPYRIGHT HOLDER: Quantstack
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2025 Quantstack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Generated by roxygen2: do not edit by hand
2
+
3
+ S3method(mime_bundle,default)
4
+ S3method(mime_types,default)
5
+ S3method(mime_types,help_files_with_topic)
6
+ S3method(mime_types,htmlwidget)
7
+ S3method(mime_types,shiny.tag)
8
+ S3method(mime_types,shiny.tag.list)
9
+ export(Comm)
10
+ export(CommManager)
11
+ export(View)
12
+ export(cell_options)
13
+ export(clear_output)
14
+ export(complete)
15
+ export(display)
16
+ export(display_data)
17
+ export(is_elara)
18
+ export(mime_bundle)
19
+ export(mime_types)
20
+ import(glue)
21
+ importFrom(IRdisplay,display)
22
+ importFrom(IRdisplay,prepare_mimebundle)
23
+ importFrom(R6,R6Class)
24
+ importFrom(grDevices,pdf)
25
+ importFrom(grDevices,png)
26
+ importFrom(jsonlite,fromJSON)
27
+ importFrom(jsonlite,toJSON)
28
+ importFrom(jsonlite,unbox)
29
+ importFrom(rlang,caller_env)
30
+ importFrom(utils,capture.output)
31
+ importFrom(utils,head)
32
+ importFrom(utils,tail)
@@ -0,0 +1,7 @@
1
+ # hera (development version)
2
+
3
+ * Output from a single long-running expression (a loop of print() calls) now streams as it is produced instead of arriving when the expression finishes.
4
+
5
+ # hera 0.1.1
6
+
7
+ * Initial CRAN submission.
@@ -0,0 +1,13 @@
1
+ #' Options for current jupyter cell
2
+ #'
3
+ #' @param ... options to set locally to the notebook cell. Forwarded to [rlang::local_options()].
4
+ #'
5
+ #' @examples
6
+ #' \dontrun{
7
+ #' cell_options(repr.plot.bg = "gray")
8
+ #' }
9
+ #'
10
+ #' @export
11
+ cell_options <- function(...) {
12
+ rlang::local_options(..., .frame = the$frame_cell_execute)
13
+ }
@@ -0,0 +1,228 @@
1
+ .CommManager__register_target_callback <- function(comm, request) {
2
+ callback <- CommManager$target_callback(request$content$target_name)
3
+ callback(comm, request)
4
+ }
5
+
6
+ #' Comm Manager class
7
+ #'
8
+ #' @rdname CommManager
9
+ CommManagerClass <- R6::R6Class("CommManagerClass",
10
+ public = list(
11
+
12
+ #' @param ... currently unused
13
+ #' @param error_call see [rlang::args_error_context()]
14
+ initialize = function(..., error_call = caller_env()) {
15
+ rlang::check_dots_empty(call = error_call)
16
+
17
+ private$env_targets <- new.env()
18
+ private$env_comms <- new.env()
19
+ },
20
+
21
+ #' @param target_name name of the comm target, e.g. "jupyter.widgets"
22
+ #' @param callback callback function taking two arguments 'comm' and 'message'.
23
+ register_comm_target = function(target_name, callback = function(comm, message){}) {
24
+ private$env_targets[[target_name]] <- callback
25
+ invisible(hera_dot_call("CommManager__register_target", target_name))
26
+ },
27
+
28
+ #' @param target_name name of the comm target
29
+ unregister_comm_target = function(target_name) {
30
+ rm(list = target_name, private$env_targets)
31
+ invisible(hera_dot_call("CommManager__unregister_target", target_name))
32
+ },
33
+
34
+ #' @param target_name name of the target
35
+ #' @param description description of the comm
36
+ new_comm = function(target_name, description = "") {
37
+ hera_dot_call("CommManager__new_comm", target_name, description)
38
+ },
39
+
40
+ #' @return the list of currently open comms
41
+ comms = function() {
42
+ as.list(private$env_comms)
43
+ },
44
+
45
+ #' @param target_name name of the comm target
46
+ #'
47
+ #' @return the callback for that target name
48
+ target_callback = function(target_name) {
49
+ private$env_targets[[target_name]]
50
+ },
51
+
52
+ #' @param comm [Comm] instance to preserve
53
+ preserve = function(comm) {
54
+ assign(comm$id, comm, envir = private$env_comms)
55
+ },
56
+
57
+ #' @param comm [Comm] instance to release
58
+ release = function(comm) {
59
+ rm(list = comm$id, envir = private$env_comms)
60
+ },
61
+
62
+ #' @param target_name name of the target to get info about all comm opens. If NULL, info for comms for all targets are returned.
63
+ get_comm_info = function(target_name = NULL) {
64
+ hera_dot_call("CommManager__get_comm_info", target_name)
65
+ }
66
+ ),
67
+
68
+ private = list(
69
+ env_targets = NULL,
70
+ env_comms = NULL
71
+ )
72
+ )
73
+
74
+ # set later in zzz.R
75
+
76
+ #' Comm manager
77
+ #'
78
+ #' Instance of the [CommManagerClass] class.
79
+ #'
80
+ #' @export
81
+ CommManager <- NULL
82
+
83
+ #' Comm class
84
+ #'
85
+ #' @export
86
+ Comm <- R6::R6Class("Comm",
87
+ public = list(
88
+
89
+ #' @param xp external pointer to an instance of the C++ class 'adrastea::Comm'
90
+ #' @param description description of this comm
91
+ initialize = function(xp, description = "") {
92
+ private$xp <- xp
93
+ private$description <- description
94
+ CommManager$preserve(self)
95
+ },
96
+
97
+ #' @param data data
98
+ #' @param metadata metadata
99
+ #' @param buffers list of raw vectors to send as binary buffers
100
+ open = function(data = NULL, metadata = NULL, buffers = list()) {
101
+ stopifnot(is.list(buffers), all(vapply(buffers, is.raw, logical(1))))
102
+ js_metadata <- enc2utf8(jsonlite::toJSON(metadata, auto_unbox = TRUE, null = if (is.null(metadata)) "list" else "null"))
103
+ js_data <- enc2utf8(jsonlite::toJSON(data, auto_unbox = TRUE, null = "null"))
104
+
105
+ invisible(hera_dot_call("Comm__open", private$xp, js_metadata, js_data, buffers))
106
+ },
107
+
108
+ #' @param data data
109
+ #' @param metadata metadata
110
+ #' @param buffers list of raw vectors to send as binary buffers
111
+ close = function(data = NULL, metadata = NULL, buffers = list()) {
112
+ stopifnot(is.list(buffers), all(vapply(buffers, is.raw, logical(1))))
113
+ js_metadata <- enc2utf8(jsonlite::toJSON(metadata, auto_unbox = TRUE, null = if (is.null(metadata)) "list" else "null"))
114
+ js_data <- enc2utf8(jsonlite::toJSON(data, auto_unbox = TRUE, null = "null"))
115
+
116
+ invisible(hera_dot_call("Comm__close", private$xp, js_metadata, js_data, buffers))
117
+ },
118
+
119
+ #' @param data data
120
+ #' @param metadata metadata
121
+ #' @param buffers list of raw vectors to send as binary buffers
122
+ send = function(data = NULL, metadata = NULL, buffers = list()) {
123
+ stopifnot(is.list(buffers), all(vapply(buffers, is.raw, logical(1))))
124
+ js_metadata <- enc2utf8(jsonlite::toJSON(metadata, auto_unbox = TRUE, null = if (is.null(metadata)) "list" else "null"))
125
+ js_data <- enc2utf8(jsonlite::toJSON(data, auto_unbox = TRUE, null = "null"))
126
+
127
+ invisible(hera_dot_call("Comm__send", private$xp, js_metadata, js_data, buffers))
128
+ },
129
+
130
+ #' @param handler function to call when the comm is closed
131
+ on_close = function(handler) {
132
+ private$close_handler <- function(request) {
133
+ handler(request)
134
+ self$finalize()
135
+ }
136
+ invisible(hera_dot_call("Comm__on_close", private$xp, private$close_handler))
137
+ },
138
+
139
+ #' @param handler function to call when receiving a message
140
+ on_message = function(handler) {
141
+ private$message_handler <- handler
142
+ invisible(hera_dot_call("Comm__on_message", private$xp, private$message_handler))
143
+ },
144
+
145
+ #' @return information about the comm
146
+ print = function() {
147
+ if (identical(private$description, "")) {
148
+ writeLines(glue("<Comm id={self$id} target_name='{self$target_name}'>"))
149
+ } else {
150
+ writeLines(glue("<Comm id={self$id} target_name='{self$target_name}' description='{private$description}' >"))
151
+ }
152
+ }
153
+ ),
154
+
155
+ active = list(
156
+
157
+ #' @field id
158
+ #' id of the comm. read only.
159
+ id = function() {
160
+ hera_dot_call("Comm__id", private$xp)
161
+ },
162
+
163
+ #' @field target_name
164
+ #' name of the target for this comm
165
+ target_name = function() {
166
+ hera_dot_call("Comm__target_name", private$xp)
167
+ }
168
+ ),
169
+
170
+ private = list(
171
+ xp = NULL,
172
+ description = "",
173
+ close_handler = NULL,
174
+ message_handler = NULL,
175
+
176
+ finalize = function() {
177
+ CommManager$release(self)
178
+ }
179
+ )
180
+ )
181
+
182
+ Message <- R6::R6Class("Message",
183
+ public = list(
184
+ initialize = function(xp) {
185
+ private$xp <- xp
186
+ },
187
+
188
+ print = function() {
189
+ print(cli::rule("$content"))
190
+ str(self$content)
191
+
192
+ print(cli::rule("$header"))
193
+ str(self$header)
194
+
195
+ print(cli::rule("$parent_header"))
196
+ str(self$parent_header)
197
+
198
+ print(cli::rule("$metadata"))
199
+ str(self$metadata)
200
+ }
201
+ ),
202
+
203
+ active = list(
204
+ content = function() {
205
+ jsonlite::fromJSON(hera_dot_call("Message__get_content", private$xp))
206
+ },
207
+
208
+ header = function() {
209
+ jsonlite::fromJSON(hera_dot_call("Message__get_header", private$xp))
210
+ },
211
+
212
+ parent_header = function() {
213
+ jsonlite::fromJSON(hera_dot_call("Message__get_parent_header", private$xp))
214
+ },
215
+
216
+ metadata = function() {
217
+ jsonlite::fromJSON(hera_dot_call("Message__get_metadata", private$xp))
218
+ },
219
+
220
+ buffers = function() {
221
+ hera_dot_call("Message__get_buffers", private$xp)
222
+ }
223
+ ),
224
+
225
+ private = list(
226
+ xp = NULL
227
+ )
228
+ )
@@ -0,0 +1,54 @@
1
+ triple_colon <- function(pkg, fun) {
2
+ eval(rlang::call2(":::", as.symbol(pkg), as.symbol(fun)))
3
+ }
4
+
5
+ utils___assignLineBuffer <- triple_colon("utils", ".assignLinebuffer")
6
+ utils___assignEnd <- triple_colon("utils", ".assignEnd")
7
+ utils___guessTokenFromLine <- triple_colon("utils", ".guessTokenFromLine")
8
+ utils___completeToken <- triple_colon("utils", ".completeToken")
9
+ utils___retrieveCompletions <- triple_colon("utils", ".retrieveCompletions")
10
+
11
+ #' Code completion
12
+ #'
13
+ #' @param code R code to complete
14
+ #' @param cursor_pos position of the cursor
15
+ #'
16
+ #' @examples
17
+ #' complete("rnorm(")
18
+ #'
19
+ #' @return a list that contains potential completions as the first item
20
+ #'
21
+ #' @export
22
+ complete <- function(code, cursor_pos = nchar(code)) {
23
+ # Find which line we're on and position within that line
24
+ lines <- strsplit(code, '\n', fixed = TRUE)[[1]]
25
+ chars_before_line <- 0L
26
+ for (line in lines) {
27
+ new_cursor_pos <- cursor_pos - nchar(line) - 1L # -1 for the newline
28
+ if (new_cursor_pos < 0L) {
29
+ break
30
+ }
31
+ cursor_pos <- new_cursor_pos
32
+ chars_before_line <- chars_before_line + nchar(line) + 1L
33
+ }
34
+
35
+ # guard from errors when completion is invoked in empty cells
36
+ if (is.null(line)) {
37
+ line <- ''
38
+ }
39
+
40
+ utils___assignLineBuffer(line)
41
+ utils___assignEnd(cursor_pos)
42
+
43
+ info <- utils___guessTokenFromLine(update = FALSE)
44
+ utils___guessTokenFromLine()
45
+ utils___completeToken()
46
+
47
+ start_position <- chars_before_line + info$start
48
+ comps <- utils___retrieveCompletions()
49
+
50
+ list(
51
+ comps,
52
+ c(start_position, start_position + nchar(info$token))
53
+ )
54
+ }
@@ -0,0 +1,199 @@
1
+ # evaluate captures stdout into a temporary file and only hands it over when a
2
+ # top-level expression finishes (or a message/warning/plot happens), so one
3
+ # long expression -- for (i in 1:1e6) print(i), purrr::walk(x, print) -- shows
4
+ # nothing until it is over. Making evaluate's sink a *split* sink
5
+ # (sink(con, split = TRUE)) tees the same output to R's console too, and the
6
+ # console (Elara's WriteConsoleEx hook) publishes it to the client at once.
7
+ # evaluate only does that through its debug flag, which also echoes every
8
+ # expression's source, so the one internal function that creates the sink is
9
+ # wrapped instead. try()'s output is redirected to the same place (evaluate
10
+ # points it at the sink's file, which the tee does not cover).
11
+ #
12
+ # Returns whether the wrapper is in place; when it is not (a different
13
+ # evaluate), output simply keeps arriving per expression, as before.
14
+ patch_evaluate_sink <- function() {
15
+ ns <- asNamespace("evaluate")
16
+ name <- "local_persistent_sink_connection"
17
+ original <- get0(name, envir = ns, inherits = FALSE)
18
+ if (!is.function(original) || !identical(names(formals(original)), c("debug", "frame"))) {
19
+ return(FALSE)
20
+ }
21
+
22
+ patched <- function(debug = FALSE, frame = parent.frame()) {
23
+ tee <- isTRUE(the$tee_stdout)
24
+ reader <- original(debug = debug || tee, frame = frame)
25
+ if (tee) {
26
+ # evaluate saved the previous value and restores it when it finishes.
27
+ options(try.outFile = stdout())
28
+ }
29
+ reader
30
+ }
31
+
32
+ tryCatch({
33
+ unlockBinding(name, ns)
34
+ assign(name, patched, envir = ns)
35
+ lockBinding(name, ns)
36
+ TRUE
37
+ }, error = function(e) FALSE)
38
+ }
39
+
40
+ handle_message <- function(msg) {
41
+ publish_stream("stderr", conditionMessage(msg))
42
+ }
43
+
44
+ handle_warning <- function(w) {
45
+ call <- conditionCall(w)
46
+ call <- if (is.null(call)) '' else sprintf(' in %s', deparse(call)[[1]])
47
+ msg <- sprintf('Warning message%s:\n%s\n', call, dQuote(conditionMessage(w)))
48
+
49
+ publish_stream("stderr", msg)
50
+ }
51
+
52
+ handle_error <- function(e) {
53
+ if (inherits(e, "rlang_error") && isNamespaceLoaded("rlang")) {
54
+ assign("last_error", e, triple_colon("rlang", "the"))
55
+
56
+ the$last_error <- structure(
57
+ list(ename = "ERROR", evalue = "", format(e, backtrace = TRUE)),
58
+ class = "error_reply"
59
+ )
60
+ } else {
61
+ sys_calls <- sys.calls()
62
+ sys_calls <- head(tail(sys_calls, -16), -3)
63
+ stack <- capture.output(traceback(sys_calls, max.lines = 1L))
64
+
65
+ evalue <- paste(conditionMessage(e), collapse = "\n")
66
+ trace_back <- c(
67
+ cli::col_red("--- Error"),
68
+ evalue,
69
+ "",
70
+ cli::col_red("--- Traceback (most recent call last)"),
71
+ stack
72
+ )
73
+ the$last_error <- structure(list(ename = "ERROR", evalue = evalue, trace_back), class = "error_reply")
74
+ }
75
+ }
76
+
77
+ handle_value <- function(obj, visible) {
78
+ set_last_value(obj, visible)
79
+
80
+ if (visible && inherits(obj, "ggplot")) {
81
+ print(obj)
82
+ the$last_visible <- FALSE
83
+ }
84
+
85
+ }
86
+
87
+ handle_graphics <- function(plot) {
88
+ attr(plot, ".irkernel_width") <- getOption('repr.plot.width' , repr::repr_option_defaults$repr.plot.width)
89
+ attr(plot, ".irkernel_height") <- getOption('repr.plot.height', repr::repr_option_defaults$repr.plot.height)
90
+ attr(plot, ".irkernel_res") <- getOption('repr.plot.res', repr::repr_option_defaults$repr.plot.res)
91
+ attr(plot, ".irkernel_ppi") <- attr(plot, ".irkernel_res") / getOption('jupyter.plot_scale', 2)
92
+
93
+ if (!plot_builds_upon(the$last_plot, plot)) {
94
+ send_plot(the$last_plot)
95
+ }
96
+
97
+ the$last_plot <- plot
98
+ }
99
+
100
+ send_plot <- function(plot) {
101
+ w <- attr(plot, '.irkernel_width')
102
+ h <- attr(plot, '.irkernel_height')
103
+ res <- attr(plot, ".irkernel_res")
104
+ ppi <- attr(plot, ".irkernel_ppi")
105
+
106
+ data <- namedlist()
107
+ metadata <- namedlist()
108
+
109
+ for (mime in getOption('jupyter.plot_mimetypes')) {
110
+ data[[mime]] <- repr::mime2repr[[mime]](plot, width = w, height = h, res = res)
111
+
112
+ if (!identical(mime, 'text/plain')) {
113
+ metadata[[mime]] <- list(
114
+ width = w * ppi,
115
+ height = h * ppi
116
+ )
117
+ }
118
+
119
+ # Isolating SVGs (putting them in an iframe) avoids strange
120
+ # interactions with CSS on the page.
121
+ if (identical(mime, 'image/svg+xml')) {
122
+ metadata[[mime]]$isolated <- TRUE
123
+ }
124
+
125
+ }
126
+ display_data(data, metadata)
127
+ }
128
+
129
+ # currently not exported, because it is only meant to be called
130
+ # from xeus-r / interpreter::execute_request_impl
131
+ execute <- function(code, execution_counter, silent = FALSE, eval_env = rlang::global_env()) {
132
+ the$last_error <- NULL
133
+
134
+ parsed <- tryCatch(
135
+ parse(text = code),
136
+ error = function(e) {
137
+ msg <- paste(conditionMessage(e), collapse = "\n")
138
+ the$last_error <- structure(list(ename = "PARSE ERROR", evalue = msg), class = "error_reply")
139
+ }
140
+ )
141
+ if (!is.null(the$last_error)) return(the$last_error)
142
+
143
+ if (is.null(the$evaluate_sink_patched)) {
144
+ the$evaluate_sink_patched <- patch_evaluate_sink()
145
+ }
146
+ # A silent execution must not show anything, so it is not tee'd.
147
+ the$tee_stdout <- !silent && isTRUE(the$evaluate_sink_patched)
148
+
149
+ output_handler <- if (silent) {
150
+ evaluate::new_output_handler()
151
+ } else {
152
+ evaluate::new_output_handler(
153
+ # With the tee on, stdout was already published live from the console.
154
+ text = if (the$tee_stdout) function(txt) NULL else function(txt) publish_stream("stdout", txt),
155
+ graphics = handle_graphics,
156
+ message = handle_message,
157
+ warning = handle_warning,
158
+ error = handle_error,
159
+ value = handle_value
160
+ )
161
+ }
162
+
163
+ the$last_plot <- NULL
164
+ the$last_visible <- FALSE
165
+
166
+ filename <- glue("[{execution_counter}]")
167
+
168
+ the$frame_cell_execute <- environment()
169
+ evaluate::evaluate(
170
+ code,
171
+ envir = eval_env,
172
+ output_handler = output_handler,
173
+ stop_on_error = 1L,
174
+ filename = filename
175
+ )
176
+ if (!is.null(the$last_error)) return(the$last_error)
177
+
178
+ if (!silent && !is.null(the$last_plot)) {
179
+ tryCatch(send_plot(the$last_plot), error = handle_error)
180
+ }
181
+ if (!is.null(the$last_error)) return(the$last_error)
182
+
183
+ if (isTRUE(the$last_visible)) {
184
+ obj <- .Last.value
185
+
186
+ bundle <- mime_bundle(obj)
187
+
188
+ structure(class = "execution_result",
189
+ list(
190
+ # data = toJSON(bundle$data, auto_unbox = TRUE),
191
+ # metadata = toJSON(bundle$metadata, auto_unbox = TRUE)
192
+ # Before toJSON, ensure data is UTF-8
193
+ data = enc2utf8(toJSON(sanitize_raw_for_json(bundle$data), auto_unbox = TRUE)),
194
+ metadata = enc2utf8(toJSON(bundle$metadata, auto_unbox = TRUE))
195
+ )
196
+ )
197
+ }
198
+
199
+ }