ecl 1.2.1 → 3.0.0.pre.alpha1
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 +5 -5
- data/.gitignore +0 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +48 -0
- data/README.md +112 -89
- data/Rakefile +1 -0
- data/eclair.gemspec +7 -4
- data/exe/ecl +62 -4
- data/lib/eclair.rb +21 -18
- data/lib/eclair/color.rb +3 -0
- data/lib/eclair/config.rb +68 -21
- data/lib/eclair/grid.rb +242 -274
- data/lib/eclair/group_item.rb +33 -0
- data/lib/eclair/item.rb +42 -0
- data/lib/eclair/less_viewer.rb +2 -1
- data/lib/eclair/provider.rb +15 -0
- data/lib/eclair/providers/ec2.rb +2 -0
- data/lib/eclair/providers/ec2/ec2_group_item.rb +16 -0
- data/lib/eclair/providers/ec2/ec2_item.rb +136 -0
- data/lib/eclair/providers/ec2/ec2_provider.rb +118 -0
- data/lib/eclair/providers/gce.rb +2 -0
- data/lib/eclair/providers/gce/gce_group_item.rb +15 -0
- data/lib/eclair/providers/gce/gce_item.rb +117 -0
- data/lib/eclair/providers/gce/gce_provider.rb +34 -0
- data/lib/eclair/providers/k8s.rb +2 -0
- data/lib/eclair/providers/k8s/k8s_group_item.rb +15 -0
- data/lib/eclair/providers/k8s/k8s_item.rb +92 -0
- data/lib/eclair/providers/k8s/k8s_provider.rb +34 -0
- data/lib/eclair/version.rb +2 -1
- data/out.gif +0 -0
- data/templates/eclrc.template +81 -35
- metadata +60 -27
- data/bin/console +0 -10
- data/lib/eclair/cell.rb +0 -101
- data/lib/eclair/column.rb +0 -52
- data/lib/eclair/console.rb +0 -56
- data/lib/eclair/group.rb +0 -55
- data/lib/eclair/helpers/aws_helper.rb +0 -157
- data/lib/eclair/helpers/benchmark_helper.rb +0 -19
- data/lib/eclair/helpers/common_helper.rb +0 -24
- data/lib/eclair/instance.rb +0 -165
- data/lib/eclair/matcher.rb +0 -19
data/lib/eclair/config.rb
CHANGED
@@ -1,9 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'curses'
|
3
|
+
|
1
4
|
module Eclair
|
5
|
+
module ConfigHelper
|
6
|
+
def config
|
7
|
+
Eclair.config
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
2
11
|
class Config
|
3
|
-
|
4
|
-
|
12
|
+
KEYS_DIR = "#{ENV['HOME']}/.ecl/keys"
|
13
|
+
CACHE_DIR = "#{ENV['HOME']}/.ecl/.cache"
|
5
14
|
|
6
15
|
def initialize
|
16
|
+
@done = false
|
17
|
+
@config_file = ENV["ECLRC"] || "#{ENV['HOME']}/.ecl/config.rb"
|
7
18
|
@aws_region = nil
|
8
19
|
@columns = 4
|
9
20
|
@group_by = lambda do |instance|
|
@@ -21,17 +32,14 @@ module Eclair
|
|
21
32
|
"ec2-user"
|
22
33
|
end
|
23
34
|
end
|
35
|
+
@ssh_command = "ssh"
|
24
36
|
@ssh_keys = {}
|
25
|
-
@ssh_hostname = :public_ip_address
|
26
37
|
@ssh_ports = [22].freeze
|
27
38
|
@ssh_options = "-o ConnectTimeout=1 -o StrictHostKeyChecking=no".freeze
|
28
|
-
@
|
29
|
-
@
|
30
|
-
@
|
31
|
-
@
|
32
|
-
@disabled_color = [COLOR_BLACK, -1, A_BOLD].freeze
|
33
|
-
@search_color = [COLOR_BLACK, COLOR_YELLOW].freeze
|
34
|
-
@help_color = [COLOR_BLACK, COLOR_WHITE].freeze
|
39
|
+
@dir_keys = {}
|
40
|
+
@exec_format = "{ssh_command} {ssh_options} -p{port} {ssh_key} {username}@{host}"
|
41
|
+
@provider = :ec2
|
42
|
+
@get_pods_option = ""
|
35
43
|
|
36
44
|
instance_variables.each do |var|
|
37
45
|
Config.class_eval do
|
@@ -39,23 +47,55 @@ module Eclair
|
|
39
47
|
end
|
40
48
|
end
|
41
49
|
|
42
|
-
|
50
|
+
# Migrate old ~/.eclrc to ~/.ecl/config.rb
|
51
|
+
old_conf = "#{ENV['HOME']}/.eclrc"
|
52
|
+
new_dir = "#{ENV['HOME']}/.ecl"
|
53
|
+
new_conf = "#{ENV['HOME']}/.ecl/config.rb"
|
54
|
+
|
55
|
+
if !File.exists?(new_conf) && File.exists?(old_conf)
|
56
|
+
FileUtils.mkdir_p new_dir
|
57
|
+
FileUtils.mv old_conf, new_conf
|
58
|
+
puts "#{old_conf} migrated to #{new_conf}"
|
59
|
+
puts "Please re-run eclair"
|
60
|
+
exit
|
61
|
+
end
|
62
|
+
|
63
|
+
unless File.exists? @config_file
|
43
64
|
template_path = File.join(File.dirname(__FILE__), "..", "..", "templates", "eclrc.template")
|
44
|
-
FileUtils.
|
45
|
-
|
65
|
+
FileUtils.mkdir_p(File.dirname(@config_file))
|
66
|
+
FileUtils.cp(template_path, @config_file)
|
67
|
+
puts "#{@config_file} successfully created. Edit it and run again!"
|
46
68
|
exit
|
47
69
|
end
|
70
|
+
|
71
|
+
key_path = "#{new_dir}/keys"
|
72
|
+
FileUtils.mkdir_p key_path unless Dir.exists? key_path
|
73
|
+
# FileUtils.mkdir_p CACHE_DIR unless Dir.exists? CACHE_DIR
|
74
|
+
end
|
75
|
+
|
76
|
+
def after_load
|
77
|
+
dir_keys = {}
|
78
|
+
|
79
|
+
Dir["#{KEYS_DIR}/*"].each do |key|
|
80
|
+
if File.file? key
|
81
|
+
dir_keys[File.basename(key, ".*")] = key
|
82
|
+
end
|
83
|
+
end
|
84
|
+
@ssh_keys.merge!(dir_keys)
|
48
85
|
end
|
49
86
|
end
|
50
87
|
|
51
88
|
extend self
|
52
|
-
|
53
|
-
def config
|
54
|
-
unless @config
|
55
|
-
@config = Config.new
|
56
|
-
load Config::RCFILE
|
57
|
-
end
|
58
89
|
|
90
|
+
def init_config
|
91
|
+
@config = Config.new
|
92
|
+
load @config.config_file
|
93
|
+
raise unless @done
|
94
|
+
@config.after_load
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def config
|
59
99
|
if @config.aws_region
|
60
100
|
::Aws.config.update(region: @config.aws_region)
|
61
101
|
end
|
@@ -63,7 +103,14 @@ module Eclair
|
|
63
103
|
@config
|
64
104
|
end
|
65
105
|
|
66
|
-
def
|
67
|
-
|
106
|
+
def profile
|
107
|
+
ENV["ECL_PROFILE"] || "default"
|
108
|
+
end
|
109
|
+
|
110
|
+
def configure name = "default"
|
111
|
+
if profile == name
|
112
|
+
@done = true
|
113
|
+
yield config
|
114
|
+
end
|
68
115
|
end
|
69
116
|
end
|
data/lib/eclair/grid.rb
CHANGED
@@ -1,355 +1,323 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "eclair/group_item"
|
3
|
+
require "eclair/color"
|
4
|
+
|
1
5
|
module Eclair
|
2
|
-
|
3
|
-
|
4
|
-
|
6
|
+
class Grid
|
7
|
+
attr_reader :mode
|
8
|
+
|
9
|
+
def initialize keyword = ""
|
10
|
+
case config.provider
|
11
|
+
when :ec2
|
12
|
+
require "eclair/providers/ec2"
|
13
|
+
@provider = EC2Provider
|
14
|
+
when :k8s
|
15
|
+
require "eclair/providers/k8s"
|
16
|
+
@provider = K8sProvider
|
17
|
+
when :gce
|
18
|
+
require "eclair/providers/gce"
|
19
|
+
@provider = GCEProvider
|
20
|
+
end
|
21
|
+
@item_class = @provider.item_class
|
5
22
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
23
|
+
@scroll = config.columns.times.map{0}
|
24
|
+
@header_rows = 4
|
25
|
+
@cursor = [0,0]
|
26
|
+
@cell_width = Curses.stdscr.maxx/config.columns
|
27
|
+
@maxy = Curses.stdscr.maxy - @header_rows
|
28
|
+
@mode = :assign
|
29
|
+
@search_buffer = ""
|
10
30
|
|
11
|
-
|
12
|
-
|
31
|
+
@provider.prepare keyword
|
32
|
+
assign
|
33
|
+
at(*@cursor).select(true)
|
34
|
+
draw_all
|
35
|
+
transit_mode(:nav)
|
13
36
|
end
|
14
|
-
|
15
|
-
def render_header
|
16
|
-
if mode == :search
|
17
|
-
if cursor
|
18
|
-
header = ["Searching #{@search_str}", "Found #{cursor.name}", ""]
|
19
|
-
else
|
20
|
-
header = ["Searching #{@search_str}", "None Found", ""]
|
21
|
-
end
|
22
|
-
else
|
23
|
-
header = cursor.header
|
24
|
-
end
|
25
37
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
38
|
+
def move key
|
39
|
+
return unless at(*@cursor)
|
40
|
+
x,y = @cursor
|
41
|
+
mx,my = {
|
42
|
+
Curses::KEY_UP => [0,-1],
|
43
|
+
?k => [0,-1],
|
44
|
+
Curses::KEY_DOWN => [0,1],
|
45
|
+
?j => [0,1],
|
46
|
+
Curses::KEY_LEFT => [-1,0],
|
47
|
+
?h => [-1,0],
|
48
|
+
Curses::KEY_RIGHT => [1,0],
|
49
|
+
?l => [1,0],
|
50
|
+
}[key]
|
33
51
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
attron(Color.fetch(*config.help_color)) do
|
47
|
-
addstr helps.map{ |key, action|
|
48
|
-
" #{key} => #{action}"
|
49
|
-
}.join(" ").slice(0,stdscr.maxx).ljust(stdscr.maxx)
|
52
|
+
newx = x
|
53
|
+
loop do
|
54
|
+
newx = (newx + mx) % @grid.length
|
55
|
+
break if @grid[newx].length > 0
|
56
|
+
end
|
57
|
+
newy = (y + my - @scroll[x] + @scroll[newx])
|
58
|
+
if my != 0
|
59
|
+
newy %= @grid[newx].length
|
60
|
+
end
|
61
|
+
if newy >= @grid[newx].length
|
62
|
+
newy = @grid[newx].length-1
|
50
63
|
end
|
51
|
-
end
|
52
64
|
|
53
|
-
|
54
|
-
stdscr.maxx/column_count
|
65
|
+
move_cursor(newx, newy)
|
55
66
|
end
|
56
67
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
render_all
|
61
|
-
end
|
62
|
-
|
63
|
-
def assign
|
64
|
-
sort_function = lambda {|i| [i.name.downcase, -i.launch_time.to_i]}
|
65
|
-
@group_map ||= {}
|
66
|
-
if config.group_by
|
67
|
-
Aws.instances.group_by(&config.group_by).each do |group, instances|
|
68
|
-
if @group_map[group]
|
69
|
-
group_cell = @group_map[group]
|
70
|
-
else
|
71
|
-
col = columns[target_col]
|
72
|
-
group_cell = Group.new(group, col)
|
73
|
-
col << group_cell
|
74
|
-
@group_map[group] = group_cell
|
75
|
-
end
|
76
|
-
instances.each do |i|
|
77
|
-
unless group_cell.find{|j| j.instance_id == i.instance_id}
|
78
|
-
obj = Instance.new(i.instance_id, col)
|
79
|
-
group_cell << obj
|
80
|
-
end
|
81
|
-
end
|
82
|
-
group_cell.items.sort_by!(&sort_function)
|
83
|
-
end
|
84
|
-
else
|
85
|
-
col_limit = (Aws.instances.count - 1) / config.columns + 1
|
86
|
-
iter = Aws.instances.map{|i| Instance.new(i.instance_id)}.sort_by(&sort_function).each
|
87
|
-
columns.each do |col|
|
88
|
-
col_limit.times do
|
89
|
-
begin
|
90
|
-
i = iter.next
|
91
|
-
i.column = col
|
92
|
-
col << i
|
93
|
-
rescue StopIteration
|
94
|
-
break
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
68
|
+
def space
|
69
|
+
if @mode == :nav
|
70
|
+
transit_mode(:sel)
|
98
71
|
end
|
99
|
-
end
|
100
72
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
c.render
|
106
|
-
end
|
73
|
+
at(*@cursor)&.toggle_select
|
74
|
+
|
75
|
+
if @mode == :sel && @provider.items.all?{|i| !i.selected}
|
76
|
+
transit_mode(:nav)
|
107
77
|
end
|
108
|
-
|
109
|
-
|
78
|
+
|
79
|
+
|
80
|
+
draw(*@cursor)
|
110
81
|
end
|
111
82
|
|
112
|
-
def
|
113
|
-
targets =
|
83
|
+
def action
|
84
|
+
targets = @provider.items.select{|i| i.selected && i.connectable?}
|
85
|
+
|
114
86
|
return if targets.empty?
|
115
|
-
close_screen
|
87
|
+
Curses.close_screen
|
116
88
|
|
117
|
-
|
118
|
-
|
119
|
-
target = targets.first
|
120
|
-
cmd = target.ssh_cmd
|
89
|
+
if targets.length == 1
|
90
|
+
cmd = targets.first.command
|
121
91
|
else
|
122
92
|
cmds = []
|
123
|
-
|
124
|
-
session_cmd = nil
|
93
|
+
target_cmd = ""
|
125
94
|
|
126
95
|
targets.each_with_index do |target, i|
|
127
|
-
if i==0
|
128
|
-
if ENV['TMUX']
|
129
|
-
|
130
|
-
|
96
|
+
if i == 0
|
97
|
+
if ENV['TMUX'] # Eclair called inside of tmux
|
98
|
+
# Create new session and save window id
|
99
|
+
window_name = `tmux new-window -P -- '#{target.command}'`.strip
|
100
|
+
target_cmd = "-t #{window_name}"
|
101
|
+
else # Eclair called from outside of tmux
|
102
|
+
# Create new session and save session
|
131
103
|
session_name = "eclair#{Time.now.to_i}"
|
132
|
-
|
133
|
-
|
104
|
+
target_cmd = "-t #{session_name}"
|
105
|
+
`tmux new-session -d -s #{session_name} -- '#{target.command}'`
|
134
106
|
end
|
135
|
-
else
|
136
|
-
cmds << "
|
137
|
-
cmds << "
|
107
|
+
else # Split layout and
|
108
|
+
cmds << "split-window #{target_cmd} -- '#{target.command}'"
|
109
|
+
cmds << "select-layout #{target_cmd} tiled"
|
138
110
|
end
|
139
111
|
end
|
140
|
-
cmds << "
|
141
|
-
cmds << "
|
142
|
-
cmd = cmds.join("
|
112
|
+
cmds << "set-window-option #{target_cmd} synchronize-panes on"
|
113
|
+
cmds << "attach #{target_cmd}" unless ENV['TMUX']
|
114
|
+
cmd = "tmux #{cmds.join(" \\; ")}"
|
143
115
|
end
|
144
|
-
system
|
145
|
-
exit
|
116
|
+
system(cmd)
|
117
|
+
exit()
|
118
|
+
resize
|
146
119
|
end
|
147
120
|
|
148
|
-
def
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
def rows
|
157
|
-
columns.map(&:count).max
|
158
|
-
end
|
159
|
-
|
160
|
-
def target_col
|
161
|
-
counts = columns.map(&:count)
|
162
|
-
counts.index(counts.min)
|
121
|
+
def resize
|
122
|
+
Curses.clear
|
123
|
+
@scroll.fill(0)
|
124
|
+
@cell_width = Curses.stdscr.maxx/config.columns
|
125
|
+
@maxy = Curses.stdscr.maxy - @header_rows
|
126
|
+
rescroll(*@cursor)
|
127
|
+
draw_all
|
163
128
|
end
|
164
129
|
|
165
|
-
def
|
166
|
-
|
167
|
-
end
|
130
|
+
def transit_mode to
|
131
|
+
return if to == @mode
|
168
132
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
if mode == :navi
|
176
|
-
@mode = :select
|
177
|
-
cursor.toggle_select
|
178
|
-
end
|
179
|
-
cursor.toggle_select
|
180
|
-
if selected.empty?
|
181
|
-
@mode = :navi
|
182
|
-
cursor.toggle_select
|
133
|
+
case @mode
|
134
|
+
when :nav
|
135
|
+
at(*@cursor)&.select(false)
|
136
|
+
when :sel
|
137
|
+
when :search
|
138
|
+
when :assign
|
183
139
|
end
|
184
|
-
end
|
185
140
|
|
186
|
-
|
187
|
-
end_search if mode == :search
|
188
|
-
mx,my = {
|
189
|
-
KEY_UP => [0,-1],
|
190
|
-
"k" => [0,-1],
|
191
|
-
KEY_DOWN => [0,1],
|
192
|
-
"j" => [0,1],
|
193
|
-
KEY_LEFT => [-1,0],
|
194
|
-
"h" => [-1,0],
|
195
|
-
KEY_RIGHT => [1,0],
|
196
|
-
"l" => [1,0],
|
197
|
-
}[key]
|
141
|
+
@mode = to
|
198
142
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
143
|
+
case @mode
|
144
|
+
when :nav
|
145
|
+
at(*@cursor)&.select(true)
|
146
|
+
when :sel
|
147
|
+
when :search
|
148
|
+
when :assign
|
149
|
+
move_cursor(0,0)
|
206
150
|
end
|
207
151
|
|
208
|
-
|
152
|
+
draw_all
|
209
153
|
end
|
210
154
|
|
211
|
-
def
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
155
|
+
def start_search
|
156
|
+
transit_mode(:search)
|
157
|
+
end
|
158
|
+
|
159
|
+
def end_search
|
160
|
+
if @provider.items.any?{|i| i.selected}
|
161
|
+
transit_mode(:sel)
|
216
162
|
else
|
217
|
-
|
163
|
+
transit_mode(:nav)
|
218
164
|
end
|
219
165
|
end
|
220
166
|
|
221
|
-
def
|
222
|
-
|
223
|
-
|
224
|
-
return nil if result.score(@search_str) == 0.0
|
225
|
-
result
|
167
|
+
def clear_search
|
168
|
+
@search_buffer = ""
|
169
|
+
update_search
|
226
170
|
end
|
227
171
|
|
228
|
-
def
|
229
|
-
|
230
|
-
@rollback_mode = @mode
|
231
|
-
@search_str = ""
|
232
|
-
end
|
172
|
+
def append_search key
|
173
|
+
return unless key
|
233
174
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
175
|
+
if @search_buffer.length > 0 && key == 127 # backspace
|
176
|
+
@search_buffer = @search_buffer.chop
|
177
|
+
elsif key.to_s.length == 1
|
178
|
+
begin
|
179
|
+
@search_buffer = @search_buffer + key.to_s
|
180
|
+
rescue
|
181
|
+
return
|
182
|
+
end
|
238
183
|
else
|
239
|
-
|
184
|
+
return
|
240
185
|
end
|
241
|
-
end
|
242
186
|
|
243
|
-
|
244
|
-
x,y = @rollback_cursor
|
245
|
-
move_cursor(x: x, y: y, mode: @rollback_mode)
|
187
|
+
update_search
|
246
188
|
end
|
247
189
|
|
190
|
+
private
|
248
191
|
|
249
|
-
def move_cursor
|
250
|
-
|
251
|
-
|
252
|
-
cursor.decurrent
|
253
|
-
end
|
192
|
+
def move_cursor x, y
|
193
|
+
prev = @cursor.dup
|
194
|
+
@cursor = [x, y]
|
254
195
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
@mode = new_mode
|
196
|
+
prev_item = at(*prev)
|
197
|
+
curr_item = at(*@cursor)
|
198
|
+
rescroll(*@cursor)
|
199
|
+
if @mode == :nav
|
200
|
+
prev_item.select(false)
|
201
|
+
curr_item.select(true)
|
262
202
|
end
|
203
|
+
draw(*prev) if prev_item
|
204
|
+
draw(*@cursor) if curr_item
|
205
|
+
update_header(curr_item.header) if curr_item
|
206
|
+
end
|
263
207
|
|
264
|
-
if block
|
265
|
-
@x, @y = block.call
|
266
|
-
else
|
267
|
-
@x = options.delete(:x) || @x
|
268
|
-
@y = options.delete(:y) || @y
|
269
|
-
end
|
270
208
|
|
271
|
-
|
272
|
-
|
273
|
-
|
209
|
+
def update_header str, pos = 0
|
210
|
+
Curses.setpos(0, 0)
|
211
|
+
Curses.clrtoeol
|
212
|
+
Curses.addstr(@mode.to_s)
|
213
|
+
str.split("\n").map(&:strip).each_with_index do |line, i|
|
214
|
+
Curses.setpos(i + pos + 1, 0)
|
215
|
+
Curses.clrtoeol
|
216
|
+
Curses.addstr(line)
|
274
217
|
end
|
275
218
|
end
|
276
219
|
|
277
|
-
def
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
if key
|
282
|
-
@search_str = @search_str+key
|
283
|
-
else
|
284
|
-
@search_str.chop!
|
285
|
-
end
|
220
|
+
def update_search
|
221
|
+
assign
|
222
|
+
Curses.clear
|
223
|
+
draw_all
|
286
224
|
|
287
|
-
|
225
|
+
Curses.setpos(@header_rows - 1, 0)
|
226
|
+
Curses.clrtoeol
|
227
|
+
if @mode != :search && @search_buffer.empty?
|
228
|
+
update_header('/: Start search')
|
229
|
+
else
|
230
|
+
update_header("/#{@search_buffer}")
|
231
|
+
end
|
232
|
+
end
|
288
233
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
234
|
+
def rescroll x, y
|
235
|
+
unless (@scroll[x]...@maxy+@scroll[x]).include? y
|
236
|
+
if y < @scroll[x]
|
237
|
+
@scroll[x] = y
|
238
|
+
elsif y >= @maxy
|
239
|
+
@scroll[x] = y - @maxy + 1
|
240
|
+
end
|
241
|
+
(@scroll[x]...@maxy+@scroll[x]).each do |ty|
|
242
|
+
draw_item(x, ty)
|
296
243
|
end
|
297
|
-
|
298
|
-
result || [-1,-1]
|
299
244
|
end
|
300
|
-
|
301
|
-
render_header
|
302
245
|
end
|
303
246
|
|
304
|
-
def
|
305
|
-
|
306
|
-
render_all
|
307
|
-
cursor.check_scroll
|
247
|
+
def at x, y
|
248
|
+
@grid[x][y]
|
308
249
|
end
|
309
250
|
|
310
|
-
def
|
311
|
-
|
312
|
-
|
313
|
-
|
251
|
+
def make_label target
|
252
|
+
ind = (@mode != :nav && target.selected) ? "*" : " "
|
253
|
+
label = "#{target.label} #{ind}"
|
254
|
+
label.slice(0, @cell_width).ljust(@cell_width)
|
314
255
|
end
|
315
256
|
|
316
|
-
def
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
257
|
+
def draw_all
|
258
|
+
@grid.each_with_index do |column, x|
|
259
|
+
column.each_with_index do |_, y|
|
260
|
+
draw_item(x,y)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
case @mode
|
264
|
+
when :nav, :sel
|
265
|
+
update_header(at(*@cursor)&.header || "No Match")
|
266
|
+
end
|
322
267
|
end
|
323
268
|
|
324
|
-
def
|
325
|
-
@
|
326
|
-
|
327
|
-
|
269
|
+
def color x, y
|
270
|
+
if @cursor == [x,y]
|
271
|
+
Color.fetch(Curses::COLOR_BLACK, Curses::COLOR_CYAN)
|
272
|
+
else
|
273
|
+
Color.fetch(*@grid[x][y].color)
|
274
|
+
end
|
328
275
|
end
|
329
276
|
|
330
|
-
def
|
331
|
-
|
277
|
+
def draw_item x, y
|
278
|
+
target = @grid[x].select{|item| item.visible}[y]
|
279
|
+
|
280
|
+
drawy = y - @scroll[x]
|
281
|
+
if drawy < 0 || drawy + @header_rows >= Curses.stdscr.maxy
|
282
|
+
return
|
283
|
+
end
|
284
|
+
cell_color = color(x,y)
|
285
|
+
Curses.setpos(drawy + @header_rows, x * @cell_width)
|
286
|
+
Curses.attron(cell_color) do
|
287
|
+
Curses.addstr make_label(target)
|
288
|
+
end
|
289
|
+
Curses.refresh
|
332
290
|
end
|
333
291
|
|
334
|
-
def
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
292
|
+
def draw x, y
|
293
|
+
target = @grid[x][y]
|
294
|
+
draw_item(x, y)
|
295
|
+
if target.is_a?(GroupItem)
|
296
|
+
(y+1).upto(y+target.length).each do |ny|
|
297
|
+
draw_item(x, ny)
|
340
298
|
end
|
341
299
|
end
|
342
|
-
@x, @y = stored_cursor.x, stored_cursor.y
|
343
|
-
render_all
|
344
300
|
end
|
345
301
|
|
346
|
-
def
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
302
|
+
def assign
|
303
|
+
old_mode = @mode
|
304
|
+
transit_mode(:assign)
|
305
|
+
@grid = config.columns.times.map{[]}
|
306
|
+
visible_items = @provider.filter_items(@search_buffer)
|
307
|
+
@groups = visible_items.group_by(&config.group_by)
|
308
|
+
@groups.each do |name, items|
|
309
|
+
group_name = "#{name} (#{items.length})"
|
310
|
+
target = @grid.min_by(&:length)
|
311
|
+
target << @provider.group_class.new(group_name, items)
|
312
|
+
items.sort_by(&:label).each do |item|
|
313
|
+
target << item
|
314
|
+
end
|
315
|
+
end
|
316
|
+
transit_mode(old_mode)
|
317
|
+
end
|
318
|
+
|
319
|
+
def config
|
320
|
+
Eclair.config
|
353
321
|
end
|
354
322
|
end
|
355
323
|
end
|