typr 1.8.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02fb4a45192b9a6bc2b3a751033a71e058b2abcb33b77d8873c991a2d6031dd1
4
- data.tar.gz: 878f286e0c40eb45b0b5c5216633bc0a9ff82c7f6bc03d18531a80090936e2af
3
+ metadata.gz: 5c61c73e0ce9201962acda993655743ab611ae86a2a3102d64dd05bacb223751
4
+ data.tar.gz: d0474937ff2a6ad8cb5eb39918eac2302d7114998e9e6775a99f6b52a1727b13
5
5
  SHA512:
6
- metadata.gz: 921906f7edae965d3ea48ed62e8eae91a770ad63dcf7cc3f20bb8463c9bbef280bcca2d2577317e4dd6c71d48b3740630bfaedf2d8c617cc9878d01381eebb9e
7
- data.tar.gz: d0db793255d8969a1181193dee6b06227efc2c0248fff99ca6407f6d5944ece37dc330e8cdabafa26229e98a299a576ebc245ea4e4ebd5a25661b20526d094cc
6
+ metadata.gz: 76773590e95c2693fdc3df40a9020a07f5817357748cb9aca13490d26deb27c8d07c54815b4a8e118ea562e1167672edfea312ed4f3b372e2cc4be00edfa0845
7
+ data.tar.gz: 3452ef2e2b11a67f19ffbca386ebac9c3abddd730db58c5dd09038edbafbc528c04fcf13042fef4339ce3edb7a2af62b8be99a673d98ecfd4be3b63886c07c1c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [vx.y.z] — __DATE__
4
4
 
5
+ ## [v1.9.2] — 2026-10-13
6
+
7
+ ### Fixed
8
+ - Text renders multibyte UTF-8 correctly — `show` split rows through StringIO, whose byte-based reads and positions shifted and garbled every row after the first multibyte character; page rows are sliced by character offsets instead. Chunks that fill the viewport exactly also record a line break now instead of merging with (and hiding) their continuation
9
+
10
+ ## [v1.9.1] — 2026-10-12
11
+
12
+ ### Changed
13
+ - `rake docs:check` — fails when any class, method, attribute, or constant is undocumented (CI-style gate for the RI pass; currently 221/221)
14
+
15
+ ## [v1.9.0] — 2026-10-12
16
+
17
+ ### Changed
18
+ - RI documentation pass — every class, method, attribute, and constant now carries a doc comment with behavior, `@param`/`@return` contracts, and runnable examples: RDoc reports 222/222 parts documented (previously 38 undocumented). Grouped comments were split so RDoc attaches them to each sibling attribute/method, and the generated `Space` edge accessors are documented through their `attr_writer` entry
19
+ - `Typr#prepare` slimmed to pad-or-delegate — the 13-line manual width scan duplicated `real_size` and `clip`, so it now clips overflow via `clip` (the single truncation engine) and pads the result to exact width; the uncalled and broken `fade` flag (standalone `fade` is unchanged) is dropped
20
+ - Read-line internals made private — `Typr.slice_width` and `word_prev`/`word_next` serve only `read_line`, so they are `private_class_method` now; their doc blocks stay for RI
21
+ - Live list narrowing is labeled `[/]:narrow`, not `[/]:search` — the bar action shrinks the listing as you type, while `search` keeps meaning only pager find-in-text (`/`, `n`, `p`). The method is renamed `Browser#search` → `#narrow` (`search` stays as an alias, so stale configs wired to `!search` keep working)
22
+
23
+ ### Fixed
24
+ - Aborting the mark-range prompt no longer crashes — Escape returns nil from `ask` and `mark_range` passed it straight to `join`; it now returns nil like `mark`
25
+ - Overtype guard scoped to `Grid#pick` and fixed for canonical ttys — private `drain_quiet` (called bare by `read_key`) waits out an armed window in raw mode, since canonical-buffered keystrokes are invisible to `select` and the old guard timed out only to deliver them late; each arrival restarts the window; `flush_overtype` and the `quiet_window?` predicate are gone, `drain_input` is back to a plain pending-bytes flush, and `suppress_keys` remains the sole arming seam for `Grid#pick` selections (filecon drops its two `flush_overtype` calls)
26
+ - RI accepted-values pass — every enum-like parameter now lists its values with examples: `Stack#pick` documents all types (`:row`/`:rows`, `:column`/`:columns`, `:field(s)`, `:relative_row`, `:none`) with per-type returns and the Grid/Text-only constraint; `Grid` format tokens (`Integer`/`:min`/`:max`) explained and the `:right`-in-format examples corrected (alignment is `align:`, numbers auto-right-align); `Grid#pick` column and `add_filter` column are `Integer`/`:all` only; `Line#ask` documents the `with:`-as-question convention; `read_line` gains full `@param`s plus the single-arg completions contract; `Stack#send`/`Browser#pick` gain `@param`/`@return`; stale claims removed (`keysyms`, `colors[:dir_history]`, `cd` dir_history, `sorted_by` false, README `cli` `completions:`); three one-word code fixes where docs were right and code wrong (`Grid#<<`/`Text#build` return `self`, `Line#append` returns the content, as their examples claim)
27
+ - Doc comments corrected where they disagreed with the code — `Grid#header=` defines `Typr::NAME` holding the column index (not `NAME_0 = "name"`); the mouse header-click sort doc sat on `sort_by` instead of `Grid#send`; `Grid#pick` now documents the single-match auto-return, the 0.5s overtype quiet window, and `:none` disabling both; `Line#ask` documents the `[:line, { initial: }]` inline-editor form; `Line#cli` no longer mentions a removed `completions:` option; `Browser#bottom` reserves only the command-line row (the path bar is `top`)
28
+
5
29
  ## [v1.8.0] — 2026-10-11
6
30
 
7
31
  ### Changed
data/README.md CHANGED
@@ -67,7 +67,7 @@ Typr.init
67
67
  ["Carol", 35, "manager"]
68
68
  ],
69
69
  header: ["Name", "Age", "Role"],
70
- format: [:min, :right, :max],
70
+ format: [:min, :min, :max],
71
71
  colors: { columns: [:yellow, :cyan, :magenta] }
72
72
  )
73
73
 
@@ -139,7 +139,7 @@ class MyApp
139
139
  ["Item C", 20, true]
140
140
  ],
141
141
  header: ["Name", "Qty", "Active"],
142
- format: [:max, :right, :min],
142
+ format: [:max, :min, :min],
143
143
  colors: { columns: [:yellow, :cyan, :green] }
144
144
  )
145
145
  @prompt = Typr::Line.new(
@@ -202,10 +202,10 @@ Strict question types:
202
202
  The question and answer are rendered in `@colors[:question]`/`@colors[:answer]`.
203
203
 
204
204
  ### `Line#cli`
205
- Prompts for a single command line with TAB completion matched against the shell (bash commands, options, and files). `completions: true` switches on the content; `initial:` seeds the line; `redraw:` repaints while the completion grid is up. With `regex: :shell` the typed command tints red for the first word, blue for `--options`. Completion candidates are plain strings.
205
+ Prompts for a single command line with TAB completion matched against the shell (bash commands, options, and files). `initial:` seeds the line; `history:` recalls previous commands with Up/Down; `prefixes:` lists command-start characters stripped before matching; `redraw:` repaints while the completion grid is up. With `regex: :shell` the typed command tints red for the first word, blue for `--options`. Completion candidates are plain strings.
206
206
 
207
207
  ```ruby
208
- line.cli "> ", initial: "cp %name %dir", completions: true, redraw: -> { show }
208
+ line.cli "> ", initial: "ls ", history: @recent
209
209
  ```
210
210
 
211
211
  ### Layout properties (all components)
data/lib/browser.rb CHANGED
@@ -5,6 +5,7 @@ require_relative 'line.rb'
5
5
 
6
6
  module Typr
7
7
 
8
+ # Bundled extension => MIME-type database (share/mimetypes).
8
9
  MIMETYPES = eval File.read( __dir__ + "/../share/mimetypes" )
9
10
 
10
11
  # A file-system directory browser built on Grid. Renders entries as rows
@@ -33,26 +34,54 @@ module Typr
33
34
  # Escape | quit, returns nil
34
35
 
35
36
  class Browser < Grid
37
+ ##
38
+ # Browser state: +directory+ the current path, +user+ the login name
39
+ # shown in the info bar, +pwd+ alias of +directory+, +view+ the active
40
+ # column view name.
41
+ #
42
+ # browser.directory #=> "/tmp/"
43
+ # browser.view #=> :compact
44
+
36
45
  attr_accessor :directory, :user, :pwd, :view
46
+ ##
47
+ # +mimetype_db+ toggles the bundled extension database, +mimetype_magic+
48
+ # the +file --mime-type+ fallback for unknown extensions.
49
+ #
50
+ # browser.mimetype_db = false # skip extension lookups
51
+
37
52
  attr_accessor :mimetype_db, :mimetype_magic
38
53
 
39
- ## Height minus 2 rows reserved for path bar and command line.
54
+ ##
55
+ # Bottom edge pulled in one row for the command line (the path bar is
56
+ # reserved by {#top}).
57
+ #
58
+ # browser.bottom #=> terminal_bottom - 1
59
+ #
60
+ # @return [Integer]
40
61
 
41
62
  def bottom; super - 1 end
42
63
 
43
- ## Top offset plus one row for the path info bar.
64
+ ##
65
+ # Top edge pushed down one row for the path info bar.
66
+ #
67
+ # browser.top #=> constructor top + 1
68
+ #
69
+ # @return [Integer]
44
70
 
45
71
  def top; super + 1 end
46
72
 
47
73
  ##
48
- # Change to +dir+ (absolute path, +".."+, +"~"+, or integer index into
49
- # dir_history). When +append+ is false (default), clears data and colors
50
- # before scanning. Pushes onto dir_history.
74
+ # Change to +dir+ (absolute path, +".."+, or +"~"+). Unless +append+,
75
+ # clears data and colors before scanning.
51
76
  #
52
77
  # browser.cd "/tmp" # => changes to /tmp
53
78
  # browser.cd "~" # => changes to home
54
79
  # browser.cd ".." # => parent directory
55
- # browser.cd 2 # => jump to history entry 2
80
+ #
81
+ # @param dir [String] target path
82
+ # @param append [Boolean] scan without clearing
83
+ # @param trim [Integer] path prefix trim for the +path+ column
84
+ # @return [false, nil] false when the directory cannot be opened
56
85
 
57
86
  def cd dir, append=false, trim=0
58
87
  return unless dir
@@ -164,6 +193,8 @@ module Typr
164
193
  # browser.change_view :full # all columns
165
194
  # browser.change_view 0 # first view key
166
195
  # browser.change_view :stats # name, size, perms, owner, group, modified
196
+ #
197
+ # @return [nil]
167
198
 
168
199
  def change_view view=nil
169
200
  return unless view
@@ -176,10 +207,15 @@ module Typr
176
207
  end
177
208
 
178
209
  ##
179
- # Resolves hint characters typed on a row. Returns the matching row id
180
- # for fast navigation, otherwise falls through to Stack#send.
210
+ # Resolve hint characters typed on a row to a row id; a click on the
211
+ # info bar or command line dispatches that binding instead. Falls
212
+ # through to {Stack#send} for keys and navigation.
213
+ #
214
+ # browser.send "1" # => row id if hint "1" is visible
215
+ # browser.send ?s # => runs the sort command (a bound key)
181
216
  #
182
- # browser.send "1" # => row id if hint "1" is visible
217
+ # @param key [Typr::Mouse, String] mouse event, hint char, or key sequence
218
+ # @return [Integer, String, Symbol, nil] picked row id, bound command result, or super's response
183
219
 
184
220
  def send key
185
221
  if key.is_a?(Typr::Mouse) and key.press? and key.left?
@@ -200,23 +236,32 @@ module Typr
200
236
  else super end
201
237
  end
202
238
 
203
- ## Returns the current directory path as the confirm result.
239
+ ##
240
+ # Returns the current directory path as the confirm result.
241
+ #
242
+ # browser.confirm #=> "/tmp/"
243
+ #
244
+ # @return [String]
204
245
 
205
246
  def confirm; return @directory end
206
247
 
207
248
  ## Prompts to select rows by index. Returns chosen ids or nil.
208
249
  #
209
250
  # browser.mark # => [0, 2, 5] or nil
251
+ #
252
+ # @return [Array<Integer>, nil]
210
253
 
211
254
  def mark; return @user.ask( 'mark files': [self, :rows] ) end
212
255
 
213
256
  ## Toggles selection for every row within a "from..to" range.
214
257
  #
215
258
  # browser.mark_range # prompts for from/to, toggles selection
259
+ #
260
+ # @return [Array<Integer>, nil] the toggled ids, or nil on abort
216
261
 
217
- def mark_range; return ids[ ( eval @user.ask(
262
+ def mark_range; return unless picked = @user.ask(
218
263
  'mark from': [self, :relative_row], 'to': [self, :relative_row]
219
- ).join '..' ) ].each{|id| @selected[:rows].include?(id) ?
264
+ ); return ids[ ( eval picked.join '..' ) ].each{|id| @selected[:rows].include?(id) ?
220
265
  @selected[:rows].delete(id) : @selected[:rows] << id } end
221
266
 
222
267
  ## Selects all non-directory rows.
@@ -234,24 +279,32 @@ module Typr
234
279
  ## Navigate up one directory.
235
280
  #
236
281
  # browser.back # equivalent to cd ".."
282
+ #
283
+ # @return [void]
237
284
 
238
285
  def back; cd ".." end
239
286
 
240
287
  ## Navigate to the root directory.
241
288
  #
242
289
  # browser.root # => cd "/"
290
+ #
291
+ # @return [void]
243
292
 
244
293
  def root; cd ?/ end
245
294
 
246
295
  ## Navigate to the home directory.
247
296
  #
248
297
  # browser.home # => cd "~"
298
+ #
299
+ # @return [void]
249
300
 
250
301
  def home; cd ?~ end
251
302
 
252
303
  ## Toggle visibility of hidden files (those starting with a dot).
253
304
  #
254
- # browser.toggle_hidden
305
+ # browser.toggle_hidden # rescans the directory
306
+ #
307
+ # @return [void]
255
308
 
256
309
  def toggle_hidden
257
310
  @show_hidden = !@show_hidden
@@ -259,6 +312,10 @@ module Typr
259
312
  end
260
313
 
261
314
  ## Display a help grid listing all keybindings.
315
+ #
316
+ # browser.help # => picked binding row id or nil
317
+ #
318
+ # @return [Integer, nil]
262
319
 
263
320
  def help; @user.help end
264
321
 
@@ -266,35 +323,46 @@ module Typr
266
323
  # Reset display, selection, and position state then re-sort.
267
324
  #
268
325
  # browser.reset_view
326
+ #
327
+ # @return [void]
269
328
 
270
329
  def reset_view; reset [:display, :selection]; cd @directory; sort end
271
330
 
272
331
  ## Open a filter popup on the chosen row/column and apply the input.
273
332
  #
274
333
  # browser.filter_view # prompts for filter text
334
+ #
335
+ # @return [void]
275
336
 
276
337
  def filter_view; add_filter *@user.ask(
277
338
  filter: [ popup( type: :"[f]ilter" ), :row, NAME, { column: 0 }], with: :line ) end
278
339
 
279
- ## Open live `/` search filtered on the name column.
340
+ ## Open live `/` narrowing filtered on the name column.
341
+ #
342
+ # browser.narrow # type to narrow, Escape restores, Enter commits
280
343
  #
281
- # browser.search # type to narrow, Escape restores, Enter commits
344
+ # @return [void]
282
345
 
283
- def search
346
+ def narrow
284
347
  @suppress_user = true
285
348
  pick :none, column: NAME
286
349
  @suppress_user = false
287
350
  end
351
+ alias search narrow
288
352
 
289
353
  ## Open a sort-picker popup to choose a column to sort by.
290
354
  #
291
355
  # browser.sort_view # pick a column header to sort
356
+ #
357
+ # @return [void]
292
358
 
293
359
  def sort_view; sort_by @user.ask( "sort by": [ popup( type: :"[s]ort" ), :row, nil, { column: 0 }] ) end
294
360
 
295
361
  ## Open a view-picker popup and switch to the selected view.
296
362
  #
297
363
  # browser.views # choose :simple, :compact, :stats, or :full
364
+ #
365
+ # @return [void]
298
366
 
299
367
  def views; change_view( popup( type: :"[v]iews", input: @views.keys ).pick( :row, column: 0 ) ) end
300
368
 
@@ -315,6 +383,12 @@ module Typr
315
383
  # browser.pick # => /home/user/file.txt
316
384
  # browser.pick 'image' # => /path/to/photo.png (images only)
317
385
  # browser.pick nil # => anything, same as 'file'
386
+ #
387
+ # @param type [String, Symbol, nil] MIME family, +"file"+ (default), nil for anything,
388
+ # or a picker type (+:row+, +:rows+, +:column+, +:columns+, +:field+,
389
+ # +:fields+, +:relative_row+, +:none+) forwarded to {Stack#pick}
390
+ # @param kw [Hash] +column:+ for / search (forwarded to {Grid#pick})
391
+ # @return [String, nil] quoted path(s), the directory on Enter (directory type), or nil
318
392
 
319
393
  def pick type='file', **kw
320
394
  type ||= 'file'
@@ -402,6 +476,8 @@ module Typr
402
476
  # within each prefix grouping.
403
477
  #
404
478
  # browser.sort # re-sorts with dirs first
479
+ #
480
+ # @return [nil]
405
481
 
406
482
  def sort
407
483
  @sort ||= NAME
@@ -432,10 +508,13 @@ module Typr
432
508
  # Option | Default | Description
433
509
  # --------------------|---------------------------------|-----------------------------------
434
510
  # +directory+ | +"~"+ | Starting directory to browse
511
+ # +views+ | {} | Extra named column lists (Array of headers) merged over the built-ins
512
+ # +bindings+ | (built-in keybar) | Bottom-bar label to method-name map (e.g. +"[v]iews"+ to +"!views"+)
513
+ # +filter_dirs+ | nil | When set, directories must pass +filters+ like files
514
+ # +keymap+ | {} | Custom name-to-key mappings (merged over navigation defaults)
435
515
  # +mimetype_db+ | true | Load MIME from file extensions
436
516
  # +mimetype_magic+ | true | Use +file --mime-type+ for unknowns
437
517
  # +colors[:actions]+ | { filter: :red, ... } | Status bar item colors
438
- # +colors[:dir_history]+ | :yellow | History picker column color
439
518
  # +colors[:types]+ | (see below) | Per MIME-type foreground color
440
519
  #
441
520
  # == Type Color Defaults
@@ -551,13 +630,13 @@ full: %i[ name size permissions executable printable owner group
551
630
  @user ||= Line.new( parent:self, left:->{@parent.left}, top:->{@parent.bottom+1},
552
631
  bindings: bindings_labels )
553
632
  else
554
- cmds = { "[h]elp" => :help, "[/]:search" => :search,
633
+ cmds = { "[h]elp" => :help, "[/]:narrow" => :narrow,
555
634
  "[f]ilter" => :filter_view, "[s]ort" => :sort_view, "[v]iews" => :views,
556
635
  "[r]eset" => :reset_view, "[m]ark" => :mark,
557
636
  "[M]ark_range" => :mark_range, "mark_[a]ll" => :mark_all,
558
637
  "[i]nvert_mark" => :mark_invert, "[~]:home" => :home,
559
638
  "[H]idden_files" => :toggle_hidden }
560
- plain = ["[h]elp", "[/]:search"] +
639
+ plain = ["[h]elp", "[/]:narrow"] +
561
640
  @colors[:actions].map { |name, _| "[" + name.to_s[0] + "]" + name.to_s[1..] } +
562
641
  %w[ [r]eset [m]ark [M]ark_range mark_[a]ll
563
642
  [i]nvert_mark [~]:home [H]idden_files [back]:.. [up] [down] [pageup] [pagedown] [home] [end] [esc]:quit [return]:confirm\ ]
data/lib/grid.rb CHANGED
@@ -8,17 +8,51 @@ module Typr
8
8
  # grid = Typr::Grid.new(
9
9
  # input: [ ["Alice", 30], ["Bob", 25] ],
10
10
  # header: [:name, :age],
11
- # format: [:max, :right]
12
- # )
13
- # grid.show
14
- #
15
- # Column format tokens: +Integer+ (fixed width), +:min+ (auto), +:max+ (fill remaining).
11
+ # format: [:max, :min]
12
+ # )
13
+ # grid.show
14
+ #
15
+ # Column widths (+format+): an +Integer+ fixes the width; +:min+ sizes to
16
+ # the widest visible cell; +:max+ sizes to content and absorbs leftover
17
+ # viewport width. Anything else behaves like +:min+. Numbers right-align
18
+ # automatically (see +align+). When the content overflows the viewport,
19
+ # the widest columns shrink to fit.
16
20
  #
17
21
  # Built-in procs: +:filetree+, +:datetime+, +:magnitudes+, +:convert+.
18
22
 
19
23
  class Grid < Stack # Table
24
+ ##
25
+ # Display and transform state: +format+ per-column width tokens,
26
+ # +procs+ per-column processors, +filters+ the active [column, query]
27
+ # pairs, +data+ the rows (Array or Hash), and +map+ the visible row
28
+ # ids after filtering/sorting.
29
+ #
30
+ # grid.filters #=> [[0, "Ali"]]
31
+ # grid.map #=> [0, 2]
32
+ #
33
+ # +procs+ values are built-in names (+:filetree+, +:datetime+,
34
+ # +:magnitudes+, +:convert+) or callables: a lambda takes the cell,
35
+ # a full proc takes cell, row, and column.
36
+ #
37
+ # grid.procs[1] = ->(v) { v * 2 } # cell only
38
+
20
39
  attr_accessor :format, :procs, :filters, :data, :map
40
+ ##
41
+ # +align+ is per-column alignment, +rawsort+/+rawfilter+ flag columns
42
+ # to bypass their +procs+, and +reverse+ flips the sort direction.
43
+ # +align+ is +:left+ or +:right+ (numeric cells default to +:right+);
44
+ # +rawsort+/+rawfilter+ are arrays indexed by column where truthy
45
+ # bypasses the column's proc.
46
+ #
47
+ # grid.reverse = true
48
+ # grid.rawsort[0] = true # sort raw values for column 0
49
+
21
50
  attr_accessor :align, :rawsort, :rawfilter, :reverse
51
+ ##
52
+ # Overrides the column render order (see {#sequence}).
53
+ #
54
+ # grid.sequence = [2, 0, 1]
55
+
22
56
  attr_writer :sequence
23
57
 
24
58
  ##
@@ -52,7 +86,8 @@ module Typr
52
86
  ##
53
87
  # Column render order. Defaults to 0..n-1; override with +sequence=+.
54
88
  #
55
- # grid.sequence #=> [0, 1, 2]
89
+ # grid.sequence #=> [0, 1, 2]
90
+ # grid.sequence = [2, 0, 1]
56
91
  #
57
92
  # @return [Array<Integer>]
58
93
 
@@ -115,12 +150,16 @@ module Typr
115
150
  end
116
151
 
117
152
  ##
118
- # Set column headers. Symbols are auto-defined as +UPCASE_I+ constants.
153
+ # Set column headers. Symbols are auto-defined as +UPCASE+ constants
154
+ # holding the column index.
155
+ # A plain String instead draws as a label bar with no column meaning.
119
156
  #
120
- # grid.header = [:name, :age]
121
- # # defines NAME_0 = "name", AGE_1 = "age"
157
+ # grid.header = ["name", "age"]
158
+ # Typr::NAME #=> 0
159
+ # Typr::AGE #=> 1
160
+ # grid.header = "People" # label bar, defines no constants
122
161
  #
123
- # @param h [Array<String, Symbol>]
162
+ # @param h [Array<String, Symbol>, String] header labels or a label bar
124
163
 
125
164
  def header=( h ); super
126
165
  h.each_with_index{ |h,i|
@@ -182,8 +221,8 @@ module Typr
182
221
  # grid.add_filter(1, /^\d{2}$/) # age matches regex
183
222
  # grid.add_filter(:all, "ip") # any column contains "ip"
184
223
  #
185
- # @param column [Integer, Symbol]
186
- # @param query [String, Regexp]
224
+ # @param column [Integer, :all] column index (Typr::NAME-style) or +:all+
225
+ # @param query [String, Regexp] substring or pattern the cell must match
187
226
 
188
227
  def add_filter column=nil, query=nil
189
228
  return unless column and query
@@ -235,23 +274,51 @@ module Typr
235
274
  # grid.sorted_by true #=> "age"
236
275
  #
237
276
  # @param name [Boolean] pass truthy to get display name
238
- # @return [String, Integer, false, nil]
277
+ # @return [String, Integer, nil]
239
278
 
240
279
  def sorted_by name=false; (name and @sort) ? @header[@sort] : @sort end
241
280
 
242
281
  ##
243
- # Pick a row with live-filtering.
244
- #
245
- # When column is given, `Grid` overrides `Stack#pick` to add interactive
246
- # text filtering of rows by the given column. Filter text is shown at the
247
- # bottom of the grid. Passes through to `super` for all other types or
248
- # when no column is given.
249
- #
250
- # grid.pick :row, 0, column: :name
251
- # grid.pick :row, 0, column: :all # match any column
252
- #
253
- # @param type [Symbol] type is ignored (always :row); passed to super otherwise
254
- # @param column [Symbol, String] column name to filter on, or :all (default nil)
282
+ # Pick a row with live filtering (+column:+ given) or via {Stack#pick}
283
+ # (without it, supporting all its types). In filter mode the query
284
+ # narrows the list as you type; when one row remains it returns
285
+ # immediately (no Enter needed) and arms a 0.5s overtype quiet window
286
+ # via +Typr.suppress_keys+. +:none+ disables the auto-return (used by
287
+ # {Browser#narrow}, where Escape must restore); other types behave as
288
+ # single picks here.
289
+ #
290
+ # Without +column:+, all {Stack#pick} types apply. Anything ending in
291
+ # +s+ multi-selects: hint presses toggle, Return confirms with the
292
+ # selection, Escape aborts (confirming an empty selection returns nil).
293
+ #
294
+ # Type | Picks
295
+ # -------------------|------------------------------------------------
296
+ # +:row+ | Single row id
297
+ # +:rows+ | Multi-select row ids (Return confirms)
298
+ # +:column+ | One column of the given row (real index via {#sequence})
299
+ # +:columns+ | Multi-select columns (Return confirms)
300
+ # +:field+ | One [row, column] pair, picked row-then-column
301
+ # +:fields+ | Multi-select [row, column] pairs
302
+ # +:relative_row+ | Viewport-absolute row index (0-based)
303
+ # +:none+ | Navigation only; always returns nil
304
+ #
305
+ # grid.pick :row, 0, column: 0 # type to narrow, auto-returns at 1 match
306
+ # grid.pick :row, 0, column: :all # match any column
307
+ # grid.pick :none, column: 0 # narrowing only, Enter commits
308
+ # grid.pick :column, 3 # column of the 4th visible row (page index, not id)
309
+ # grid.pick :rows # multi-select rows
310
+ #
311
+ # Pressing Alt with a hint digit (ESC + digit) picks that hint row and
312
+ # sets +modifier+ to +:alt+ for the caller. The Return and Escape keys
313
+ # come from +@keymap+ (+confirm+/+exit+, Return/Escape by default).
314
+ #
315
+ # Mouse: left-click a row to pick it (row pickers only); wheel up/down
316
+ # scrolls the page; clicking outside the grid cancels like Escape.
317
+ #
318
+ # @param type [Symbol, String] picker type (tables above; symbols documented)
319
+ # @param row [Integer] unused in filter mode; page-relative row for +:column+ hints otherwise
320
+ # @param column [Integer, :all] column index (Typr::NAME-style) or +:all+
321
+ # @return [Integer, Array, nil] row id, [row, column], confirmed selection, or nil
255
322
 
256
323
  def pick type = :row, row=0, column: nil
257
324
  return super(type, row) unless column
@@ -308,12 +375,15 @@ module Typr
308
375
  end
309
376
 
310
377
  ##
311
- # Sort by column (toggle direction on repeat).
378
+ # Dispatch a mouse press on the header row to a column sort (clicking
379
+ # the header cell for a column calls +sort_by+); everything else falls
380
+ # through to {Stack#send}.
312
381
  #
313
- # grid.sort_by :age
314
- # grid.sort_by :age # reverses
382
+ # grid.send Typr::Mouse.new(0, 12, grid.top + 1, :press, 0) #=> :scroll
383
+ # grid.send Typr::KEY_DOWN # scrolls
315
384
  #
316
- # @param column [Integer, Symbol]
385
+ # @param key [Typr::Mouse, String] mouse event or key sequence
386
+ # @return [Symbol, Object] +:scroll+ on a header click, super's response otherwise
317
387
 
318
388
  def send key
319
389
  if key.is_a?(Typr::Mouse) and key.press? and key.left? and
@@ -328,6 +398,15 @@ module Typr
328
398
  super
329
399
  end
330
400
 
401
+ ##
402
+ # Sort by column (toggle direction on repeat).
403
+ #
404
+ # grid.sort_by :age
405
+ # grid.sort_by :age # reverses
406
+ #
407
+ # @param column [Integer, Symbol] column index or header-name symbol
408
+ # @return [void]
409
+
331
410
  def sort_by column
332
411
  column = @header.index( column.to_s ) if column.is_a? Symbol
333
412
  if @sort and (column == @sort) then @reverse = !@reverse
@@ -338,7 +417,7 @@ module Typr
338
417
  ##
339
418
  # Execute the sort and update +@map+.
340
419
  #
341
- # @return [false, void]
420
+ # @return [false, nil]
342
421
 
343
422
  def sort
344
423
  return false unless @sort
@@ -468,6 +547,7 @@ module Typr
468
547
  data.each_with_index{ |row, id|
469
548
  self[ is_hash? ? row.shift : id + skip ] = row }
470
549
  sort
550
+ self
471
551
  end
472
552
 
473
553
  ##
@@ -481,7 +561,16 @@ module Typr
481
561
  # colors: { columns: [:cyan, nil] }
482
562
  # )
483
563
  #
484
- # @param args [Hash] +input+, +header+, +format+, +procs+, +colors+, +align+, +functions+
564
+ # @param args [Hash]
565
+ #
566
+ # Option | Value
567
+ # ----------|--------------------------------------------------------
568
+ # +input+ | Array of rows, or Hash of key to row
569
+ # +header+ | column labels (see {#header=})
570
+ # +format+ | per-column +Integer+ width, +:min+, or +:max+
571
+ # +procs+ | column index to built-in name or callable (see above)
572
+ # +colors+ | +columns:+ per-column, +fields:+ per +[row, col]+
573
+ # +align+ | per-column +:left+/+:right+ (numbers default +:right+)
485
574
 
486
575
  def initialize args={}
487
576
  @columns,@functions = 0,{}