Class: Rubino::UI::PasteStore
- Inherits:
-
Object
- Object
- Rubino::UI::PasteStore
- Defined in:
- lib/rubino/ui/paste_store.rb
Overview
The per-session PASTE store behind the composer’s file-backed paste pipeline (Hermes-style, two tiers).
A large bracketed paste does not flood the composer: the body is registered here and a single compact PLACEHOLDER token —“[Pasted text #1 +123 lines]” — is inserted into the editable buffer instead. The token rides the draft like normal text (editable around, history-recalled, queueable) and is EXPANDED to the full body only at the message-build seam, where the line leaves the composer for the agent loop (ChatCommand#run_turn): the model sees everything, while the transcript echo keeps the placeholder so scrollback stays clean.
Two tiers, both behind one placeholder shape:
* Tier 1 — PLACEHOLDER COLLAPSE: a paste longer than
`paste.collapse_lines` lines (default 5) is held in memory and the
token expands to the verbatim body at submit.
* Tier 2 — FILE OVERFLOW: a paste bigger than
`paste.file_threshold_tokens` (default 8000, estimated at the same
chars/4 rule Context::TokenBudget uses) is written to a session-
scoped file — <RUBINO_HOME>/sessions/<id>/paste_N.txt — and the
token expands to a one-line pointer telling the model to read the
file with the read tool. The home sessions dir is where session
artifacts already live, it never pollutes the workspace tree, and
the read tool is deliberately un-sandboxed (only WRITES are gated
by Workspace roots), so the model can read it from any cwd.
Lifecycle: a tier-1 body is consumed when its token is expanded into an outgoing message (re-submitting the line from history later leaves the literal placeholder, matching Hermes); tier-2 files persist for the session so the model can re-read them in later turns. Pastes at or under the collapse threshold never reach the store — they inline into the buffer exactly as before.
Constant Summary collapse
- TOKEN_RE =
The placeholder shape, shared with the CompletionSource highlight and the composer’s whole-token backspace.
/\[Pasted text #\d+ \+\d+ lines\]/- DEFAULT_COLLAPSE_LINES =
Built-in fallbacks when config is missing/garbage.
5- DEFAULT_COLLAPSE_CHARS =
A paste longer than this many CHARS collapses to the chip even on a single line. ~80 cols × 5 rows ≈ the line-count trigger’s footprint, so a big one-line paste (a long URL/token/minified JSON) collapses just like a multi-line one instead of flooding the composer.
400- DEFAULT_THRESHOLD_TOKENS =
8000
Instance Attribute Summary collapse
-
#session_source ⇒ Object
writeonly
Late wiring for the session scope (see #initialize) — the chat command builds the store before the runner exists.
Instance Method Summary collapse
-
#append_to_placeholder_before(buffer, cursor, body) ⇒ Object
Appends
bodyto the registered placeholder that ends exactly atcursorinbuffer, returning [start, old_length, new_token] for the caller to splice into the visible draft. -
#collapse?(body) ⇒ Boolean
True when
bodyshould collapse to a placeholder instead of inlining: strictly more LINES than paste.collapse_lines, OR more CHARACTERS than paste.collapse_chars. -
#expand(text) ⇒ Object
Expands every registered placeholder in
textto its stored body (tier 1) or file pointer (tier 2) — the message-build seam. -
#expansions_in(text) ⇒ Object
The registered [token, body] pairs whose placeholder appears in
text, CONSUMING them like #expand does (re-submitting from history later leaves the literal placeholder). -
#initialize(config: nil, session_source: nil) ⇒ PasteStore
constructor
A new instance of PasteStore.
-
#placeholder_span(buffer, cursor) ⇒ Object
The [start, length] (codepoint) span of the registered placeholder covering the char just BEFORE
cursorinbuffer, or nil. -
#register(body) ⇒ Object
Registers a pasted
bodyand returns the placeholder token to insert into the buffer.
Constructor Details
#initialize(config: nil, session_source: nil) ⇒ PasteStore
Returns a new instance of PasteStore.
61 62 63 64 65 66 67 |
# File 'lib/rubino/ui/paste_store.rb', line 61 def initialize(config: nil, session_source: nil) @config = config @session_source = session_source @entries = {} # placeholder token => expansion text @bodies = {} # placeholder token => original pasted body @counter = 0 end |
Instance Attribute Details
#session_source=(value) ⇒ Object (writeonly)
Late wiring for the session scope (see #initialize) — the chat command builds the store before the runner exists.
71 72 73 |
# File 'lib/rubino/ui/paste_store.rb', line 71 def session_source=(value) @session_source = value end |
Instance Method Details
#append_to_placeholder_before(buffer, cursor, body) ⇒ Object
Appends body to the registered placeholder that ends exactly at cursor in buffer, returning [start, old_length, new_token] for the caller to splice into the visible draft. Non-adjacent pastes return nil.
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/rubino/ui/paste_store.rb', line 100 def append_to_placeholder_before(buffer, cursor, body) return nil if @entries.empty? || buffer.nil? span = placeholder_span(buffer, cursor) return nil unless span && span[0] + span[1] == cursor old_token = buffer.chars.slice(span[0], span[1]).join old_body = @bodies[old_token] return nil unless old_body num = old_token[/#(\d+)/, 1].to_i combined = [old_body, body.to_s].reject(&:empty?).join("\n") new_token = "[Pasted text ##{num} +#{combined.lines.length} lines]" @entries.delete(old_token) @bodies.delete(old_token) @entries[new_token] = oversize?(combined) ? overflow_to_file(num, combined) : combined @bodies[new_token] = combined [span[0], span[1], new_token] end |
#collapse?(body) ⇒ Boolean
True when body should collapse to a placeholder instead of inlining: strictly more LINES than paste.collapse_lines, OR more CHARACTERS than paste.collapse_chars. The char trigger makes the chip fire CONSISTENTLY —a big one-line paste (a long URL, token, or minified JSON) has few/no newlines, so the line-count rule alone never collapsed it and it flooded the composer (#437 chip should trigger for any large paste).
79 80 81 82 |
# File 'lib/rubino/ui/paste_store.rb', line 79 def collapse?(body) s = body.to_s s.lines.length > collapse_lines || s.length > collapse_chars end |
#expand(text) ⇒ Object
Expands every registered placeholder in text to its stored body (tier 1) or file pointer (tier 2) — the message-build seam. Consumed entries are dropped (“cleared on submit”); unknown placeholder-shaped text is left verbatim, so user-typed literals are never rewritten.
126 127 128 129 130 131 132 133 134 |
# File 'lib/rubino/ui/paste_store.rb', line 126 def (text) return text unless text.is_a?(String) && @entries.keys.any? { |t| text.include?(t) } text.gsub(TOKEN_RE) do |token| body = @entries.delete(token) @bodies.delete(token) if body body || token end end |
#expansions_in(text) ⇒ Object
The registered [token, body] pairs whose placeholder appears in text, CONSUMING them like #expand does (re-submitting from history later leaves the literal placeholder). Returned as an array of pairs so the tokens survive JSON round-trips intact (a token is not a valid symbol key). Empty array when text carries no live placeholder.
141 142 143 144 145 146 147 148 149 |
# File 'lib/rubino/ui/paste_store.rb', line 141 def expansions_in(text) return [] unless text.is_a?(String) text.scan(TOKEN_RE).uniq.filter_map do |token| body = @entries.delete(token) @bodies.delete(token) if body [token, body] if body end end |
#placeholder_span(buffer, cursor) ⇒ Object
The [start, length] (codepoint) span of the registered placeholder covering the char just BEFORE cursor in buffer, or nil. The composer’s backspace uses it to delete a placeholder WHOLE — a half-eaten token would neither read nor expand. Only spans the store actually registered qualify; lookalike text the user typed is edited char-by-char as usual.
157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/rubino/ui/paste_store.rb', line 157 def placeholder_span(buffer, cursor) return nil if @entries.empty? || buffer.nil? pos = 0 while (m = TOKEN_RE.match(buffer, pos)) start = m.begin(0) length = m[0].length return [start, length] if @entries.key?(m[0]) && cursor > start && cursor <= start + length pos = start + length end nil end |
#register(body) ⇒ Object
Registers a pasted body and returns the placeholder token to insert into the buffer. Oversized bodies (tier 2) are written to the session paste file here, at paste time; their token expands to the file pointer instead of the content.
88 89 90 91 92 93 94 95 |
# File 'lib/rubino/ui/paste_store.rb', line 88 def register(body) body = body.to_s n = (@counter += 1) token = "[Pasted text ##{n} +#{body.lines.length} lines]" @entries[token] = oversize?(body) ? overflow_to_file(n, body) : body @bodies[token] = body token end |