Class: Rubino::UI::InputHistory
- Inherits:
-
Object
- Object
- Rubino::UI::InputHistory
- Defined in:
- lib/rubino/ui/input_history.rb
Overview
Prompt history for the bottom composer, backed by the SAME store the old Reline idle prompt used (Reline::HISTORY) so continuity is preserved when the composer becomes the single idle input path — a session’s earlier entries (and anything Reline itself recorded) stay navigable.
Navigation model mirrors a shell / Reline: ↑ walks BACK toward older entries, ↓ walks FORWARD toward newer ones and finally back to the live draft the user was typing. The in-progress draft is stashed on the first ↑so ↓-ing all the way down restores exactly what the user had typed, never losing it.
Like LineInput#remember, consecutive duplicates are de-duped on push so a repeated command doesn’t clutter the ring.
PERSISTENCE (#2): like a shell (bash/zsh) and Hermes — which persists its input history to a .hermes_history file (see hermes_cli/profiles.py / profile_distribution.py) — rubino persists submitted lines to a plain-text file under RUBINO_HOME (default <RUBINO_HOME>/history, one entry per line) so they survive a restart. The file is LOADED into the ring at construction (composer/REPL startup) and each remembered line is APPENDED, capped to the last DEFAULT_CAP entries. EVERYTHING submitted is recorded — real prompts AND slash commands (/help, /agents, …) — matching the field standard (bash/zsh/Claude Code) where ↑ recalls the whole input line. All disk access is best-effort: a missing, unreadable or unwritable history file must never crash startup or a turn, so every file op is rescued and the in-memory ring keeps working.
Constant Summary collapse
- DEFAULT_CAP =
Default number of most-recent entries kept on disk (and trimmed to on save). A shell-sized ring: large enough to recall across sessions, bounded so the file can’t grow without limit.
1000
Class Method Summary collapse
-
.default_path ⇒ Object
Resolve the default history file under the SAME home the rest of rubino uses (RUBINO_HOME → ~/.rubino, via the config Loader), so an isolated or relocated home keeps its own history alongside config/.env/skills.
Instance Method Summary collapse
-
#down(_current = nil) ⇒ Object
Move toward NEWER entries (↓).
-
#initialize(store: Reline::HISTORY, path: :default, cap: DEFAULT_CAP) ⇒ InputHistory
constructor
A new instance of InputHistory.
-
#navigating? ⇒ Boolean
True while the cursor is walking the history ring (not on the draft).
-
#remember(line) ⇒ Object
Append a submitted line, de-duping a consecutive duplicate (matches LineInput#remember).
-
#reset! ⇒ Object
Drop navigation state (called on submit / any direct edit so a fresh ↑ starts from the newest entry and a typed edit isn’t treated as history).
-
#up(current) ⇒ Object
Move toward OLDER entries (↑).
Constructor Details
#initialize(store: Reline::HISTORY, path: :default, cap: DEFAULT_CAP) ⇒ InputHistory
Returns a new instance of InputHistory.
57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/rubino/ui/input_history.rb', line 57 def initialize(store: Reline::HISTORY, path: :default, cap: DEFAULT_CAP) @store = store @path = if path == :default store.equal?(Reline::HISTORY) ? self.class.default_path : nil else path end @cap = cap # Cursor into the history ring. nil = "on the live draft" (not navigating # history). 0 = most recent entry, increasing = older. @index = nil @draft = nil load_from_disk end |
Class Method Details
.default_path ⇒ Object
Resolve the default history file under the SAME home the rest of rubino uses (RUBINO_HOME → ~/.rubino, via the config Loader), so an isolated or relocated home keeps its own history alongside config/.env/skills.
42 43 44 45 46 |
# File 'lib/rubino/ui/input_history.rb', line 42 def self.default_path File.join(Rubino::Config::Loader.default_home_path, "history") rescue StandardError nil end |
Instance Method Details
#down(_current = nil) ⇒ Object
Move toward NEWER entries (↓). Returns the newer entry, or the stashed draft when stepping back below the newest entry, or nil when not currently navigating history (caller keeps the current buffer).
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/rubino/ui/input_history.rb', line 113 def down(_current = nil) return nil if @index.nil? entries = to_a if @index.positive? @index -= 1 entries[entries.size - 1 - @index] else # Stepped below the newest entry → back to the live draft. @index = nil d = @draft.to_s @draft = nil d end end |
#navigating? ⇒ Boolean
True while the cursor is walking the history ring (not on the draft).
130 131 132 |
# File 'lib/rubino/ui/input_history.rb', line 130 def navigating? !@index.nil? end |
#remember(line) ⇒ Object
Append a submitted line, de-duping a consecutive duplicate (matches LineInput#remember). Blank lines are not recorded. Resets navigation so the next ↑ starts from the newest entry again. EVERYTHING typed is recorded — real prompts AND slash commands — so ↑ recalls the whole input line like bash/zsh/Claude Code (#2). Also appended to the on-disk history (best-effort) so it survives a restart.
78 79 80 81 82 83 84 85 86 87 |
# File 'lib/rubino/ui/input_history.rb', line 78 def remember(line) reset! return if line.nil? stripped = line.strip return if stripped.empty? || last == stripped @store.push(stripped) append_to_disk(stripped) end |
#reset! ⇒ Object
Drop navigation state (called on submit / any direct edit so a fresh ↑starts from the newest entry and a typed edit isn’t treated as history).
136 137 138 139 |
# File 'lib/rubino/ui/input_history.rb', line 136 def reset! @index = nil @draft = nil end |
#up(current) ⇒ Object
Move toward OLDER entries (↑). current is the buffer the user is editing right now; it’s stashed as the draft on the first move up so ↓can restore it. Returns the entry to show, or nil when there’s nothing older (caller keeps the current buffer).
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/rubino/ui/input_history.rb', line 93 def up(current) entries = to_a return nil if entries.empty? if @index.nil? # dup, not to_s: String#to_s returns self, so a later in-place # @buffer.replace by the caller would mutate the stashed draft too. @draft = current.to_s.dup @index = 0 elsif @index < entries.size - 1 @index += 1 else return nil # already on the oldest entry — clamp end entries[entries.size - 1 - @index] end |