@@ -32,11 +32,57 @@ new_engine <- function(config) {
3232 eng
3333}
3434
35+ hot_swappable <- c(
36+ " css" ,
37+ " png" ,
38+ " jpg" ,
39+ " jpeg" ,
40+ " gif" ,
41+ " svg" ,
42+ " webp" ,
43+ " ico" ,
44+ " avif"
45+ )
46+
3547run_engine <- function (engine ) {
48+ restart_pending <- FALSE
49+ restart_due_at <- NULL
50+ pending_restart_changes <- character ()
51+ restart_ms <- 300L
52+
53+ hotswap_pending <- FALSE
54+ hotswap_due_at <- NULL
55+ pending_hotswap_changes <- character ()
56+ hotswap_ms <- 120L
57+
58+
3659 callback <- function (changes ) {
37- cli_file_changed(changes )
38- teardown_engine(engine )
39- buildup_engine(engine )
60+
61+ changed_files <- unique(unlist(changes , use.names = FALSE ))
62+
63+ exts <- tolower(tools :: file_ext(changed_files ))
64+
65+ is_hot_swappable <- length(exts ) > 0L &&
66+ all(exts %in% hot_swappable )
67+
68+
69+
70+ if (is_hot_swappable ) {
71+ hotswap_pending <<- TRUE
72+ pending_hotswap_changes <<- unique(c(
73+ pending_hotswap_changes ,
74+ changed_files
75+ ))
76+ hotswap_due_at <<- Sys.time() + hotswap_ms / 1000
77+
78+ } else {
79+ restart_pending <<- TRUE
80+ pending_restart_changes <<- unique(c(
81+ pending_restart_changes ,
82+ changed_files
83+ ))
84+ restart_due_at <<- Sys.time() + restart_ms / 1000
85+ }
4086 }
4187 on.exit({
4288 teardown_engine(engine )
@@ -57,11 +103,52 @@ run_engine <- function(engine) {
57103 repeat {
58104 Sys.sleep(0.05 ) # todo, allow this to be configured at some point
59105 drain_runner_log(engine )
106+
107+ if (
108+ ! isTRUE(restart_pending ) &&
109+ isTRUE(hotswap_pending ) &&
110+ Sys.time() > = hotswap_due_at
111+ ) {
112+ json <- jsonlite :: toJSON(
113+ list (
114+ type = " HW::resource" ,
115+ targets = list (pending_hotswap_changes )
116+ ),
117+ auto_unbox = TRUE
118+ )
119+ nanonext :: send(
120+ engine $ publisher ,
121+ json ,
122+ mode = " raw"
123+ )
124+ cli_hot_swapped(pending_hotswap_changes )
125+
126+ hotswap_pending <- FALSE
127+ hotswap_due_at <- NULL
128+ pending_hotswap_changes <- character ()
129+ }
130+
131+ if (isTRUE(restart_pending ) && Sys.time() > = restart_due_at ) {
132+ cli_file_changed(pending_restart_changes )
133+ restart_pending <- FALSE
134+ restart_due_at <- NULL
135+ pending_restart_changes <- character ()
136+
137+ hotswap_pending <- FALSE
138+ hotswap_due_at <- NULL
139+ pending_hotswap_changes <- character ()
140+
141+
142+ teardown_engine(engine )
143+ buildup_engine(engine )
144+ }
145+
60146 current_state <- watch_directory(
61147 engine ,
62148 current_state ,
63149 callback
64150 )
151+
65152 }
66153}
67154
@@ -87,13 +174,13 @@ buildup_engine <- function(engine) {
87174
88175 if (! res ) {
89176 cli :: cli_progress_done(result = " failed" )
177+ stop(" Failed to start Plumber server." )
90178 } else {
91179 publish_browser_reload(engine )
92180 cli :: cli_progress_done()
181+ cli_watching_directory(engine )
182+ drain_runner_log(engine )
93183 }
94-
95- cli_watching_directory(engine )
96- drain_runner_log(engine )
97184}
98185
99186teardown_engine <- function (engine ) {
@@ -144,13 +231,38 @@ drain_runner_log <- function(engine) {
144231 engine $ logpos <- size
145232
146233 if (nzchar(data )) {
234+ if (grepl(" === HOTWATER_ERROR_BEGIN ===" , data )) {
235+ msg <- sub(
236+ " .*=== HOTWATER_ERROR_BEGIN ===\\ s*([\\ s\\ S]*?)\\ s*=== HOTWATER_ERROR_END ===.*" ,
237+ " \\ 1" ,
238+ data ,
239+ perl = TRUE
240+ )
241+ msg <- trimws(msg )
242+ json <- jsonlite :: toJSON(
243+ list (
244+ type = " HW::error" ,
245+ error = msg
246+ ),
247+ auto_unbox = TRUE
248+ )
249+
250+ nanonext :: send(
251+ engine $ publisher ,
252+ json ,
253+ mode = " raw"
254+ )
255+ }
256+
147257 data <- gsub(
148258 " === HOTWATER_ERROR_BEGIN ===\\ s*([\\ s\\ S]*?)\\ s*=== HOTWATER_ERROR_END ===" ,
149259 cli :: col_red(" \\ 1" ),
150260 data ,
151261 perl = TRUE
152262 )
153263
264+
265+
154266 data <- gsub(
155267 " === HOTWATER_WARNING_BEGIN ===\\ s*([\\ s\\ S]*?)\\ s*=== HOTWATER_WARNING_END ===" ,
156268 cli :: col_yellow(" \\ 1" ),
0 commit comments