scryglass 1.1.0 → 2.0.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 +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)
|