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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +4 -4
- data/lib/browser.rb +98 -19
- data/lib/grid.rb +120 -31
- data/lib/line.rb +50 -8
- data/lib/space.rb +113 -8
- data/lib/stack.rb +106 -34
- data/lib/terminal.rb +393 -55
- data/lib/text.rb +57 -11
- data/lib/typr.rb +5 -3
- data/typr.gemspec +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c61c73e0ce9201962acda993655743ab611ae86a2a3102d64dd05bacb223751
|
|
4
|
+
data.tar.gz: d0474937ff2a6ad8cb5eb39918eac2302d7114998e9e6775a99f6b52a1727b13
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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, :
|
|
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, :
|
|
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). `
|
|
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: "
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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, +".."+, +"~"
|
|
49
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
180
|
-
#
|
|
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
|
-
#
|
|
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
|
-
##
|
|
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
|
|
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 `/`
|
|
340
|
+
## Open live `/` narrowing filtered on the name column.
|
|
341
|
+
#
|
|
342
|
+
# browser.narrow # type to narrow, Escape restores, Enter commits
|
|
280
343
|
#
|
|
281
|
-
#
|
|
344
|
+
# @return [void]
|
|
282
345
|
|
|
283
|
-
def
|
|
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, "[/]:
|
|
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", "[/]:
|
|
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, :
|
|
12
|
-
# )
|
|
13
|
-
# grid.show
|
|
14
|
-
#
|
|
15
|
-
# Column format
|
|
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
|
|
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 +
|
|
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 = [
|
|
121
|
-
#
|
|
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,
|
|
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,
|
|
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
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
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
|
-
#
|
|
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.
|
|
314
|
-
# grid.
|
|
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
|
|
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,
|
|
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]
|
|
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,{}
|