typr 1.9.0 → 1.9.3
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 +20 -0
- data/README.md +4 -4
- data/example/text +1 -1
- data/lib/browser.rb +21 -10
- data/lib/grid.rb +69 -19
- data/lib/line.rb +12 -4
- data/lib/stack.rb +49 -24
- data/lib/terminal.rb +22 -6
- data/lib/text.rb +15 -4
- data/lib/typr.rb +1 -1
- 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: cdabc3f928eb42904b0f1cd0778384bca290ae56dfbb050236cda6a6578d19e8
|
|
4
|
+
data.tar.gz: 236970a28d8cd9265651985504a60a8113cdfaf2b8378303388c0a9e4883469e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82420afecbc67164d9271e315cd571f33898f4f4ae4ee4186da231cbb6d461df5714180e47c6237d7a54224fd0742777084d44c38e221931f763418bdc19df7d
|
|
7
|
+
data.tar.gz: cafd2cf36cdbf58e70249042e7952ca1048325b61ae404b0c4a154eabcb9684cbe8bf75b3aaee239d0c169da7057826a1b7e1bcae1e6dd2b81fa9145942424d9
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## [vx.y.z] — __DATE__
|
|
4
4
|
|
|
5
|
+
## [v1.9.3] — 2026-10-19
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Grid rendering can no longer crash with `ArgumentError: negative argument` on emoji-wide cell text — the pad in `prepare` is clamped at zero, and `clip` measures grapheme clusters (flags, `‼️`-style presentation sequences) instead of single chars so its width accounting matches `real_size`
|
|
9
|
+
- Numeric hints no longer vanish when the list shrinks below the Tab-cycled hint offset (after a `move`/transfer or `cd` into a smaller directory) — `show` re-clamps `hints_start` so the existing single redraw shows the digits again without any extra drawing
|
|
10
|
+
- A one-entry filtered pick auto-returns before the popup is drawn — `Grid#pick` checks the initial match set before showing, so a single-entry grid returns its entry without ever displaying
|
|
11
|
+
- The `text` example wires `regex: :generic` colors into its Text widget, so regex highlighting is demonstrated out of the box
|
|
12
|
+
|
|
13
|
+
## [v1.9.2] — 2026-10-13
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- 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
|
|
17
|
+
|
|
18
|
+
## [v1.9.1] — 2026-10-12
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `rake docs:check` — fails when any class, method, attribute, or constant is undocumented (CI-style gate for the RI pass; currently 221/221)
|
|
22
|
+
|
|
5
23
|
## [v1.9.0] — 2026-10-12
|
|
6
24
|
|
|
7
25
|
### Changed
|
|
@@ -11,7 +29,9 @@
|
|
|
11
29
|
- 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)
|
|
12
30
|
|
|
13
31
|
### Fixed
|
|
32
|
+
- 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`
|
|
14
33
|
- 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)
|
|
34
|
+
- 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)
|
|
15
35
|
- 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`)
|
|
16
36
|
|
|
17
37
|
## [v1.8.0] — 2026-10-11
|
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/example/text
CHANGED
|
@@ -33,7 +33,7 @@ class TextTest
|
|
|
33
33
|
input: $<, header: "Text Area Header",
|
|
34
34
|
top: 2, left: 0.1, right: 0.9, bottom: -4,
|
|
35
35
|
border: :round, borders:{ left: ?┆, right: ?┆, top: ?╌, bottom: ?╌ },
|
|
36
|
-
colors: { header: [:yellow, :grey30], hints: [:black, :white] }
|
|
36
|
+
colors: { header: [:yellow, :grey30], hints: [:black, :white], regex: :generic }
|
|
37
37
|
)
|
|
38
38
|
@user = Line.new( top: -1,
|
|
39
39
|
default: "[/]:search [n]ext [p]rev, pick [r]ow/[R]ows, [w]ord/[W]ords, [esc] quit",
|
data/lib/browser.rb
CHANGED
|
@@ -71,17 +71,15 @@ module Typr
|
|
|
71
71
|
def top; super + 1 end
|
|
72
72
|
|
|
73
73
|
##
|
|
74
|
-
# Change to +dir+ (absolute path, +".."+, +"~"
|
|
75
|
-
#
|
|
76
|
-
# before scanning. Pushes onto dir_history.
|
|
74
|
+
# Change to +dir+ (absolute path, +".."+, or +"~"+). Unless +append+,
|
|
75
|
+
# clears data and colors before scanning.
|
|
77
76
|
#
|
|
78
77
|
# browser.cd "/tmp" # => changes to /tmp
|
|
79
78
|
# browser.cd "~" # => changes to home
|
|
80
79
|
# browser.cd ".." # => parent directory
|
|
81
|
-
# browser.cd 2 # => jump to history entry 2
|
|
82
80
|
#
|
|
83
|
-
# @param dir [String
|
|
84
|
-
# @param append [Boolean] scan without clearing
|
|
81
|
+
# @param dir [String] target path
|
|
82
|
+
# @param append [Boolean] scan without clearing
|
|
85
83
|
# @param trim [Integer] path prefix trim for the +path+ column
|
|
86
84
|
# @return [false, nil] false when the directory cannot be opened
|
|
87
85
|
|
|
@@ -195,6 +193,8 @@ module Typr
|
|
|
195
193
|
# browser.change_view :full # all columns
|
|
196
194
|
# browser.change_view 0 # first view key
|
|
197
195
|
# browser.change_view :stats # name, size, perms, owner, group, modified
|
|
196
|
+
#
|
|
197
|
+
# @return [nil]
|
|
198
198
|
|
|
199
199
|
def change_view view=nil
|
|
200
200
|
return unless view
|
|
@@ -257,11 +257,11 @@ module Typr
|
|
|
257
257
|
#
|
|
258
258
|
# browser.mark_range # prompts for from/to, toggles selection
|
|
259
259
|
#
|
|
260
|
-
# @return [Array<Integer
|
|
260
|
+
# @return [Array<Integer>, nil] the toggled ids, or nil on abort
|
|
261
261
|
|
|
262
|
-
def mark_range; return
|
|
262
|
+
def mark_range; return unless picked = @user.ask(
|
|
263
263
|
'mark from': [self, :relative_row], 'to': [self, :relative_row]
|
|
264
|
-
).join '..' ) ].each{|id| @selected[:rows].include?(id) ?
|
|
264
|
+
); return ids[ ( eval picked.join '..' ) ].each{|id| @selected[:rows].include?(id) ?
|
|
265
265
|
@selected[:rows].delete(id) : @selected[:rows] << id } end
|
|
266
266
|
|
|
267
267
|
## Selects all non-directory rows.
|
|
@@ -383,6 +383,12 @@ module Typr
|
|
|
383
383
|
# browser.pick # => /home/user/file.txt
|
|
384
384
|
# browser.pick 'image' # => /path/to/photo.png (images only)
|
|
385
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
|
|
386
392
|
|
|
387
393
|
def pick type='file', **kw
|
|
388
394
|
type ||= 'file'
|
|
@@ -470,6 +476,8 @@ module Typr
|
|
|
470
476
|
# within each prefix grouping.
|
|
471
477
|
#
|
|
472
478
|
# browser.sort # re-sorts with dirs first
|
|
479
|
+
#
|
|
480
|
+
# @return [nil]
|
|
473
481
|
|
|
474
482
|
def sort
|
|
475
483
|
@sort ||= NAME
|
|
@@ -500,10 +508,13 @@ module Typr
|
|
|
500
508
|
# Option | Default | Description
|
|
501
509
|
# --------------------|---------------------------------|-----------------------------------
|
|
502
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)
|
|
503
515
|
# +mimetype_db+ | true | Load MIME from file extensions
|
|
504
516
|
# +mimetype_magic+ | true | Use +file --mime-type+ for unknowns
|
|
505
517
|
# +colors[:actions]+ | { filter: :red, ... } | Status bar item colors
|
|
506
|
-
# +colors[:dir_history]+ | :yellow | History picker column color
|
|
507
518
|
# +colors[:types]+ | (see below) | Per MIME-type foreground color
|
|
508
519
|
#
|
|
509
520
|
# == Type Color Defaults
|
data/lib/grid.rb
CHANGED
|
@@ -8,11 +8,15 @@ module Typr
|
|
|
8
8
|
# grid = Typr::Grid.new(
|
|
9
9
|
# input: [ ["Alice", 30], ["Bob", 25] ],
|
|
10
10
|
# header: [:name, :age],
|
|
11
|
-
# format: [:max, :
|
|
11
|
+
# format: [:max, :min]
|
|
12
12
|
# )
|
|
13
13
|
# grid.show
|
|
14
14
|
#
|
|
15
|
-
# Column format
|
|
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
|
|
|
@@ -25,11 +29,20 @@ module Typr
|
|
|
25
29
|
#
|
|
26
30
|
# grid.filters #=> [[0, "Ali"]]
|
|
27
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
|
|
28
38
|
|
|
29
39
|
attr_accessor :format, :procs, :filters, :data, :map
|
|
30
40
|
##
|
|
31
41
|
# +align+ is per-column alignment, +rawsort+/+rawfilter+ flag columns
|
|
32
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.
|
|
33
46
|
#
|
|
34
47
|
# grid.reverse = true
|
|
35
48
|
# grid.rawsort[0] = true # sort raw values for column 0
|
|
@@ -139,12 +152,14 @@ module Typr
|
|
|
139
152
|
##
|
|
140
153
|
# Set column headers. Symbols are auto-defined as +UPCASE+ constants
|
|
141
154
|
# holding the column index.
|
|
155
|
+
# A plain String instead draws as a label bar with no column meaning.
|
|
142
156
|
#
|
|
143
157
|
# grid.header = ["name", "age"]
|
|
144
158
|
# Typr::NAME #=> 0
|
|
145
159
|
# Typr::AGE #=> 1
|
|
160
|
+
# grid.header = "People" # label bar, defines no constants
|
|
146
161
|
#
|
|
147
|
-
# @param h [Array<String, Symbol
|
|
162
|
+
# @param h [Array<String, Symbol>, String] header labels or a label bar
|
|
148
163
|
|
|
149
164
|
def header=( h ); super
|
|
150
165
|
h.each_with_index{ |h,i|
|
|
@@ -206,8 +221,8 @@ module Typr
|
|
|
206
221
|
# grid.add_filter(1, /^\d{2}$/) # age matches regex
|
|
207
222
|
# grid.add_filter(:all, "ip") # any column contains "ip"
|
|
208
223
|
#
|
|
209
|
-
# @param column [Integer,
|
|
210
|
-
# @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
|
|
211
226
|
|
|
212
227
|
def add_filter column=nil, query=nil
|
|
213
228
|
return unless column and query
|
|
@@ -259,27 +274,51 @@ module Typr
|
|
|
259
274
|
# grid.sorted_by true #=> "age"
|
|
260
275
|
#
|
|
261
276
|
# @param name [Boolean] pass truthy to get display name
|
|
262
|
-
# @return [String, Integer,
|
|
277
|
+
# @return [String, Integer, nil]
|
|
263
278
|
|
|
264
279
|
def sorted_by name=false; (name and @sort) ? @header[@sort] : @sort end
|
|
265
280
|
|
|
266
281
|
##
|
|
267
|
-
# Pick a row with live filtering
|
|
268
|
-
#
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
# auto-return (used by
|
|
272
|
-
#
|
|
273
|
-
#
|
|
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
|
|
274
304
|
#
|
|
275
305
|
# grid.pick :row, 0, column: 0 # type to narrow, auto-returns at 1 match
|
|
276
306
|
# grid.pick :row, 0, column: :all # match any column
|
|
277
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
|
|
278
310
|
#
|
|
279
|
-
#
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
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
|
|
283
322
|
|
|
284
323
|
def pick type = :row, row=0, column: nil
|
|
285
324
|
return super(type, row) unless column
|
|
@@ -290,6 +329,7 @@ module Typr
|
|
|
290
329
|
@filters = [[column, '']]
|
|
291
330
|
@map = base.select{ |id| check id }
|
|
292
331
|
@start = 0
|
|
332
|
+
return @map.first if auto and @map.one?
|
|
293
333
|
@bottom ||= bottom
|
|
294
334
|
show
|
|
295
335
|
draw_hints
|
|
@@ -378,7 +418,7 @@ module Typr
|
|
|
378
418
|
##
|
|
379
419
|
# Execute the sort and update +@map+.
|
|
380
420
|
#
|
|
381
|
-
# @return [false,
|
|
421
|
+
# @return [false, nil]
|
|
382
422
|
|
|
383
423
|
def sort
|
|
384
424
|
return false unless @sort
|
|
@@ -508,6 +548,7 @@ module Typr
|
|
|
508
548
|
data.each_with_index{ |row, id|
|
|
509
549
|
self[ is_hash? ? row.shift : id + skip ] = row }
|
|
510
550
|
sort
|
|
551
|
+
self
|
|
511
552
|
end
|
|
512
553
|
|
|
513
554
|
##
|
|
@@ -521,7 +562,16 @@ module Typr
|
|
|
521
562
|
# colors: { columns: [:cyan, nil] }
|
|
522
563
|
# )
|
|
523
564
|
#
|
|
524
|
-
# @param args [Hash]
|
|
565
|
+
# @param args [Hash]
|
|
566
|
+
#
|
|
567
|
+
# Option | Value
|
|
568
|
+
# ----------|--------------------------------------------------------
|
|
569
|
+
# +input+ | Array of rows, or Hash of key to row
|
|
570
|
+
# +header+ | column labels (see {#header=})
|
|
571
|
+
# +format+ | per-column +Integer+ width, +:min+, or +:max+
|
|
572
|
+
# +procs+ | column index to built-in name or callable (see above)
|
|
573
|
+
# +colors+ | +columns:+ per-column, +fields:+ per +[row, col]+
|
|
574
|
+
# +align+ | per-column +:left+/+:right+ (numbers default +:right+)
|
|
525
575
|
|
|
526
576
|
def initialize args={}
|
|
527
577
|
@columns,@functions = 0,{}
|
data/lib/line.rb
CHANGED
|
@@ -13,9 +13,10 @@ module Typr
|
|
|
13
13
|
|
|
14
14
|
class Line < Space
|
|
15
15
|
##
|
|
16
|
-
# Line state: +default+
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# Line state: +default+ (a String, Proc, or Space) restored by {#reset},
|
|
17
|
+
# +prompt+ (a single char, +>?+ by default) shown before answers,
|
|
18
|
+
# +bindings+ the clickable keybinding labels, +data+ the currently
|
|
19
|
+
# rendered text, +align+ (:left/:right) horizontal alignment,
|
|
19
20
|
# +trim+ (:left/:right) the side clipped on overflow.
|
|
20
21
|
#
|
|
21
22
|
# line.default = "status: ok"
|
|
@@ -46,7 +47,7 @@ class Line < Space
|
|
|
46
47
|
# line.append " world" #=> "hello world"
|
|
47
48
|
# line << "!" #=> "hello world!"
|
|
48
49
|
|
|
49
|
-
def append(str); show @data+str end
|
|
50
|
+
def append(str); show @data+str; @data end
|
|
50
51
|
alias :<< :append
|
|
51
52
|
|
|
52
53
|
# The keybinding list as a clickable grid of the line's own bindings. It
|
|
@@ -109,6 +110,11 @@ class Line < Space
|
|
|
109
110
|
# :key - a single keypress via Typr.read_key
|
|
110
111
|
# :line - interactive line editor via Typr.read_line at the cursor
|
|
111
112
|
#
|
|
113
|
+
# Every entry — including a trailing +with:+ — is simply another
|
|
114
|
+
# question in order (a +with:+ entry is labeled "with" when drawn), so
|
|
115
|
+
# +ask(pick: [grid, :row], with: :line)+ first picks, then reads
|
|
116
|
+
# free text. Hash order is insertion order.
|
|
117
|
+
#
|
|
112
118
|
# Pressing the exit key (@keymap[:exit], Escape by default) aborts the
|
|
113
119
|
# remaining questions and returns nil.
|
|
114
120
|
#
|
|
@@ -116,6 +122,7 @@ class Line < Space
|
|
|
116
122
|
# line.ask(keys: :key) #=> "a"
|
|
117
123
|
# line.ask(open: [grid, :row, :name]) #=> 2 (picked row id)
|
|
118
124
|
# line.ask(nick: [:line, { initial: "bob" }]) #=> edited string
|
|
125
|
+
# line.ask(pick: [grid, :row], with: :line) #=> [2, "typed text"]
|
|
119
126
|
# line.ask([ ["Name?", :line], ["Ok?", :key] ]) #=> ["Alice", "a"]
|
|
120
127
|
|
|
121
128
|
def ask sentence
|
|
@@ -181,6 +188,7 @@ class Line < Space
|
|
|
181
188
|
# line.cli "> ", history: @recent, prefixes: "$>"
|
|
182
189
|
#
|
|
183
190
|
# @param prompt [String] rendered before the typed input
|
|
191
|
+
# @param initial [String] prefilled input
|
|
184
192
|
# @return [String, nil] the accepted command, or nil on Escape
|
|
185
193
|
|
|
186
194
|
def cli prompt, initial: '', redraw: nil, history: nil, prefixes: nil, regex: nil
|
data/lib/stack.rb
CHANGED
|
@@ -95,7 +95,9 @@ module Typr
|
|
|
95
95
|
# stack.reset # resets position and selection
|
|
96
96
|
# stack.reset :position # only scrolls back to top
|
|
97
97
|
# stack.reset :selection # clears all selected rows/columns/fields
|
|
98
|
-
# stack.reset [:
|
|
98
|
+
# stack.reset [:position, :selection]
|
|
99
|
+
#
|
|
100
|
+
# @param type [:all, :position, :selection, Array<Symbol>] anything else is ignored
|
|
99
101
|
|
|
100
102
|
def reset type=:all
|
|
101
103
|
case type
|
|
@@ -147,6 +149,7 @@ module Typr
|
|
|
147
149
|
mode :reset
|
|
148
150
|
end
|
|
149
151
|
list = page.to_a
|
|
152
|
+
@hints_start = 0 if @hints_start > [height-1, rows-1+headspace].min
|
|
150
153
|
list[ height-1 ] = nil if list.length < height
|
|
151
154
|
for id, relative_id in list.each_with_index
|
|
152
155
|
move left,top + relative_id + headspace
|
|
@@ -165,10 +168,16 @@ module Typr
|
|
|
165
168
|
|
|
166
169
|
##
|
|
167
170
|
# Dispatches a key from the terminal to its semantic action and clamps the current page
|
|
168
|
-
# offset.
|
|
171
|
+
# offset. Unknown keys only clamp. +confirm+ dispatches to a +confirm+
|
|
172
|
+
# method, which only Browser defines.
|
|
173
|
+
#
|
|
174
|
+
# stack.send Typr::KEY_DOWN # scrolls down one row
|
|
169
175
|
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
176
|
+
# Mouse: wheel scrolls (+:scroll+), a click that misses is +:outside+,
|
|
177
|
+
# a left/right press on a row returns its id.
|
|
178
|
+
#
|
|
179
|
+
# @param key [String, Typr::Mouse] key sequence or mouse event
|
|
180
|
+
# @return [Object] the keymap action's result; +:scroll+/:outside+/row id for mouse
|
|
172
181
|
|
|
173
182
|
def send key#, map=nil
|
|
174
183
|
if key.is_a?(Typr::Mouse)
|
|
@@ -244,33 +253,45 @@ module Typr
|
|
|
244
253
|
def page_up; @start -= height; return end
|
|
245
254
|
|
|
246
255
|
##
|
|
247
|
-
# Blocks the calling thread
|
|
248
|
-
# page
|
|
249
|
-
#
|
|
256
|
+
# Blocks the calling thread for an interactive pick from the current
|
|
257
|
+
# page with hint characters or arrow keys. Single picks return the
|
|
258
|
+
# moment they're made; multi (+...+s+) picks toggle until Return
|
|
259
|
+
# confirms the selection (Escape aborts, and confirming an empty
|
|
260
|
+
# selection returns nil).
|
|
250
261
|
#
|
|
251
262
|
# The +type+ argument controls what kind of picker is shown:
|
|
252
263
|
#
|
|
253
|
-
#
|
|
254
|
-
#
|
|
255
|
-
# +:row
|
|
256
|
-
#
|
|
257
|
-
# +:column
|
|
258
|
-
# +:
|
|
259
|
-
# +:
|
|
260
|
-
#
|
|
264
|
+
# Type | Picks
|
|
265
|
+
# -------------------|------------------------------------------------
|
|
266
|
+
# +:row+ | Single row id
|
|
267
|
+
# +:rows+ | Multi-select row ids (Return confirms)
|
|
268
|
+
# +:column+ | One column of the given row (Grid: real index, Text: word offset)
|
|
269
|
+
# +:columns+ | Multi-select columns (Return confirms)
|
|
270
|
+
# +:field+ | One [row, column] pair, picked row-then-column
|
|
271
|
+
# +:fields+ | Multi-select [row, column] pairs
|
|
272
|
+
# +:relative_row+ | Viewport-absolute row index (0-based)
|
|
273
|
+
# +:none+ | Navigation only; always returns nil
|
|
274
|
+
#
|
|
275
|
+
# +:column+, +:columns+, +:field+ (+:fields+) and +:none+ need row
|
|
276
|
+
# positions, so they only work on Grid and Text — a bare Stack
|
|
277
|
+
# supports +:row+ and +:rows+. (The +"file"+ type belongs to
|
|
278
|
+
# {Browser#pick}.)
|
|
261
279
|
#
|
|
262
280
|
# stack.pick # single row picker
|
|
263
|
-
# stack.pick :column, 3 #
|
|
264
|
-
# stack.pick
|
|
281
|
+
# stack.pick :column, 3 # column of the 4th visible row (page index, not id)
|
|
282
|
+
# stack.pick :rows # multi-select rows
|
|
283
|
+
# stack.pick :relative_row # 0-based index into the viewport
|
|
265
284
|
#
|
|
266
285
|
# Pressing Alt with a hint digit (ESC + digit) picks that hint row and
|
|
267
|
-
# sets +modifier+ to +:alt+ for the caller.
|
|
286
|
+
# sets +modifier+ to +:alt+ for the caller. The Return and Escape keys
|
|
287
|
+
# come from +@keymap+ (+confirm+/+exit+, Return/Escape by default).
|
|
268
288
|
#
|
|
269
|
-
# Mouse: left-click a row to pick it; wheel up/down
|
|
289
|
+
# Mouse: left-click a row to pick it (row pickers only); wheel up/down
|
|
290
|
+
# scrolls the page; clicking outside cancels like Escape.
|
|
270
291
|
#
|
|
271
|
-
# @param type [Symbol, String] picker type (table above)
|
|
272
|
-
# @param row [Integer]
|
|
273
|
-
# @return [Integer, Array, nil]
|
|
292
|
+
# @param type [Symbol, String] picker type (table above; symbols documented)
|
|
293
|
+
# @param row [Integer] page-relative row for +:column+ hints (ignored otherwise)
|
|
294
|
+
# @return [Integer, Array, nil] row id, [row, column], confirmed selection, or nil
|
|
274
295
|
|
|
275
296
|
def pick type = :row, row=0 #, key=nil
|
|
276
297
|
type = type.to_s
|
|
@@ -360,11 +381,15 @@ module Typr
|
|
|
360
381
|
#
|
|
361
382
|
# Option | Default | Description
|
|
362
383
|
# ----------------|---------|-------------------------------------------
|
|
363
|
-
# +
|
|
364
|
-
# +hints+ | +"
|
|
384
|
+
# +keymap+ | {} | Custom name-to-key mappings (override navigation defaults)
|
|
385
|
+
# +hints+ | +"1234567890"+ | Characters used in the hint overlay
|
|
365
386
|
# +alternate+ | true | Striped row backgrounds
|
|
366
387
|
# +header+ | nil | Label shown in a header bar
|
|
367
388
|
# +separator+ | +" "+ | String drawn between columns (used by Grid)
|
|
389
|
+
# +colors+ | {} | Per-part colors (+hints+, +header+, +selected+, +alternate+, +rows+)
|
|
390
|
+
# +selected+ | {} | Pre-picked ids per type (+rows+, +columns+, +fields+)
|
|
391
|
+
#
|
|
392
|
+
# stack = Typr::Stack.new(keymap: { exit: "q" }) # quit on q instead
|
|
368
393
|
|
|
369
394
|
def initialize args={}
|
|
370
395
|
@keymap, @separator, @selected = {}, " ", {}
|
data/lib/terminal.rb
CHANGED
|
@@ -89,6 +89,7 @@ module Typr
|
|
|
89
89
|
# m.left? #=> true
|
|
90
90
|
# m.alt? #=> true (with the SGR alt bit set)
|
|
91
91
|
# m.to_s #=> "press button=0 x=10 y=5"
|
|
92
|
+
# +action+ is +:press+, +:release+, or +:motion+ (move with a button held).
|
|
92
93
|
Mouse = Struct.new(:button, :x, :y, :action, :modifiers) do
|
|
93
94
|
# True on button press (the moment to pick a row).
|
|
94
95
|
def press?; action == :press end
|
|
@@ -146,7 +147,7 @@ module Typr
|
|
|
146
147
|
|
|
147
148
|
def prepare str, max, align=:left, side=:right
|
|
148
149
|
str = clip str, max, side if real_size(str) >= max
|
|
149
|
-
pad = " " *
|
|
150
|
+
pad = " " * [max - real_size(str), 0].max
|
|
150
151
|
align == :right ? pad + str : str + pad
|
|
151
152
|
end
|
|
152
153
|
|
|
@@ -184,14 +185,14 @@ module Typr
|
|
|
184
185
|
used += size
|
|
185
186
|
elsif used < max
|
|
186
187
|
need = max - used
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
clusters = token.grapheme_clusters
|
|
189
|
+
clusters.reverse! if side == :left
|
|
189
190
|
width = 0
|
|
190
191
|
out = ""
|
|
191
|
-
|
|
192
|
-
width += Unicode::DisplayWidth.of
|
|
192
|
+
clusters.each do |cluster|
|
|
193
|
+
width += Unicode::DisplayWidth.of cluster
|
|
193
194
|
break if width > need
|
|
194
|
-
out <<
|
|
195
|
+
out << cluster
|
|
195
196
|
end
|
|
196
197
|
out.reverse! if side == :left
|
|
197
198
|
kept << out
|
|
@@ -600,6 +601,9 @@ module Typr
|
|
|
600
601
|
# until a full window passes with none — each arrival restarts it.
|
|
601
602
|
# No-op unless armed by {Typr.suppress_keys}. Best-effort: never raises.
|
|
602
603
|
#
|
|
604
|
+
# Typr.suppress_keys # arm a 0.5s window
|
|
605
|
+
# Typr.send(:drain_quiet) # wait it out, swallowing arrivals
|
|
606
|
+
#
|
|
603
607
|
# @return [nil]
|
|
604
608
|
|
|
605
609
|
def self.drain_quiet
|
|
@@ -635,6 +639,7 @@ module Typr
|
|
|
635
639
|
# of candidates; TAB then replaces the word through a pick grid. Each
|
|
636
640
|
# candidate is either a plain String or [text, color-pair] to tint the
|
|
637
641
|
# pick entry. The callable's content is arbitrary - shells, tags, ...
|
|
642
|
+
# It takes the text before the cursor and returns an Array of candidates.
|
|
638
643
|
#
|
|
639
644
|
# +regex+ is a Hash of Regexp => color (see {.paint}) applied to the
|
|
640
645
|
# query as it is rendered; matched regions restore the prompt's trailing
|
|
@@ -649,6 +654,17 @@ module Typr
|
|
|
649
654
|
# end
|
|
650
655
|
# Typr.read_line "> ", initial: "ls ", history: @recent #=> "ls -l"
|
|
651
656
|
|
|
657
|
+
# @param prompt [String] text rendered before the query (keep a color code trailing)
|
|
658
|
+
# @param left [Integer] screen column (0-based)
|
|
659
|
+
# @param top [Integer] screen row (0-based)
|
|
660
|
+
# @param initial [String] prefilled query
|
|
661
|
+
# @param completions [#call, nil] +->(prefix) { candidates }+ or nil
|
|
662
|
+
# @param redraw [#call, nil] zero-arg repaint after a completion pick
|
|
663
|
+
# @param regex [Hash, Symbol, nil] paint map or scheme (see {.paint})
|
|
664
|
+
# @param history [Array<String>, nil] previous queries for Up/Down
|
|
665
|
+
# @param block [Proc] +->(key, query, cursor)+ inspector (non-nil short-circuits)
|
|
666
|
+
# @return [Object, nil] the query, a block short-circuit, or nil on Escape
|
|
667
|
+
|
|
652
668
|
def self.read_line prompt='', left: 0, top: 0, initial: '', completions: nil, redraw: nil, regex: nil, history: nil, &block
|
|
653
669
|
return $stdin.gets&.chomp unless $stdin.tty?
|
|
654
670
|
query, cursor = initial.dup, initial.length
|
data/lib/text.rb
CHANGED
|
@@ -4,7 +4,9 @@ require_relative 'stack.rb'
|
|
|
4
4
|
module Typr
|
|
5
5
|
|
|
6
6
|
# A vertically scrollable text viewer with word-wrapping, header bar,
|
|
7
|
-
# row selection via hint characters
|
|
7
|
+
# row selection via hint characters (+pick :row+), word selection via
|
|
8
|
+
# +pick :column+ (hints sit on word boundaries), and pager-style
|
|
9
|
+
# search (/ n p).
|
|
8
10
|
#
|
|
9
11
|
# Accepts String, IO, or StringIO input. Content is split into display
|
|
10
12
|
# rows by newlines and line width, then rendered as a sliding window.
|
|
@@ -37,6 +39,9 @@ module Typr
|
|
|
37
39
|
# text.build("one\ntwo three") # => #<Typr::Text ...>
|
|
38
40
|
# text << "appended line" # alias for build
|
|
39
41
|
|
|
42
|
+
# @param input [String, IO, nil] text (or stream) to append; nil reflows
|
|
43
|
+
# @return [Text] self
|
|
44
|
+
|
|
40
45
|
def build input=nil
|
|
41
46
|
( input, @data, @lines = @data, "", [0] ) if rebuild = !input
|
|
42
47
|
return self if rebuild && input == ""
|
|
@@ -82,7 +87,7 @@ module Typr
|
|
|
82
87
|
else
|
|
83
88
|
@tail = ""
|
|
84
89
|
end
|
|
85
|
-
@lines << @data.length if
|
|
90
|
+
@lines << @data.length if line.include?("\n") || pos < str.size || !@tail.empty?
|
|
86
91
|
end
|
|
87
92
|
until @tail.empty?
|
|
88
93
|
wrap_idx = @tail.index("\n") || @tail.rindex(" ") || @tail.length
|
|
@@ -97,6 +102,7 @@ module Typr
|
|
|
97
102
|
end
|
|
98
103
|
@lines << @data.length unless @lines.last == @data.length
|
|
99
104
|
highlight if @re
|
|
105
|
+
self
|
|
100
106
|
end
|
|
101
107
|
alias :<< :build
|
|
102
108
|
|
|
@@ -137,8 +143,7 @@ module Typr
|
|
|
137
143
|
def show
|
|
138
144
|
(build; @lastwidth = width) if width != @lastwidth
|
|
139
145
|
range = @lines[@start..@start+height]
|
|
140
|
-
page =
|
|
141
|
-
@page=range[1..-1].map{|pos| page.read(pos-range.first-page.pos).rstrip}
|
|
146
|
+
@page = range.each_cons(2).map{ |a, b| @data[a...b].rstrip }
|
|
142
147
|
super
|
|
143
148
|
end
|
|
144
149
|
|
|
@@ -229,6 +234,12 @@ module Typr
|
|
|
229
234
|
#
|
|
230
235
|
# text = Typr::Text.new(input: "hello", header: "Title")
|
|
231
236
|
# text = Typr::Text.new(input: $stdin, tabspace: 4)
|
|
237
|
+
#
|
|
238
|
+
# Option | Value
|
|
239
|
+
# -----------|------------------------------------------------
|
|
240
|
+
# +input+ | String, IO, or StringIO (nil reflows from empty)
|
|
241
|
+
# +tabspace+ | tabstop width in spaces (default 2)
|
|
242
|
+
# +colors+ | +regex:+ paint map or scheme name (see {Typr.paint})
|
|
232
243
|
|
|
233
244
|
def initialize args={}
|
|
234
245
|
@tabspace = 2
|
data/lib/typr.rb
CHANGED
data/typr.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kilian Reitmayr
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-19 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: unicode-display_width
|