typr 1.8.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/line.rb CHANGED
@@ -12,6 +12,17 @@ module Typr
12
12
  # line.ask([ ["Name?", :line], ["Age?", :line] ])
13
13
 
14
14
  class Line < Space
15
+ ##
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,
20
+ # +trim+ (:left/:right) the side clipped on overflow.
21
+ #
22
+ # line.default = "status: ok"
23
+ # line.prompt = "?"
24
+ # line.align = :left
25
+
15
26
  attr_accessor :default, :prompt, :bindings, :data, :align, :trim
16
27
 
17
28
  # Render +msg+ (String, Proc, or Space) on this line.
@@ -36,7 +47,7 @@ class Line < Space
36
47
  # line.append " world" #=> "hello world"
37
48
  # line << "!" #=> "hello world!"
38
49
 
39
- def append(str); show @data+str end
50
+ def append(str); show @data+str; @data end
40
51
  alias :<< :append
41
52
 
42
53
  # The keybinding list as a clickable grid of the line's own bindings. It
@@ -92,16 +103,26 @@ class Line < Space
92
103
  # Array - interactive pick via <widget>.pick(<type>); an optional third
93
104
  # element renders the chosen row's field into the line, and an
94
105
  # optional fourth element is passed as pick keyword arguments
95
- # (e.g. { column: 0 } to open the pick in / search mode)
106
+ # (e.g. { column: 0 } to open the pick in / search mode).
107
+ # Special case: [:line, { initial: "text" }] opens the inline
108
+ # editor via Typr.read_line prefilled with +initial+ instead
109
+ # of a widget pick.
96
110
  # :key - a single keypress via Typr.read_key
97
111
  # :line - interactive line editor via Typr.read_line at the cursor
98
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
+ #
99
118
  # Pressing the exit key (@keymap[:exit], Escape by default) aborts the
100
119
  # remaining questions and returns nil.
101
120
  #
102
121
  # line.ask(name: :line) #=> "Alice"
103
122
  # line.ask(keys: :key) #=> "a"
104
123
  # line.ask(open: [grid, :row, :name]) #=> 2 (picked row id)
124
+ # line.ask(nick: [:line, { initial: "bob" }]) #=> edited string
125
+ # line.ask(pick: [grid, :row], with: :line) #=> [2, "typed text"]
105
126
  # line.ask([ ["Name?", :line], ["Ok?", :key] ]) #=> ["Alice", "a"]
106
127
 
107
128
  def ask sentence
@@ -149,15 +170,26 @@ class Line < Space
149
170
  return ( answer.one? ? answer.first : answer )
150
171
  end
151
172
 
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).
173
+ ##
174
+ # Prompt for a single command line at the cursor: +prompt+ rendered in
175
+ # the line's default color, typed input in the answer color. TAB
176
+ # matches the current word against the shell (commands, options, files)
177
+ # and replaces it through the pick grid. Regions of the typed input
178
+ # matching @colors[:regex] are colored as they are typed (with the
179
+ # :shell scheme: the first word red, double-dash options blue).
180
+ #
181
+ # +history+ is an Array whose entries Up/Down walk; +prefixes+ lists
182
+ # command-start characters stripped before completion matching;
183
+ # +redraw+ repaints the caller's screen after a completion pick;
184
+ # +regex+ overrides @colors[:regex] for this prompt.
158
185
  #
159
186
  # line.cli "> ", initial: "cp %name"
160
187
  # #=> "cp source.txt" (or nil on Escape)
188
+ # line.cli "> ", history: @recent, prefixes: "$>"
189
+ #
190
+ # @param prompt [String] rendered before the typed input
191
+ # @param initial [String] prefilled input
192
+ # @return [String, nil] the accepted command, or nil on Escape
161
193
 
162
194
  def cli prompt, initial: '', redraw: nil, history: nil, prefixes: nil, regex: nil
163
195
  re = /\A[#{Regexp.escape(prefixes || '|+=!')}]/
@@ -197,6 +229,16 @@ class Line < Space
197
229
 
198
230
  public
199
231
 
232
+ ##
233
+ # Construct a Line. Position via the {Space} boundaries; +default+ sets
234
+ # the text shown after {#reset}, +bindings+ the keybinding labels.
235
+ #
236
+ # line = Typr::Line.new(top: -1, left: 0,
237
+ # default: "(f)ilter, (s)ort, [esc] quit")
238
+ #
239
+ # @param args [Hash] boundary and display options (see {Space#initialize})
240
+ # @return [Line]
241
+
200
242
  def initialize args
201
243
  @right= -1
202
244
  @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
- def %s_setting; @%s; end
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]*11) ) }
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 "[/]:search", or nil
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
- # Stop the background refresh thread.
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
- # Set border style: :light/:heavy/:double/:round, a char repeated across
101
- # the box, or nil for none.
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, {}
data/lib/stack.rb CHANGED
@@ -17,10 +17,25 @@ module Typr
17
17
  # KEY_HOME / KEY_END (jump to first or last pageable row)
18
18
 
19
19
  class Stack < Space
20
- ## Widget configuration. +left+, +top+, +right+, +bottom+ set the viewport; see {Space}.
21
- ## +alternate+ toggles striped rows (+:grey10+ background alternating with the default bg).
20
+ ##
21
+ # Widget configuration and state: +left+/+top+/+right+/+bottom+ set
22
+ # the viewport (see {Space}), +alternate+ toggles striped rows
23
+ # (+:grey10+ against the default bg), +start+ is the scroll offset,
24
+ # +keymap+ maps key sequences to methods, +selected+ holds picked ids
25
+ # per type, and +header+ is the top-bar label.
26
+ #
27
+ # stack.start = 3
28
+ # stack.header = "Title"
22
29
 
23
30
  attr_accessor :start, :keymap, :selected, :header
31
+ ##
32
+ # +separator+ is the gap between columns, +hints+ the hint characters,
33
+ # +hints_start+ the offset of the current hint chunk ({#cycle}), and
34
+ # +modifier+ is +:alt+ after an Alt-digit hint press, +nil+ otherwise.
35
+ #
36
+ # stack.hints = "1234567890"
37
+ # stack.modifier #=> :alt (after picking with Alt+digit)
38
+
24
39
  attr_accessor :separator, :hints, :hints_start, :modifier
25
40
 
26
41
  ##
@@ -80,7 +95,9 @@ module Typr
80
95
  # stack.reset # resets position and selection
81
96
  # stack.reset :position # only scrolls back to top
82
97
  # stack.reset :selection # clears all selected rows/columns/fields
83
- # stack.reset [:display, :format]
98
+ # stack.reset [:position, :selection]
99
+ #
100
+ # @param type [:all, :position, :selection, Array<Symbol>] anything else is ignored
84
101
 
85
102
  def reset type=:all
86
103
  case type
@@ -150,10 +167,16 @@ module Typr
150
167
 
151
168
  ##
152
169
  # Dispatches a key from the terminal to its semantic action and clamps the current page
153
- # offset.
170
+ # offset. Unknown keys only clamp. +confirm+ dispatches to a +confirm+
171
+ # method, which only Browser defines.
172
+ #
173
+ # stack.send Typr::KEY_DOWN # scrolls down one row
154
174
  #
155
- # stack.send Typr::KEY_UP # scrolls up one row
156
- # stack.send Typr::KEY_RETURN # confirms selection
175
+ # Mouse: wheel scrolls (+:scroll+), a click that misses is +:outside+,
176
+ # a left/right press on a row returns its id.
177
+ #
178
+ # @param key [String, Typr::Mouse] key sequence or mouse event
179
+ # @return [Object] the keymap action's result; +:scroll+/:outside+/row id for mouse
157
180
 
158
181
  def send key#, map=nil
159
182
  if key.is_a?(Typr::Mouse)
@@ -174,63 +197,100 @@ module Typr
174
197
  return response
175
198
  end
176
199
 
177
- ## Scroll to the top of the dataset.
200
+ ##
201
+ # Scroll to the top of the dataset.
178
202
  #
179
- # stack.to_top
203
+ # stack.to_top # @start => 0
204
+ #
205
+ # @return [nil]
180
206
 
181
207
  def to_top; @start = 0; return end
182
208
 
183
- ## Scroll to the bottom pageable position (last row minus viewport height).
209
+ ##
210
+ # Scroll to the bottom pageable position (last row minus viewport height).
184
211
  #
185
- # stack.to_bottom
212
+ # stack.to_bottom # @start => rows - height
213
+ #
214
+ # @return [nil]
186
215
 
187
216
  def to_bottom; @start = rows - height; return end
188
217
 
189
- ## Step down by one row.
218
+ ##
219
+ # Step down by one row.
220
+ #
221
+ # stack.down # @start += 1
190
222
  #
191
- # stack.down
223
+ # @return [nil]
192
224
 
193
225
  def down; @start += 1; return end
194
226
 
195
- ## Step up by one row.
227
+ ##
228
+ # Step up by one row.
229
+ #
230
+ # stack.up # @start -= 1
196
231
  #
197
- # stack.up
232
+ # @return [nil]
198
233
 
199
234
  def up; @start -= 1; return end
200
235
 
201
- ## Jump down by one viewport of rows.
236
+ ##
237
+ # Jump down by one viewport of rows.
238
+ #
239
+ # stack.page_down # @start += height
202
240
  #
203
- # stack.page_down
241
+ # @return [nil]
204
242
 
205
243
  def page_down; @start += height; return end
206
244
 
207
- ## Jump up by one viewport of rows.
245
+ ##
246
+ # Jump up by one viewport of rows.
247
+ #
248
+ # stack.page_up # @start -= height
208
249
  #
209
- # stack.page_up
250
+ # @return [nil]
210
251
 
211
252
  def page_up; @start -= height; return end
212
253
 
213
254
  ##
214
- # Blocks the calling thread and lets the user interactively pick items from the current
215
- # page using hint characters or arrow keys. After selection, the user must press Return to
216
- # confirm (or Escape to abort).
255
+ # Blocks the calling thread for an interactive pick from the current
256
+ # page with hint characters or arrow keys. Single picks return the
257
+ # moment they're made; multi (+...+s+) picks toggle until Return
258
+ # confirms the selection (Escape aborts, and confirming an empty
259
+ # selection returns nil).
217
260
  #
218
261
  # The +type+ argument controls what kind of picker is shown:
219
262
  #
220
- # Symbol/String type | Purpose
221
- # -----------------------------|-------------------------------------------
222
- # +:row, +"file"+ | Pick a single row id
223
- # +"rows"+ | Multi-select rows
224
- # +:column, +"field"+ | Nested: pick a row then a column
225
- # +:fields+ | Multi-select fields
226
- # +:none+ | Navigation-only mode
227
- # anything with +relative_| | Returns the internal integer index
263
+ # Type | Picks
264
+ # -------------------|------------------------------------------------
265
+ # +:row+ | Single row id
266
+ # +:rows+ | Multi-select row ids (Return confirms)
267
+ # +:column+ | One column of the given row (Grid: real index, Text: word offset)
268
+ # +:columns+ | Multi-select columns (Return confirms)
269
+ # +:field+ | One [row, column] pair, picked row-then-column
270
+ # +:fields+ | Multi-select [row, column] pairs
271
+ # +:relative_row+ | Viewport-absolute row index (0-based)
272
+ # +:none+ | Navigation only; always returns nil
273
+ #
274
+ # +:column+, +:columns+, +:field+ (+:fields+) and +:none+ need row
275
+ # positions, so they only work on Grid and Text — a bare Stack
276
+ # supports +:row+ and +:rows+. (The +"file"+ type belongs to
277
+ # {Browser#pick}.)
228
278
  #
229
279
  # stack.pick # single row picker
230
- # stack.pick :column, 3 # pick a column in row 3
231
- # stack.pick "rows" # multi-select rows
280
+ # stack.pick :column, 3 # column of the 4th visible row (page index, not id)
281
+ # stack.pick :rows # multi-select rows
282
+ # stack.pick :relative_row # 0-based index into the viewport
232
283
  #
233
- # Mouse: left-click a row to pick it; wheel up/down scrolls the page.
284
+ # Pressing Alt with a hint digit (ESC + digit) picks that hint row and
285
+ # sets +modifier+ to +:alt+ for the caller. The Return and Escape keys
286
+ # come from +@keymap+ (+confirm+/+exit+, Return/Escape by default).
287
+ #
288
+ # Mouse: left-click a row to pick it (row pickers only); wheel up/down
289
+ # scrolls the page; clicking outside cancels like Escape.
290
+ #
291
+ # @param type [Symbol, String] picker type (table above; symbols documented)
292
+ # @param row [Integer] page-relative row for +:column+ hints (ignored otherwise)
293
+ # @return [Integer, Array, nil] row id, [row, column], confirmed selection, or nil
234
294
 
235
295
  def pick type = :row, row=0 #, key=nil
236
296
  type = type.to_s
@@ -298,6 +358,14 @@ module Typr
298
358
  return
299
359
  end
300
360
 
361
+ ##
362
+ # Walk the hint characters backwards by one chunk (mirror of {#cycle}),
363
+ # wrapping to the last full chunk before the start of the list.
364
+ #
365
+ # stack.cycle_back # hints_start -= hints.size (wraps at 0)
366
+ #
367
+ # @return [nil]
368
+
301
369
  def cycle_back
302
370
  max = [height-1, rows-1+headspace].min
303
371
  @hints_start -= @hints.size
@@ -312,11 +380,15 @@ module Typr
312
380
  #
313
381
  # Option | Default | Description
314
382
  # ----------------|---------|-------------------------------------------
315
- # +keysyms+ | {} | Custom key-to-method mappings
316
- # +hints+ | +"1234..."+ | Characters used in the hint overlay
383
+ # +keymap+ | {} | Custom name-to-key mappings (override navigation defaults)
384
+ # +hints+ | +"1234567890"+ | Characters used in the hint overlay
317
385
  # +alternate+ | true | Striped row backgrounds
318
386
  # +header+ | nil | Label shown in a header bar
319
387
  # +separator+ | +" "+ | String drawn between columns (used by Grid)
388
+ # +colors+ | {} | Per-part colors (+hints+, +header+, +selected+, +alternate+, +rows+)
389
+ # +selected+ | {} | Pre-picked ids per type (+rows+, +columns+, +fields+)
390
+ #
391
+ # stack = Typr::Stack.new(keymap: { exit: "q" }) # quit on q instead
320
392
 
321
393
  def initialize args={}
322
394
  @keymap, @separator, @selected = {}, " ", {}