Comparison with Other R Tools

July 12, 2026 · View on GitHub

This page compares Raven with other R tools across two dimensions: language intelligence (what the language server provides — completions, diagnostics, navigation) and R session integration (the R console, plot viewer, data viewer, and help viewer that ship as part of Raven's VS Code extension).

Language intelligence

Compares Raven's language server against the language servers and code-intelligence systems in RStudio IDE, Positron (via Ark), and REditorSupport/languageserver (the R-package-based LSP that powers the REditorSupport VS Code extension and various other LSP clients).

Two newer Rust-based tools occupy adjacent niches rather than competing on the same axis, so they aren't in the table below: Air (Posit) is a code formatter (and formatting language server), and Jarl (Etienne Bacher) is a linter. Raven is a cross-file language server. It does fold a couple of lighter conveniences into the same process — configurable syntax-aware indentation as you type, and an opt-in subset of common style lints (off by default) — so if all you need is basic tidying, you can get it without launching a separate tool, saving the CPU and memory a second process would cost. But it doesn't reformat whole files, and the lint set is deliberately a subset, so Raven is meant to complement these, not compete with them. The comparisons here are therefore against the language servers and IDEs that overlap with what Raven does.

FeatureRavenRStudio IDEPositron (Ark)REditorSupport/languageserver
Cross-file awarenesssource()-aware: follows source() chains and # raven: / @lsp- directives, treats workspace-root .Rprofile as a startup prelude, builds a dependency graph, position-aware scopeWorkspace function-symbol index (for "Go to File/Function") plus runtime view of globalenv()Workspace-wide tree-sitter indexer of top-level symbols (functions, variables, R6/S7 methods); does not trace source() chainsIndexes top-level symbols from open documents (and R/*.R at startup for projects with a DESCRIPTION file); does not trace source() chains
DiagnosticsStatic: scope- and position-aware undefined variables across the resolved source() graph and workspace-root .Rprofile prelude, plus missing packages, circular deps, scope violationsBuilt-in static "Code Diagnostics": current-file scope/order-aware undefined variables, style/syntax warnings, runtime errors on execution; does not follow source() chainsStatic name-existence checks over document, flat workspace, package, and live-session symbols, plus namespace and missing-package checks; not source()- or execution-order-scopedStatic style + correctness linting via lintr / codetools (e.g. undefined globals); no independent syntax/parse diagnostics and no source() graph
NSE-aware undefined-variable checksYes: resolves the callee and suppresses only captured / data-masked / tidy-selected arguments, while still checking ordinary argumentsConfigurable but not NSE-aware: RStudio can check inside function calls, but its documented NSE workaround is to turn off diagnostics within all R function calls; it does not distinguish captured vs ordinary argumentsNo: Ark currently skips missing-symbol diagnostics inside call-like arguments and [ / [[ indices rather than applying per-callee argument policiesNo dedicated per-argument LSP model; relies on lintr / codetools heuristics and user/package global declarations
CompletionsScope-aware static: in-file scope + cross-file (dep-graph) + package exports, position-filteredMostly runtime: globalenv() and search path, plus function-argument hints; static for current-file local symbolsStatic document symbols + flat workspace top-level symbols across files + runtime helpers for $ / @ accessorsStatic, scope-aware: in-file scope + symbols from tracked/open documents (and package R/*.R when applicable) + installed package signatures
$ / @ accessorStatic completions and go-to-definition against tracked list/data-frame/S4 shapesRuntime column completion when the object exists in globalenv(); no cross-file def/refsRuntime completions (requires a live R session); no static defs or refs for accessor RHSToken-based static completions (any identifier previously used after $ or inside [[ ]] anywhere in the file, not tied to the LHS object); no go-to-def or find-references for accessor RHS
Go-to-definitionCross-file (functions and variables) via dep graphCross-file but functions only (Code > Go to Function Definition); no go-to-def for ordinary variable bindingsCross-file for functions and top-level variables via the workspace indexerAcross tracked/open documents (and package R/*.R when applicable); functions and top-level variables
Find referencesCross-file via dep graphNo first-class find-references; the practical workflow is "Find in Files" text search plus scope-local renameCross-file via the workspace indexerAcross tracked/open documents (and package R/*.R when applicable)
Package awarenessStatic NAMESPACE parsing + on-demand R subprocess for exports; position-awareFull runtime access via embedded R sessionRuntime (live R kernel) + tree-sitter detection of library() / require() callsRuntime helpers from the in-process R session for installed package signatures
Language / runtimeRust, no R session requiredElectron desktop (and a browser-based Server edition) bundled with an embedded R sessionRust LSP backed by a live R kernelR package, runs inside an R session
Editor supportAny LSP client (VS Code, Zed, Neovim, etc.)RStudio onlyCurrently only exposed through Positron; Ark's upstream README notes the LSP "will be made available to other frontends in the future"Any LSP client (vscode-R, ESS, Sublime, etc.)
Performance modelStarts without launching an R session; memory use is not tied to an R runtimeTied to R session lifetimeTied to R kernel startupTied to R session startup

When to choose Raven for language intelligence

No single alternative is closest to Raven on every axis. RStudio is closer for one important diagnostic behavior: it is current-file scope/order-aware, so it can distinguish a name defined earlier in the same file from one defined later. But RStudio does not follow source() chains, so that model stops at the file boundary. Positron (via Ark) has broader workspace reach: its workspace indexer sees variables and functions defined anywhere in your project. The trade-off is that Positron's current diagnostics treat that workspace as one flat symbol set — a symbol defined in any indexed file can satisfy the "is this name known?" check — whereas Raven builds a dependency graph from your source() chains, models the workspace-root .Rprofile as a startup prelude for ordinary scripts, and resolves what's in scope at each cursor position based on the actual order of execution.

Raven also differs in how it handles non-standard evaluation. In Ark's current source, undefined-symbol diagnostics are deliberately conservative around NSE: missing-symbol checks are skipped inside call-like arguments and [ / [[ indices because Ark does not yet model the argument-evaluation policy of calls like quote(), mutate(), or data.table [. RStudio exposes a broader switch for diagnostics inside function calls, and its own docs recommend toggling that off when NSE-heavy code produces incorrect diagnostics. Raven models the policy per call and per argument: paste(undefined_var) and lst[[typo]] are flagged, with(df, col + 1) checks df but suppresses col, and substitute(expr, env = typo_env) suppresses expr but still flags typo_env. That makes Raven's diagnostics, completions, and navigation reflect execution order in multi-file scripted projects, including circular-dependency and scope-violation detection, while avoiding a blanket "everything inside a function call might be NSE" suppression. Raven's static analysis also covers accessors (currently one level deep): it can complete fruit$apple from shapes it sees in your code, with no R session, where Positron's accessor completions come from a live session. The analysis is static throughout; Raven spawns R subprocesses on demand for package metadata (exports, NAMESPACE entries, function signatures) — short-lived processes it launches and controls, separate from your interactive R session — but it doesn't need a live R session to compute scope or accessor shapes.

Why Raven exists

Raven began as a port of Sight, a similar tool I'd written for Stata to experiment with agentic coding workflows on a language without a language server. I wanted those features in R too, so I started porting Sight's scope engine — which became Raven.

Raven's language server differs from REditorSupport/languageserver and Ark in two main ways. First, both rely on a live R session for at least some of their features, so part of what they offer reflects code you've actually run, not just what's written in the file. Second, neither resolves scope the way Raven does: symbols indexed across the workspace are offered as one flat set, regardless of what's in scope at the cursor.

Raven takes the same approach as language servers for statically-typed languages like TypeScript or Rust — parse the file, build an AST, resolve scope, follow source() chains across files. That has four practical consequences:

  • It's available immediately, even for code you haven't run. Answers come the moment you open a file — including code that errors halfway, is missing a dependency, or that you're only reading (onboarding to an unfamiliar repo, reviewing a pull request).
  • It reflects what your code says, not what your session remembers. A tool tied to a live session sees whatever is in globalenv() right now — what you happened to run, in whatever order, possibly stale. Comment out library(dplyr) while the package is still attached in your session, and a session-based tool keeps completing dplyr functions and won't flag them; Raven reads the file and knows dplyr isn't loaded at that point. The same goes for a variable you've renamed in the script but whose old binding lingers in the session.
  • It's read-only and side-effect-free. Computing scope never executes your code, so there's no risk of triggering what that code does (writing files, hitting a database, a long-running job). This is also what makes Raven safe to run behind an agentic/AI tool.
  • It runs in CI and other headless environments. Because scope resolution needs no live R session, Raven's diagnostics and lints can run in a CI pipeline or any headless context. CI means automated checks on pushes and pull requests; Raven uses it to catch analysis-code errors before merge. See Automated checks in CI for GitHub Actions and Bitbucket Pipelines examples. (Raven still shells out to its own R subprocess for package metadata when R is available — never your interactive session — but the core static analysis doesn't depend on it.)

Those two approaches aren't exclusive: you can install Raven alongside the REditorSupport extension and run both language servers at once — their different models let them coexist, each contributing what it's best at. Raven also detects when REditorSupport is enabled and declines to register its own R console by default, so REditorSupport's R-session integration stays in charge of running code, rendering plots, and opening data frames. See Coexistence for details.

What REditorSupport's language server offers that Raven doesn't

  • Session-aware completions — When the session watcher is enabled, REditorSupport can complete symbols from the live R session's globalenv(), including column names from data frames that only exist at runtime. Raven's completions are purely static.
  • Full lintr compatibility — REditorSupport runs the actual lintr package, so it covers lintr's complete rule set (e.g. object_usage_linter, line_length_linter, trailing_whitespace_linter) plus any custom or reconfigured linters. Raven implements 18 of these natively — most of lintr's default rule set (see Linting) — but doesn't run lintr itself; for rules outside that subset, you can run lintr via REditorSupport alongside Raven. For the rules it implements, Raven reads a documented subset of your existing .lintr for backward compatibility, and can also be configured through raven.toml or VS Code settings (point-and-click in the Settings UI).

R session integration

Raven's VS Code extension also includes an R console, plot viewer, data viewer, and help viewer. The REditorSupport extension provides equivalents (it's a long-standing, widely used extension), and Positron has its own first-party versions. We chose to build these features rather than rely on REditorSupport because they let us address specific limitations our team has run into — described below.

These comparisons are based on reading the current upstream sources (links cited inline) at the time of writing. Both projects iterate, so a claim that's accurate today may be out of date tomorrow — if you spot something that no longer matches the code, please file an issue.

R console

REditorSupport sends code from the editor to R via VS Code's terminal.sendText() API — that is, by simulating typing into the integrated terminal. Its implementation in vscode-R/src/rTerminal.ts splits multi-line code on newlines and awaits an rtermSendDelay (default 8 ms) between lines, optionally wrapping the block in bracketed-paste sequences. On a fresh local R terminal, bracketed paste is generally reliable; the inter-line delay is a deliberate trade-off for reliability, but it's noticeable on large blocks — pasting a 1,000-line block adds at least 8 seconds before R starts parsing.

For targeting a terminal other than the one REditorSupport manages — for example, R running inside tmux so the session survives VS Code restarts — REditorSupport offers a workspace-wide setting (r.alwaysUseActiveTerminal) that routes every send to the currently active terminal. That works well when one terminal is always the right destination, but it's a global toggle rather than a per-invocation choice; the alternative (binding workbench.action.terminal.sendSequence) only sends the current line, with no multi-line statement detection.

Raven's raven.sendToR.sendMethod (default auto) pastes short blocks directly and switches to a temp-file source() once a block reaches raven.sendToR.autoTempFileThresholdLines (default 25). The temp-file path bypasses the typing-simulation pipe entirely: R reads the file from disk, so for those larger blocks there's no inter-line delay. Alongside the main send commands (which target Raven's managed R terminal), Raven also exposes a separate Terminal submenu in the editor toolbar (and matching commands in the Command Palette) that targets whatever terminal is currently active — R inside tmux, a Docker container, or any other shell — using the same statement detection and temp-file fallback. This lets you keep one default destination while still routing a particular send elsewhere without flipping a setting. See R Console: Editor Toolbar and Send method for details.

Data viewer

REditorSupport's View() keeps the data frame live in R's memory and serves row windows to the webview on demand.

Raven's View() takes a different staging route: it writes the frame to an Apache Arrow IPC (Feather v2) file on disk, and the webview decodes only the row windows currently visible directly from that file. The two choices have different trade-offs — REditorSupport keeps the data live in R memory (so a refresh against an updated frame is cheap), while Raven snapshots the frame to disk and reads windows without going through R (so paging doesn't have to wait on R's main thread when R is busy). Webview-side memory and rendering scale with the viewport in both designs; on the R side they differ — REditorSupport materializes the whole frame in R memory up front, where Raven streams it out to Arrow once and lets the webview read from the file. In our own smoke tests the on-disk Arrow path has stayed responsive on very large frames where the R-staged path can hit memory limits. The two viewers also handle value-labelled data differently: Raven recognizes haven_labelled plus foreign / readstata13-style label maps and substitutes the label when its Labels toggle is on (see Data Viewer → Labels); REditorSupport classifies labelled columns as formatted numerics and renders the underlying codes. See Data Viewer for Raven's full implementation.

Sorting differs across R data viewers in three places — gesture, key derivation for labelled data, and whether the sort survives a View() re-invocation:

GestureMulti-columnLabelled-column keySurvives reopen
RStudioHeader clickNo (single column only)Factor integer codeNo
REditorSupport (ag-grid)Header click; Shift-click appendsYestolower(as.character(...))No
PositronHeader right-click / ellipsis menuYes (repeated invocation)Factor integer codeSession-only
RavenHeader right-click; Shift+pick appendsYesFollows the Labels toggle (WYSIWYG)Yes, per panel × schema hash

NA / NaN placement is the one universal: every viewer puts missing values last in both directions, matching order(..., na.last = TRUE). See Data Viewer → Sorting for Raven's full behavior.

Row filtering also differs across viewers. RStudio shows a per-column filter row below the headers: numeric columns get a histogram-brush range selector, factor columns a single-select dropdown, and other columns a substring text box; there is also a global search box that scans all columns. Positron has a typed-predicate filter bar that lets you build AND/OR chains of column-predicate chips, evaluated server-side by the R kernel. vscode-R uses ag-Grid's built-in per-column funnel menu (floating set filter for character columns, number filter for numerics); ag-Grid's free set filter does not offer factor multi-select or regex matching, and there is no global search separate from ag-Grid's quick filter. None of the three persist filter state across View() re-invocations. Data Wrangler takes a different approach: filters are modelled as cleaning steps that generate transformation code, not as a live view into the original frame. Raven's filters are typed per-column predicates (numeric, factor checklist, character with regex, boolean, date, universal empty/not-empty) with a kebab chip strip that supports per-chip enable/disable and combined AND composition; factor and value-labelled string columns route predicates through the displayed string following the Labels toggle (WYSIWYG); labelled-numeric columns (e.g. haven_labelled on a numeric base) are the exception — their is one of / is not one of set-membership filters match the underlying code regardless of the Labels toggle (see Data Viewer → Filtering); and filter state persists per panel × schema hash so a later View(df) restores the configuration. See Data Viewer → Filtering for the full behavior.

Hover help

REditorSupport's hover help is rendered by languageserver inside its R process. When it can't pin a symbol to a single package, it falls back to a help lookup with no package specified, which can return matches spanning multiple same-named topics — so hovering over filter in a script that loads dplyr may surface dplyr::filter, stats::filter, and others together, rather than the one in scope at the cursor.

Raven takes a different approach. Its language server is a separate Rust process that statically traces library() / require() calls in the file at the cursor and across the source() chain — including files the user hasn't opened — plus namespace qualifiers (pkg::fn) and # raven: / @lsp- directives, to compute which package is in scope at the cursor position. It then shows a single help link for that package. Raven spawns R subprocesses to read package metadata (exports, NAMESPACE entries, function signatures), but the disambiguation logic itself is static — it doesn't depend on whether the user has opened, run, or attached anything. See Help Viewer.

What REditorSupport's VS Code extension offers that Raven doesn't

  • Workspace viewer — A sidebar panel that introspects the live R session, showing objects in globalenv() with their types and dimensions, plus attached and loaded namespaces. Objects can be viewed or removed directly from the panel.
  • htmlwidget / Shiny viewer — Interactive HTML output (plotly, DT, profvis, etc.) and Shiny apps render in VS Code webview panels.
  • List / environment viewerView() on lists and environments opens a collapsible tree view. Raven's View() only handles data frames and matrices.

Coexistence

See Coexistence with Other R Extensions for how Raven's R-session features interact with the REditorSupport extension and Positron, how raven.rConsole.activation works, and how to run REditorSupport's lintr alongside Raven.