typr 1.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75b5f8bf6bcd9c1e957f87f8ef47eaec83784cefcc3903ea9e68b25f0280d7af
4
- data.tar.gz: 1e92d998f96105408ac13e6c43f49ae2a44cdf0b2e6e34596f55749228beff29
3
+ metadata.gz: 74b0a2cd082b9a2ee58f9fb64356a7ed4f5a970c0427768acae334ae3325748a
4
+ data.tar.gz: af90bbcfff025ba60f1d8de7cae5171152f8939a00a7fac5975cc95785a28547
5
5
  SHA512:
6
- metadata.gz: 3fb8fb7f24580c81250951138b3600037ce3eef21acc48a17c79db4c3fe5cceddd8037e1cb04548d85d2fde048426fa90c08846b09b9c8b2ad06472106813058
7
- data.tar.gz: 83f72c57b64e9515c27ddea087bdce0ad2d935e98b697a7e8b5d0de9885aa9dab18f15143437e4b8f0136899d5a6b08c5b7712dbd8f5b8d2f41ab56cb8c5d598
6
+ metadata.gz: c29e087834938ef34608086cb90ec221760ca50f0be054e7d9602f46cc6e63c73fbe06a960c8b55e551b17159f404d796f275e3a11cddcb3f70baccefc67636b
7
+ data.tar.gz: a33662ba4009f34cef2e91b5295a6fc1e3f2b17ebd9edf118052caa016e6dc0d8dd4d6f2c3fb468244f2e488e589c1ec79236f2a7001bc4e6622064fcf9a3365
data/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
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
+
17
+ ## [v1.8.0] — 2026-10-11
18
+
19
+ ### Changed
20
+ - Sort indicator uses solid triangles (▲▼) instead of thin arrows, for a heavier mark at the same single-cell width
21
+
22
+ ### Fixed
23
+ - Text paints new spans once at entry and rebuilds only reflow: scheme and content colors survive resizes and appends without stacking or stripping
24
+ - Terminal self-test renders without scrolling and covers all named colors — status line uses raw output on the last row (`p` scrolled with inspect quotes and a newline), rows are capped to the visible area, the 256-ramp starts at a known position, the color list includes all `COLOR_MAP` names plus the full grey ramp, and the fg/bg steppers clamp to valid ids
25
+ - String headers fit inside minimal-width boxes — the box ignored the margin in the header width, so the border overwrote the last header character; single `:max` columns provably span the widget width
26
+ - Word jumps are documented as alt-arrow, not ctrl-arrow — README, the `read_line` doc comment, and the v1.2.0 entry called them ctrl-arrow while the bindings are `KEY_ALT_LEFT`/`KEY_ALT_RIGHT` (SGR modifier 3 = alt); the package's key modifier is alt everywhere
27
+ - Command-line redraw overwrites in place — `read_line` cleared the row on every keypress, flashing on cursor moves; each frame now pads to end-of-line with spaces instead, so shrinking content leaves no ghosts either
28
+ - Cursor stays hidden while the command line redraws — every keypress parked the visible cursor at the prompt between the home move and the edit-position move; each frame now hides it first and shows it at the edit position
29
+ - Mode table corrected to standard SGR order (`dim` restored, so `slow→5` and `invert→7`) with `:steady` at position 25 for blink-off; `mode_code` stays index-by-position
30
+
5
31
  ## [v1.7.0] — 2026-10-02
6
32
 
7
33
  ### Fixed
@@ -100,7 +126,7 @@
100
126
  ## [v1.2.0] — 2026-08-02
101
127
 
102
128
  ### Added
103
- - `Typr.read_key` / `Typr.read_line` — the public terminal input API: `read_key` reads a raw key; `read_line` is the shared interactive line editor that renders a prompt + query at any position with arrow keys, ctrl-arrow word jumps, Home/End/Delete/Backspace, Escape aborts (`nil`), Enter confirms, and an optional block runs after each keypress
129
+ - `Typr.read_key` / `Typr.read_line` — the public terminal input API: `read_key` reads a raw key; `read_line` is the shared interactive line editor that renders a prompt + query at any position with arrow keys, alt-arrow word jumps, Home/End/Delete/Backspace, Escape aborts (`nil`), Enter confirms, and an optional block runs after each keypress
104
130
  - `Line#ask` strict question types — `Array` (pick from an object/column), `:key`, `:line`; the question and answer are rendered in `@colors[:question]`/`@colors[:answer]`
105
131
  - `Line#align` attribute (`:left`/`:right`) for text alignment in draw
106
132
  - `dark_*` named colors (`dark_red`, `dark_green`, `dark_yellow`, `dark_blue`, `dark_magenta`, `dark_cyan`) and a `COLOR_MAP` of popular named colors in `color_code`
data/README.md CHANGED
@@ -185,7 +185,7 @@ Clears the full screen or a single line (`:line`).
185
185
  Reads a single keypress in raw mode. Returns the key as a String (`KEY_ESCAPE`, `?q`, arrow escape sequences), a `Typr::Mouse` struct for mouse reports, or `nil` when input is unavailable.
186
186
 
187
187
  ### `Typr.read_line(prompt, ...)`
188
- The shared interactive line editor. Renders a prompt and query at any position with arrow keys, ctrl-arrow word jumps, Home/End/Delete/Backspace; Enter confirms, Escape aborts (`nil`). An optional block runs after each keypress and may short-circuit with a result. Pass `completions:` — a callable `->(query, cursor) { candidates }` — and TAB matches the current word against `candidates` in a pick grid: a candidate is either a string or `[text, color-pair]` to tint its pick entry. Pass `regex:` — a Hash of `Regexp => color`, or a scheme name (`:generic`, `:shell`) — and matching regions of the query are colored as they are typed, restoring the prompt's trailing color code. The editor is agnostic to where candidates come from.
188
+ The shared interactive line editor. Renders a prompt and query at any position with arrow keys, alt-arrow word jumps, Home/End/Delete/Backspace; Enter confirms, Escape aborts (`nil`). An optional block runs after each keypress and may short-circuit with a result. Pass `completions:` — a callable `->(query, cursor) { candidates }` — and TAB matches the current word against `candidates` in a pick grid: a candidate is either a string or `[text, color-pair]` to tint its pick entry. Pass `regex:` — a Hash of `Regexp => color`, or a scheme name (`:generic`, `:shell`) — and matching regions of the query are colored as they are typed, restoring the prompt's trailing color code. The editor is agnostic to where candidates come from.
189
189
 
190
190
  ```ruby
191
191
  Typr.read_line "/", completions: ->(query, cursor) {
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
- ## Height minus 2 rows reserved for path bar and command line.
54
+ ##
55
+ # Bottom edge pulled in one row for the command line (the path bar is
56
+ # reserved by {#top}).
57
+ #
58
+ # browser.bottom #=> terminal_bottom - 1
59
+ #
60
+ # @return [Integer]
40
61
 
41
62
  def bottom; super - 1 end
42
63
 
43
- ## Top offset plus one row for the path info bar.
64
+ ##
65
+ # Top edge pushed down one row for the path info bar.
66
+ #
67
+ # browser.top #=> constructor top + 1
68
+ #
69
+ # @return [Integer]
44
70
 
45
71
  def top; super + 1 end
46
72
 
@@ -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
- # Resolves hint characters typed on a row. Returns the matching row id
180
- # for fast navigation, otherwise falls through to Stack#send.
210
+ # Resolve hint characters typed on a row to a row id; a click on the
211
+ # info bar or command line dispatches that binding instead. Falls
212
+ # through to {Stack#send} for keys and navigation.
213
+ #
214
+ # browser.send "1" # => row id if hint "1" is visible
215
+ # browser.send ?s # => runs the sort command (a bound key)
181
216
  #
182
- # browser.send "1" # => row id if hint "1" is visible
217
+ # @param key [Typr::Mouse, String] mouse event, hint char, or key sequence
218
+ # @return [Integer, String, Symbol, nil] picked row id, bound command result, or super's response
183
219
 
184
220
  def send key
185
221
  if key.is_a?(Typr::Mouse) and key.press? and key.left?
@@ -200,19 +236,28 @@ module Typr
200
236
  else super end
201
237
  end
202
238
 
203
- ## Returns the current directory path as the confirm result.
239
+ ##
240
+ # Returns the current directory path as the confirm result.
241
+ #
242
+ # browser.confirm #=> "/tmp/"
243
+ #
244
+ # @return [String]
204
245
 
205
246
  def confirm; return @directory end
206
247
 
207
248
  ## Prompts to select rows by index. Returns chosen ids or nil.
208
249
  #
209
250
  # browser.mark # => [0, 2, 5] or nil
251
+ #
252
+ # @return [Array<Integer>, nil]
210
253
 
211
254
  def mark; return @user.ask( 'mark files': [self, :rows] ) end
212
255
 
213
256
  ## Toggles selection for every row within a "from..to" range.
214
257
  #
215
258
  # browser.mark_range # prompts for from/to, toggles selection
259
+ #
260
+ # @return [Array<Integer>] 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 `/` search filtered on the name column.
340
+ ## Open live `/` narrowing filtered on the name column.
280
341
  #
281
- # browser.search # type to narrow, Escape restores, Enter commits
342
+ # browser.narrow # type to narrow, Escape restores, Enter commits
343
+ #
344
+ # @return [void]
282
345
 
283
- def search
346
+ def narrow
284
347
  @suppress_user = true
285
348
  pick :none, column: NAME
286
349
  @suppress_user = false
287
350
  end
351
+ alias search narrow
288
352
 
289
353
  ## Open a sort-picker popup to choose a column to sort by.
290
354
  #
291
355
  # browser.sort_view # pick a column header to sort
356
+ #
357
+ # @return [void]
292
358
 
293
359
  def sort_view; sort_by @user.ask( "sort by": [ popup( type: :"[s]ort" ), :row, nil, { column: 0 }] ) end
294
360
 
295
361
  ## Open a view-picker popup and switch to the selected view.
296
362
  #
297
363
  # browser.views # choose :simple, :compact, :stats, or :full
364
+ #
365
+ # @return [void]
298
366
 
299
367
  def views; change_view( popup( type: :"[v]iews", input: @views.keys ).pick( :row, column: 0 ) ) end
300
368
 
@@ -503,7 +571,7 @@ full: %i[ name size permissions executable printable owner group
503
571
  input: [ [->{Etc.getpwuid(Process.euid).name rescue ENV["USER"] || Etc.getlogin}, ->{@parent.directory},
504
572
  ->{@parent.filter_display},
505
573
  ->{ (s = @parent.sorted_by(true)) &&
506
- "#{s} #{@parent.reverse ? "\u2193" : "\u2191"}"},
574
+ "#{s} #{@parent.reverse ? "\u25BC" : "\u25B2"}"},
507
575
  ->{@parent.view} ] ], format: [:min, :max, :min, :min, :min],
508
576
  colors: { columns: [@colors[:default], @colors[:default],
509
577
  @colors[:actions][:filter], @colors[:actions][:sort],
@@ -549,21 +617,20 @@ full: %i[ name size permissions executable printable owner group
549
617
  wire.(label, cmd)
550
618
  end
551
619
  @user ||= Line.new( parent:self, left:->{@parent.left}, top:->{@parent.bottom+1},
552
- colors: { regex: @colors[:regex] }, bindings: bindings_labels )
620
+ bindings: bindings_labels )
553
621
  else
554
- cmds = { "[h]elp" => :help, "[/]:search" => :search,
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", "[/]:search"] +
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\ ]
564
632
  plain.each { |label| wire.(label, cmds[label]) if cmds[label] }
565
633
  @user ||= Line.new( parent:self, left:->{@parent.left}, top:->{@parent.bottom+1},
566
- colors: { regex: @colors[:regex] },
567
634
  bindings: plain.map { |label|
568
635
  name = label.gsub( /[\[\]]/, '' ).downcase.to_sym
569
636
  @colors[:actions][name] ?
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 #=> [0, 1, 2]
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 +UPCASE_I+ constants.
140
+ # Set column headers. Symbols are auto-defined as +UPCASE+ constants
141
+ # holding the column index.
119
142
  #
120
- # grid.header = [:name, :age]
121
- # # defines NAME_0 = "name", AGE_1 = "age"
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-filtering.
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
- # When column is given, `Grid` overrides `Stack#pick` to add interactive
246
- # text filtering of rows by the given column. Filter text is shown at the
247
- # bottom of the grid. Passes through to `super` for all other types or
248
- # when no column is given.
273
+ # With no +column+, passes through to {Stack#pick}.
249
274
  #
250
- # grid.pick :row, 0, column: :name
251
- # grid.pick :row, 0, column: :all # match any column
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 is ignored (always :row); passed to super otherwise
254
- # @param column [Symbol, String] column name to filter on, or :all (default nil)
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
- # Sort by column (toggle direction on repeat).
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.sort_by :age
314
- # grid.sort_by :age # reverses
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 column [Integer, Symbol]
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
@@ -411,7 +451,7 @@ module Typr
411
451
  gaps = @margin.size + @separator.size * (columns - 1)
412
452
  sum = @widths.sum
413
453
  @max = sum + gaps - 1
414
- @max = [ @max, @header.length ].max if @header.is_a?(String)
454
+ @max = [ @max, @header.length + @margin.size ].max if @header.is_a?(String)
415
455
  space = width - gaps
416
456
  if (extra = sum - space ) > 0
417
457
  extra.times{ |i| @widths[ @widths.index(@widths.max) ] -= 1 }
@@ -434,7 +474,7 @@ module Typr
434
474
  def print row=nil
435
475
  return super unless row
436
476
  header = ( row == :header )
437
- return draw @header[0..width-1].ljust(width) if
477
+ return draw @header[0...(width - @margin.size)].ljust(width - @margin.size) if
438
478
  header and @header.is_a? String
439
479
  return unless row_data = header ? @header : @data[row]
440
480
  fields = row_data.dup
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,23 +163,33 @@ class Line < Space
149
163
  return ( answer.one? ? answer.first : answer )
150
164
  end
151
165
 
152
- # Prompt for a single command line at the cursor: +prompt+ rendered in the
153
- # line's default color, typed input in the answer color. When +completions+
154
- # is truthy, TAB matches the current word against the shell (commands,
155
- # options, files) and replaces it through the pick grid. Regions of the
156
- # typed input matching @colors[:regex] are colored as they are typed
157
- # (with the :shell scheme: the first word red, double-dash options blue).
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
- def cli prompt, initial: '', redraw: nil, history: nil, prefixes: nil
186
+ def cli prompt, initial: '', redraw: nil, history: nil, prefixes: nil, regex: nil
163
187
  re = /\A[#{Regexp.escape(prefixes || '|+=!')}]/
164
188
  source = ->(query) {
165
189
  bash_candidates(query.sub(re, '')).split("\n").reject(&:empty?).uniq }
166
190
  colored = color_code(@colors[:default]).to_s + prompt + color_code(@colors[:answer])
167
191
  Typr.read_line(colored, left: left, top: top, initial: initial,
168
- completions: source, redraw: redraw, regex: @colors[:regex], history: history)
192
+ completions: source, redraw: redraw, regex: regex || @colors[:regex], history: history)
169
193
  end
170
194
 
171
195
  private
@@ -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