Working in the R console
ry repl gives you an R prompt whose Tab completion comes from ry’s type checker, and
ry run executes a script through the same session.
What it is
Section titled “What it is”ry does not reimplement R. It finds the R already installed on your machine, loads it into its own
process, and hands control to R’s real main loop. Everything R does, R still does: parsing, evaluation,
autoprinting, error messages, browser(), readline(), S4 dispatch. Your .Rprofile is sourced.
Packages install and attach normally.
What ry replaces is the line editor in front of R. That is where the difference shows up:
$ ry repl... R's own startup banner ...ry R console — R at /usr/lib/R (q() or Ctrl-D quits)> account <- list(holder = "ada", balance = 120.5)> account$balance doubleholder characterR’s own console can complete account$balance because by the time you press Tab the object exists in
memory. ry reaches the same answer by type-checking what you have typed so far, which also lets
it show that balance is a double, complete full signatures, and complete names inside #:
annotations. The trade-off: completion never inspects the live R session.
The console is also the only part of ry that needs R at all — see What needs R.
Starting it, and the R it finds
Section titled “Starting it, and the R it finds”ry repl # start a sessionry repl --keybindings vi # vi editing instead of emacsry repl -f setup.R # run a script first, then hand you the promptry locates R in two steps:
R_HOME, if it is set and non-empty.- Otherwise it runs
R RHOMEusing yourPATHand takes the answer.
Whatever it settles on is exported back as R_HOME before R starts, so R.home() inside the session
agrees with the banner line — which is how you check at a glance which installation you got. On Unix,
R_SHARE_DIR, R_INCLUDE_DIR and R_DOC_DIR are recovered from R’s own bin/R script too, so
R.home("share") stays correct on distributions that relocate those directories.
To pin a specific R, set R_HOME for the one command:
R_HOME=/opt/R/4.5.1/lib/R ry replStartup problems fail immediately and say what to do, with exit status 2:
$ ry run analysis.Rerror: no R found: R_HOME is not set and running `R RHOME` failed (No such file or directory (os error 2)). Install R or set R_HOME to an R installation.
$ R_HOME=/nonexistent ry run analysis.Rerror: R_HOME is set to /nonexistent, but that directory does not existOne hard requirement beyond “R is installed”: it must have been built as a shared library
(--enable-R-shlib). Every CRAN binary distribution is. If yours is not, ry says so and names the
directory it looked in.
R 4.2 or newer is what this is developed and tested against, and it is required on Windows. Nothing checks the version, so an older R will simply fail at load time with a missing-symbol error rather than a clear message.
Sessions start clean and leave nothing behind: no .RData is restored on the way in, and nothing is
saved on the way out. ry repl also needs a terminal — piping into it does nothing useful, since
the editor cannot run and the session immediately sees end of input. Use ry run for anything
non-interactive.
Where completions come from
Section titled “Where completions come from”Press Tab and ry type-checks the session so far — every line you have accepted, plus the line you are editing — then offers what fits at the cursor. The right-hand column is the type it inferred.
> ncharnchar fn(x: Any, [type]: character, [allowNA]: logical, [keepNA]: logical) -> integernzchar fn(x: Any, [keepNA]: logical) -> logicalgetDLLRegisteredRoutines.character From the `base` package.The first two have typed stubs shipped with ry, so you get the full
signature and which arguments are optional ([type]). The third is a real base export with no stub
yet, so you get the name and its package. Tab again to cycle, Enter to accept.
Six kinds of completion are available:
| You type | You get |
|---|---|
| a bare name | Session bindings you defined, standard-library functions, R reserved words |
stats::rnor | That namespace’s exports, with signatures where a stub exists |
account$ | Fields of the record type ry inferred, each with its own type |
obj@ | S4 slot names already spelled after @ earlier in the session — not read from setClass |
#: chara | Type names — inside an annotation comment the completer switches from values to types |
x[[" | Record fields, when the string subscripts a record |
Anything you have defined is available on the next line, and anything reachable through :: is
available whether or not the package is attached:
> monthly_rate <- function(annual) annual / 12> monthlymonthly_rate
> stats::rnorrnorm fn(n: Any, [mean]: Any, [sd]: Any) -> double[]rlnorm fn(n: Any, [meanlog]: Any, [sdlog]: Any) -> double[]order.dendrogramMatching is fuzzy and smart-case: exact beats prefix, prefix beats substring, substring beats subsequence (which needs three characters). Names you defined outrank standard-library names.
Three things it will not complete
Section titled “Three things it will not complete”Completion is static analysis over the session transcript, and only that transcript. Each of these looks like a bug the first time:
- Objects R knows about but ry never saw you type. Anything created by
source()or defined in a file you passed to-fis invisible to Tab.ry repl -f setup.Rfeeds the script straight to R, socompound()from that file runs fine but does not complete. Paste the definition into the prompt if you want it completed. - Your project’s files and
ry.toml. The console analyzes one document — the session. It does not read your working directory, your project sources, or yourstubs/*.Rtypesoverrides. Use the language server for typed work inside project files. - Third-party packages. Only the standard library is available.
library(dplyr)attaches dplyr in R as usual, butdplyr::muta+ Tab still offers nothing typed — activating those stubs needs project metadata that a console session does not have.
Editing
Section titled “Editing”The editor is ry’s, and it behaves the way a modern shell does.
| Key | Effect |
|---|---|
| Tab | Open the completion menu; Tab again cycles candidates |
| Ctrl-R | Reverse search through history |
| Up / Down | Walk history |
| Ctrl-C | At the prompt, clear the line. During evaluation, interrupt it |
| Ctrl-D | End the session (same as q()) |
History is persistent and shared across sessions — the last 1000 entries, in
~/.local/share/ry/history.txt on Linux, ~/Library/Application Support/ry/ on macOS. It is
ry’s own file, separate from R’s .Rhistory. As you type, the greyed-out text ahead of the cursor
is a suggestion from that history; Right arrow accepts it. Input is syntax-highlighted by the same lexer
ry check uses — keywords blue, constants like TRUE purple, strings green, numbers cyan,
comments grey.
Multi-line input is one editable buffer, not a sequence of lines you can no longer reach:
> compound <- function(principal, rate) {+ principal * (1 + rate)+ }> compound(100, 0.05)[1] 105ry decides a line is incomplete from the syntax alone — an open bracket, a trailing operator, a
dangling comma, an unclosed string. It is deliberately conservative: if it guesses “complete” and R
disagrees, R asks for the rest with its own + prompt and nothing is lost.
Every prompt R produces passes through unchanged, because ry does not render prompts of its own. So debugging works exactly as you expect:
> f <- function(x) { browser(); x * 2 }> f(21)Called from: f(21)Browse[1]> x[1] 21Browse[1]> c[1] 42Interrupts are cooperative, the same as in stock R: Ctrl-C sets a pending flag that R honors at its next check point. A long R loop stops promptly and drops you back at the prompt:
> for (i in 1:100000000) x <- sqrt(i)^C> 1 + 1[1] 2Compiled code that never checks for interrupts will not respond — again, exactly as in stock R.
ry run
Section titled “ry run”ry run FILE is the same embedded session with the editor removed: the file is fed to R, R
evaluates it, and the process exits.
$ cat report.Rrates <- c(0.02, 0.035, 0.05)cat("mean rate:", mean(rates), "\n")
$ ry run report.R... R startup banner ...mean rate: 0.035$ echo $?0An error at top level halts the script and fails the run — the same halt-on-error contract Rscript
gives you:
$ ry run report.R # cat("before\n"); stop("rate table is empty"); cat("after\n")beforeError: rate table is empty$ echo $?1after never printed. Parse errors behave the same way: output before the bad line is kept, the run
stops there, and the status is 1. An explicit q(status = 7) in your script passes straight through.
For the full status table, see the CLI reference.
Four things to know before you reach for it as an Rscript replacement:
ry rundoes no analysis. No type checking, no findings, no diagnostic codes — the bytes go to R. Runry checkif you want the script checked.interactive()returnsTRUE. The embedded session is always an interactive one, which is a real divergence fromRscript. Code that branches oninteractive()takes the other path.- R’s startup banner is printed, and your
.Rprofileis sourced. There is no quiet or vanilla mode. - If your script sets its own
options(error = ...), it replaces the handler that produces the failing exit status, and a failing script may then exit 0.
ry repl -f FILE uses the same feeding mechanism but keeps you at the prompt afterwards, which is
the fastest way to load a scratch setup and start poking at it.
What needs R
Section titled “What needs R”Only the console does. Everything else in ry is self-contained: type information for the standard library is compiled into the binary, not read from an R installation.
| Command | Needs R installed? |
|---|---|
ry check | No |
ry fmt | No |
ry server (the language server) | No |
ry repl | Yes — at runtime |
ry run | Yes — at runtime |
This is why continuous integration for a ry project needs no R at all; see Continuous integration.