Automatically connecting to remote R sessions in Emacs using ESS

in-use

RStudio

I routinely use RStudio for R development and “doing science”. For the most part, RStudio is incredible. Live views of your data, automatically saving your workspace, easy-to-use literate programming tools with RMarkdown and Knitr – and the list keeps going.

But, unfortunately, I spent my entire undergrad and the first year of graduate school doing everything I possibly could in Emacs. So, now, I’m unfortunately ruined and apparently can’t do anything outside of it.

Also unfortunately, while RStudio has a server edition that allows you to use the platform through a web browser on a beefy server, you can only run a single session at a time.

Emacs Speaks Statistics

Thankfully, other people just as insane as I am have been maintaining an incredible project, dubbed Emacs Speaks Statistics. ESS ports all of the functionality available in RStudio, and more, inside emacs.

One of the best features of ESS is the ability to open R or RMarkdown files locally, but run their code in remote R sessions on your Big Beefy Server over the web. The easiest way I’ve found to do this is by adding this elisp function to your installation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(defvar R-remote-host "your-remote-server")
(defvar R-remote-session "R-session-name")
(defvar R-remote-directory "/path/to/your/project/directory")
(defun R-remote (&optional remote-host session directory)
"Connect to the remote-host's dtach session running R."
(interactive (list
(read-from-minibuffer "R remote host: " R-remote-host)
(read-from-minibuffer "R remote session: " R-remote-session)
(read-from-minibuffer "R remote directory: " R-remote-directory)))
(pop-to-buffer (make-comint (concat "remote-" session)
"ssh" nil "-Y" "-C" "-t" remote-host
"cd" directory ";"
"dtach" "-A" (concat ".dtach-" session)
"-z" "-E" "-r" "none"
inferior-R-program-name "--no-readline"
inferior-R-args))
(ess-remote (process-name (get-buffer-process (current-buffer))) "R")
(setq comint-process-echoes t))

When inside a R file or project, you can run M-x R-remote, and Emacs will prompt you for a hostname to SSH to, a name for the session, and a directory to run R in. You can then simply run C-c C-c on an R command to send the text to the remote R session!

It’s resilient to ssh drops since it runs the R session in dtach. If your ssh session drops, you can simply re-connect and keep your session going. By using the -Y flag to ssh, figures are forwarded to your local machine using X forwarding. Not quite the same as having the plots inline in a buffer, but the community hasn’t quite figured out how to do that yet.

Using Projectile

This is all great, but if you get disconnected, it requires you to manually type in all your session details again and remember what name you gave to your session, which I can never do. Enter Projectile.

I use Projectile to manage projects in emacs. Occasionally, I set local project-specific variables using dir-locals.el.

Projectile can allow for an extremely streamlined remote ESS experience in emacs.

First, I wrote a script that sits on my client machine to connect to some remote server, open a screen session that attaches to the dtach socket, and runs R. I keep all my project dtach socket files in a specific directory, ~/research/Rprocs/.dtach-{session} – I find having them in once place makes runaway sessions easier to manage.

1
2
3
#!/usr/bin/env bash
# place somewhere on your path
mosh $1 -- sh -c "screen -dR r-$2 bash -c 'dtach -A ~/research/Rprocs/.dtach-$2 -zEr none R --no-readline $3'"

This is already pretty great, since I can now run connect-R <host> <session> on my client machine and be in a resilient, long-running interactive R console on a remote server!

By using .dir-locals.el with projectile, I can also set default variables for the remote host and session name. For example, in a project called mixing I like to run on a remote host I have configured as carcosa, I can set my .dir-locals.el in my local working directory to:

1
2
3
; place in /path/to/project/.dir-locals.el
((nil . ((eval . (progn (setq R-remote-session "mixing") ;; the name you'd like to give to this session
(setq R-remote-host "ison")))))) ;; hostname you are connecting to

Finally, we can change our R-remote elisp command to use the new script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; place in ~/.emacs.d
(defun R-remote (&optional remote-host session directory)
"Connect to the remote-host's dtach session running R."
(interactive (list
(read-from-minibuffer "R remote host: " R-remote-host)
(read-from-minibuffer "R remote session: " R-remote-session)))
(pop-to-buffer (make-comint (concat "remote-" session)
"ssh" nil "-Y" "-C" "-t" remote-host
"dtach" "-A" (concat ".dtach-" session)
"-z" "-E" "-r" "none"
inferior-R-program-name "--no-readline"
inferior-R-args))
(ess-remote (process-name (get-buffer-process (current-buffer))) "R")
(setq comint-process-echoes t))

Now, when I call R-remote in the mixing project, it will still ask me interactively for a hostname and session name, but their values will be populated with the defaults specified in .dir-locals.el.