Skip to content

feat: add :objects browser for .GlobalEnv inspection#237

Draft
eitsupi wants to merge 6 commits into
mainfrom
feature/objects-browser
Draft

feat: add :objects browser for .GlobalEnv inspection#237
eitsupi wants to merge 6 commits into
mainfrom
feature/objects-browser

Conversation

@eitsupi

@eitsupi eitsupi commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add :objects / :objs meta-command that opens a TUI browser showing .GlobalEnv bindings (Phase 1 of the objects browser epic)
  • Read bindings via R's C API directly (R_lsInternal3, R_BindingIsActive, Rf_findVarInFrame, etc.) — no external R packages required
  • Active bindings and unforced promises are labelled without being evaluated
  • Dot-prefixed names hidden by default (matches ls() default); toggle with a

Changes

crates/arf-libr

  • Add C API symbols: Rf_findVarInFrame, R_existsVarInFrame, R_BindingIsActive, R_lsInternal3, Rf_getAttrib, R_ClassSymbol, Rf_isS4
  • Add public helpers: r_unbound_value(), r_class_symbol()

crates/arf-harp

  • New workspace module: EnvEntry struct + workspace_snapshot(all_names: bool)

crates/arf-console

  • New pager/objects_browser.rs: crossterm-based TUI with columns Name │ Class │ Type │ Length
  • Key bindings: j/k navigate, / filter (fuzzy), a toggle hidden, r refresh, q/Esc exit
  • Add :objects / :objs to meta-command dispatch and tab-completion

Test plan

  • :objects opens the browser and lists .GlobalEnv bindings
  • Dot-prefixed names are hidden by default; a key shows them
  • / filter matches by name and class (fuzzy)
  • r refresh picks up newly assigned variables
  • Active bindings shown as (active binding) without being forced
  • Narrow terminal (< 66 cols) shows "terminal too small" warning
  • q / Esc returns to the REPL prompt cleanly
  • Tab-completing :obj suggests :objects and :objs

🤖 Generated with Claude Code

eitsupi and others added 6 commits June 30, 2026 14:10
Add a TUI browser accessible via `:objects` (or `:objs`) that displays
the current .GlobalEnv bindings as a flat list with name, class, type,
and length columns.

Key design decisions:
- Active bindings detected via R_BindingIsActive *before* Rf_findVarInFrame
  to avoid forcing them (ordering prevents unintended evaluation)
- PROMSXP bindings labelled "(promise)" without forcing
- Explicit class from Rf_getAttrib(R_ClassSymbol); implicit class derived
  from typeof for objects without an explicit class attribute
- Filtering with / using the existing fuzzy_match infrastructure
- Refresh with r re-calls workspace_snapshot() without leaving the browser

New C API symbols added to arf-libr:
  Rf_findVarInFrame, R_existsVarInFrame, R_BindingIsActive,
  R_lsInternal3, Rf_getAttrib, R_ClassSymbol, Rf_isS4

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Register :objects and :objs in MetaCommandCompleter so they appear
  in tab-completion
- Change workspace_snapshot() to take an all_names parameter; default
  to false (matching R's ls() default) so dot-prefixed names including
  arf's own .arf_error_state/.arf_prev_error_handler are hidden
- Add 'a' key toggle in the browser to show/hide dot-prefixed names;
  header shows "[all]" indicator when enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These two internal bindings were stored in .GlobalEnv, causing them to
appear in the user's workspace (e.g. :objects browser, ls()).

Move them to baseenv() instead, which is the correct location for
arf's runtime infrastructure. Dot-prefixed names in baseenv are not
visible to ls()/objects() by default, so the user workspace stays clean.

Also switch the Rust-side lookups from rf_findvar (which follows the
enclosure chain) to rf_findvarinframe (which looks only in the exact
frame), since we now know the exact environment to query.

Addresses the root cause flagged during :objects browser review; the
lq45 task (R_withCallingErrorHandler migration) remains open as the
proper long-term fix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
baseenv() does not allow adding new bindings in R. Store .arf_error_state
and .arf_prev_error_handler back in globalenv with dot prefix — these are
hidden from the objects browser (all_names=FALSE default) and from ls()
by default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix fuzzy_match argument order (pattern, target) — filtering was
  effectively broken
- Fix FIXED_COLS to account for 3-char " │ " separators; rows no
  longer exceed terminal width
- Clamp scroll_offset in update_filter so the visible window stays
  anchored to the cursor after filter/reload
- Truncate footer to terminal width before padding to prevent wrapping
  at narrow terminals
- Guard STRING_ELT(class_sexp, 0) with a length check to avoid
  indexing an empty class vector across the FFI boundary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Raise MIN_SIZE.cols from 60 to 66 (= FIXED_COLS + NAME_MIN) so the
  browser rejects terminals that would cause row wrapping
- Use display_width-aware padding for the footer so wide Unicode
  characters in feedback messages do not overflow the terminal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@eitsupi eitsupi changed the title feat: add :objects browser for .GlobalEnv inspection feat: add :objects browser for .GlobalEnv inspection Jun 30, 2026

// Workspace snapshot functions
// Rf_findVarInFrame(rho, sym) — look up sym in env rho only (no parent search)
pub rf_findvarinframe: unsafe extern "C" fn(SEXP, SEXP) -> SEXP,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-API?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant