working_set 1.0.1
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/.gitignore +16 -0
- data/.ruby-version +1 -0
- data/API_FOR_EDITOR_INTEGRATION.md +97 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +107 -0
- data/Rakefile +2 -0
- data/bin/working_set +4 -0
- data/lib/api_input_actor.rb +75 -0
- data/lib/basic_actor.rb +8 -0
- data/lib/colors.rb +16 -0
- data/lib/live_updater_actor.rb +29 -0
- data/lib/set_builder_actor.rb +23 -0
- data/lib/set_builder_adapter.rb +2 -0
- data/lib/set_builder_adapter/ag.rb +132 -0
- data/lib/set_viewer_actor.rb +146 -0
- data/lib/user_input_actor.rb +177 -0
- data/lib/view/base.rb +13 -0
- data/lib/view/help.rb +28 -0
- data/lib/view/welcome_user.rb +40 -0
- data/lib/view/working_set.rb +313 -0
- data/lib/working_set.rb +34 -0
- data/lib/working_set_cli.rb +205 -0
- data/lib/working_set_item.rb +40 -0
- data/working_set.gemspec +35 -0
- metadata +226 -0
data/lib/view/base.rb
ADDED
data/lib/view/help.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class View::Help < View::Base
|
2
|
+
|
3
|
+
def self.render
|
4
|
+
new.render
|
5
|
+
end
|
6
|
+
|
7
|
+
def render
|
8
|
+
UserInputActor.set_user_input_mode :help
|
9
|
+
stdscr.clear
|
10
|
+
stdscr.move 0, 0
|
11
|
+
stdscr.printw "Key Bindings\n"
|
12
|
+
stdscr.printw "------------\n\n"
|
13
|
+
|
14
|
+
UserInputActor::USER_INPUT_MAPPINGS.each_pair do |k,v|
|
15
|
+
with_color(stdscr, :cyan) do
|
16
|
+
stdscr.printw " #{v[:key_desc] || k}"
|
17
|
+
end
|
18
|
+
stdscr.printw " - #{v[:desc]}\n\n"
|
19
|
+
end
|
20
|
+
with_color(stdscr, :blue) do
|
21
|
+
stdscr.printw "Press 'q' to go back."
|
22
|
+
end
|
23
|
+
|
24
|
+
stdscr.refresh
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class View::WelcomeUser < View::Base
|
2
|
+
|
3
|
+
def self.render
|
4
|
+
new.render
|
5
|
+
end
|
6
|
+
|
7
|
+
def render
|
8
|
+
UserInputActor.set_user_input_mode :welcome_user
|
9
|
+
stdscr.clear
|
10
|
+
print_centered \
|
11
|
+
[:cyan, "Working Set"],
|
12
|
+
"",
|
13
|
+
"v#{WorkingSet::VERSION}",
|
14
|
+
"by Jim Garvin et al.",
|
15
|
+
"",
|
16
|
+
[:blue, "Press '?' for help."],
|
17
|
+
[:blue, "Press 'q' to quit."]
|
18
|
+
stdscr.refresh
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def print_centered(*lines)
|
24
|
+
start_y = Ncurses.LINES / 2 - lines.size / 2
|
25
|
+
lines.each_with_index do |line, i|
|
26
|
+
color, msg = if Array === line
|
27
|
+
line
|
28
|
+
else
|
29
|
+
[:white, line]
|
30
|
+
end
|
31
|
+
x = Ncurses.COLS / 2 - msg.size / 2
|
32
|
+
stdscr.move start_y + i, x
|
33
|
+
with_color(stdscr, color) do
|
34
|
+
stdscr.printw msg
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,313 @@
|
|
1
|
+
# +--------------------------------------------------------------------------+
|
2
|
+
# | Search or Working Set Name + (indicates dirty/saved state) |
|
3
|
+
# +--------------------------------------------------------------------------+
|
4
|
+
# | Note if present |
|
5
|
+
# +--------------------------------------------------------------------------+
|
6
|
+
# | |
|
7
|
+
# | app/models/foo.rb |
|
8
|
+
# | 10 def foo |
|
9
|
+
# | > 11 puts "bar" (highlight match) |
|
10
|
+
# | 12 end |
|
11
|
+
# | |
|
12
|
+
# | app/models/bar.rb |
|
13
|
+
# | 10 def bar |
|
14
|
+
# | 11 puts "foo" (highlight match) |
|
15
|
+
# | 12 end |
|
16
|
+
# | |
|
17
|
+
# | 10 def bar |
|
18
|
+
# | 11 puts "foo" (highlight match) |
|
19
|
+
# | 12 end |
|
20
|
+
# +--------------------------------------------------------------------------+
|
21
|
+
# | 1-10 of 16 items |
|
22
|
+
# +--------------------------------------------------------------------------+
|
23
|
+
#
|
24
|
+
# * Highlight "selected item" with colors.
|
25
|
+
|
26
|
+
class View::WorkingSet < View::Base
|
27
|
+
attr_accessor :working_set, :selected_item_index, :file_index, :scroll_top, :scrollable_height, :show_match_lines
|
28
|
+
|
29
|
+
TITLE_ROWS = 2
|
30
|
+
FOOTER_ROWS = 2
|
31
|
+
|
32
|
+
def self.render(working_set)
|
33
|
+
new(working_set).render
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(working_set)
|
37
|
+
self.working_set = working_set
|
38
|
+
self.file_index = index_files(working_set)
|
39
|
+
self.selected_item_index = 0
|
40
|
+
self.scroll_top = 0
|
41
|
+
self.show_match_lines = true
|
42
|
+
end
|
43
|
+
|
44
|
+
def index_files(working_set)
|
45
|
+
index = {}
|
46
|
+
prev_file = nil
|
47
|
+
sorted_items.each_with_index do |item, idx|
|
48
|
+
if prev_file == nil
|
49
|
+
# first item in set
|
50
|
+
prev_file = index[item.file_path] = { file_path: item.file_path, item_index: idx, prev_file: nil }
|
51
|
+
elsif item.file_path == prev_file[:file_path]
|
52
|
+
# another match within file, ignore it.
|
53
|
+
else
|
54
|
+
# next file
|
55
|
+
current_file = index[item.file_path] = { file_path: item.file_path, item_index: idx, prev_file: prev_file }
|
56
|
+
prev_file[:next_file] = current_file
|
57
|
+
prev_file = current_file
|
58
|
+
end
|
59
|
+
end
|
60
|
+
index
|
61
|
+
end
|
62
|
+
|
63
|
+
def selected_item
|
64
|
+
sorted_items[selected_item_index]
|
65
|
+
end
|
66
|
+
|
67
|
+
def restore_selection_state(from_working_set_view)
|
68
|
+
if idx = sorted_items.find_index(from_working_set_view.selected_item)
|
69
|
+
self.selected_item_index = idx
|
70
|
+
self.show_match_lines = from_working_set_view.show_match_lines
|
71
|
+
render_items_and_footer # have to render to set @scrollable_line_count or the next call won't work
|
72
|
+
set_scroll_top(from_working_set_view.scroll_top)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def title
|
77
|
+
if working_set.name
|
78
|
+
"Name: #{working_set.name}"
|
79
|
+
else
|
80
|
+
"Search: #{working_set.search}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def note
|
85
|
+
working_set.note
|
86
|
+
end
|
87
|
+
|
88
|
+
def needs_save?
|
89
|
+
!working_set.saved
|
90
|
+
end
|
91
|
+
|
92
|
+
def sorted_items
|
93
|
+
@_sorted_items ||= working_set.items.sort_by { |i| [i.file_path, i.row.to_i] }
|
94
|
+
end
|
95
|
+
|
96
|
+
def render
|
97
|
+
UserInputActor.set_user_input_mode :working_set
|
98
|
+
stdscr.clear
|
99
|
+
stdscr.refresh
|
100
|
+
render_title.refresh
|
101
|
+
render_items.refresh
|
102
|
+
render_footer.refresh
|
103
|
+
end
|
104
|
+
|
105
|
+
def toggle_match_lines
|
106
|
+
self.show_match_lines = !show_match_lines
|
107
|
+
self.scroll_top = 0
|
108
|
+
@item_win.clear
|
109
|
+
render_items.refresh
|
110
|
+
end
|
111
|
+
|
112
|
+
def render_items_and_footer
|
113
|
+
render_items.refresh
|
114
|
+
render_footer.refresh
|
115
|
+
end
|
116
|
+
|
117
|
+
def select_next_file
|
118
|
+
next_file = file_index[selected_item.file_path][:next_file]
|
119
|
+
self.selected_item_index = next_file[:item_index] if next_file
|
120
|
+
render_items_and_footer
|
121
|
+
end
|
122
|
+
|
123
|
+
def select_prev_file
|
124
|
+
prev_file = file_index[selected_item.file_path][:prev_file]
|
125
|
+
self.selected_item_index = prev_file[:item_index] if prev_file
|
126
|
+
render_items_and_footer
|
127
|
+
end
|
128
|
+
|
129
|
+
def select_next_item
|
130
|
+
self.selected_item_index += 1 unless selected_item_index >= sorted_items.size - 1
|
131
|
+
render_items_and_footer
|
132
|
+
end
|
133
|
+
|
134
|
+
def select_prev_item
|
135
|
+
self.selected_item_index -= 1 unless selected_item_index <= 0
|
136
|
+
render_items_and_footer
|
137
|
+
end
|
138
|
+
|
139
|
+
def scroll(delta)
|
140
|
+
return if @scrollable_line_count <= scrollable_height
|
141
|
+
set_scroll_top(scroll_top + delta)
|
142
|
+
render_items_and_footer
|
143
|
+
end
|
144
|
+
|
145
|
+
def set_scroll_top(value)
|
146
|
+
self.scroll_top = if value < 2
|
147
|
+
# Reached top
|
148
|
+
0
|
149
|
+
elsif (value + scrollable_height) > @scrollable_line_count
|
150
|
+
# Reached bottom
|
151
|
+
[@scrollable_line_count - scrollable_height, 0].max
|
152
|
+
else
|
153
|
+
# Normal scroll
|
154
|
+
value
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def scrollable_height
|
159
|
+
Ncurses.LINES - TITLE_ROWS - FOOTER_ROWS
|
160
|
+
end
|
161
|
+
|
162
|
+
def scroll_bottom
|
163
|
+
scroll_top + scrollable_height
|
164
|
+
end
|
165
|
+
|
166
|
+
def render_title
|
167
|
+
# Height, Width, Y, X note: (0 width == full width)
|
168
|
+
@title_win ||= Ncurses.newwin(1, 0, 0, 0)
|
169
|
+
@title_win.move 0, 0
|
170
|
+
print_field @title_win, :left, calc_cols(1), " "
|
171
|
+
@title_win.move 0, 0
|
172
|
+
with_color @title_win, :blue do
|
173
|
+
@title_win.printw title
|
174
|
+
end
|
175
|
+
if working_set.options["whole_word"]
|
176
|
+
with_color @title_win, :red do
|
177
|
+
@title_win.printw " [w]"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
# if needs_save?
|
181
|
+
# with_color @title_win, :red do
|
182
|
+
# @title_win.printw " +"
|
183
|
+
# end
|
184
|
+
# end
|
185
|
+
@title_win
|
186
|
+
end
|
187
|
+
|
188
|
+
def render_footer
|
189
|
+
# Height, Width, Y, X note: (0 width == full width)
|
190
|
+
@footer_win ||= Ncurses.newwin(1, 0, Ncurses.LINES - 1, 0)
|
191
|
+
@footer_win.move 0, 0
|
192
|
+
with_color @footer_win, :blue do
|
193
|
+
print_field @footer_win, :right, calc_cols(1), "#{selected_item_index + 1} of #{sorted_items.size} (#{file_index.keys.size} files)"
|
194
|
+
end
|
195
|
+
@footer_win
|
196
|
+
end
|
197
|
+
|
198
|
+
def render_items
|
199
|
+
|
200
|
+
# Height, Width, Y, X note: (0 width == full width)
|
201
|
+
@item_win ||= Ncurses.newwin(scrollable_height, 0, TITLE_ROWS, 0)
|
202
|
+
|
203
|
+
previous_file_path = nil
|
204
|
+
previous_row = 0
|
205
|
+
@scrollable_line_number = 0
|
206
|
+
@scrollable_line_count = 0
|
207
|
+
|
208
|
+
sorted_items.each do |item|
|
209
|
+
|
210
|
+
if !show_match_lines
|
211
|
+
if item.file_path != previous_file_path
|
212
|
+
color = item.file_path == selected_item.file_path ? :cyan : :green
|
213
|
+
puts_scrollable_item 0, color, item.file_path
|
214
|
+
end
|
215
|
+
else
|
216
|
+
# Print file name if it's a new file, a "--" separator if it's the same
|
217
|
+
# file but non-consecutive lines, otherwise just nothing.
|
218
|
+
if item.file_path == previous_file_path
|
219
|
+
if item.row > previous_row + 1
|
220
|
+
puts_scrollable_item 0, :white, " --"
|
221
|
+
end
|
222
|
+
else
|
223
|
+
if previous_file_path
|
224
|
+
puts_scrollable_item 0, :white, ""
|
225
|
+
end
|
226
|
+
color = item.file_path == selected_item.file_path ? :cyan : :green
|
227
|
+
puts_scrollable_item 0, color, item.file_path
|
228
|
+
end
|
229
|
+
|
230
|
+
# Print pre-match lines.
|
231
|
+
item.pre_match_lines.each_with_index do |line, i|
|
232
|
+
print_scrollable_item 0, :white, " #{item.row - item.pre_match_lines.size + i}"
|
233
|
+
puts_scrollable_item 5, :white, line
|
234
|
+
end
|
235
|
+
|
236
|
+
# Record match line number
|
237
|
+
if item == selected_item
|
238
|
+
@selected_item_line_number = @scrollable_line_number
|
239
|
+
end
|
240
|
+
|
241
|
+
# Print match line.
|
242
|
+
print_scrollable_item 0, :blue, "#{item == selected_item ? ">" : " "}"
|
243
|
+
print_scrollable_item 2, :yellow, item.row
|
244
|
+
puts_scrollable_item 5, :yellow, item.match_line
|
245
|
+
|
246
|
+
# Print post-match lines.
|
247
|
+
item.post_match_lines.each_with_index do |line, i|
|
248
|
+
print_scrollable_item 0, :white, " #{item.row + 1 + i}"
|
249
|
+
puts_scrollable_item 5, :white, line
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
previous_file_path = item.file_path
|
254
|
+
previous_row = item.row + item.post_match_lines.size
|
255
|
+
end
|
256
|
+
|
257
|
+
@scrollable_line_count = @scrollable_line_number
|
258
|
+
|
259
|
+
@item_win
|
260
|
+
end
|
261
|
+
|
262
|
+
def puts_scrollable_item(start_col, color_name, content)
|
263
|
+
print_scrollable_item(start_col, color_name, content)
|
264
|
+
@scrollable_line_number += 1
|
265
|
+
end
|
266
|
+
|
267
|
+
def print_scrollable_item(start_col, color_name, content, *color_content_pairs)
|
268
|
+
if scrolled_into_view?(@scrollable_line_number)
|
269
|
+
y = scrollable_item_line_number_to_screen_row(@scrollable_line_number)
|
270
|
+
x = start_col
|
271
|
+
with_color @item_win, color_name do
|
272
|
+
@item_win.mvprintw y, x, "%-#{Ncurses.COLS - start_col}s", content
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def scrollable_item_line_number_to_screen_row(line_number)
|
278
|
+
line_number - scroll_top
|
279
|
+
end
|
280
|
+
|
281
|
+
def scrolled_into_view?(line_number, context_lines: 0)
|
282
|
+
result = (line_number - context_lines) >= scroll_top && (line_number + context_lines) < scroll_bottom
|
283
|
+
debug_message "scrolled_into_view line_number: #{line_number} context_lines: #{context_lines} result: #{result.inspect}"
|
284
|
+
result
|
285
|
+
end
|
286
|
+
|
287
|
+
def selected_item_in_view?
|
288
|
+
scrolled_into_view? @selected_item_line_number, context_lines: $CONTEXT_LINES
|
289
|
+
end
|
290
|
+
|
291
|
+
def selected_item_scroll_delta
|
292
|
+
scroll_padding = 2 + $CONTEXT_LINES
|
293
|
+
debug_message "scrolling #{@selected_item_line_number} | #{scroll_top} | #{scroll_bottom}"
|
294
|
+
if scroll_top > (@selected_item_line_number - $CONTEXT_LINES)
|
295
|
+
# scroll up
|
296
|
+
((scroll_top - @selected_item_line_number) * -1) - scroll_padding
|
297
|
+
elsif scroll_bottom < (@selected_item_line_number + $CONTEXT_LINES)
|
298
|
+
# scroll down
|
299
|
+
@selected_item_line_number - scroll_bottom + scroll_padding
|
300
|
+
else
|
301
|
+
0
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def print_field(window, align, width, content)
|
306
|
+
window.printw "%#{align == :left ? "-" : ""}#{width}s", content
|
307
|
+
end
|
308
|
+
|
309
|
+
def calc_cols(percentage)
|
310
|
+
(Ncurses.COLS * percentage).to_i
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
data/lib/working_set.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class WorkingSet
|
4
|
+
attr_accessor :search, :options, :items, :name, :note, :saved
|
5
|
+
|
6
|
+
VERSION = "1.0.1"
|
7
|
+
|
8
|
+
def initialize(search = nil, options = nil, items = [])
|
9
|
+
self.search = search
|
10
|
+
self.options = options
|
11
|
+
self.items = []
|
12
|
+
items.each { |i| self.add i }
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(item)
|
16
|
+
if item.kind_of? WorkingSetItem
|
17
|
+
items.push item
|
18
|
+
else
|
19
|
+
items.push WorkingSetItem.new(item)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def inspect
|
24
|
+
str = <<EOS
|
25
|
+
WorkingSet #{object_id}
|
26
|
+
Search: #{search}
|
27
|
+
Options: #{options}
|
28
|
+
Items:
|
29
|
+
|
30
|
+
#{items.map(&:inspect).join("\n")}
|
31
|
+
EOS
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# DEV ONLY
|
2
|
+
# Among other things, this adds bundle-installed gems to the load path so the
|
3
|
+
# dependencies are require-able.
|
4
|
+
require 'bundler/setup' if ENV["WORKING_SET_DEV"] == "true"
|
5
|
+
|
6
|
+
# External gem dependencies are loaded here.
|
7
|
+
require 'celluloid/current'
|
8
|
+
require 'celluloid/io'
|
9
|
+
require 'ncurses'
|
10
|
+
require 'clipboard'
|
11
|
+
require 'tty-option'
|
12
|
+
|
13
|
+
# And zeitwerk takes care of auto-loading the ruby files in this gem.
|
14
|
+
require 'zeitwerk'
|
15
|
+
loader = Zeitwerk::Loader.for_gem
|
16
|
+
loader.setup
|
17
|
+
|
18
|
+
class WorkingSetCli
|
19
|
+
|
20
|
+
include TTY::Option
|
21
|
+
|
22
|
+
usage do
|
23
|
+
|
24
|
+
no_command # Doesn't seem to work as expected...
|
25
|
+
command nil # So I have to do this.
|
26
|
+
|
27
|
+
header "Working Set: A powerful companion for your favorite text editor."
|
28
|
+
|
29
|
+
desc "Working Set facilitates very fast searching powered by `ag`, has a convenient ncurses-based interface, and robust editor integration via bi-directional socket API."
|
30
|
+
desc "It pairs great with tmux and vim."
|
31
|
+
desc "See the README for more:\nhttps://github.com/coderifous/working_set"
|
32
|
+
|
33
|
+
example <<~EOS
|
34
|
+
Run with defaults:
|
35
|
+
$ working_set
|
36
|
+
EOS
|
37
|
+
|
38
|
+
example <<~EOS
|
39
|
+
Specify socket file:
|
40
|
+
$ working_set --socket=/tmp/ws_sock
|
41
|
+
EOS
|
42
|
+
|
43
|
+
example <<~EOS
|
44
|
+
Enable watching and auto-refresh for current directory:
|
45
|
+
$ working_set --watch
|
46
|
+
EOS
|
47
|
+
|
48
|
+
example <<~EOS
|
49
|
+
Enable watching and auto-refresh for specific directory, e.g. the app/ directory of a rails project:
|
50
|
+
$ working_set --watch app
|
51
|
+
EOS
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
flag :help do
|
56
|
+
short "-h"
|
57
|
+
long "--help"
|
58
|
+
desc "Print usage"
|
59
|
+
end
|
60
|
+
|
61
|
+
option :watch do
|
62
|
+
desc "Auto-refresh working set when file changes detected"
|
63
|
+
short "-w"
|
64
|
+
long "--watch=[path]"
|
65
|
+
end
|
66
|
+
|
67
|
+
option :socket do
|
68
|
+
desc "Set path for IPC socket file (for comms with text editor)"
|
69
|
+
short "-s"
|
70
|
+
long "--socket=path"
|
71
|
+
default ".working_set_socket"
|
72
|
+
end
|
73
|
+
|
74
|
+
option :context do
|
75
|
+
desc "How many lines around matches to show"
|
76
|
+
short "-c'"
|
77
|
+
long "--context=number"
|
78
|
+
convert :int
|
79
|
+
default 1
|
80
|
+
end
|
81
|
+
|
82
|
+
option :debug do
|
83
|
+
hidden
|
84
|
+
desc "Set path for debug logging."
|
85
|
+
short "-d"
|
86
|
+
long "--debug=[path]"
|
87
|
+
end
|
88
|
+
|
89
|
+
def run
|
90
|
+
parse
|
91
|
+
if params[:help]
|
92
|
+
print help
|
93
|
+
exit
|
94
|
+
else
|
95
|
+
init
|
96
|
+
$supervisor = AppSupervisor.run!
|
97
|
+
sleep 0.5 while $supervisor.alive? # I've need to occupy the main thread otherwise the program exits here.
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class AppSupervisor < Celluloid::Supervision::Container
|
102
|
+
supervise type: SetViewerActor, as: :set_viewer
|
103
|
+
supervise type: SetBuilderActor, as: :set_builder
|
104
|
+
supervise type: ApiInputActor, as: :api_input
|
105
|
+
supervise type: UserInputActor, as: :user_input
|
106
|
+
|
107
|
+
finalizer :clean_up_ncurses
|
108
|
+
|
109
|
+
def self.enable_live_watch!
|
110
|
+
supervise type: LiveUpdaterActor, as: :live_updater
|
111
|
+
end
|
112
|
+
|
113
|
+
# It seems exiting cleanly requires:
|
114
|
+
# - shutdown: to kill the supervised actors
|
115
|
+
# - terminate: to kill the supervisor itself
|
116
|
+
def do_shutdown
|
117
|
+
shutdown
|
118
|
+
terminate
|
119
|
+
end
|
120
|
+
|
121
|
+
def clean_up_ncurses
|
122
|
+
debug_message "cleaning up Ncurses"
|
123
|
+
Ncurses.echo
|
124
|
+
Ncurses.nocbreak
|
125
|
+
Ncurses.nl
|
126
|
+
Ncurses.endwin
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def init
|
134
|
+
init_debug
|
135
|
+
init_socket_file
|
136
|
+
init_live_watch
|
137
|
+
init_ncurses
|
138
|
+
$CONTEXT_LINES = params[:context]
|
139
|
+
end
|
140
|
+
|
141
|
+
def init_ncurses
|
142
|
+
Ncurses.initscr
|
143
|
+
Ncurses.cbreak # unbuffered input
|
144
|
+
Ncurses.noecho # turn off input echoing
|
145
|
+
Ncurses.nonl # turn off newline translation
|
146
|
+
Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt
|
147
|
+
Ncurses.stdscr.keypad(true) # turn on keypad mode
|
148
|
+
Ncurses.curs_set(0) # hidden cursor
|
149
|
+
|
150
|
+
Ncurses.start_color
|
151
|
+
Ncurses.use_default_colors
|
152
|
+
|
153
|
+
Colors.each_pair do |k,v|
|
154
|
+
Ncurses.init_pair v[:number], v[:pair][0], v[:pair][1]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def init_live_watch
|
159
|
+
AppSupervisor.enable_live_watch! if params.key?(:watch)
|
160
|
+
$LIVE_UPDATE_WATCH_PATH = params.key?(:watch) ? (params[:watch] || ".") : false
|
161
|
+
end
|
162
|
+
|
163
|
+
def init_socket_file
|
164
|
+
$SOCKET_PATH = params[:socket]
|
165
|
+
check_for_existing_socket_file
|
166
|
+
end
|
167
|
+
|
168
|
+
def check_for_existing_socket_file
|
169
|
+
if File.exists?($SOCKET_PATH)
|
170
|
+
puts "File #{$SOCKET_PATH.inspect} exists. Overwrite it? (y/N)"
|
171
|
+
require "io/console"
|
172
|
+
if STDIN.getch =~ /y/i
|
173
|
+
File.delete($SOCKET_PATH)
|
174
|
+
else
|
175
|
+
puts "Ok, exiting program."
|
176
|
+
exit
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def init_debug
|
182
|
+
if params.key?(:debug)
|
183
|
+
require 'tty-logger'
|
184
|
+
path = params[:debug] || "working_set.log"
|
185
|
+
log_file = File.open(path, "a")
|
186
|
+
log_file.sync = true
|
187
|
+
$logger = TTY::Logger.new do |config|
|
188
|
+
config.metadata = [:time]
|
189
|
+
config.level = :debug
|
190
|
+
config.output = log_file
|
191
|
+
end
|
192
|
+
Celluloid.logger = $logger
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
class Object
|
199
|
+
def debug_message(msg)
|
200
|
+
$logger.debug msg if $logger
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
WorkingSetCli.new.run
|
205
|
+
|