This idea came to my mind to help with debugging R shiny apps, that you can interact with when it’s running. You can type anything into R console while shiny app is up.

So I thought, can I use interactive shell inside browser written in JavaScript? this post is the result of me writing this interactive shell.

First when we want write R language interpreter we need eval function that will return the result of a string. This function look like this.

r.eval <- function(code) {

paste(capture.output(eval(parse(text = code))), collapse = "

")

}

Now inside JavaScript we need to send, string that we want to evaluate, from browser to R process.

var index = 0;

function exec(str) {

var id = index++;

Shiny.onInputChange('__EVAL__', {id: id, value: str});

}

We use very unusual event name, you should pick another one, so you don’t get hack if someone will try this string in your app that is public. The other solution is to add this code only for specific users (like developers or admins).

We will improve this function later, it will be function that return a promise with result from R.

Now we need to write observeEvent (inside server function), to get the value from JavaScript and evaluate the value in R and send the result back to browser.

observeEvent(input[["__EVAL__"]], {

data <- input[["__EVAL__"]]

tryCatch({

payload <- list(id = data$id, result = r.eval(data$value))

session$sendCustomMessage("__EVAL__", payload)

}, error = function(cond) {

error <- paste(capture.output(traceback(cond)), collapse = "

")

payload <- list(id = data$id, error = error)

session$sendCustomMessage("__EVAL__", payload)

})

})

Inside R we have everything. Next we need better JavaScript code. If you don’t care about this code, and want solution, at the end there is bookmark that can be used to create the terminal without writing any JavaScript.

For those who want to follow, we need to write better exec function. Here it is:

var exec = (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()) { // no data

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});

});

}

return exec;

})();

The code use ES5 syntax, feel free to refactor it to use ES6 (ES2015) or any new version of JavaScript.

Now you can in JavaScript developer tools console writs evaluate R code and get response:

await exec("10 + 10")

[1] 20

top level await may work only in developer tools. if it don’t work you can use:

exec("10 + 10").then(result => console.log(result));

Now all it’s need to do, is to write nice looking terminal so you can type commands and get response from inside shiny app. I will use my JavaScript library jQuery Terminal.

We need to include the libraries first:

We can also want to have R syntax highlighting, so it look nicer. We need this html to include proper libraries:

<link href="https://unpkg.com/prismjs/themes/prism-coy.css" rel="stylesheet"/>

<script src="https://unpkg.com/prismjs/prism.js"></script>

<script src="https://unpkg.com/jquery.terminal/js/prism.js"></script>

<script src="https://unpkg.com/prismjs/components/prism-r.min.js"></script>

After adding dependencies we need to create the terminal. We want to have terminal at the bottom of the screen, so we first write basic html structure.

<div class="shell-wrapper">

<span class="shell-destroy">[x]</span>

<div id="r-term"></div>

</div>

Now we need to give some style:

.terminal {

--size: 1.2;

height: 100%;

}

.shell-wrapper {

position: fixed;

z-index: 99999;

bottom: 0;

left: 0;

right: 0;

height: 150px;

}

.shell-destroy {

position: absolute;

right: 10px;

color: #ccc;

top: 10px;

z-index: 10;

cursor: pointer;

font-family: monospace;

}

Now the final part is to create the terminal with interactive R shell.

var term = $('#r-term').terminal(function(cmd, term) {

if (!cmd.trim()) {

// we ignore empty command but exec will also handle that

return;

}

return exec(cmd).then(function(result) {

result = result.trim();

if (result) {

term.echo($.terminal.escape_brackets(result));

}

}).catch(function(e) {

term.error(e);

});

}, {

greetings: 'R console

'

});

And now you have real terminal in browser, that give you real interactive R session in JavaScript.

You can also use little plugin called tilda instead the code above, you only need to use tilda instead of terminal. And you will have quake like console in your app, that is open when you press tidla ~ on your keyboard.

Tidla can be found on GitHub as one of jQuery Terminal examples. Adding Quake like R console is left as exercise to the reader.

And as I’ve promised here is link to bookmarklet, it’s not very nice looking but it have everything from this post. It load all the files and you can click it multiple times it will destroy previous terminal.

UPDATE:

While I was integrating this debugger into my shiny app, I’ve made few improvements, that I want to share.

I’ve created two functions debugger.init() and debugger(). Init should be called inside server function, because it require input and session. And debugger function can be used to change the context, where interpreter is evaluating expressions, just like browser(), but the app is still running.

And here is the code, that can be put into a package.