typr 1.1.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 +7 -0
- data/example/browser +45 -0
- data/example/grid +80 -0
- data/example/text +65 -0
- data/example/top500.csv +501 -0
- data/lib/browser.rb +431 -0
- data/lib/curses.rb +153 -0
- data/lib/frontend.rb +33 -0
- data/lib/graphical.rb +174 -0
- data/lib/grid.rb +431 -0
- data/lib/line.rb +99 -0
- data/lib/space.rb +85 -0
- data/lib/stack.rb +278 -0
- data/lib/terminal.rb +286 -0
- data/lib/text.rb +140 -0
- data/lib/typr.rb +55 -0
- data/share/mimetypes +1 -0
- metadata +73 -0
data/lib/space.rb
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require_relative 'terminal.rb'
|
|
2
|
+
|
|
3
|
+
module Typr
|
|
4
|
+
|
|
5
|
+
##
|
|
6
|
+
# Base layout class for all TUI widgets. Defines a viewport rectangle and border drawing.
|
|
7
|
+
#
|
|
8
|
+
# Boundaries accept integers (absolute), floats (0..1 fraction of terminal), Procs, or
|
|
9
|
+
# negative ints (offset from opposite edge: -1 = last row/column).
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# box = Space.new(left: 0, top: 2, right: 0.5, bottom: -4, border: :round)
|
|
13
|
+
# box.draw
|
|
14
|
+
|
|
15
|
+
class Space
|
|
16
|
+
include Typr
|
|
17
|
+
|
|
18
|
+
%w[left top right bottom].each{ |name| method = <<STR
|
|
19
|
+
def %s_setting; @%s; end
|
|
20
|
+
def %s
|
|
21
|
+
max=Typr.#{ name[/left|right/] ? 'width' : 'height' }
|
|
22
|
+
if !@%s;[max, #{name=='right' ? 'left+@max' :
|
|
23
|
+
'top+rows-1+headspace'}].min
|
|
24
|
+
elsif @%s.is_a? Proc; @%s.call
|
|
25
|
+
elsif @%s.is_a? Float; (max * @%s).round
|
|
26
|
+
elsif @%s.negative?; max+@%s+1
|
|
27
|
+
else @%s end
|
|
28
|
+
end
|
|
29
|
+
STR
|
|
30
|
+
eval( method % ([name]*11) ) }
|
|
31
|
+
|
|
32
|
+
attr_writer :left, :top, :right, :bottom
|
|
33
|
+
attr_accessor :margin, :colors, :interval, :borders
|
|
34
|
+
|
|
35
|
+
def width; right - left + 1 end
|
|
36
|
+
def height; bottom - top + 1 end
|
|
37
|
+
|
|
38
|
+
def start; @updater = Thread.new{ loop{draw; sleep @interval }} end
|
|
39
|
+
def stop; @updater.terminate end
|
|
40
|
+
|
|
41
|
+
def draw_border x,y,char
|
|
42
|
+
show move_code(x, y) + char unless not char or
|
|
43
|
+
x < 0 or x > Typr.width or y < 0 or y > Typr.height
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def draw
|
|
47
|
+
return if @borders.empty?
|
|
48
|
+
color @colors[:border]
|
|
49
|
+
draw_border(left - 1, top - 1, @borders[:top_left])
|
|
50
|
+
draw_border(left, top - 1, @borders[:top] * width) if @borders[:top]
|
|
51
|
+
draw_border(right + 1, top - 1, @borders[:top_right])
|
|
52
|
+
(height+1).times do |pos|
|
|
53
|
+
draw_border(left - 1, top + pos, @borders[:left] )
|
|
54
|
+
draw_border(right + 1, top + pos, @borders[:right] )
|
|
55
|
+
end
|
|
56
|
+
draw_border(left - 1, bottom + 1, @borders[:bottom_left] )
|
|
57
|
+
draw_border(left, bottom + 1, @borders[:bottom] * width ) if @borders[:bottom]
|
|
58
|
+
draw_border(right + 1, bottom + 1, @borders[:bottom_right])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def border=(border)
|
|
62
|
+
chars = case border
|
|
63
|
+
when Symbol
|
|
64
|
+
{ light: '──││┌┐└┘', heavy: '━━┃┃┏┓┗┛', double: '══║║╔╗╚╝',
|
|
65
|
+
round: '──││╭╮╰╯' }[border]
|
|
66
|
+
when String; if border.size == 1 then border * 8 else border.chars end
|
|
67
|
+
when nil; [nil] * 8
|
|
68
|
+
end
|
|
69
|
+
@borders = %w[top bottom left right top_left top_right bottom_left
|
|
70
|
+
bottom_right ].map.with_index{ |part,id| [part.to_sym, chars[id]] }.to_h if chars
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize args={}
|
|
74
|
+
@max, @keymap, @colors, @margin = 0, {}, {}, ' '
|
|
75
|
+
@left, @top, @interval, @borders = 0, 0, 1, {}
|
|
76
|
+
args.each{ |name, value|
|
|
77
|
+
instance_variable_set ?@ + name.to_s, value }
|
|
78
|
+
@keymap = { exit: KEY_ESCAPE }.merge @keymap
|
|
79
|
+
@colors = { default: [:white, :black], border: [:white, :black] }.merge(@colors)
|
|
80
|
+
borders = @borders.dup
|
|
81
|
+
self.border = @border
|
|
82
|
+
@borders.merge! borders
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/stack.rb
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
module Typr
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# A vertically scrollable list of items displayed one per row or column.
|
|
9
|
+
#
|
|
10
|
+
# Provides paging, keyboard navigation, key-hint overlays, and alternating
|
|
11
|
+
# row colors. Subclasses Grid, Text, and Browser add domain-specific
|
|
12
|
+
# rendering on top of this foundation.
|
|
13
|
+
#
|
|
14
|
+
# == Key bindings (overridable via +keymap+):
|
|
15
|
+
#
|
|
16
|
+
# KEY_TAB -> cycle (advance hint numbering to the next chunk)
|
|
17
|
+
# KEY_RETURN -> confirm (returns currently selected items, or nil)
|
|
18
|
+
# KEY_ESCAPE -> exit (abort selection and return nil)
|
|
19
|
+
# KEY_UP / KEY_DOWN (step by one row in the given direction)
|
|
20
|
+
# KEY_PAGEUP / KEY_PAGEDOWN (jump by one viewport height)
|
|
21
|
+
# KEY_HOME / KEY_END (jump to first or last pageable row)
|
|
22
|
+
|
|
23
|
+
class Stack < Space
|
|
24
|
+
## Widget configuration. +left+, +top+, +right+, +bottom+ set the viewport; see {Space}.
|
|
25
|
+
## +alternate+ toggles striped rows (+:grey10+ background alternating with the default bg).
|
|
26
|
+
|
|
27
|
+
attr_accessor :start, :keymap, :selected, :header
|
|
28
|
+
attr_accessor :separator, :hints, :hints_start
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Returns a +Range+ of internal data indices visible on the current page.
|
|
32
|
+
#
|
|
33
|
+
# stack.page #=> 0..14
|
|
34
|
+
|
|
35
|
+
def page; @start..(@start + height - 1) end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# The number of rows reserved for a header bar within the widget's footprint.
|
|
39
|
+
#
|
|
40
|
+
# stack.headspace #=> 1 (if header is set)
|
|
41
|
+
# stack.headspace #=> 0 (if no header)
|
|
42
|
+
|
|
43
|
+
def headspace; @header ? 1 : 0 end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# The usable height after subtracting any header space.
|
|
47
|
+
#
|
|
48
|
+
# stack.height #=> 14
|
|
49
|
+
|
|
50
|
+
def height; super - headspace end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Placeholder renderer used when no subclass overrides +print+ for an item id.
|
|
54
|
+
#
|
|
55
|
+
# stack.print 42 # renders a blank row
|
|
56
|
+
|
|
57
|
+
def print id; show " " * (width-@margin.size) end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Resets widget state by one or more categories.
|
|
61
|
+
#
|
|
62
|
+
# stack.reset # resets position and selection
|
|
63
|
+
# stack.reset :position # only scrolls back to top
|
|
64
|
+
# stack.reset :selection # clears all selected rows/columns/fields
|
|
65
|
+
# stack.reset [:display, :format]
|
|
66
|
+
|
|
67
|
+
def reset type=:all
|
|
68
|
+
case type
|
|
69
|
+
when Array; type.each{ |type| reset type }
|
|
70
|
+
when :position; @start = @hints_start = 0
|
|
71
|
+
when :selection; @selected = @selected.keys.map{ |type| [ type, [] ] }.to_h
|
|
72
|
+
when :all; reset [ :position, :selection ]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Renders the numeric key-hint overlay for items in the current page.
|
|
78
|
+
#
|
|
79
|
+
# stack.draw_hints # vertical hints along left margin
|
|
80
|
+
# stack.draw_hints :column, 2 # horizontal hints across columns of row 2
|
|
81
|
+
|
|
82
|
+
def draw_hints type=nil, row_id=0
|
|
83
|
+
color @colors[ :hints ]
|
|
84
|
+
if type.to_s[ /column/ ]
|
|
85
|
+
positions( row_id )[ @hints_start..-1 ].each_with_index{ |pos, idx|
|
|
86
|
+
move( left + pos, top + row_id )
|
|
87
|
+
show @hints[idx] }
|
|
88
|
+
else @hints.chars.each_with_index{ |char, idx|
|
|
89
|
+
pos = idx + @hints_start + headspace
|
|
90
|
+
break if pos > [height, rows-1+headspace].min
|
|
91
|
+
move( left, top + pos )
|
|
92
|
+
show char }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
##
|
|
97
|
+
# Renders the full widget: header bar (if present), all visible items with background
|
|
98
|
+
# coloring and alternating stripes, a selected-item highlight, and bottom borders.
|
|
99
|
+
#
|
|
100
|
+
# This method is called by your application's main loop each frame; it does not handle
|
|
101
|
+
# events itself (see {#send}).
|
|
102
|
+
|
|
103
|
+
def draw
|
|
104
|
+
if @header
|
|
105
|
+
color @colors[ :header ]
|
|
106
|
+
move left,top
|
|
107
|
+
show @margin
|
|
108
|
+
print :header
|
|
109
|
+
end
|
|
110
|
+
list = page.to_a
|
|
111
|
+
list[ height-1 ] = nil if list.length < height
|
|
112
|
+
for id, relative_id in list.each_with_index
|
|
113
|
+
move left,top + relative_id + headspace
|
|
114
|
+
foreground ( @colors[:rows][id] || @colors[:default][0] )
|
|
115
|
+
select = @selected[ :rows ].include?( id )
|
|
116
|
+
dark=!dark if @alternate
|
|
117
|
+
background ( select ? @colors[:selected] :
|
|
118
|
+
(@alternate and dark) ? @colors[:alternate] : @colors[:default][1] )
|
|
119
|
+
show @margin
|
|
120
|
+
print id
|
|
121
|
+
background if select
|
|
122
|
+
end
|
|
123
|
+
background
|
|
124
|
+
super
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
##
|
|
128
|
+
# Dispatches a key from the terminal to its semantic action and clamps the current page
|
|
129
|
+
# offset.
|
|
130
|
+
#
|
|
131
|
+
# stack.send Typr::KEY_UP # scrolls up one row
|
|
132
|
+
# stack.send Typr::KEY_RETURN # confirms selection
|
|
133
|
+
|
|
134
|
+
def send key#, map=nil
|
|
135
|
+
response = eval @keymap.invert[key].to_s if @keymap.values.include? key
|
|
136
|
+
@start = rows - height if @start > rows - height
|
|
137
|
+
@start = 0 if @start < 0
|
|
138
|
+
return response
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
## Scroll to the top of the dataset.
|
|
142
|
+
#
|
|
143
|
+
# stack.to_top
|
|
144
|
+
|
|
145
|
+
def to_top; @start = 0; return end
|
|
146
|
+
|
|
147
|
+
## Scroll to the bottom pageable position (last row minus viewport height).
|
|
148
|
+
#
|
|
149
|
+
# stack.to_bottom
|
|
150
|
+
|
|
151
|
+
def to_bottom; @start = rows - height; return end
|
|
152
|
+
|
|
153
|
+
## Step down by one row.
|
|
154
|
+
#
|
|
155
|
+
# stack.down
|
|
156
|
+
|
|
157
|
+
def down; @start += 1; return end
|
|
158
|
+
|
|
159
|
+
## Step up by one row.
|
|
160
|
+
#
|
|
161
|
+
# stack.up
|
|
162
|
+
|
|
163
|
+
def up; @start -= 1; return end
|
|
164
|
+
|
|
165
|
+
## Jump down by one viewport of rows.
|
|
166
|
+
#
|
|
167
|
+
# stack.page_down
|
|
168
|
+
|
|
169
|
+
def page_down; @start += height; return end
|
|
170
|
+
|
|
171
|
+
## Jump up by one viewport of rows.
|
|
172
|
+
#
|
|
173
|
+
# stack.page_up
|
|
174
|
+
|
|
175
|
+
def page_up; @start -= height; return end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Blocks the calling thread and lets the user interactively pick items from the current
|
|
179
|
+
# page using hint characters or arrow keys. After selection, the user must press Return to
|
|
180
|
+
# confirm (or Escape to abort).
|
|
181
|
+
#
|
|
182
|
+
# The +type+ argument controls what kind of picker is shown:
|
|
183
|
+
#
|
|
184
|
+
# Symbol/String type | Purpose
|
|
185
|
+
# -----------------------------|-------------------------------------------
|
|
186
|
+
# +:row, +"file"+ | Pick a single row id
|
|
187
|
+
# +"rows"+ | Multi-select rows
|
|
188
|
+
# +:column, +"field"+ | Nested: pick a row then a column
|
|
189
|
+
# +:fields+ | Multi-select fields
|
|
190
|
+
# +:none+ | Navigation-only mode
|
|
191
|
+
# anything with +relative_| | Returns the internal integer index
|
|
192
|
+
#
|
|
193
|
+
# stack.pick # single row picker
|
|
194
|
+
# stack.pick :column, 3 # pick a column in row 3
|
|
195
|
+
# stack.pick "rows" # multi-select rows
|
|
196
|
+
|
|
197
|
+
def pick type = :row, row=0 #, key=nil
|
|
198
|
+
@hints_start = 0
|
|
199
|
+
type = type.to_s
|
|
200
|
+
multiple = type[-1] == ?s
|
|
201
|
+
loop do
|
|
202
|
+
if type['field']
|
|
203
|
+
row = pick( :row ) or return
|
|
204
|
+
column = pick( :column, page.to_a.index( row + headspace)) or return
|
|
205
|
+
value = [ row , column ]
|
|
206
|
+
else
|
|
207
|
+
draw
|
|
208
|
+
draw_hints type, row unless type['none']
|
|
209
|
+
limit = type['row'] ? height : positions(row).count
|
|
210
|
+
key = Typr.read :key
|
|
211
|
+
if key.is_a?(String) and value =
|
|
212
|
+
@hints[0..limit-@hints_start-1].index(key)
|
|
213
|
+
value += @hints_start
|
|
214
|
+
relative = value + @start if type['relative_']
|
|
215
|
+
if type['row'] then value = page.to_a[ value ]
|
|
216
|
+
elsif type['column'] and @sequence; value = @sequence[value] end
|
|
217
|
+
end unless type['none']
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if value
|
|
221
|
+
type = type[9..-1] if type['relative_']
|
|
222
|
+
type += ?s unless multiple
|
|
223
|
+
type = type.to_sym
|
|
224
|
+
if @selected[type].include? value
|
|
225
|
+
@selected[type].delete value
|
|
226
|
+
else @selected[type] << value end if multiple
|
|
227
|
+
return relative || value unless multiple
|
|
228
|
+
else
|
|
229
|
+
case key
|
|
230
|
+
when @keymap[:exit]; return
|
|
231
|
+
when @keymap[:confirm]; return @selected[type]
|
|
232
|
+
else send key #if type['id']
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
##
|
|
239
|
+
# Advance the numeric hints by one full set of hint characters so that newly-fitted
|
|
240
|
+
# items beyond the first viewport also get labeled numbers.
|
|
241
|
+
#
|
|
242
|
+
# stack.cycle # hints now start at index 10 (or wrap back to 0)
|
|
243
|
+
|
|
244
|
+
def cycle
|
|
245
|
+
@hints_start += @hints.size
|
|
246
|
+
@hints_start = 0 if @hints_start > [height-1, rows-1+headspace].min
|
|
247
|
+
return
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
##
|
|
251
|
+
# Constructs a Stack widget.
|
|
252
|
+
#
|
|
253
|
+
# All positional and visual parameters from {Space#initialize} are available. Additionally:
|
|
254
|
+
#
|
|
255
|
+
# Option | Default | Description
|
|
256
|
+
# ----------------|---------|-------------------------------------------
|
|
257
|
+
# +keysyms+ | {} | Custom key-to-method mappings
|
|
258
|
+
# +hints+ | +"1234..."+ | Characters used in the hint overlay
|
|
259
|
+
# +alternate+ | true | Striped row backgrounds
|
|
260
|
+
# +header+ | nil | Label shown in a header bar
|
|
261
|
+
# +separator+ | +" "+ | String drawn between columns (used by Grid)
|
|
262
|
+
|
|
263
|
+
def initialize args={}
|
|
264
|
+
@keymap, @separator, @selected = {}, " ", {}
|
|
265
|
+
@start, @hints_start, @highlight = 0, 0, 0#A_STANDOUT
|
|
266
|
+
@alternate = true
|
|
267
|
+
super #args
|
|
268
|
+
@colors = { hints: [255, :black ], header: [232,:grey60 ],
|
|
269
|
+
selected: :grey25, alternate: :grey10, rows: {} }.merge @colors
|
|
270
|
+
@selected = { fields:[], columns:[], rows:[] }.merge @selected
|
|
271
|
+
@keymap = { cycle: KEY_TAB, confirm: KEY_RETURN, exit: KEY_ESCAPE,
|
|
272
|
+
page_down: KEY_PAGEDOWN, page_up: KEY_PAGEUP, up: KEY_UP,
|
|
273
|
+
down: KEY_DOWN, to_top: KEY_HOME, to_bottom: KEY_END }.merge @keymap
|
|
274
|
+
@hints ||= "1234567890qwertyuiopasdfghjklzxcvbnm-=[];'\,./"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
end
|
data/lib/terminal.rb
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
require 'readline'
|
|
5
|
+
require 'unicode/display_width'
|
|
6
|
+
|
|
7
|
+
module Typr
|
|
8
|
+
# alias :show :print
|
|
9
|
+
|
|
10
|
+
INPUT = (IO.new IO.sysopen "/dev/tty", "r")
|
|
11
|
+
OUTPUT = (IO.new IO.sysopen "/dev/stdin", "w")
|
|
12
|
+
`infocmp -L1`.split.each{ |info| key,value = info[0..-2].split("=")
|
|
13
|
+
if value
|
|
14
|
+
# p key + value if key[/dis/]
|
|
15
|
+
# value[1] = ?e if value[0,2] == "\\E"
|
|
16
|
+
value.gsub! '\E', '\e'
|
|
17
|
+
# value = '\x' + value[/\d+/].to_i(8).to_s(16).upcase if value[/\\\d+/]
|
|
18
|
+
# value = "\\x" + value[/\d+/].to_i(8).to_s(16).upcase if value[/\\\d+/]
|
|
19
|
+
value += "\\" if value[-1] == "\\"
|
|
20
|
+
# eval "%s=\'%s\'" % [key.upcase,value] if key
|
|
21
|
+
eval "%s=\"%s\"" % [key.upcase,value] if key
|
|
22
|
+
#and key.start_with? "key_"
|
|
23
|
+
end }
|
|
24
|
+
|
|
25
|
+
KEY_ESCAPE = "\e"
|
|
26
|
+
KEY_RETURN = CARRIAGE_RETURN
|
|
27
|
+
KEY_TAB = "\t"
|
|
28
|
+
KEY_ENTER = CARRIAGE_RETURN
|
|
29
|
+
KEY_PAGEDOWN = KEY_NPAGE
|
|
30
|
+
KEY_PAGEUP = KEY_PPAGE
|
|
31
|
+
KEY_INSERT = KEY_IC
|
|
32
|
+
KEY_DELETE = KEY_DC
|
|
33
|
+
|
|
34
|
+
MODES = %i[ reset bold faint italic underline slow fast invert ]
|
|
35
|
+
COLORS = %i[ black red green yellow blue magenta cyan white ]
|
|
36
|
+
|
|
37
|
+
def prepare str, max, align=:left, side=:right, fade=false
|
|
38
|
+
stop = width = real_size( str )
|
|
39
|
+
# unless str[0..max-1].ascii_only? and width == str.size
|
|
40
|
+
unless str.ascii_only? and width == str.size
|
|
41
|
+
stop = width = 0
|
|
42
|
+
str.each_char do |c|
|
|
43
|
+
if c == ?\e
|
|
44
|
+
width -= str[stop..-1][/^\x1b\[[^m]+m|/].size-1
|
|
45
|
+
stop += 1
|
|
46
|
+
elsif (cwidth = (c.ascii_only? ? 1 : Unicode::DisplayWidth.of(c))) +
|
|
47
|
+
width >= max
|
|
48
|
+
break
|
|
49
|
+
else width += cwidth; stop += 1 end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
# return
|
|
53
|
+
if ( space = ( max - width ) ) > 0 #.clamp 0, max
|
|
54
|
+
str = [ str[0..stop-1], " " * space ]
|
|
55
|
+
str.reverse! if align == :right
|
|
56
|
+
str.join
|
|
57
|
+
else #case align
|
|
58
|
+
# when :left; str = str[0..max-1]
|
|
59
|
+
# when :right;
|
|
60
|
+
str = str[side == :left ? -max..-1 : 0..max-1]
|
|
61
|
+
# end
|
|
62
|
+
# str = fade str, align, str.length / 8 + 1 if fade
|
|
63
|
+
str = fade str, side, fade if fade
|
|
64
|
+
return str
|
|
65
|
+
end
|
|
66
|
+
# return str.join
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def fade str, side=:left, num=3
|
|
70
|
+
isleft = side == :left
|
|
71
|
+
# chars = (side == ? 0..num : (-num-1..-2).to_a.reverse)
|
|
72
|
+
chars = str[ isleft ? 0..num : -num..-1 ]
|
|
73
|
+
chars = chars.chars.map.with_index do |char,id|
|
|
74
|
+
id = chars.size-id unless isleft
|
|
75
|
+
color_code( "grey#{(80/chars.size)*id+10}".to_sym ) + char
|
|
76
|
+
end.join
|
|
77
|
+
# chars.each.with_index do |pos,id|
|
|
78
|
+
# str.insert pos, color_code( "grey#{(80/chars.size)*id+10}".to_sym )
|
|
79
|
+
# str.insert pos, color_code( "grey#{(100/id+3)*10}".to_sym )
|
|
80
|
+
# end
|
|
81
|
+
return ( isleft ? chars + color_code($color[0]) + str[num+1..-1] :
|
|
82
|
+
str[0..-num-1] + chars )
|
|
83
|
+
|
|
84
|
+
#+ color_code( $color[1] )
|
|
85
|
+
# return str
|
|
86
|
+
# length.times{|i|
|
|
87
|
+
# color [ ("grey"+((i+3)*10).to_s ).to_sym, @colors[:default][1] ]
|
|
88
|
+
# show @directory[-space+i] }
|
|
89
|
+
# color @colors[:default]
|
|
90
|
+
# show @directory[-space+6..-1]
|
|
91
|
+
end
|
|
92
|
+
# def printables str; str.dump.gsub(/\\e\[.+m|\"/, '') end
|
|
93
|
+
def printables str; str.gsub(/\x1b\[[^m]+m|/,'') end
|
|
94
|
+
# def real_size str; printables(str).size end
|
|
95
|
+
def real_size str; Unicode::DisplayWidth.of( printables(str) ) end
|
|
96
|
+
|
|
97
|
+
# $>.print "\e[%iJ" % %w[down line screen].index(mode.to_s)
|
|
98
|
+
def move x=0,y=0; show move_code( x, y ) end
|
|
99
|
+
def move_code x=0,y=0; "\e[%i;%if" % [ y+1, x+1 ] end
|
|
100
|
+
|
|
101
|
+
def mode name; show mode_code(name) end
|
|
102
|
+
def mode_code name; if id = MODES.index(name.to_s) then "\e[#{ id }m" end end
|
|
103
|
+
|
|
104
|
+
def color_code color, bg=false
|
|
105
|
+
color = color.to_sym if color.is_a? String
|
|
106
|
+
case color
|
|
107
|
+
when Symbol; #color = color.to_s
|
|
108
|
+
if id = COLORS.index(color); "\e[#{ id + (bg ? 40 : 30) }m"
|
|
109
|
+
elsif color[/^gr[ae]y\d{,2}$/]
|
|
110
|
+
"\e[%i;5;%im" % [bg ? 48 : 38, 232 + (color[/\d+/].to_f/100*23).to_i]
|
|
111
|
+
end
|
|
112
|
+
when Integer; "\e[%i;5;%im" % [ bg ? 48 : 38, color]
|
|
113
|
+
when Array; case color.count
|
|
114
|
+
when 3; "\e[%i;2;%i;%i;%im" % [ bg ? 48 : 38, *color ]
|
|
115
|
+
when 2; color_code( color[0] ) + color_code( color[1], true )
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def get_background; $color[1] end
|
|
121
|
+
def get_foreground; $color[0] end
|
|
122
|
+
def foreground c=$default[0]; $color[0]=c; show color_code(c) end
|
|
123
|
+
def background c=$default[1]; $color[1]=c; show color_code(c,true) end
|
|
124
|
+
# def color c=$default
|
|
125
|
+
# c = [c] unless c.is_a? Array and c.count == 2
|
|
126
|
+
# show color_code(c)
|
|
127
|
+
# end
|
|
128
|
+
def color c=$default
|
|
129
|
+
c = [c] unless c.is_a? Array and c.count == 2
|
|
130
|
+
foreground c[0] if c[0]
|
|
131
|
+
background c[1] if c[1]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def show str; $>.print str end
|
|
135
|
+
# def enable attr; mode attr end
|
|
136
|
+
# def disable attr=nil; mode :reset end
|
|
137
|
+
# def refresh; end
|
|
138
|
+
def self.clear mode=:screen
|
|
139
|
+
$>.print( { screen: CLEAR_SCREEN, line: DELETE_LINE }[mode] )
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.read obj, prompt=''
|
|
143
|
+
case obj
|
|
144
|
+
when :key then INPUT.raw{ |tty| tty.sysread 6 } rescue return
|
|
145
|
+
when :line then line=Readline.readline prompt; print CURSOR_INVISIBLE;line
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def self.position
|
|
150
|
+
result = ''
|
|
151
|
+
$stdin.raw do |stdin|
|
|
152
|
+
$stdout << USER7 #"\e[6n"
|
|
153
|
+
$stdout.flush
|
|
154
|
+
until (char = stdin.getc) == 'R'
|
|
155
|
+
result << char if char
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
result[/[\d;]+/].split(?;).map &:to_i
|
|
159
|
+
# m = res.match /(?<row>\d+);(?<column>\d+)/
|
|
160
|
+
# { row: Integer(m[:row]), column: Integer(m[:column]) }
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.size; IO.console.winsize end
|
|
164
|
+
def self.width; size.last-1 end
|
|
165
|
+
def self.height; size.first end
|
|
166
|
+
def self.row; position.first end
|
|
167
|
+
def self.column; position.last end
|
|
168
|
+
def self.init default=[ :white, :black ]
|
|
169
|
+
$color = $default = default
|
|
170
|
+
# include Typr
|
|
171
|
+
# extend self
|
|
172
|
+
# Object.undef_method :read
|
|
173
|
+
print CURSOR_INVISIBLE
|
|
174
|
+
system "tput smkx"
|
|
175
|
+
end
|
|
176
|
+
def self.exit;
|
|
177
|
+
# background
|
|
178
|
+
extend self
|
|
179
|
+
|
|
180
|
+
$>.print CURSOR_NORMAL;
|
|
181
|
+
$>.print ORIG_COLORS; color; clear
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# include Typr
|
|
187
|
+
# extend Typr
|
|
188
|
+
|
|
189
|
+
if __FILE__ == $0
|
|
190
|
+
begin
|
|
191
|
+
include Typr
|
|
192
|
+
Typr.init #background: :blue
|
|
193
|
+
colors = COLORS + (1..9).map{|i| "grey#{i*10}" }
|
|
194
|
+
chars = (33..126).map(&:chr)
|
|
195
|
+
slice = chars.count / colors.count + 1
|
|
196
|
+
characters = chars.each_slice( slice ).map(&:join)
|
|
197
|
+
# key = read :key
|
|
198
|
+
key,fg,bg = "",150,0
|
|
199
|
+
Typr.clear
|
|
200
|
+
loop do
|
|
201
|
+
# move 0,0
|
|
202
|
+
colors.each_with_index{ |color,y|
|
|
203
|
+
characters.each_with_index{ |chars,x|
|
|
204
|
+
foreground color.to_sym;
|
|
205
|
+
background colors[x].to_sym;
|
|
206
|
+
move x*slice,y
|
|
207
|
+
show chars
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
(0...256).each{ |i| background i; show ' ' }
|
|
211
|
+
|
|
212
|
+
move 0, Typr.height-1
|
|
213
|
+
color [:white, :black]
|
|
214
|
+
# show "KEY: #{key}"
|
|
215
|
+
p "KEY: %s / %s" % [key, key]
|
|
216
|
+
move 15, Typr.height-1
|
|
217
|
+
color [fg,bg]
|
|
218
|
+
print " %s / %s " % [fg,bg] #).center 20
|
|
219
|
+
key = Typr.read(:key)
|
|
220
|
+
case key
|
|
221
|
+
when KEY_UP; fg+=1
|
|
222
|
+
when KEY_DOWN; fg-=1
|
|
223
|
+
when KEY_LEFT; bg-=1
|
|
224
|
+
when KEY_RIGHT; bg+=1
|
|
225
|
+
when KEY_ESCAPE; exit
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
ensure
|
|
229
|
+
Typr.exit
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
# KEY_ESC = 27
|
|
235
|
+
# KEY_TAB =
|
|
236
|
+
# KEY_ESC = 27
|
|
237
|
+
# A_STANDOUT = :invert
|
|
238
|
+
# BG = :black
|
|
239
|
+
# `infocmp -L`.read.join.split(', ').each{ |info| }
|
|
240
|
+
# .split('=').to_h
|
|
241
|
+
|
|
242
|
+
# KEY_PAGEDOWN = "\e[6~"
|
|
243
|
+
# KEY_PAGEUP = "\e[5~"
|
|
244
|
+
# KEY_LEFT = "\e[D"
|
|
245
|
+
# KEY_RIGHT = "\e[C"
|
|
246
|
+
# KEY_UP = "\e[A"
|
|
247
|
+
# KEY_DOWN = "\e[B"
|
|
248
|
+
# KEY_HOME = "\e[H" #xterm
|
|
249
|
+
# KEY_END = "\e[F" #xterm
|
|
250
|
+
# INSERT = "\e[2~" #xterm
|
|
251
|
+
# DELETE = "\e[3~" #xterm
|
|
252
|
+
# KEY_BACKSPACE = "\b" #xterm
|
|
253
|
+
|
|
254
|
+
# case ENV["TERM"]
|
|
255
|
+
# when
|
|
256
|
+
# DELETE = "\e[P" #st
|
|
257
|
+
# INSERT = "\e[4h" #st
|
|
258
|
+
# BACKSPACE = "\x7F" #st
|
|
259
|
+
# KEY_END = "\e[4~" #st
|
|
260
|
+
|
|
261
|
+
# KEY_HOME = "\e[7~" #urxvt
|
|
262
|
+
# KEY_END = "\e[8~" #urxvt
|
|
263
|
+
|
|
264
|
+
# HIDE_CURSOR = "\e[?25l"
|
|
265
|
+
# SHOW_CURSOR = "\e[?25h"
|
|
266
|
+
# HEIGHT,WIDTH = IO.console.winsize
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# def mod key,char,extra;
|
|
270
|
+
# if key.length == 1; key = char + key.upcase
|
|
271
|
+
# elsif key[/~$/]; key[0..-2] + char
|
|
272
|
+
# else key[0..-2] + extra + key[-1].downcase end
|
|
273
|
+
# end
|
|
274
|
+
# def shift key; mod key, "$" end
|
|
275
|
+
# def ctrl key; mod key, "^", "O" end
|
|
276
|
+
# def alt key; KEYMAP[:esc] + key end
|
|
277
|
+
# def clear mode=2; IO.console.erase_screen 1 end
|
|
278
|
+
# def mode name; "\e[%im" % [(MODES.index name.to_s)] end
|
|
279
|
+
# def ansi arg, type, inc=0
|
|
280
|
+
# case arg
|
|
281
|
+
# when Integer; show "\e[%i;5;%im" % [ inc + 8, arg]
|
|
282
|
+
# else show "\e[%im" % [ ( type.index arg.to_s ) + inc ]
|
|
283
|
+
# end
|
|
284
|
+
# end
|
|
285
|
+
|
|
286
|
+
|