Skip to content

Instantly share code, notes, and snippets.

@jcubic
Last active April 8, 2024 00:33
Show Gist options
  • Save jcubic/665c15b9867ef8d0d6f6f2f4509fa1f7 to your computer and use it in GitHub Desktop.
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
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);
#' 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