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
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Scryglass
|
4
|
+
class SessionManager
|
5
|
+
using AnsilessStringRefinement
|
6
|
+
using ArrayFitToRefinement
|
7
|
+
using ClipStringRefinement
|
8
|
+
|
9
|
+
attr_accessor :scry_sessions
|
10
|
+
attr_accessor :binding_trackers_by_receiver
|
11
|
+
attr_accessor :current_binding_receiver
|
12
|
+
attr_accessor :unused_tab_icons
|
13
|
+
|
14
|
+
SESSION_CLOSED_MESSAGE = '(Exited scry! Resume session with just `scry`)'
|
15
|
+
|
16
|
+
NAMED_VARIABLES_MESSAGE = "\nCustom instance variables:"
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
self.scry_sessions = []
|
20
|
+
self.binding_trackers_by_receiver = {}
|
21
|
+
self.current_binding_receiver = nil
|
22
|
+
|
23
|
+
alphabet = ('A'..'Z').to_a
|
24
|
+
digits = ('2'..'9').to_a
|
25
|
+
self.unused_tab_icons =
|
26
|
+
alphabet + digits.product(alphabet).map { |pair| pair.reverse.join }
|
27
|
+
end
|
28
|
+
|
29
|
+
# For consistency, we reference the same binding tracker (and thus
|
30
|
+
# console_binding) every time for a given receiver.
|
31
|
+
def track_binding!(console_binding)
|
32
|
+
self.current_binding_receiver = console_binding.receiver
|
33
|
+
self.binding_trackers_by_receiver[current_binding_receiver] ||=
|
34
|
+
Scryglass::BindingTracker.new(console_binding: console_binding)
|
35
|
+
end
|
36
|
+
|
37
|
+
def <<(session)
|
38
|
+
set_current_session!(session)
|
39
|
+
session.session_manager = self
|
40
|
+
session.tab_icon = unused_tab_icons.shift
|
41
|
+
# TODO: name that session?
|
42
|
+
self.scry_sessions << session
|
43
|
+
end
|
44
|
+
|
45
|
+
def session_tabs_bar
|
46
|
+
_screen_height, screen_width = $stdout.winsize
|
47
|
+
|
48
|
+
tab_indicators = scry_sessions.map do |session|
|
49
|
+
session.tab_string.clip_at(screen_width / 3, ignore_ansi_codes: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
compressed_tab_indicators = tab_indicators.compress_to(screen_width, ignore_ansi_codes: true)
|
53
|
+
|
54
|
+
packed_tabs = compressed_tab_indicators.join
|
55
|
+
pad_length = screen_width - packed_tabs.ansiless_length
|
56
|
+
packed_tabs + ('#' * pad_length) + "\n" + ('#' * screen_width)
|
57
|
+
end
|
58
|
+
|
59
|
+
def current_session
|
60
|
+
scry_sessions.find(&:session_is_current)
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_console_binding
|
64
|
+
current_binding_tracker.console_binding
|
65
|
+
end
|
66
|
+
|
67
|
+
def run_scry_ui
|
68
|
+
while current_session
|
69
|
+
session_return = current_session.run_scry_ui
|
70
|
+
|
71
|
+
case current_session.signal_to_manager
|
72
|
+
when :return
|
73
|
+
visually_close_ui
|
74
|
+
return session_return
|
75
|
+
when :quit
|
76
|
+
visually_close_ui
|
77
|
+
return
|
78
|
+
when :quit_from_help
|
79
|
+
visually_close_ui(floor_the_cursor: true)
|
80
|
+
return
|
81
|
+
when :delete
|
82
|
+
old_session = current_session
|
83
|
+
visually_close_ui
|
84
|
+
if scry_sessions.index(old_session) > 0
|
85
|
+
change_session_left!
|
86
|
+
else
|
87
|
+
change_session_right!
|
88
|
+
end
|
89
|
+
delete_session!(old_session)
|
90
|
+
when :change_session_left # and if there's only one session?
|
91
|
+
change_session_left!
|
92
|
+
when :change_session_right # and if there's only one session?
|
93
|
+
change_session_right!
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def current_binding_tracker
|
99
|
+
binding_trackers_by_receiver[current_binding_receiver]
|
100
|
+
end
|
101
|
+
|
102
|
+
def current_user_named_variables
|
103
|
+
current_binding_tracker.user_named_variables
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def visually_close_ui(floor_the_cursor: false)
|
109
|
+
_screen_height, screen_width = $stdout.winsize
|
110
|
+
current_session.set_console_cursor_below_content(
|
111
|
+
floor_the_cursor: floor_the_cursor
|
112
|
+
)
|
113
|
+
puts '·' * screen_width, "\n"
|
114
|
+
puts SESSION_CLOSED_MESSAGE
|
115
|
+
puts user_named_variables_outro if current_user_named_variables.any?
|
116
|
+
end
|
117
|
+
|
118
|
+
def user_named_variables_outro
|
119
|
+
puts NAMED_VARIABLES_MESSAGE
|
120
|
+
puts current_user_named_variables.map { |s| " #{s}\n" }
|
121
|
+
end
|
122
|
+
|
123
|
+
def delete_session!(session)
|
124
|
+
scry_sessions.delete(session)
|
125
|
+
end
|
126
|
+
|
127
|
+
def session_right_of(session)
|
128
|
+
return scry_sessions.first if session == scry_sessions.last
|
129
|
+
|
130
|
+
index_of_session = scry_sessions.index(session)
|
131
|
+
scry_sessions[index_of_session + 1]
|
132
|
+
end
|
133
|
+
|
134
|
+
def session_left_of(session)
|
135
|
+
index_of_session = scry_sessions.index(session)
|
136
|
+
scry_sessions[index_of_session - 1]
|
137
|
+
end
|
138
|
+
|
139
|
+
def change_session_left!
|
140
|
+
next_session = session_left_of(current_session)
|
141
|
+
set_current_session!(next_session)
|
142
|
+
end
|
143
|
+
|
144
|
+
def change_session_right!
|
145
|
+
next_session = session_right_of(current_session)
|
146
|
+
set_current_session!(next_session)
|
147
|
+
end
|
148
|
+
|
149
|
+
def set_current_session!(session)
|
150
|
+
scry_sessions.each { |session| session.session_is_current = false }
|
151
|
+
session.session_is_current = true
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/scryglass/tree_panel.rb
CHANGED
@@ -71,7 +71,9 @@ module Scryglass
|
|
71
71
|
last_search_message = " Last search: #{last_search}"
|
72
72
|
end
|
73
73
|
if [special_targets_message, number_to_move_message, last_search].none?
|
74
|
-
|
74
|
+
controls_key = Scryglass::Session::KEY_MAP[:control_screen]
|
75
|
+
help_key_reminder = "Press '#{controls_key}' for controls " \
|
76
|
+
"(v#{Scryglass::VERSION})"
|
75
77
|
end
|
76
78
|
|
77
79
|
tree_header_items = [
|
@@ -91,27 +93,29 @@ module Scryglass
|
|
91
93
|
|
92
94
|
split_lines = uncut_body_string.split("\n")
|
93
95
|
sliced_lines = split_lines.map do |string|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
# opacify here, I need to account for nils when the view is fully
|
98
|
-
# beyond the shorter lines.
|
96
|
+
string.ansi_slice(current_view_coords[:x], screen_width) || '' # If I
|
97
|
+
# don't want to opacify here, I need to account for nils when the view
|
98
|
+
# is fully beyond the shorter lines.
|
99
99
|
end
|
100
100
|
|
101
|
-
sliced_lines
|
101
|
+
sliced_lines
|
102
102
|
end
|
103
103
|
|
104
104
|
def recalculate_y_boundaries
|
105
|
-
|
105
|
+
number_of_lines = scry_session.all_ros.select(&:visible?).count
|
106
|
+
preview_row = 1
|
107
|
+
self.y_boundaries = 0...(number_of_lines + preview_row)
|
106
108
|
end
|
107
109
|
|
108
110
|
def recalculate_x_boundaries
|
109
111
|
_screen_height, screen_width = $stdout.winsize
|
110
112
|
|
111
113
|
split_lines = uncut_body_string.split("\n")
|
112
|
-
length_of_longest_line = split_lines.map(&:
|
114
|
+
length_of_longest_line = split_lines.map(&:ansiless_length).max
|
113
115
|
max_line_length = [length_of_longest_line, screen_width].max
|
114
|
-
|
116
|
+
preview_column = 1
|
117
|
+
|
118
|
+
self.x_boundaries = 0...(max_line_length + preview_column)
|
115
119
|
end
|
116
120
|
|
117
121
|
def top_visible_ro_of_tree_view
|
data/lib/scryglass/version.rb
CHANGED
data/lib/scryglass/view_panel.rb
CHANGED
@@ -72,11 +72,53 @@ module Scryglass
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def visible_body_string
|
75
|
-
|
75
|
+
_screen_height, screen_width = $stdout.winsize
|
76
|
+
screen_bottom_index = (body_screen_height - 1)
|
77
|
+
screen_right_edge_index = (screen_width - 1)
|
78
|
+
|
79
|
+
body_array = visible_body_slice(uncut_body_string)
|
80
|
+
|
81
|
+
marked_body_array =
|
82
|
+
body_array.map do |line|
|
83
|
+
last_visible_char_of_line =
|
84
|
+
line.ansiless_pick(screen_right_edge_index)
|
85
|
+
|
86
|
+
if last_visible_char_of_line
|
87
|
+
line.ansiless_set!(screen_right_edge_index, '·')
|
88
|
+
end
|
89
|
+
|
90
|
+
line
|
91
|
+
end
|
92
|
+
|
93
|
+
bottom_edge_line = marked_body_array[screen_bottom_index]
|
94
|
+
|
95
|
+
if bottom_edge_line
|
96
|
+
bottom_edge_line_has_content =
|
97
|
+
!bottom_edge_line.ansiless.tr(' ·', '').empty?
|
98
|
+
|
99
|
+
if bottom_edge_line_has_content
|
100
|
+
bottom_edge_line_dot_preview =
|
101
|
+
bottom_edge_line.ansiless.gsub(/[^\s]/, '·')
|
102
|
+
end
|
103
|
+
|
104
|
+
marked_body_array[screen_bottom_index] =
|
105
|
+
bottom_edge_line_dot_preview ||
|
106
|
+
bottom_edge_line_default_truncation_dots
|
107
|
+
end
|
108
|
+
|
109
|
+
marked_body_array.join("\n")
|
76
110
|
end
|
77
111
|
|
78
112
|
private
|
79
113
|
|
114
|
+
def bottom_edge_line_default_truncation_dots
|
115
|
+
_screen_height, screen_width = $stdout.winsize
|
116
|
+
dots = '· ·· ··· ·········· ··· ·· ·'
|
117
|
+
pad_length = (screen_width - dots.length) / 2
|
118
|
+
|
119
|
+
(' ' * pad_length) + dots
|
120
|
+
end
|
121
|
+
|
80
122
|
def visible_header_slice(uncut_header_string)
|
81
123
|
Hexes.simple_screen_slice(uncut_header_string)
|
82
124
|
end
|
data/scryglass.gemspec
CHANGED
@@ -43,5 +43,8 @@ Gem::Specification.new do |spec|
|
|
43
43
|
|
44
44
|
spec.add_development_dependency 'bundler', '~> 2.1'
|
45
45
|
spec.add_development_dependency 'rake', '~> 12.0'
|
46
|
-
|
46
|
+
spec.add_development_dependency 'pry-rescue'
|
47
|
+
spec.add_runtime_dependency 'amazing_print'
|
48
|
+
spec.add_runtime_dependency 'method_source'
|
49
|
+
spec.add_runtime_dependency 'binding_of_caller'
|
47
50
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scryglass
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gavin Myers
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,62 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '12.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-rescue
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: amazing_print
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: method_source
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: binding_of_caller
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
41
97
|
description: |-
|
42
98
|
Scryglass is a ruby console tool for visualizing and actively exploring objects (large, nested, interrelated, or unfamiliar). You can navigate nested arrays, hashes, instance variables, ActiveRecord relations, and unknown Enumerable types like anexpandable/collapsable file tree in an intuitive UI.
|
43
99
|
|
@@ -50,6 +106,7 @@ extra_rdoc_files: []
|
|
50
106
|
files:
|
51
107
|
- ".DS_Store"
|
52
108
|
- ".gitignore"
|
109
|
+
- ".irbrc"
|
53
110
|
- ".tool-versions"
|
54
111
|
- CHANGELOG.md
|
55
112
|
- Gemfile
|
@@ -63,18 +120,19 @@ files:
|
|
63
120
|
- lib/example_material.rb
|
64
121
|
- lib/hexes.rb
|
65
122
|
- lib/prog.rb
|
66
|
-
- lib/refinements/ansi_slice_string_refinement.rb
|
67
123
|
- lib/refinements/ansiless_string_refinement.rb
|
68
124
|
- lib/refinements/array_fit_to_refinement.rb
|
69
125
|
- lib/refinements/clip_string_refinement.rb
|
70
126
|
- lib/refinements/constant_defined_string_refinement.rb
|
71
127
|
- lib/scryglass.rb
|
128
|
+
- lib/scryglass/binding_tracker.rb
|
72
129
|
- lib/scryglass/config.rb
|
73
130
|
- lib/scryglass/lens_helper.rb
|
74
131
|
- lib/scryglass/lens_panel.rb
|
75
132
|
- lib/scryglass/ro.rb
|
76
133
|
- lib/scryglass/ro_builder.rb
|
77
134
|
- lib/scryglass/session.rb
|
135
|
+
- lib/scryglass/session_manager.rb
|
78
136
|
- lib/scryglass/tree_panel.rb
|
79
137
|
- lib/scryglass/version.rb
|
80
138
|
- lib/scryglass/view_panel.rb
|
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module AnsiSliceStringRefinement
|
3
|
-
refine String do
|
4
|
-
## (This really might not do what you expect if your string has (or you are
|
5
|
-
## printing) unescaped newlines).
|
6
|
-
def ansi_slice(target_range)
|
7
|
-
unless target_range.is_a?(Range)
|
8
|
-
raise ArgumentError, 'ansi_slice takes a Range as its argument!'
|
9
|
-
end
|
10
|
-
if target_range.min.negative? || target_range.max.negative?
|
11
|
-
raise ArgumentError, 'Range argument must be entirely positive!'
|
12
|
-
end
|
13
|
-
|
14
|
-
return self[target_range] if (self =~ /\e\[[\d\;]*m/).nil? # Just quick
|
15
|
-
|
16
|
-
mock_index = 0 # A scanning index that *doesn't* count ANSI codes
|
17
|
-
result_string_array = []
|
18
|
-
|
19
|
-
ansi_string_breakout.each do |char|
|
20
|
-
char_is_ansi_escape_code = char.is_ansi_escape_code?
|
21
|
-
within_target_range =
|
22
|
-
target_range.include?(mock_index)
|
23
|
-
# char_is_applicable_ansi_code =
|
24
|
-
# (char_is_ansi_escape_code && mock_index <= target_range.max)
|
25
|
-
|
26
|
-
if within_target_range || char_is_ansi_escape_code
|
27
|
-
result_string_array << char
|
28
|
-
end
|
29
|
-
|
30
|
-
mock_index += 1 unless char_is_ansi_escape_code
|
31
|
-
end
|
32
|
-
|
33
|
-
result_string_array.join('')
|
34
|
-
end
|
35
|
-
|
36
|
-
## Splits string into characters, with each ANSI escape code being its own
|
37
|
-
## grouped item, like so:
|
38
|
-
## irb> "PLAIN\e[32mCOLOR\e[0mPLAIN".ansi_string_breakout
|
39
|
-
## => ["P", "L", "A", "I", "N", "\e[32m", "C", "O", "L", "O", "R",
|
40
|
-
## "\e[0m", "P", "L", "A", "I", "N"]
|
41
|
-
def ansi_string_breakout
|
42
|
-
breakout_array = []
|
43
|
-
working_self = self.dup
|
44
|
-
|
45
|
-
while working_self[0]
|
46
|
-
if (working_self =~ /\e\[[\d\;]*m/) == 0 # if begins with
|
47
|
-
end_of_escape_code = (working_self.index('m'))
|
48
|
-
leading_escape_code = working_self[0..end_of_escape_code]
|
49
|
-
breakout_array << leading_escape_code
|
50
|
-
working_self = working_self[(end_of_escape_code + 1)..-1]
|
51
|
-
else
|
52
|
-
leading_character = working_self[0]
|
53
|
-
breakout_array << leading_character
|
54
|
-
working_self = working_self[1..-1]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
breakout_array
|
59
|
-
end
|
60
|
-
|
61
|
-
def is_ansi_escape_code?
|
62
|
-
(self =~ /^\e\[[\d\;]*m$/) == 0
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|