Last active
April 8, 2024 00:33
-
-
Save jcubic/665c15b9867ef8d0d6f6f2f4509fa1f7 to your computer and use it in GitHub Desktop.
JavaScript bookmark that can be used to open interactive R shell in shiny app
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
javascript:(function($) { | |
var handlers = []; | |
if (typeof Shiny !== 'undefined') { | |
Shiny.addCustomMessageHandler("__EVAL__", function(data) { | |
handlers.forEach(function(handler) { | |
handler(data); | |
}); | |
}); | |
} | |
var index = 0; | |
function exec(str, callback) { | |
if (!str.trim()) { | |
return Promise.resolve(); | |
} | |
return new Promise(function(resolve, reject) { | |
var id = index++; | |
handlers.push(function handler(data) { | |
if (data.id === id) { | |
if (data.error) { | |
reject(data.error); | |
} else { | |
resolve(data.result); | |
} | |
} | |
handlers = handlers.filter(function(f) { | |
return f !== handler; | |
}); | |
}); | |
Shiny.onInputChange('__EVAL__', {id: id, value: str}); | |
}); | |
} | |
function init() { | |
var t = $('div.terminal'); | |
if (t.length) { | |
t.each(function() { | |
$(this).terminal().destroy().remove(); | |
}); | |
} | |
$('.shell-wrapper').remove(); | |
var wrapper = $('<div>').addClass('shell-wrapper').appendTo('body'); | |
var nav = $('<nav/>').appendTo(wrapper); | |
$('<span class="shell-destroy">[x]</span>').click(function() { | |
term.destroy(); | |
wrapper.remove(); | |
}).appendTo(nav); | |
$.terminal.defaults.formatters = [$.terminal.nested_formatting]; | |
$.terminal.syntax('r'); | |
var term = window.term = $('<div>').appendTo('body').terminal(function(cmd, term) { | |
if (!cmd.trim()) { | |
return; | |
} | |
return exec(cmd).then(function(result) { | |
if (result) { | |
if (result instanceof Array) { | |
result = result.join('\n'); | |
} | |
if (typeof result !== 'string') { | |
result = String(result); | |
} | |
result = result.trim(); | |
if (result) { | |
term.echo($.terminal.escape_brackets(result)); | |
} | |
} | |
}).catch(function(e) { | |
if (e instanceof Array) { | |
e = e.join('\n'); | |
} | |
term.error(e); | |
}); | |
}, { | |
greetings: 'R console\n' | |
}); | |
term.appendTo(wrapper); | |
$('style.terminal').remove(); | |
$('<style class="terminal">.terminal { font-size-adjust: none; --size: 1.2;height: calc(100% - 31px); } .shell-wrapper nav {color:#ccc;border-bottom:1px solid #ccc;font-family:monospace;text-align: right;background: black;} .shell-wrapper {position: fixed;z-index:99999;bottom:0;left:0;right:0;height:150px; }.shell-destroy {padding: 5px;cursor:pointer;display: inline-block;}</style>').appendTo('head'); | |
} | |
['https://unpkg.com/jquery.terminal/css/jquery.terminal.min.css', | |
'https://unpkg.com/prismjs/themes/prism-coy.css' | |
].forEach(function(url) { | |
var link = $('<link href="' + url + '" rel="stylesheet"/>'); | |
var head = $('head'); | |
if (head.length) { | |
link.appendTo(head); | |
} else { | |
link.appendTo('body'); | |
} | |
}); | |
if ($.terminal && $.terminal.prism) { | |
init(); | |
} else { | |
var scripts = [ | |
'https://unpkg.com/jquery.terminal/js/jquery.terminal.min.js', | |
'https://unpkg.com/prismjs/prism.js', | |
'https://unpkg.com/jquery.terminal/js/prism.js', | |
'https://unpkg.com/prismjs/components/prism-r.min.js' | |
]; | |
(function recur() { | |
var script = scripts.shift(); | |
if (!script) { | |
init(); | |
} else { | |
$.getScript(script, recur); | |
} | |
})(); | |
} | |
})(jQuery); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#' To use Debugger you need to call debugger.init(input, session) | |
#' inside shiny server function. Then you need complementary JS code | |
#' that is avaiable as bookmarklet. Then in any context you call | |
#' debugger function to change the context (it's optional). | |
#' If you call that function in constructor you will be able | |
#' to access self and private properties of the object. | |
#' eval to string function that work the same as R REPL | |
#' @param code - string to evaluate | |
#' @param env - enviroment in which to evaluate the code | |
#' @export | |
r.eval <- function(code, env = parent.frame()) { | |
paste( | |
capture.output(eval(parse(text = code), envir = env)), | |
collapse = "\n" | |
) | |
} | |
#' debugger environment | |
.global <- list2env(list(env = NULL)) | |
#' function to use instead of browser() and put context of debugger in this place | |
#' @export | |
debugger <- function() { | |
.global$env <- parent.frame() | |
} | |
#' Initialization of the debugger | |
#' @param session - shiny session | |
#' @export | |
debugger.init <- function(session, env = parent.frame()) { | |
.global$env <- env | |
shiny::observeEvent(session$input[["__EVAL__"]], { | |
data <- session$input[["__EVAL__"]] | |
tryCatch({ | |
payload <- list( | |
id = data$id, | |
result = r.eval(data$value, env = .global$env) | |
) | |
session$sendCustomMessage("__EVAL__", payload) | |
}, error = function(cond) { | |
error <- paste( | |
capture.output(traceback(cond)), | |
collapse = "\n" | |
) | |
payload <- list(id = data$id, error = error) | |
session$sendCustomMessage("__EVAL__", payload) | |
}) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment