scryglass 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.irbrc +9 -0
- data/CHANGELOG.md +46 -0
- data/Gemfile.lock +18 -1
- data/README.md +28 -15
- data/example_config.rb +12 -2
- data/lib/refinements/ansiless_string_refinement.rb +145 -0
- data/lib/refinements/array_fit_to_refinement.rb +30 -9
- data/lib/refinements/clip_string_refinement.rb +22 -7
- data/lib/scryglass.rb +78 -49
- data/lib/scryglass/binding_tracker.rb +10 -0
- data/lib/scryglass/config.rb +11 -2
- data/lib/scryglass/lens_helper.rb +52 -7
- data/lib/scryglass/lens_panel.rb +45 -21
- data/lib/scryglass/ro.rb +84 -15
- data/lib/scryglass/ro_builder.rb +95 -16
- data/lib/scryglass/session.rb +333 -126
- data/lib/scryglass/session_manager.rb +154 -0
- data/lib/scryglass/tree_panel.rb +14 -10
- data/lib/scryglass/version.rb +1 -1
- data/lib/scryglass/view_panel.rb +43 -1
- data/scryglass.gemspec +4 -1
- metadata +61 -3
- data/lib/refinements/ansi_slice_string_refinement.rb +0 -65
data/lib/scryglass/ro.rb
CHANGED
@@ -43,14 +43,22 @@ module Scryglass
|
|
43
43
|
self.key_string = key.to_s.clip_at(key_clip_length)
|
44
44
|
self.key = key.model
|
45
45
|
else
|
46
|
-
|
46
|
+
# Note: `.inspect` may return *true newlines* for objects with a custom
|
47
|
+
# `.inspect`, which will sabotage scry's display, so we gsub thusly:
|
48
|
+
self.key_string = key.inspect
|
49
|
+
.gsub("\n", "\\n")
|
50
|
+
.clip_at(key_clip_length)
|
47
51
|
self.key = key
|
48
52
|
end
|
49
53
|
if val.class == Scryglass::ViewWrapper
|
50
54
|
self.value_string = val.to_s.clip_at(value_clip_length)
|
51
55
|
self.value = val.model
|
52
56
|
else
|
53
|
-
|
57
|
+
# Note: `.inspect` may return *true newlines* for objects with a custom
|
58
|
+
# `.inspect`, which will sabotage scry's display, so we gsub thusly:
|
59
|
+
self.value_string = val.inspect
|
60
|
+
.gsub("\n", "\\n")
|
61
|
+
.clip_at(value_clip_length)
|
54
62
|
self.value = val
|
55
63
|
end
|
56
64
|
|
@@ -78,9 +86,10 @@ module Scryglass
|
|
78
86
|
|
79
87
|
key_value_spacer =
|
80
88
|
key_value_pair? ? key_string + key_value_relationship_indicator : ''
|
81
|
-
|
89
|
+
dot = '•'
|
90
|
+
dot = "\e[36m#{dot}\e[00m" if Scryglass.config.dot_coloring # cyan then back to *default*
|
82
91
|
special_sub_ro_expansion_indicator =
|
83
|
-
special_sub_ros.any? && !expanded ?
|
92
|
+
special_sub_ros.any? && !expanded ? dot : ' '
|
84
93
|
|
85
94
|
left_fill_string + special_sub_ro_expansion_indicator +
|
86
95
|
key_value_spacer + value_indicator
|
@@ -168,12 +177,12 @@ module Scryglass
|
|
168
177
|
!!key_value_relationship_indicator
|
169
178
|
end
|
170
179
|
|
171
|
-
private
|
172
|
-
|
173
180
|
def special_sub_ros
|
174
181
|
sub_ros.select(&:special_sub_ro_type)
|
175
182
|
end
|
176
183
|
|
184
|
+
private
|
185
|
+
|
177
186
|
def normal_sub_ros
|
178
187
|
sub_ros.reject(&:special_sub_ro_type)
|
179
188
|
end
|
@@ -187,7 +196,9 @@ module Scryglass
|
|
187
196
|
# communicating with a solid preexisting symbol), but keeping the idea here:
|
188
197
|
# sub_ros_order_of_magnitude = normal_sub_ros.count.to_s.length
|
189
198
|
# wrappers.dup.insert(1, '•' * sub_ros_order_of_magnitude)
|
190
|
-
|
199
|
+
dots = '•••'
|
200
|
+
dots = "\e[36m#{dots}\e[00m" if Scryglass.config.dot_coloring # cyan then back to *default*
|
201
|
+
wrappers.dup.insert(1, dots)
|
191
202
|
else
|
192
203
|
wrappers
|
193
204
|
end
|
@@ -214,20 +225,78 @@ module Scryglass
|
|
214
225
|
(tab_length * depth) + consistent_margin
|
215
226
|
end
|
216
227
|
|
228
|
+
def cursor_char
|
229
|
+
Scryglass::Session::CURSOR_CHARACTER
|
230
|
+
end
|
231
|
+
|
217
232
|
def cursor_string
|
218
|
-
cursor =
|
233
|
+
cursor = cursor_char * cursor_length
|
234
|
+
|
235
|
+
cursor[0] = enum_status_char
|
236
|
+
cursor[1] = iv_status_char
|
237
|
+
cursor[2] = ar_status_char
|
238
|
+
|
239
|
+
cursor
|
240
|
+
end
|
241
|
+
|
242
|
+
def enum_status_char
|
243
|
+
enum_worth_checking = nugget? && value.is_a?(Enumerable)
|
244
|
+
return cursor_char unless enum_worth_checking
|
245
|
+
|
246
|
+
enum_check = Scryglass::Ro.safe_quick_check do
|
247
|
+
# value.any? Can take an eternity for a few specific objects, breaking
|
248
|
+
# the session when the cursor passes over them. Also breaks on read-
|
249
|
+
# locked IO objects.
|
250
|
+
enum_sub_ros.empty? && value.any?
|
251
|
+
end
|
252
|
+
|
253
|
+
return 'X' if enum_check.nil?
|
219
254
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
255
|
+
return '(' if enum_check
|
256
|
+
|
257
|
+
cursor_char
|
258
|
+
end
|
259
|
+
|
260
|
+
def iv_status_char
|
261
|
+
return cursor_char unless iv_sub_ros.empty?
|
262
|
+
|
263
|
+
iv_check = Scryglass::Ro.safe_quick_check do
|
264
|
+
value.instance_variables.any?
|
224
265
|
end
|
225
266
|
|
226
|
-
|
227
|
-
|
267
|
+
return 'X' if iv_check.nil?
|
268
|
+
|
269
|
+
return '@' if iv_check
|
270
|
+
|
271
|
+
cursor_char
|
272
|
+
end
|
273
|
+
|
274
|
+
def ar_status_char
|
275
|
+
return cursor_char unless ar_sub_ros.empty?
|
276
|
+
|
277
|
+
iv_check = Scryglass::Ro.safe_quick_check do
|
278
|
+
# Currently, this will always indicate hidden secrets if the object, with
|
279
|
+
# the given Scryglass config, doesn't yield any ar_sub_ros upon trying '.'
|
280
|
+
value.class.respond_to?(:reflections) # TODO: maybe dig more here?
|
228
281
|
end
|
229
282
|
|
230
|
-
|
283
|
+
return 'X' if iv_check.nil?
|
284
|
+
|
285
|
+
return '·' if iv_check
|
286
|
+
|
287
|
+
cursor_char
|
288
|
+
end
|
289
|
+
|
290
|
+
class << self
|
291
|
+
def safe_quick_check
|
292
|
+
begin
|
293
|
+
Timeout.timeout(0.05) do
|
294
|
+
yield
|
295
|
+
end
|
296
|
+
rescue
|
297
|
+
nil
|
298
|
+
end
|
299
|
+
end
|
231
300
|
end
|
232
301
|
end
|
233
302
|
end
|
data/lib/scryglass/ro_builder.rb
CHANGED
@@ -135,8 +135,49 @@ module Scryglass
|
|
135
135
|
new_ro
|
136
136
|
end
|
137
137
|
|
138
|
+
def add_message_no_items_found
|
139
|
+
message = { text: "No new sub-items were found with '#{last_keypress}'",
|
140
|
+
end_time: Time.now + 1.5 }
|
141
|
+
self.current_warning_messages << message
|
142
|
+
end
|
143
|
+
|
144
|
+
def smart_open_target_ros
|
145
|
+
original_ro_count = all_ros.count
|
146
|
+
original_special_sub_ro_count = current_ro.special_sub_ros.count
|
147
|
+
|
148
|
+
if special_command_targets.any?
|
149
|
+
task = Prog::Task.new(max_count: special_command_targets.count)
|
150
|
+
progress_bar << task
|
151
|
+
|
152
|
+
target_ros = special_command_targets.dup # dup because some commands
|
153
|
+
# create ros which are added to all_ros and then this process starts
|
154
|
+
# adding them to the list of things it tries to act on!
|
155
|
+
target_ros.each do |target_ro|
|
156
|
+
smart_open(target_ro)
|
157
|
+
task.tick
|
158
|
+
print_progress_bar
|
159
|
+
end
|
160
|
+
self.special_command_targets = []
|
161
|
+
else
|
162
|
+
smart_open(current_ro)
|
163
|
+
|
164
|
+
new_special_sub_ro_count = current_ro.special_sub_ros.count
|
165
|
+
new_sub_ros_were_added = new_special_sub_ro_count != original_special_sub_ro_count
|
166
|
+
|
167
|
+
if new_sub_ros_were_added
|
168
|
+
expand!(current_ro)
|
169
|
+
else
|
170
|
+
add_message_no_items_found
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
new_ro_count = all_ros.count
|
175
|
+
recalculate_indeces unless new_ro_count == original_ro_count
|
176
|
+
end
|
177
|
+
|
138
178
|
def build_instance_variables_for_target_ros
|
139
|
-
|
179
|
+
original_ro_count = all_ros.count
|
180
|
+
original_iv_sub_ro_count = current_ro.iv_sub_ros.count
|
140
181
|
|
141
182
|
if special_command_targets.any?
|
142
183
|
task = Prog::Task.new(max_count: special_command_targets.count)
|
@@ -153,15 +194,24 @@ module Scryglass
|
|
153
194
|
self.special_command_targets = []
|
154
195
|
else
|
155
196
|
build_iv_sub_ros_for(current_ro)
|
156
|
-
|
197
|
+
|
198
|
+
new_iv_sub_ro_count = current_ro.iv_sub_ros.count
|
199
|
+
new_iv_sub_ros_were_added = new_iv_sub_ro_count != original_iv_sub_ro_count
|
200
|
+
|
201
|
+
if new_iv_sub_ros_were_added
|
202
|
+
expand!(current_ro)
|
203
|
+
else
|
204
|
+
add_message_no_items_found
|
205
|
+
end
|
157
206
|
end
|
158
207
|
|
159
|
-
|
160
|
-
recalculate_indeces unless
|
208
|
+
new_ro_count = all_ros.count
|
209
|
+
recalculate_indeces unless new_ro_count == original_ro_count
|
161
210
|
end
|
162
211
|
|
163
212
|
def build_activerecord_relations_for_target_ros
|
164
|
-
|
213
|
+
original_ro_count = all_ros.count
|
214
|
+
original_ar_sub_ro_count = current_ro.ar_sub_ros.count
|
165
215
|
|
166
216
|
if special_command_targets.any?
|
167
217
|
task = Prog::Task.new(max_count: special_command_targets.count)
|
@@ -178,15 +228,24 @@ module Scryglass
|
|
178
228
|
self.special_command_targets = []
|
179
229
|
else
|
180
230
|
build_ar_sub_ros_for(current_ro)
|
181
|
-
|
231
|
+
|
232
|
+
new_ar_sub_ro_count = current_ro.ar_sub_ros.count
|
233
|
+
new_ar_sub_ros_were_added = new_ar_sub_ro_count != original_ar_sub_ro_count
|
234
|
+
|
235
|
+
if new_ar_sub_ros_were_added
|
236
|
+
expand!(current_ro)
|
237
|
+
else
|
238
|
+
add_message_no_items_found
|
239
|
+
end
|
182
240
|
end
|
183
|
-
|
241
|
+
new_ro_count = all_ros.count
|
184
242
|
|
185
|
-
recalculate_indeces unless
|
243
|
+
recalculate_indeces unless new_ro_count == original_ro_count
|
186
244
|
end
|
187
245
|
|
188
246
|
def build_enum_children_for_target_ros
|
189
|
-
|
247
|
+
original_ro_count = all_ros.count
|
248
|
+
original_enum_sub_ro_count = current_ro.enum_sub_ros.count
|
190
249
|
|
191
250
|
if special_command_targets.any?
|
192
251
|
task = Prog::Task.new(max_count: special_command_targets.count)
|
@@ -196,18 +255,32 @@ module Scryglass
|
|
196
255
|
# create ros which are added to all_ros and then this process starts
|
197
256
|
# adding them to the list of things it tries to act on!
|
198
257
|
target_ros.each do |target_ro|
|
199
|
-
|
258
|
+
build_enum_sub_ros_for(target_ro)
|
200
259
|
task.tick
|
201
260
|
print_progress_bar
|
202
261
|
end
|
203
262
|
self.special_command_targets = []
|
204
263
|
else
|
205
|
-
|
206
|
-
|
264
|
+
build_enum_sub_ros_for(current_ro)
|
265
|
+
|
266
|
+
new_enum_sub_ro_count = current_ro.enum_sub_ros.count
|
267
|
+
new_enum_sub_ros_were_added = new_enum_sub_ro_count != original_enum_sub_ro_count
|
268
|
+
|
269
|
+
if new_enum_sub_ros_were_added
|
270
|
+
expand!(current_ro)
|
271
|
+
else
|
272
|
+
add_message_no_items_found
|
273
|
+
end
|
207
274
|
end
|
208
275
|
|
209
|
-
|
210
|
-
recalculate_indeces unless
|
276
|
+
new_ro_count = all_ros.count
|
277
|
+
recalculate_indeces unless new_ro_count == original_ro_count
|
278
|
+
end
|
279
|
+
|
280
|
+
def smart_open(ro)
|
281
|
+
build_ar_sub_ros_for(ro) ||
|
282
|
+
build_iv_sub_ros_for(ro) ||
|
283
|
+
build_enum_sub_ros_for(ro)
|
211
284
|
end
|
212
285
|
|
213
286
|
def recalculate_indeces
|
@@ -254,6 +327,8 @@ module Scryglass
|
|
254
327
|
prog_task.tick
|
255
328
|
print_progress_bar
|
256
329
|
end
|
330
|
+
|
331
|
+
true
|
257
332
|
end
|
258
333
|
|
259
334
|
def build_ar_sub_ros_for(ro)
|
@@ -322,7 +397,7 @@ module Scryglass
|
|
322
397
|
relation_name.to_s
|
323
398
|
end
|
324
399
|
|
325
|
-
if (!ar_value || ar_value.empty?) || include_empty_associations
|
400
|
+
if (!ar_value || (ar_value.respond_to?(:empty?) && ar_value.empty?)) || include_empty_associations
|
326
401
|
ar_key = Scryglass::ViewWrapper.new(
|
327
402
|
relation_name,
|
328
403
|
string: relation_representation
|
@@ -338,9 +413,11 @@ module Scryglass
|
|
338
413
|
task.tick
|
339
414
|
print_progress_bar
|
340
415
|
end
|
416
|
+
|
417
|
+
true if ro.ar_sub_ros.any?
|
341
418
|
end
|
342
419
|
|
343
|
-
def
|
420
|
+
def build_enum_sub_ros_for(ro)
|
344
421
|
return if ro.enum_sub_ros.any?
|
345
422
|
return if ro.bucket?
|
346
423
|
return unless ro.value.is_a?(Enumerable)
|
@@ -383,6 +460,8 @@ module Scryglass
|
|
383
460
|
print_progress_bar
|
384
461
|
end
|
385
462
|
end
|
463
|
+
|
464
|
+
true
|
386
465
|
end
|
387
466
|
|
388
467
|
def rescue_to_viewwrapped_error
|
data/lib/scryglass/session.rb
CHANGED
@@ -9,15 +9,18 @@ class Scryglass::Session
|
|
9
9
|
|
10
10
|
attr_accessor :current_view_coords, :current_lens, :current_subject_type,
|
11
11
|
:view_panels, :current_panel_type,
|
12
|
-
:progress_bar
|
12
|
+
:progress_bar, :current_warning_messages
|
13
13
|
|
14
14
|
attr_accessor :user_signals, :last_search, :number_to_move
|
15
15
|
|
16
|
+
attr_accessor :session_manager, :signal_to_manager, :session_is_current,
|
17
|
+
:tab_icon, :session_view_start_time
|
18
|
+
|
16
19
|
CURSOR_CHARACTER = '–' # These are en dashes (alt+dash), not hyphens or em dashes.
|
17
20
|
|
18
|
-
SEARCH_PROMPT = "\e[7mSearch for (regex, case-sensitive):
|
21
|
+
SEARCH_PROMPT = "\e[7mSearch for (regex, case-sensitive): /\e[00m"
|
19
22
|
|
20
|
-
|
23
|
+
VARNAME_PROMPT = "\e[7mName your object(s): @\e[00m"
|
21
24
|
|
22
25
|
SUBJECT_TYPES = [
|
23
26
|
:value,
|
@@ -26,6 +29,66 @@ class Scryglass::Session
|
|
26
29
|
|
27
30
|
CSI = "\e[" # "(C)ontrol (S)equence (I)ntroducer" for ANSI sequences
|
28
31
|
|
32
|
+
KEY_MAP = {
|
33
|
+
escape: 'esc', # Not a normal keystroke, see: genuine_escape_key_press
|
34
|
+
ctrl_c: "\u0003",
|
35
|
+
quit_session: 'q',
|
36
|
+
delete_session_tab: 'Q',
|
37
|
+
change_session_right: "\t", # Tab
|
38
|
+
change_session_left: 'Z', # Shift+Tab (well, one of its signals, after "\e" and "[")
|
39
|
+
digit_1: '1',
|
40
|
+
digit_2: '2',
|
41
|
+
digit_3: '3',
|
42
|
+
digit_4: '4',
|
43
|
+
digit_5: '5',
|
44
|
+
digit_6: '6',
|
45
|
+
digit_7: '7',
|
46
|
+
digit_8: '8',
|
47
|
+
digit_9: '9',
|
48
|
+
digit_0: '0',
|
49
|
+
move_cursor_up: 'A', # Up arrow (well, one of its signals, after "\e" and "[")
|
50
|
+
move_cursor_down: 'B', # Down arrow (well, one of its signals, after "\e" and "[")
|
51
|
+
open_bucket: 'C', # Right arrow (well, one of its signals, after "\e" and "[")
|
52
|
+
close_bucket: 'D', # Left arrow (well, one of its signals, after "\e" and "[")
|
53
|
+
homerow_move_cursor_up: 'k', # To be like VIM arrow keys
|
54
|
+
homerow_move_cursor_up_fast: 'K', # To be like VIM arrow keys
|
55
|
+
homerow_move_cursor_down: 'j', # To be like VIM arrow keys
|
56
|
+
homerow_move_cursor_down_fast: 'J', # To be like VIM arrow keys
|
57
|
+
homerow_open_bucket: 'l', # To be like VIM arrow keys
|
58
|
+
homerow_close_bucket: 'h', # To be like VIM arrow keys
|
59
|
+
# Note, shift-UP and shift-DOWN are not here, as those work very
|
60
|
+
# differently: by virtue of the type-a-number-first functionality.
|
61
|
+
toggle_view_panel: ' ',
|
62
|
+
switch_lens: '>',
|
63
|
+
switch_subject_type: '<',
|
64
|
+
move_view_up: 'w',
|
65
|
+
move_view_down: 's',
|
66
|
+
move_view_left: 'a',
|
67
|
+
move_view_right: 'd',
|
68
|
+
move_view_up_fast: '∑', # Alt+w
|
69
|
+
move_view_down_fast: 'ß', # Alt+s
|
70
|
+
move_view_left_fast: 'å', # Alt+a
|
71
|
+
move_view_right_fast: '∂', # Alt+d
|
72
|
+
control_screen: '?',
|
73
|
+
build_instance_variables: '@',
|
74
|
+
build_ar_relations: '.',
|
75
|
+
build_enum_children: '(',
|
76
|
+
smart_open: 'o',
|
77
|
+
select_siblings: '|',
|
78
|
+
select_all: '*',
|
79
|
+
select_current: '-',
|
80
|
+
start_search: '/',
|
81
|
+
continue_search: 'n',
|
82
|
+
return_objects: "\r", # [ENTER],
|
83
|
+
name_objects: "="
|
84
|
+
}.freeze
|
85
|
+
|
86
|
+
PATIENT_ACTIONS = [
|
87
|
+
:control_screen,
|
88
|
+
:escape,
|
89
|
+
:name_objects,
|
90
|
+
].freeze
|
91
|
+
|
29
92
|
def initialize(seed)
|
30
93
|
self.all_ros = []
|
31
94
|
self.current_lens = 0
|
@@ -35,6 +98,12 @@ class Scryglass::Session
|
|
35
98
|
self.number_to_move = ''
|
36
99
|
self.user_signals = []
|
37
100
|
self.progress_bar = Prog::Pipe.new
|
101
|
+
self.current_warning_messages = []
|
102
|
+
self.session_manager = nil
|
103
|
+
self.signal_to_manager = nil
|
104
|
+
self.tab_icon = nil
|
105
|
+
self.session_is_current = false
|
106
|
+
self.session_view_start_time = nil
|
38
107
|
|
39
108
|
top_ro = roify(seed, parent_ro: nil, depth: 1)
|
40
109
|
top_ro.has_cursor = true
|
@@ -48,9 +117,19 @@ class Scryglass::Session
|
|
48
117
|
}
|
49
118
|
end
|
50
119
|
|
51
|
-
def
|
52
|
-
|
120
|
+
def top_ro
|
121
|
+
all_ros.first
|
122
|
+
end
|
123
|
+
|
124
|
+
def last_keypress
|
125
|
+
last_two_signals = user_signals.last(2)
|
126
|
+
last_two_signals.last || last_two_signals.first
|
127
|
+
end
|
128
|
+
|
129
|
+
def run_scry_ui
|
53
130
|
redraw = true
|
131
|
+
signal_to_manager = nil
|
132
|
+
self.session_view_start_time = Time.now # For this particular tab/session
|
54
133
|
|
55
134
|
## On hold: Record/Playback Functionality:
|
56
135
|
# case actions
|
@@ -67,7 +146,7 @@ class Scryglass::Session
|
|
67
146
|
# write over any previous valuable content the user had in the console.
|
68
147
|
print Hexes.opacify_screen_string(Hexes.simple_screen_slice(boot_screen))
|
69
148
|
|
70
|
-
while
|
149
|
+
while true
|
71
150
|
draw_screen if redraw
|
72
151
|
redraw = true
|
73
152
|
|
@@ -93,108 +172,131 @@ class Scryglass::Session
|
|
93
172
|
|
94
173
|
case new_signal
|
95
174
|
when nil
|
96
|
-
when
|
175
|
+
when KEY_MAP[:escape]
|
97
176
|
case current_panel_type
|
98
177
|
when :lens
|
99
178
|
self.current_panel_type = :tree
|
100
179
|
when :tree
|
101
180
|
clear_tracked_values
|
102
181
|
end
|
103
|
-
when
|
182
|
+
when KEY_MAP[:ctrl_c]
|
104
183
|
set_console_cursor_below_content
|
105
184
|
raise IRB::Abort, 'Ctrl+C Detected'
|
106
|
-
when
|
107
|
-
|
108
|
-
|
109
|
-
when
|
185
|
+
when KEY_MAP[:quit_session]
|
186
|
+
self.signal_to_manager = :quit
|
187
|
+
return
|
188
|
+
when KEY_MAP[:delete_session_tab]
|
189
|
+
self.signal_to_manager = :delete
|
190
|
+
return
|
191
|
+
when KEY_MAP[:control_screen]
|
192
|
+
remain_in_scry_session = run_help_screen_ui
|
193
|
+
unless remain_in_scry_session
|
194
|
+
self.signal_to_manager = :quit_from_help
|
195
|
+
return
|
196
|
+
end
|
197
|
+
when KEY_MAP[:digit_1]
|
110
198
|
self.number_to_move += '1'
|
111
|
-
|
112
|
-
# quickly and still have it process all the digits
|
113
|
-
|
199
|
+
# This allows you to type multi-digit number very
|
200
|
+
# quickly and still have it process all the digits:
|
201
|
+
redraw = false
|
202
|
+
when KEY_MAP[:digit_2]
|
114
203
|
self.number_to_move += '2'
|
115
204
|
redraw = false
|
116
|
-
when
|
205
|
+
when KEY_MAP[:digit_3]
|
117
206
|
self.number_to_move += '3'
|
118
207
|
redraw = false
|
119
|
-
when
|
208
|
+
when KEY_MAP[:digit_4]
|
120
209
|
self.number_to_move += '4'
|
121
210
|
redraw = false
|
122
|
-
when
|
211
|
+
when KEY_MAP[:digit_5]
|
123
212
|
self.number_to_move += '5'
|
124
213
|
redraw = false
|
125
|
-
when
|
214
|
+
when KEY_MAP[:digit_6]
|
126
215
|
self.number_to_move += '6'
|
127
216
|
redraw = false
|
128
|
-
when
|
217
|
+
when KEY_MAP[:digit_7]
|
129
218
|
self.number_to_move += '7'
|
130
219
|
redraw = false
|
131
|
-
when
|
220
|
+
when KEY_MAP[:digit_8]
|
132
221
|
self.number_to_move += '8'
|
133
222
|
redraw = false
|
134
|
-
when
|
223
|
+
when KEY_MAP[:digit_9]
|
135
224
|
self.number_to_move += '9'
|
136
225
|
redraw = false
|
137
|
-
when
|
226
|
+
when KEY_MAP[:digit_0]
|
138
227
|
if number_to_move[0] # You can append zeros to existing number_to_move...
|
139
228
|
self.number_to_move += '0'
|
140
229
|
redraw = false
|
141
230
|
else # ...but otherwise it's understood to be a view||cursor reset.
|
142
231
|
reset_the_view_or_cursor
|
143
232
|
end
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
when
|
233
|
+
|
234
|
+
when KEY_MAP[:move_cursor_up]
|
235
|
+
move_cursor_up_action
|
236
|
+
when KEY_MAP[:move_cursor_down]
|
237
|
+
move_cursor_down_action
|
238
|
+
when KEY_MAP[:open_bucket]
|
239
|
+
expand_targets
|
240
|
+
when KEY_MAP[:close_bucket]
|
241
|
+
collapse_targets
|
242
|
+
|
243
|
+
when KEY_MAP[:homerow_move_cursor_up]
|
244
|
+
move_cursor_up_action
|
245
|
+
when KEY_MAP[:homerow_move_cursor_up_fast]
|
246
|
+
move_cursor_up_action(12) # 12 matches the digits provided by shift+up
|
247
|
+
when KEY_MAP[:homerow_move_cursor_down]
|
248
|
+
move_cursor_down_action
|
249
|
+
when KEY_MAP[:homerow_move_cursor_down_fast]
|
250
|
+
move_cursor_down_action(12) # 12 matches the digits provided by shift+down
|
251
|
+
when KEY_MAP[:homerow_open_bucket]
|
157
252
|
expand_targets
|
158
|
-
when
|
253
|
+
when KEY_MAP[:homerow_close_bucket]
|
159
254
|
collapse_targets
|
160
|
-
|
255
|
+
|
256
|
+
when KEY_MAP[:toggle_view_panel]
|
161
257
|
toggle_view_panel
|
162
|
-
when
|
258
|
+
when KEY_MAP[:switch_lens]
|
163
259
|
scroll_lens_type
|
164
|
-
when
|
260
|
+
when KEY_MAP[:switch_subject_type]
|
165
261
|
toggle_current_subject_type
|
166
|
-
|
262
|
+
|
263
|
+
when KEY_MAP[:move_view_up]
|
167
264
|
current_view_panel.move_view_up(5)
|
168
|
-
when
|
265
|
+
when KEY_MAP[:move_view_down]
|
169
266
|
current_view_panel.move_view_down(5)
|
170
|
-
when
|
267
|
+
when KEY_MAP[:move_view_left]
|
171
268
|
current_view_panel.move_view_left(5)
|
172
|
-
when
|
269
|
+
when KEY_MAP[:move_view_right]
|
173
270
|
current_view_panel.move_view_right(5)
|
174
|
-
|
271
|
+
|
272
|
+
when KEY_MAP[:move_view_up_fast]
|
175
273
|
current_view_panel.move_view_up(50)
|
176
|
-
when
|
274
|
+
when KEY_MAP[:move_view_down_fast]
|
177
275
|
current_view_panel.move_view_down(50)
|
178
|
-
when
|
276
|
+
when KEY_MAP[:move_view_left_fast]
|
179
277
|
current_view_panel.move_view_left(50)
|
180
|
-
when
|
278
|
+
when KEY_MAP[:move_view_right_fast]
|
181
279
|
current_view_panel.move_view_right(50)
|
182
|
-
|
183
|
-
|
184
|
-
when '@'
|
280
|
+
|
281
|
+
when KEY_MAP[:build_instance_variables]
|
185
282
|
build_instance_variables_for_target_ros
|
186
283
|
tree_view.slide_view_to_cursor # Just a nice-to-have
|
187
|
-
when
|
284
|
+
when KEY_MAP[:build_ar_relations]
|
188
285
|
build_activerecord_relations_for_target_ros
|
189
286
|
tree_view.slide_view_to_cursor # Just a nice-to-have
|
190
|
-
when
|
287
|
+
when KEY_MAP[:build_enum_children]
|
191
288
|
build_enum_children_for_target_ros
|
192
289
|
tree_view.slide_view_to_cursor # Just a nice-to-have
|
193
|
-
when
|
290
|
+
when KEY_MAP[:smart_open]
|
291
|
+
smart_open_target_ros
|
292
|
+
tree_view.slide_view_to_cursor # Just a nice-to-have
|
293
|
+
|
294
|
+
when KEY_MAP[:select_siblings]
|
194
295
|
sibling_ros = if current_ro.top_ro?
|
195
296
|
[top_ro]
|
196
297
|
else
|
197
|
-
current_ro.parent_ro.sub_ros.dup
|
298
|
+
current_ro.parent_ro.sub_ros.dup
|
299
|
+
# ^If we don't dup,
|
198
300
|
# then '-' can remove ros from `sub_ros`.
|
199
301
|
end
|
200
302
|
if special_command_targets.sort == sibling_ros.sort
|
@@ -202,61 +304,126 @@ class Scryglass::Session
|
|
202
304
|
else
|
203
305
|
self.special_command_targets = sibling_ros
|
204
306
|
end
|
205
|
-
when
|
206
|
-
all_the_ros = all_ros.dup
|
307
|
+
when KEY_MAP[:select_all]
|
308
|
+
all_the_ros = all_ros.dup
|
309
|
+
# ^If we don't dup,
|
207
310
|
# then '-' can remove ros from all_ros.
|
208
311
|
if special_command_targets.sort == all_the_ros.sort
|
209
312
|
self.special_command_targets = []
|
210
313
|
else
|
211
314
|
self.special_command_targets = all_the_ros
|
212
315
|
end
|
213
|
-
when
|
316
|
+
when KEY_MAP[:select_current]
|
214
317
|
if special_command_targets.include?(current_ro)
|
215
318
|
special_command_targets.delete(current_ro)
|
216
319
|
else
|
217
320
|
special_command_targets << current_ro
|
218
321
|
end
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
224
|
-
$stdout.print SEARCH_PROMPT
|
225
|
-
$stdout.write "#{CSI}1;#{SEARCH_PROMPT.ansiless_length + 1}H" # (Moves
|
226
|
-
# console cursor to just after the search prompt, before user types)
|
227
|
-
query = $stdin.gets.chomp
|
228
|
-
unless query.empty?
|
229
|
-
self.last_search = query
|
230
|
-
go_to_next_search_result
|
231
|
-
end
|
232
|
-
when 'n'
|
322
|
+
|
323
|
+
when KEY_MAP[:start_search]
|
324
|
+
initiate_search
|
325
|
+
when KEY_MAP[:continue_search]
|
233
326
|
if last_search
|
234
327
|
go_to_next_search_result
|
235
328
|
else
|
236
|
-
|
237
|
-
|
238
|
-
sleep 2
|
329
|
+
message = { text: 'No Search has been entered', end_time: Time.now + 2 }
|
330
|
+
self.current_warning_messages << message
|
239
331
|
end
|
240
|
-
|
241
|
-
|
242
|
-
|
332
|
+
|
333
|
+
when KEY_MAP[:change_session_right]
|
334
|
+
self.signal_to_manager = :change_session_right
|
335
|
+
return
|
336
|
+
when KEY_MAP[:change_session_left]
|
337
|
+
self.signal_to_manager = :change_session_left
|
338
|
+
return
|
339
|
+
when KEY_MAP[:name_objects]
|
340
|
+
name_subjects_of_target_ros
|
341
|
+
when KEY_MAP[:return_objects]
|
342
|
+
self.signal_to_manager = :return
|
343
|
+
subjects = subjects_of_target_ros
|
344
|
+
self.special_command_targets = []
|
345
|
+
return subjects
|
243
346
|
end
|
244
347
|
|
245
|
-
|
348
|
+
beep_if_user_had_to_wait(wait_start_time)
|
246
349
|
end
|
247
350
|
end
|
248
351
|
|
249
|
-
def
|
250
|
-
|
352
|
+
def set_console_cursor_below_content(floor_the_cursor:)
|
353
|
+
if floor_the_cursor
|
354
|
+
screen_height, _screen_width = $stdout.winsize
|
355
|
+
$stdout.write "#{CSI}#{screen_height};1H\n" # (Moves console cursor to bottom left corner, then one more)
|
356
|
+
return
|
357
|
+
end
|
358
|
+
|
359
|
+
bare_screen_string =
|
360
|
+
current_view_panel.visible_header_string + "\n" +
|
361
|
+
current_view_panel.visible_body_string
|
362
|
+
split_lines = bare_screen_string.split("\n")
|
363
|
+
rows_filled = split_lines.count
|
364
|
+
$stdout.write "#{CSI}#{rows_filled};1H\n" # Moves console cursor to bottom
|
365
|
+
# of *content*, then one more.
|
251
366
|
end
|
252
367
|
|
253
|
-
def
|
254
|
-
|
255
|
-
|
368
|
+
def tab_string
|
369
|
+
top_ro_preview = top_ro.value_string
|
370
|
+
tab = if session_is_current
|
371
|
+
"\e[7m #{tab_icon}: #{top_ro_preview} \e[00m"
|
372
|
+
else
|
373
|
+
" \e[7m#{tab_icon}:\e[00m #{top_ro_preview} "
|
374
|
+
end
|
375
|
+
tab
|
376
|
+
end
|
377
|
+
|
378
|
+
def subjects_of_target_ros
|
379
|
+
if special_command_targets.any?
|
380
|
+
return special_command_targets.map(&:current_subject)
|
381
|
+
end
|
382
|
+
|
383
|
+
current_ro.current_subject
|
256
384
|
end
|
257
385
|
|
258
386
|
private
|
259
387
|
|
388
|
+
def beep_if_user_had_to_wait(wait_start_time)
|
389
|
+
patient_keys = KEY_MAP.slice(*PATIENT_ACTIONS).values
|
390
|
+
user_has_waited_at_least_four_seconds =
|
391
|
+
Time.now - wait_start_time > 4 &&
|
392
|
+
!patient_keys.include?(last_keypress)
|
393
|
+
print "\a" if user_has_waited_at_least_four_seconds # (Audio 'beep')
|
394
|
+
end
|
395
|
+
|
396
|
+
def initiate_search
|
397
|
+
_screen_height, screen_width = $stdout.winsize
|
398
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
399
|
+
$stdout.print ' ' * screen_width
|
400
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
401
|
+
$stdout.print SEARCH_PROMPT
|
402
|
+
$stdout.write "#{CSI}1;#{SEARCH_PROMPT.ansiless_length + 1}H" # (Moves
|
403
|
+
# console cursor to just after the search prompt, before user types)
|
404
|
+
query = $stdin.gets.chomp
|
405
|
+
unless query.empty?
|
406
|
+
self.last_search = query
|
407
|
+
go_to_next_search_result
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
def move_cursor_up_action(action_count = nil)
|
412
|
+
action_count ||= !number_to_move.empty? ? number_to_move.to_i : 1
|
413
|
+
navigate_up_multiple(action_count)
|
414
|
+
|
415
|
+
self.number_to_move = ''
|
416
|
+
tree_view.slide_view_to_cursor
|
417
|
+
end
|
418
|
+
|
419
|
+
def move_cursor_down_action(action_count = nil)
|
420
|
+
action_count ||= !number_to_move.empty? ? number_to_move.to_i : 1
|
421
|
+
navigate_down_multiple(action_count)
|
422
|
+
|
423
|
+
self.number_to_move = ''
|
424
|
+
tree_view.slide_view_to_cursor
|
425
|
+
end
|
426
|
+
|
260
427
|
def clear_tracked_values
|
261
428
|
self.special_command_targets = []
|
262
429
|
self.last_search = nil
|
@@ -270,6 +437,27 @@ class Scryglass::Session
|
|
270
437
|
print bar unless bar.tr(' ', '').empty?
|
271
438
|
end
|
272
439
|
|
440
|
+
def print_current_warning_messages
|
441
|
+
return if current_warning_messages.empty?
|
442
|
+
|
443
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
444
|
+
wing = ' ' * 3
|
445
|
+
|
446
|
+
self.current_warning_messages.reject! { |message| Time.now > message[:end_time] }
|
447
|
+
messages = current_warning_messages.map { |message| message[:text] }
|
448
|
+
print messages.map { |message| "\e[7m#{wing + message + wing}\e[00m" }.join("\n")
|
449
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
450
|
+
end
|
451
|
+
|
452
|
+
def print_session_tabs_bar_if_changed
|
453
|
+
seconds_in_tab = Time.now - session_view_start_time
|
454
|
+
if seconds_in_tab < 2
|
455
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
456
|
+
print session_manager.session_tabs_bar
|
457
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
273
461
|
def current_view_panel
|
274
462
|
view_panels[current_panel_type]
|
275
463
|
end
|
@@ -282,14 +470,6 @@ class Scryglass::Session
|
|
282
470
|
view_panels[:lens]
|
283
471
|
end
|
284
472
|
|
285
|
-
def colorize(screen_string)
|
286
|
-
dot = '•'
|
287
|
-
cyan_dot = "\e[36m#{dot}\e[00m" # cyan then back to *default*
|
288
|
-
screen_string.gsub!('•', cyan_dot)
|
289
|
-
|
290
|
-
screen_string
|
291
|
-
end
|
292
|
-
|
293
473
|
def display_active_searching_indicator
|
294
474
|
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
295
475
|
message = ' Searching... '
|
@@ -332,13 +512,8 @@ class Scryglass::Session
|
|
332
512
|
tree_view.current_view_coords = { y: 0, x: 0 }
|
333
513
|
tree_view.slide_view_to_cursor
|
334
514
|
else
|
335
|
-
|
336
|
-
|
337
|
-
pad = SEARCH_PROMPT.length - message.length
|
338
|
-
wing = '-' * (pad / 2)
|
339
|
-
|
340
|
-
$stdout.write "\e[7m#{wing + message + wing}\e[00m"
|
341
|
-
sleep 2
|
515
|
+
message = { text: 'No Match Found', end_time: Time.now + 2 }
|
516
|
+
self.current_warning_messages << message
|
342
517
|
end
|
343
518
|
end
|
344
519
|
|
@@ -346,7 +521,7 @@ class Scryglass::Session
|
|
346
521
|
previous_signal = user_signals.last
|
347
522
|
new_signal =
|
348
523
|
begin
|
349
|
-
Timeout.timeout(0.
|
524
|
+
Timeout.timeout(0.3) { $stdin.getch }
|
350
525
|
rescue Timeout::Error
|
351
526
|
nil
|
352
527
|
end
|
@@ -381,16 +556,17 @@ class Scryglass::Session
|
|
381
556
|
new_signal = fetch_user_signal
|
382
557
|
|
383
558
|
case new_signal
|
384
|
-
when
|
559
|
+
when nil
|
560
|
+
when KEY_MAP[:escape]
|
385
561
|
return true
|
386
|
-
when
|
562
|
+
when KEY_MAP[:control_screen]
|
387
563
|
current_help_screen_index += 1
|
388
|
-
when
|
564
|
+
when KEY_MAP[:quit_session]
|
389
565
|
$stdout.write "#{CSI}#{screen_height};1H" # (Moves console cursor to
|
390
566
|
# bottom left corner). This helps 'q' not print the console prompt at
|
391
567
|
# the top of the screen, overlapping with the old display.
|
392
568
|
return false
|
393
|
-
when
|
569
|
+
when KEY_MAP[:ctrl_c]
|
394
570
|
screen_height, _screen_width = $stdout.winsize
|
395
571
|
puts "\n" * screen_height
|
396
572
|
raise IRB::Abort, 'Ctrl+C Detected'
|
@@ -446,37 +622,68 @@ class Scryglass::Session
|
|
446
622
|
current_view_panel.ensure_correct_view_coords
|
447
623
|
screen_string = current_view_panel.screen_string
|
448
624
|
|
449
|
-
screen_string = colorize(screen_string) if Scryglass.config.dot_coloring
|
450
625
|
Hexes.overwrite_screen(screen_string)
|
451
626
|
$stdout.write "#{CSI}1;1H" # Moves terminal cursor to top left corner,
|
452
627
|
# mostly for consistency.
|
628
|
+
print_current_warning_messages
|
629
|
+
print_session_tabs_bar_if_changed
|
453
630
|
end
|
454
631
|
|
455
|
-
def
|
456
|
-
bare_screen_string =
|
457
|
-
current_view_panel.visible_header_string + "\n" +
|
458
|
-
current_view_panel.visible_body_string
|
459
|
-
split_lines = bare_screen_string.split("\n")
|
460
|
-
rows_filled = split_lines.count
|
461
|
-
$stdout.write "#{CSI}#{rows_filled};1H\n" # Moves console cursor to bottom
|
462
|
-
# of *content*, then one more.
|
463
|
-
end
|
464
|
-
|
465
|
-
def visually_close_ui
|
632
|
+
def get_subject_name_from_user
|
466
633
|
_screen_height, screen_width = $stdout.winsize
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
634
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
635
|
+
$stdout.print ' ' * screen_width
|
636
|
+
$stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
|
637
|
+
$stdout.print VARNAME_PROMPT
|
638
|
+
$stdout.write "#{CSI}1;#{VARNAME_PROMPT.ansiless_length + 1}H" # (Moves
|
639
|
+
# console cursor to just after the varname prompt, before user types)
|
640
|
+
$stdin.gets.chomp
|
641
|
+
end
|
642
|
+
|
643
|
+
def name_subjects_of_target_ros
|
644
|
+
typed_name = get_subject_name_from_user
|
645
|
+
typed_name = typed_name.tr(' ', '')
|
646
|
+
|
647
|
+
if typed_name.empty?
|
648
|
+
message = { text: 'Instance Variable name cannot be blank',
|
649
|
+
end_time: Time.now + 2 }
|
650
|
+
self.current_warning_messages << message
|
651
|
+
print "\a" # (Audio 'beep')
|
652
|
+
return
|
653
|
+
end
|
471
654
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
655
|
+
current_console_binding = session_manager.current_console_binding
|
656
|
+
preexisting_iv_names = current_console_binding
|
657
|
+
.eval('instance_variables') # Different than just `.instance_variables`
|
658
|
+
.map { |iv| iv.to_s.tr('@', '') }
|
659
|
+
all_method_names = preexisting_iv_names |
|
660
|
+
current_console_binding.methods |
|
661
|
+
current_console_binding.singleton_methods |
|
662
|
+
current_console_binding.private_methods
|
663
|
+
conflicting_method_name = all_method_names.find do |method_name|
|
664
|
+
pure_method_name = method_name.to_s.tr('=', '')
|
665
|
+
typed_name == pure_method_name
|
477
666
|
end
|
478
667
|
|
479
|
-
|
668
|
+
if conflicting_method_name
|
669
|
+
message = { text: 'Instance Variable name conflict',
|
670
|
+
end_time: Time.now + 2 }
|
671
|
+
self.current_warning_messages << message
|
672
|
+
print "\a" # (Audio 'beep')
|
673
|
+
return
|
674
|
+
end
|
675
|
+
|
676
|
+
set_iv_name_in_console =
|
677
|
+
"@#{typed_name} = " \
|
678
|
+
"$scry_session_manager.current_session.subjects_of_target_ros"
|
679
|
+
current_console_binding.eval(set_iv_name_in_console)
|
680
|
+
session_manager.current_binding_tracker.user_named_variables << "@#{typed_name}"
|
681
|
+
|
682
|
+
message = { text: "#{subjects_of_target_ros.class} assigned to: @#{typed_name}",
|
683
|
+
end_time: Time.now + 2 }
|
684
|
+
self.current_warning_messages << message
|
685
|
+
|
686
|
+
self.special_command_targets = []
|
480
687
|
end
|
481
688
|
|
482
689
|
def navigate_up_multiple(action_count)
|