typr 1.8.0 → 1.9.0
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 +12 -0
- data/lib/browser.rb +80 -12
- data/lib/grid.rb +61 -21
- data/lib/line.rb +41 -7
- data/lib/space.rb +113 -8
- data/lib/stack.rb +62 -14
- data/lib/terminal.rb +377 -55
- data/lib/text.rb +42 -7
- data/lib/typr.rb +4 -2
- 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: 74b0a2cd082b9a2ee58f9fb64356a7ed4f5a970c0427768acae334ae3325748a
|
|
4
|
+
data.tar.gz: af90bbcfff025ba60f1d8de7cae5171152f8939a00a7fac5975cc95785a28547
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c29e087834938ef34608086cb90ec221760ca50f0be054e7d9602f46cc6e63c73fbe06a960c8b55e551b17159f404d796f275e3a11cddcb3f70baccefc67636b
|
|
7
|
+
data.tar.gz: a33662ba4009f34cef2e91b5295a6fc1e3f2b17ebd9edf118052caa016e6dc0d8dd4d6f2c3fb468244f2e488e589c1ec79236f2a7001bc4e6622064fcf9a3365
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [vx.y.z] — __DATE__
|
|
4
4
|
|
|
5
|
+
## [v1.9.0] — 2026-10-12
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- 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
|
|
9
|
+
- `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
|
|
10
|
+
- 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
|
|
11
|
+
- 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
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- 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)
|
|
15
|
+
- 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
|
+
|
|
5
17
|
## [v1.8.0] — 2026-10-11
|
|
6
18
|
|
|
7
19
|
### Changed
|
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,14 +34,39 @@ 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
|
|
|
@@ -53,6 +79,11 @@ module Typr
|
|
|
53
79
|
# browser.cd "~" # => changes to home
|
|
54
80
|
# browser.cd ".." # => parent directory
|
|
55
81
|
# browser.cd 2 # => jump to history entry 2
|
|
82
|
+
#
|
|
83
|
+
# @param dir [String, Integer] target path or history index
|
|
84
|
+
# @param append [Boolean] scan without clearing (used by history)
|
|
85
|
+
# @param trim [Integer] path prefix trim for the +path+ column
|
|
86
|
+
# @return [false, nil] false when the directory cannot be opened
|
|
56
87
|
|
|
57
88
|
def cd dir, append=false, trim=0
|
|
58
89
|
return unless dir
|
|
@@ -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,19 +236,28 @@ 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>] the toggled ids
|
|
216
261
|
|
|
217
262
|
def mark_range; return ids[ ( eval @user.ask(
|
|
218
263
|
'mark from': [self, :relative_row], 'to': [self, :relative_row]
|
|
@@ -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.
|
|
280
341
|
#
|
|
281
|
-
# browser.
|
|
342
|
+
# browser.narrow # type to narrow, Escape restores, Enter commits
|
|
343
|
+
#
|
|
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
|
|
|
@@ -551,13 +619,13 @@ full: %i[ name size permissions executable printable owner group
|
|
|
551
619
|
@user ||= Line.new( parent:self, left:->{@parent.left}, top:->{@parent.bottom+1},
|
|
552
620
|
bindings: bindings_labels )
|
|
553
621
|
else
|
|
554
|
-
cmds = { "[h]elp" => :help, "[/]:
|
|
622
|
+
cmds = { "[h]elp" => :help, "[/]:narrow" => :narrow,
|
|
555
623
|
"[f]ilter" => :filter_view, "[s]ort" => :sort_view, "[v]iews" => :views,
|
|
556
624
|
"[r]eset" => :reset_view, "[m]ark" => :mark,
|
|
557
625
|
"[M]ark_range" => :mark_range, "mark_[a]ll" => :mark_all,
|
|
558
626
|
"[i]nvert_mark" => :mark_invert, "[~]:home" => :home,
|
|
559
627
|
"[H]idden_files" => :toggle_hidden }
|
|
560
|
-
plain = ["[h]elp", "[/]:
|
|
628
|
+
plain = ["[h]elp", "[/]:narrow"] +
|
|
561
629
|
@colors[:actions].map { |name, _| "[" + name.to_s[0] + "]" + name.to_s[1..] } +
|
|
562
630
|
%w[ [r]eset [m]ark [M]ark_range mark_[a]ll
|
|
563
631
|
[i]nvert_mark [~]:home [H]idden_files [back]:.. [up] [down] [pageup] [pagedown] [home] [end] [esc]:quit [return]:confirm\ ]
|
data/lib/grid.rb
CHANGED
|
@@ -9,16 +9,37 @@ module Typr
|
|
|
9
9
|
# input: [ ["Alice", 30], ["Bob", 25] ],
|
|
10
10
|
# header: [:name, :age],
|
|
11
11
|
# format: [:max, :right]
|
|
12
|
-
# )
|
|
13
|
-
# grid.show
|
|
14
|
-
#
|
|
12
|
+
# )
|
|
13
|
+
# grid.show
|
|
14
|
+
#
|
|
15
15
|
# Column format tokens: +Integer+ (fixed width), +:min+ (auto), +:max+ (fill remaining).
|
|
16
16
|
#
|
|
17
17
|
# Built-in procs: +:filetree+, +:datetime+, +:magnitudes+, +:convert+.
|
|
18
18
|
|
|
19
19
|
class Grid < Stack # Table
|
|
20
|
+
##
|
|
21
|
+
# Display and transform state: +format+ per-column width tokens,
|
|
22
|
+
# +procs+ per-column processors, +filters+ the active [column, query]
|
|
23
|
+
# pairs, +data+ the rows (Array or Hash), and +map+ the visible row
|
|
24
|
+
# ids after filtering/sorting.
|
|
25
|
+
#
|
|
26
|
+
# grid.filters #=> [[0, "Ali"]]
|
|
27
|
+
# grid.map #=> [0, 2]
|
|
28
|
+
|
|
20
29
|
attr_accessor :format, :procs, :filters, :data, :map
|
|
30
|
+
##
|
|
31
|
+
# +align+ is per-column alignment, +rawsort+/+rawfilter+ flag columns
|
|
32
|
+
# to bypass their +procs+, and +reverse+ flips the sort direction.
|
|
33
|
+
#
|
|
34
|
+
# grid.reverse = true
|
|
35
|
+
# grid.rawsort[0] = true # sort raw values for column 0
|
|
36
|
+
|
|
21
37
|
attr_accessor :align, :rawsort, :rawfilter, :reverse
|
|
38
|
+
##
|
|
39
|
+
# Overrides the column render order (see {#sequence}).
|
|
40
|
+
#
|
|
41
|
+
# grid.sequence = [2, 0, 1]
|
|
42
|
+
|
|
22
43
|
attr_writer :sequence
|
|
23
44
|
|
|
24
45
|
##
|
|
@@ -52,7 +73,8 @@ module Typr
|
|
|
52
73
|
##
|
|
53
74
|
# Column render order. Defaults to 0..n-1; override with +sequence=+.
|
|
54
75
|
#
|
|
55
|
-
# grid.sequence
|
|
76
|
+
# grid.sequence #=> [0, 1, 2]
|
|
77
|
+
# grid.sequence = [2, 0, 1]
|
|
56
78
|
#
|
|
57
79
|
# @return [Array<Integer>]
|
|
58
80
|
|
|
@@ -115,12 +137,14 @@ module Typr
|
|
|
115
137
|
end
|
|
116
138
|
|
|
117
139
|
##
|
|
118
|
-
# Set column headers. Symbols are auto-defined as +
|
|
140
|
+
# Set column headers. Symbols are auto-defined as +UPCASE+ constants
|
|
141
|
+
# holding the column index.
|
|
119
142
|
#
|
|
120
|
-
# grid.header = [
|
|
121
|
-
#
|
|
143
|
+
# grid.header = ["name", "age"]
|
|
144
|
+
# Typr::NAME #=> 0
|
|
145
|
+
# Typr::AGE #=> 1
|
|
122
146
|
#
|
|
123
|
-
# @param h [Array<String, Symbol>]
|
|
147
|
+
# @param h [Array<String, Symbol>] header labels
|
|
124
148
|
|
|
125
149
|
def header=( h ); super
|
|
126
150
|
h.each_with_index{ |h,i|
|
|
@@ -240,18 +264,22 @@ module Typr
|
|
|
240
264
|
def sorted_by name=false; (name and @sort) ? @header[@sort] : @sort end
|
|
241
265
|
|
|
242
266
|
##
|
|
243
|
-
# Pick a row with live
|
|
267
|
+
# Pick a row with live filtering. When the query narrows the list to a
|
|
268
|
+
# single row it returns immediately (no Enter needed) and arms a 0.5s
|
|
269
|
+
# overtype quiet window via +Typr.suppress_keys+, so a trailing Enter
|
|
270
|
+
# or extra characters cannot re-trigger anything. +:none+ disables the
|
|
271
|
+
# auto-return (used by {Browser#narrow}, where Escape must restore).
|
|
244
272
|
#
|
|
245
|
-
#
|
|
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.
|
|
273
|
+
# With no +column+, passes through to {Stack#pick}.
|
|
249
274
|
#
|
|
250
|
-
# grid.pick :row, 0, column:
|
|
251
|
-
# grid.pick :row, 0, column: :all
|
|
275
|
+
# grid.pick :row, 0, column: 0 # type to narrow, auto-returns at 1 match
|
|
276
|
+
# grid.pick :row, 0, column: :all # match any column
|
|
277
|
+
# grid.pick :none, column: 0 # narrowing only, Enter commits
|
|
252
278
|
#
|
|
253
|
-
# @param type [Symbol] type
|
|
254
|
-
# @param
|
|
279
|
+
# @param type [Symbol, String] picker type; +:none+ disables auto-return
|
|
280
|
+
# @param row [Integer] ignored (always :row); passed to super otherwise
|
|
281
|
+
# @param column [Integer, Symbol, String] column index/name to filter on, or :all
|
|
282
|
+
# @return [Integer, nil] picked row id, or nil on abort
|
|
255
283
|
|
|
256
284
|
def pick type = :row, row=0, column: nil
|
|
257
285
|
return super(type, row) unless column
|
|
@@ -308,12 +336,15 @@ module Typr
|
|
|
308
336
|
end
|
|
309
337
|
|
|
310
338
|
##
|
|
311
|
-
#
|
|
339
|
+
# Dispatch a mouse press on the header row to a column sort (clicking
|
|
340
|
+
# the header cell for a column calls +sort_by+); everything else falls
|
|
341
|
+
# through to {Stack#send}.
|
|
312
342
|
#
|
|
313
|
-
# grid.
|
|
314
|
-
# grid.
|
|
343
|
+
# grid.send Typr::Mouse.new(0, 12, grid.top + 1, :press, 0) #=> :scroll
|
|
344
|
+
# grid.send Typr::KEY_DOWN # scrolls
|
|
315
345
|
#
|
|
316
|
-
# @param
|
|
346
|
+
# @param key [Typr::Mouse, String] mouse event or key sequence
|
|
347
|
+
# @return [Symbol, Object] +:scroll+ on a header click, super's response otherwise
|
|
317
348
|
|
|
318
349
|
def send key
|
|
319
350
|
if key.is_a?(Typr::Mouse) and key.press? and key.left? and
|
|
@@ -328,6 +359,15 @@ module Typr
|
|
|
328
359
|
super
|
|
329
360
|
end
|
|
330
361
|
|
|
362
|
+
##
|
|
363
|
+
# Sort by column (toggle direction on repeat).
|
|
364
|
+
#
|
|
365
|
+
# grid.sort_by :age
|
|
366
|
+
# grid.sort_by :age # reverses
|
|
367
|
+
#
|
|
368
|
+
# @param column [Integer, Symbol] column index or header-name symbol
|
|
369
|
+
# @return [void]
|
|
370
|
+
|
|
331
371
|
def sort_by column
|
|
332
372
|
column = @header.index( column.to_s ) if column.is_a? Symbol
|
|
333
373
|
if @sort and (column == @sort) then @reverse = !@reverse
|
data/lib/line.rb
CHANGED
|
@@ -12,6 +12,16 @@ module Typr
|
|
|
12
12
|
# line.ask([ ["Name?", :line], ["Age?", :line] ])
|
|
13
13
|
|
|
14
14
|
class Line < Space
|
|
15
|
+
##
|
|
16
|
+
# Line state: +default+ text restored by {#reset}, +prompt+ character
|
|
17
|
+
# before answers, +bindings+ the clickable keybinding labels, +data+ the
|
|
18
|
+
# currently rendered text, +align+ (:left/:right) horizontal alignment,
|
|
19
|
+
# +trim+ (:left/:right) the side clipped on overflow.
|
|
20
|
+
#
|
|
21
|
+
# line.default = "status: ok"
|
|
22
|
+
# line.prompt = "?"
|
|
23
|
+
# line.align = :left
|
|
24
|
+
|
|
15
25
|
attr_accessor :default, :prompt, :bindings, :data, :align, :trim
|
|
16
26
|
|
|
17
27
|
# Render +msg+ (String, Proc, or Space) on this line.
|
|
@@ -92,7 +102,10 @@ class Line < Space
|
|
|
92
102
|
# Array - interactive pick via <widget>.pick(<type>); an optional third
|
|
93
103
|
# element renders the chosen row's field into the line, and an
|
|
94
104
|
# optional fourth element is passed as pick keyword arguments
|
|
95
|
-
# (e.g. { column: 0 } to open the pick in / search mode)
|
|
105
|
+
# (e.g. { column: 0 } to open the pick in / search mode).
|
|
106
|
+
# Special case: [:line, { initial: "text" }] opens the inline
|
|
107
|
+
# editor via Typr.read_line prefilled with +initial+ instead
|
|
108
|
+
# of a widget pick.
|
|
96
109
|
# :key - a single keypress via Typr.read_key
|
|
97
110
|
# :line - interactive line editor via Typr.read_line at the cursor
|
|
98
111
|
#
|
|
@@ -102,6 +115,7 @@ class Line < Space
|
|
|
102
115
|
# line.ask(name: :line) #=> "Alice"
|
|
103
116
|
# line.ask(keys: :key) #=> "a"
|
|
104
117
|
# line.ask(open: [grid, :row, :name]) #=> 2 (picked row id)
|
|
118
|
+
# line.ask(nick: [:line, { initial: "bob" }]) #=> edited string
|
|
105
119
|
# line.ask([ ["Name?", :line], ["Ok?", :key] ]) #=> ["Alice", "a"]
|
|
106
120
|
|
|
107
121
|
def ask sentence
|
|
@@ -149,15 +163,25 @@ class Line < Space
|
|
|
149
163
|
return ( answer.one? ? answer.first : answer )
|
|
150
164
|
end
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
166
|
+
##
|
|
167
|
+
# Prompt for a single command line at the cursor: +prompt+ rendered in
|
|
168
|
+
# the line's default color, typed input in the answer color. TAB
|
|
169
|
+
# matches the current word against the shell (commands, options, files)
|
|
170
|
+
# and replaces it through the pick grid. Regions of the typed input
|
|
171
|
+
# matching @colors[:regex] are colored as they are typed (with the
|
|
172
|
+
# :shell scheme: the first word red, double-dash options blue).
|
|
173
|
+
#
|
|
174
|
+
# +history+ is an Array whose entries Up/Down walk; +prefixes+ lists
|
|
175
|
+
# command-start characters stripped before completion matching;
|
|
176
|
+
# +redraw+ repaints the caller's screen after a completion pick;
|
|
177
|
+
# +regex+ overrides @colors[:regex] for this prompt.
|
|
158
178
|
#
|
|
159
179
|
# line.cli "> ", initial: "cp %name"
|
|
160
180
|
# #=> "cp source.txt" (or nil on Escape)
|
|
181
|
+
# line.cli "> ", history: @recent, prefixes: "$>"
|
|
182
|
+
#
|
|
183
|
+
# @param prompt [String] rendered before the typed input
|
|
184
|
+
# @return [String, nil] the accepted command, or nil on Escape
|
|
161
185
|
|
|
162
186
|
def cli prompt, initial: '', redraw: nil, history: nil, prefixes: nil, regex: nil
|
|
163
187
|
re = /\A[#{Regexp.escape(prefixes || '|+=!')}]/
|
|
@@ -197,6 +221,16 @@ class Line < Space
|
|
|
197
221
|
|
|
198
222
|
public
|
|
199
223
|
|
|
224
|
+
##
|
|
225
|
+
# Construct a Line. Position via the {Space} boundaries; +default+ sets
|
|
226
|
+
# the text shown after {#reset}, +bindings+ the keybinding labels.
|
|
227
|
+
#
|
|
228
|
+
# line = Typr::Line.new(top: -1, left: 0,
|
|
229
|
+
# default: "(f)ilter, (s)ort, [esc] quit")
|
|
230
|
+
#
|
|
231
|
+
# @param args [Hash] boundary and display options (see {Space#initialize})
|
|
232
|
+
# @return [Line]
|
|
233
|
+
|
|
200
234
|
def initialize args
|
|
201
235
|
@right= -1
|
|
202
236
|
@align = :left
|
data/lib/space.rb
CHANGED
|
@@ -16,7 +16,21 @@ class Space
|
|
|
16
16
|
include Typr
|
|
17
17
|
|
|
18
18
|
%w[left top right bottom].each{ |name| method = <<STR
|
|
19
|
-
|
|
19
|
+
##
|
|
20
|
+
# Raw +%s+ value as passed to the constructor, before resolution
|
|
21
|
+
# (nil, Integer, Float, or Proc). Useful for introspection.
|
|
22
|
+
#
|
|
23
|
+
# space.%s_setting #=> 0
|
|
24
|
+
#
|
|
25
|
+
def %s_setting; @%s; end
|
|
26
|
+
##
|
|
27
|
+
# Resolved +%s+ edge: Integer (absolute position), Float (fraction of
|
|
28
|
+
# the terminal), Proc (evaluated each layout pass), or a negative
|
|
29
|
+
# Integer (offset from the opposite edge, -1 = last). Defaults to the
|
|
30
|
+
# widget's natural extent when unset.
|
|
31
|
+
#
|
|
32
|
+
# space.%s #=> 4
|
|
33
|
+
#
|
|
20
34
|
def %s
|
|
21
35
|
max=Typr.#{ name[/left|right/] ? 'width' : 'height' }
|
|
22
36
|
if !@%s;[max, #{ case name
|
|
@@ -30,9 +44,18 @@ else 'top+[(respond_to?(:rows) ? rows : 0), 1].max-1+(respond_to?(:headspace) ?
|
|
|
30
44
|
else @%s end
|
|
31
45
|
end
|
|
32
46
|
STR
|
|
33
|
-
eval( method % ([name]*
|
|
47
|
+
eval( method % ([name]*15) ) }
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Deep-convert String keys to Symbols so string-keyed config (e.g. YAML)
|
|
51
|
+
# is honored. Non-Hash values pass through untouched.
|
|
52
|
+
#
|
|
53
|
+
# space.symbolize "colors" => { "border" => :red }
|
|
54
|
+
# #=> {colors: {border: :red}}
|
|
55
|
+
#
|
|
56
|
+
# @param obj [Object] hash or value to convert
|
|
57
|
+
# @return [Object] converted copy, or the value itself when not a Hash
|
|
34
58
|
|
|
35
|
-
# Deep-convert String keys to Symbols so string-keyed config (e.g. YAML) is honored.
|
|
36
59
|
def symbolize obj
|
|
37
60
|
case obj
|
|
38
61
|
when Hash
|
|
@@ -41,12 +64,44 @@ eval( method % ([name]*11) ) }
|
|
|
41
64
|
else obj end
|
|
42
65
|
end
|
|
43
66
|
|
|
67
|
+
##
|
|
68
|
+
# Viewport edges, readable and assignable. Each accepts an Integer
|
|
69
|
+
# (absolute position), a Float (fraction of the terminal), a Proc
|
|
70
|
+
# (evaluated each layout pass), or a negative Integer (offset from the
|
|
71
|
+
# opposite edge, -1 = last row/column); unset edges fall back to the
|
|
72
|
+
# widget's natural extent. +left_setting+ and friends return the raw
|
|
73
|
+
# constructor value before resolution.
|
|
74
|
+
#
|
|
75
|
+
# space.left = 0
|
|
76
|
+
# space.right = 0.5
|
|
77
|
+
# space.bottom = -1
|
|
78
|
+
# space.right_setting #=> 0.5
|
|
44
79
|
attr_writer :left, :top, :right, :bottom
|
|
80
|
+
##
|
|
81
|
+
# +margin+ is the left-edge padding string, +colors+ the per-part color
|
|
82
|
+
# hash, +interval+ the redraw period in seconds for {#start}, and
|
|
83
|
+
# +borders+ the raw edge characters (prefer {#border=}).
|
|
84
|
+
#
|
|
85
|
+
# space.margin = " "
|
|
86
|
+
# space.interval = 2
|
|
45
87
|
attr_accessor :margin, :colors, :interval, :borders
|
|
46
88
|
|
|
89
|
+
##
|
|
47
90
|
# Viewport width in cells (right - left + 1).
|
|
91
|
+
#
|
|
92
|
+
# space.width #=> 80
|
|
93
|
+
#
|
|
94
|
+
# @return [Integer]
|
|
95
|
+
|
|
48
96
|
def width; right - left + 1 end
|
|
97
|
+
|
|
98
|
+
##
|
|
49
99
|
# Viewport height in cells (bottom - top + 1).
|
|
100
|
+
#
|
|
101
|
+
# space.height #=> 24
|
|
102
|
+
#
|
|
103
|
+
# @return [Integer]
|
|
104
|
+
|
|
50
105
|
def height; bottom - top + 1 end
|
|
51
106
|
|
|
52
107
|
##
|
|
@@ -60,7 +115,7 @@ eval( method % ([name]*11) ) }
|
|
|
60
115
|
def data_at x, y; nil end
|
|
61
116
|
|
|
62
117
|
##
|
|
63
|
-
# The trigger key for a binding label like "[h]elp" or "[/]:
|
|
118
|
+
# The trigger key for a binding label like "[h]elp" or "[/]:narrow", or nil
|
|
64
119
|
# for named keys ("[up]", "[esc]") which stay keyboard-only.
|
|
65
120
|
#
|
|
66
121
|
# line.extract_key "[R]ecurse" # => "R"
|
|
@@ -70,18 +125,48 @@ eval( method % ([name]*11) ) }
|
|
|
70
125
|
key if key and key.size == 1
|
|
71
126
|
end
|
|
72
127
|
|
|
128
|
+
##
|
|
73
129
|
# Redraw every +interval+ seconds from a background thread.
|
|
130
|
+
#
|
|
131
|
+
# space.interval = 1
|
|
132
|
+
# space.start #=> #<Thread>
|
|
133
|
+
# space.stop # terminates it
|
|
134
|
+
#
|
|
135
|
+
# @return [Thread]
|
|
136
|
+
|
|
74
137
|
def start; @updater = Thread.new{ loop{show; sleep @interval }} end
|
|
75
|
-
|
|
138
|
+
|
|
139
|
+
##
|
|
140
|
+
# Stop the background refresh thread started by {#start}.
|
|
141
|
+
#
|
|
142
|
+
# space.stop #=> #<Thread> (terminated)
|
|
143
|
+
#
|
|
144
|
+
# @return [Thread]
|
|
145
|
+
|
|
76
146
|
def stop; @updater.terminate end
|
|
77
147
|
|
|
148
|
+
##
|
|
78
149
|
# Draw a border char at screen (x, y); skips nil and off-screen positions.
|
|
150
|
+
#
|
|
151
|
+
# space.draw_border 0, 0, "┌" #=> nil
|
|
152
|
+
#
|
|
153
|
+
# @param x [Integer] column (0-based)
|
|
154
|
+
# @param y [Integer] row (0-based)
|
|
155
|
+
# @param char [String, nil] character to draw
|
|
156
|
+
# @return [nil]
|
|
157
|
+
|
|
79
158
|
def draw_border x,y,char
|
|
80
159
|
draw move_code(x, y) + char unless not char or
|
|
81
160
|
x < 0 or x > Typr.width or y < 0 or y > Typr.height
|
|
82
161
|
end
|
|
83
162
|
|
|
163
|
+
##
|
|
84
164
|
# Draw the widget's border box (no-op when no borders are set).
|
|
165
|
+
#
|
|
166
|
+
# space.show #=> nil
|
|
167
|
+
#
|
|
168
|
+
# @return [nil]
|
|
169
|
+
|
|
85
170
|
def show
|
|
86
171
|
return if @borders.empty?
|
|
87
172
|
color @colors[:border]
|
|
@@ -97,8 +182,19 @@ eval( method % ([name]*11) ) }
|
|
|
97
182
|
draw_border(right + 1, bottom + 1, @borders[:bottom_right])
|
|
98
183
|
end
|
|
99
184
|
|
|
100
|
-
|
|
101
|
-
#
|
|
185
|
+
##
|
|
186
|
+
# Set border style: +:light+/:heavy+/:double+/:round+, a single char
|
|
187
|
+
# repeated across the box, an 8-char string (top, bottom, left, right,
|
|
188
|
+
# top-left, top-right, bottom-left, bottom-right), or nil for none.
|
|
189
|
+
#
|
|
190
|
+
# space.border = :round
|
|
191
|
+
# space.border = "─" # one char for every edge
|
|
192
|
+
# space.border = "──││┌┐└┘" # explicit edges, same as :light
|
|
193
|
+
# space.border = nil # remove the border
|
|
194
|
+
#
|
|
195
|
+
# @param border [Symbol, String, nil]
|
|
196
|
+
# @return [void]
|
|
197
|
+
|
|
102
198
|
def border=(border)
|
|
103
199
|
chars = case border
|
|
104
200
|
when Symbol
|
|
@@ -111,8 +207,17 @@ eval( method % ([name]*11) ) }
|
|
|
111
207
|
bottom_right ].map.with_index{ |part,id| [part.to_sym, chars[id]] }.to_h if chars
|
|
112
208
|
end
|
|
113
209
|
|
|
210
|
+
##
|
|
114
211
|
# Build a widget from boundary (left/top/right/bottom), border, margin,
|
|
115
|
-
# colors, interval, and keymap.
|
|
212
|
+
# colors, interval, and keymap. Every keyword becomes an instance
|
|
213
|
+
# variable (+border:+ is applied through {#border=}).
|
|
214
|
+
#
|
|
215
|
+
# box = Typr::Space.new(left: 0, top: 2, right: 0.5, bottom: -4,
|
|
216
|
+
# border: :round, colors: { border: :grey50 }, interval: 2)
|
|
217
|
+
#
|
|
218
|
+
# @param args [Hash] boundary and visual options (see the class example)
|
|
219
|
+
# @return [Space]
|
|
220
|
+
|
|
116
221
|
def initialize args={}
|
|
117
222
|
@max, @keymap, @colors, @margin = 0, {}, {}, ' '
|
|
118
223
|
@left, @top, @interval, @borders = 0, 0, 1, {}
|