Class: OKF::TUI::Workspace
- Inherits:
-
Object
- Object
- OKF::TUI::Workspace
- Defined in:
- lib/okf/tui/workspace.rb
Overview
Every bundle the session can see, which one is active, and which ones a search covers.
Two ways in, mirroring what okf server accepts: named directories are
ad-hoc and never touch the registry, and with no directories the registry
itself is the workspace. Registering stays an explicit act — an ad-hoc look
at two bundles should not enrol them in the user's durable list.
Defined Under Namespace
Instance Attribute Summary collapse
-
#active_slug ⇒ Object
readonly
Returns the value of attribute active_slug.
-
#entries ⇒ Object
readonly
Returns the value of attribute entries.
-
#home ⇒ Object
readonly
Returns the value of attribute home.
-
#registry ⇒ Object
readonly
Returns the value of attribute registry.
-
#search_error ⇒ Object
readonly
Set when a query could not be answered and the reason is worth showing.
Instance Method Summary collapse
- #active ⇒ Object
-
#add(dir) ⇒ Object
── config: every registry write lands here ─────────────────────────────.
- #add_to_group(slug, members) ⇒ Object
-
#create_group(slug, members) ⇒ Object
── groups: the writes okf's
registry group/ungroupmake ──────────. - #empty? ⇒ Boolean
- #entry(slug) ⇒ Object
- #group(slug) ⇒ Object
-
#groups ⇒ Object
The registry's groups.
-
#initialize(dirs: [], ref_slugs: {}, home: nil, cwd: nil) ⇒ Workspace
constructor
cwdis what opts this workspace into registry discovery, and it is deliberately not defaulted to Dir.pwd: okf draws the same line, where only its CLI passes a cwd and a library caller stays on the global registry. - #make_default(slug) ⇒ Object
-
#model ⇒ Object
The active bundle's Model, or nil when nothing is loadable.
-
#registry_backed? ⇒ Boolean
A registry-backed workspace is the one that can be configured; a workspace of named directories has no registry to write to.
-
#registry_path ⇒ Object
The registry file this session is on — the discovered project-local one when there is one, else the global $OKF_HOME one.
-
#reload ⇒ Object
Re-read from disk, keeping the active bundle and the scope wherever they still resolve.
- #remove(slug) ⇒ Object
- #remove_from_group(slug, members) ⇒ Object
- #rename(old_slug, new_slug) ⇒ Object
-
#scope ⇒ Object
Kept in workspace order and filtered to slugs that still exist, so a reload after a remove cannot leave a stale slug in the scope.
- #scope_all ⇒ Object
-
#scope_group(slug) ⇒ Object
Scope the search to a group — the reason a group is worth showing here at all.
- #scope_none ⇒ Object
- #scope_only(slug) ⇒ Object
- #scoped?(slug) ⇒ Boolean
-
#search(query, mode: :fuzzy) ⇒ Object
Ranked search across every scoped bundle, merged into one ordered list.
- #switch(slug) ⇒ Object
- #toggle_scope(slug) ⇒ Object
Constructor Details
#initialize(dirs: [], ref_slugs: {}, home: nil, cwd: nil) ⇒ Workspace
cwd is what opts this workspace into registry discovery, and it is
deliberately not defaulted to Dir.pwd: okf draws the same line, where only
its CLI passes a cwd and a library caller stays on the global registry. A
default here would make the registry an embedding app reads depend on the
directory its process happens to be in, and would let the suite discover a
.okf-registry.json from wherever rake was run.
ref_slugs maps a resolved directory to the slug the @ref named it by, so a
session built from refs keeps the names the user typed. See Refs#slugs.
88 89 90 91 92 93 94 |
# File 'lib/okf/tui/workspace.rb', line 88 def initialize(dirs: [], ref_slugs: {}, home: nil, cwd: nil) @home = home @cwd = cwd @dirs = Array(dirs) @ref_slugs = ref_slugs || {} load_entries end |
Instance Attribute Details
#active_slug ⇒ Object (readonly)
Returns the value of attribute active_slug.
77 78 79 |
# File 'lib/okf/tui/workspace.rb', line 77 def active_slug @active_slug end |
#entries ⇒ Object (readonly)
Returns the value of attribute entries.
77 78 79 |
# File 'lib/okf/tui/workspace.rb', line 77 def entries @entries end |
#home ⇒ Object (readonly)
Returns the value of attribute home.
77 78 79 |
# File 'lib/okf/tui/workspace.rb', line 77 def home @home end |
#registry ⇒ Object (readonly)
Returns the value of attribute registry.
77 78 79 |
# File 'lib/okf/tui/workspace.rb', line 77 def registry @registry end |
#search_error ⇒ Object (readonly)
Set when a query could not be answered and the reason is worth showing. nil after any search that ran, so it never outlives the query it describes.
244 245 246 |
# File 'lib/okf/tui/workspace.rb', line 244 def search_error @search_error end |
Instance Method Details
#active ⇒ Object
133 134 135 |
# File 'lib/okf/tui/workspace.rb', line 133 def active entry(@active_slug) end |
#add(dir) ⇒ Object
── config: every registry write lands here ─────────────────────────────
Each returns a message for the status line and reloads, so what the screen shows next is what the file now says rather than what the in-memory list was talked into believing.
252 253 254 255 256 257 258 259 260 |
# File 'lib/okf/tui/workspace.rb', line 252 def add(dir) return not_registry_backed unless registry_backed? added = registry.add(File.(dir.to_s.strip)) reload "registered @#{added.slug} → #{added.path}" rescue OKF::Error => e "could not add: #{e.}" end |
#add_to_group(slug, members) ⇒ Object
322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/okf/tui/workspace.rb', line 322 def add_to_group(slug, members) return not_registry_backed unless registry_backed? return "nothing in scope to add" if members.empty? registry.set_group(slug, refs(members)) reload # One member named, several counted: `+` adds the row under a cursor and the # line has to say *which* row landed, while `c` adds a whole scope and naming # every slug would be a list, not a message. total = count(group(slug)&.size.to_i) members.length == 1 ? "@#{members.first} joined @#{slug} — #{total}" : "@#{slug} now names #{total}" rescue OKF::Error => e "could not add to @#{slug}: #{e.}" end |
#create_group(slug, members) ⇒ Object
── groups: the writes okf's registry group / ungroup make ──────────
A group is registry configuration in exactly the sense a slug rename is, so
it belongs on the same side of the line as the writes above — see
[registry-write-boundary]. Members come from the scope rather than from a
typed list: ◉ already means "these bundles" in this view, so the gesture is
toggle what you want, then name it.
okf owns the cascades. set_group creates-or-adds and refuses a cycle,
unset_group_members deletes a group it empties, and rename/remove span
a group slug and cascade through every member list — none of which is
reimplemented here.
308 309 310 311 312 313 314 315 316 317 318 319 320 |
# File 'lib/okf/tui/workspace.rb', line 308 def create_group(slug, members) return not_registry_backed unless registry_backed? slug = slug.to_s.strip return "group cancelled: no name given" if slug.empty? return "group cancelled: no bundles in scope to name" if members.empty? group = registry.set_group(slug, refs(members)) reload "@#{group.slug} names #{count(members.length)}" rescue OKF::Error => e "could not create the group: #{e.}" end |
#empty? ⇒ Boolean
111 112 113 |
# File 'lib/okf/tui/workspace.rb', line 111 def empty? entries.empty? end |
#entry(slug) ⇒ Object
115 116 117 |
# File 'lib/okf/tui/workspace.rb', line 115 def entry(slug) entries.find { |candidate| candidate.slug == slug } end |
#group(slug) ⇒ Object
129 130 131 |
# File 'lib/okf/tui/workspace.rb', line 129 def group(slug) groups.find { |candidate| candidate.slug == slug } end |
#groups ⇒ Object
The registry's groups. Empty for an ad-hoc workspace, which has no registry to have groups in.
Read from okf rather than derived: groups_listing is what okf registry list prints and expand is what okf search @group resolves, so a group
means the same set here as it does at the command line.
125 126 127 |
# File 'lib/okf/tui/workspace.rb', line 125 def groups @groups ||= registry_backed? ? registry_groups : [] end |
#make_default(slug) ⇒ Object
272 273 274 275 276 277 278 279 280 |
# File 'lib/okf/tui/workspace.rb', line 272 def make_default(slug) return not_registry_backed unless registry_backed? registry.default = slug reload "@#{slug} is now the default" rescue OKF::Error => e "could not set default: #{e.}" end |
#model ⇒ Object
The active bundle's Model, or nil when nothing is loadable. Every single-bundle view reads through this.
139 140 141 |
# File 'lib/okf/tui/workspace.rb', line 139 def model active&.model end |
#registry_backed? ⇒ Boolean
A registry-backed workspace is the one that can be configured; a workspace of named directories has no registry to write to.
98 99 100 |
# File 'lib/okf/tui/workspace.rb', line 98 def registry_backed? @dirs.empty? end |
#registry_path ⇒ Object
The registry file this session is on — the discovered project-local one when
there is one, else the global $OKF_HOME one. Asked of the registry rather
than recomputed from home, because Registry.path only ever names the global
file and would report the wrong one out of a project directory: the header
would print a path the session is not reading.
107 108 109 |
# File 'lib/okf/tui/workspace.rb', line 107 def registry_path (registry || open_registry).path end |
#reload ⇒ Object
Re-read from disk, keeping the active bundle and the scope wherever they still resolve.
355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/okf/tui/workspace.rb', line 355 def reload previous_active = @active_slug previous_scope = @scope load_entries @active_slug = previous_active if entry(previous_active)&.loaded? @active_slug ||= first_loaded_slug kept = previous_scope.select { |slug| entry(slug) } @scope = kept.empty? ? entries.map(&:slug) : kept end |
#remove(slug) ⇒ Object
262 263 264 265 266 267 268 269 270 |
# File 'lib/okf/tui/workspace.rb', line 262 def remove(slug) return not_registry_backed unless registry_backed? registry.remove(slug) reload "removed @#{slug} from the registry (the bundle itself is untouched)" rescue OKF::Error => e "could not remove: #{e.}" end |
#remove_from_group(slug, members) ⇒ Object
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/okf/tui/workspace.rb', line 337 def remove_from_group(slug, members) return not_registry_backed unless registry_backed? return "nothing in scope to remove" if members.empty? registry.unset_group_members(slug, refs(members)) reload # okf deletes a group its last member left, so say so rather than leave the # user looking for a row that is gone. return "@#{slug} had nothing left, so it is gone" if group(slug).nil? total = count(group(slug).size) members.length == 1 ? "@#{members.first} left @#{slug} — #{total}" : "@#{slug} now names #{total}" rescue OKF::Error => e "could not remove from @#{slug}: #{e.}" end |
#rename(old_slug, new_slug) ⇒ Object
282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/okf/tui/workspace.rb', line 282 def rename(old_slug, new_slug) return not_registry_backed unless registry_backed? new_slug = new_slug.to_s.strip return "rename cancelled: no name given" if new_slug.empty? renamed = registry.rename(old_slug, new_slug) reload "@#{old_slug} is now @#{renamed.slug}" rescue OKF::Error => e "could not rename: #{e.}" end |
#scope ⇒ Object
Kept in workspace order and filtered to slugs that still exist, so a reload after a remove cannot leave a stale slug in the scope.
154 155 156 |
# File 'lib/okf/tui/workspace.rb', line 154 def scope entries.map(&:slug).select { |slug| @scope.include?(slug) } end |
#scope_all ⇒ Object
166 167 168 |
# File 'lib/okf/tui/workspace.rb', line 166 def scope_all @scope = entries.map(&:slug) end |
#scope_group(slug) ⇒ Object
Scope the search to a group — the reason a group is worth showing here at
all. okf search @mkt merges exactly these bundles into one ranking; this is
the same set, named the same way, reached with one key instead of retyping
the members.
Returns a message for the status line, like the config writes do, because each of the three ways this can decline to do anything needs saying: a scope that silently did not change reads as a broken key.
186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/okf/tui/workspace.rb', line 186 def scope_group(slug) group = group(slug) return "no such group: @#{slug}" if group.nil? return "@#{slug} names a cycle in #{registry.path} — okf cannot resolve it" if group.cyclic? return "@#{slug} resolves to no bundle here" if group.bundles.empty? # A copy: `toggle_scope` mutates the scope in place, and the group is a # description of the registry, not a scratch list. @scope = group.bundles.dup "search scope: @#{slug} — #{group.size} #{group.size == 1 ? "bundle" : "bundles"}" end |
#scope_none ⇒ Object
170 171 172 |
# File 'lib/okf/tui/workspace.rb', line 170 def scope_none @scope = [] end |
#scope_only(slug) ⇒ Object
174 175 176 |
# File 'lib/okf/tui/workspace.rb', line 174 def scope_only(slug) @scope = [ slug ] end |
#scoped?(slug) ⇒ Boolean
158 159 160 |
# File 'lib/okf/tui/workspace.rb', line 158 def scoped?(slug) @scope.include?(slug) end |
#search(query, mode: :fuzzy) ⇒ Object
Ranked search across every scoped bundle, merged into one ordered list.
They share one index on purpose: BM25 weighs a term by how rare it is in
the corpus, so per-bundle indexes would score the same match differently
depending on which bundle it came from. One index makes one corpus — the
same thing okf search @all does.
The mode is what chooses okf's engine, by declaring the capability the query needs rather than by naming an engine:
:fuzzy `fuzzy: true` → the full-text index. Ranked BM25, typo-tolerant.
:text nothing → the scan, okf's own default. Raw substring.
:regexp `regexp: true` → the scan, as a pattern.
All three are reachable because the index and the scan disagree by design,
and each is wrong for what the other is right for. okf documents the index's
limits precisely: its tokenizer splits on punctuation, so 7.2.0 indexes as
7, 2, 0, and a backtick is not punctuation, so a word inside a code span
indexes as `minifts` and the query minifts does not match it. Measured
on okf's own bundle, the index finds three of the five concepts that say
minifts, and returns fourteen for OKF_HOME where the scan returns five.
Offering only the index left the terms glued to symbols — a constant, an env
var, a version — unfindable, with nothing on screen saying so.
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/okf/tui/workspace.rb', line 220 def search(query, mode: :fuzzy) @search_error = nil terms = query.to_s.split(/\s+/).reject(&:empty?) return [] if terms.empty? corpus = search_corpus return [] if corpus.nil? run_search(corpus, terms, mode) rescue RegexpError => e # A bad pattern is the user's typo, not a broken install, and it must not # land in the blanket rescue below — "no matches" for an unparseable regexp # is the silent-wrong-answer shape this view keeps having to avoid. @search_error = "bad pattern: #{e.}" [] rescue OKF::Bundle::Search::UnsupportedQuery => e @search_error = e. [] rescue StandardError [] end |
#switch(slug) ⇒ Object
143 144 145 146 147 148 |
# File 'lib/okf/tui/workspace.rb', line 143 def switch(slug) return false unless entry(slug)&.loaded? @active_slug = slug true end |
#toggle_scope(slug) ⇒ Object
162 163 164 |
# File 'lib/okf/tui/workspace.rb', line 162 def toggle_scope(slug) @scope.include?(slug) ? @scope.delete(slug) : @scope << slug end |