scryglass 1.1.0 → 2.1.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.
@@ -6,21 +6,36 @@ module ClipStringRefinement
6
6
  def clip_at(clip_length, ignore_ansi_codes: false)
7
7
  length_method = ignore_ansi_codes ? :ansiless_length : :length
8
8
  original_length = send(length_method)
9
- ansi_length = ignore_ansi_codes ? length - ansiless_length : 0
10
- slice_length = clip_length + ansi_length
11
- clipped_string = self[0...slice_length]
9
+
10
+ clipped_string = if ignore_ansi_codes
11
+ self.ansi_slice(0...clip_length)
12
+ else
13
+ self[0...clip_length]
14
+ end
12
15
  if clipped_string.send(length_method) < original_length
13
- clipped_string = clipped_string.mark_as_abbreviated
16
+ clipped_string =
17
+ clipped_string.mark_as_abbreviated(ignore_ansi_codes: ignore_ansi_codes)
14
18
  end
15
19
 
16
20
  clipped_string
17
21
  end
18
22
 
23
+ def ansiless_clip_at(clip_length)
24
+ self.clip_at(clip_length, ignore_ansi_codes: true)
25
+ end
26
+
19
27
  # Warning: Still not going to work nicely if a string ends in an ansi code!
20
- def mark_as_abbreviated
28
+ def mark_as_abbreviated(ignore_ansi_codes: false)
21
29
  self_dup = dup
22
- self_dup[-1] = '…' if self_dup[-1]
23
- self_dup[-2] = '…' if self_dup[-2]
30
+
31
+ if ignore_ansi_codes
32
+ self_dup.ansiless_set!(-1, '…') if self_dup.ansiless_pick(-1)
33
+ self_dup.ansiless_set!(-2, '…') if self_dup.ansiless_pick(-2)
34
+ else
35
+ self_dup[-1] = '…' if self_dup[-1]
36
+ self_dup[-2] = '…' if self_dup[-2]
37
+ end
38
+
24
39
  self_dup
25
40
  end
26
41
  end
data/lib/scryglass.rb CHANGED
@@ -1,18 +1,23 @@
1
1
  # frozen_string_literal: true
2
- require 'stringio'
3
2
 
4
- ## Bookkeeping and external tools:
3
+ ## Bookkeeping
5
4
  require "scryglass/version"
5
+
6
+ ## External tools:
6
7
  require 'io/console'
8
+ require 'stringio'
7
9
  require 'pp'
10
+ require 'amazing_print' # For use as a lens
11
+ require 'method_source' # For use in lens_helper
12
+ require 'binding_of_caller'
8
13
  require 'timeout'
9
14
 
10
15
  ## Refinements and sub-tools:
11
16
  require 'refinements/ansiless_string_refinement'
12
- # require 'refinements/ansi_slice_string_refinement' # Employed soon
13
17
  require 'refinements/clip_string_refinement'
14
18
  require 'refinements/constant_defined_string_refinement'
15
19
  require 'refinements/array_fit_to_refinement'
20
+ require 'scryglass/lens_helper'
16
21
  require 'hexes'
17
22
  require 'prog'
18
23
 
@@ -20,7 +25,9 @@ require 'prog'
20
25
  require 'scryglass/config'
21
26
  require 'scryglass/ro'
22
27
  require 'scryglass/ro_builder'
28
+ require 'scryglass/binding_tracker'
23
29
  require 'scryglass/session'
30
+ require 'scryglass/session_manager'
24
31
  require 'scryglass/view_wrapper'
25
32
  require 'scryglass/view_panel'
26
33
  require 'scryglass/tree_panel'
@@ -28,57 +35,71 @@ require 'scryglass/lens_panel'
28
35
 
29
36
  ## Testing and Demoing:
30
37
  require 'example_material.rb'
31
-
38
+ #test
32
39
  module Scryglass
33
- HELP_SCREEN = <<~'HELPSCREENPAGE'
34
- q : Quit Scry ? : Cycle help panels (1/2)
40
+ HELP_SCREEN = <<~"HELPSCREENPAGE"
41
+ \e[36mq\e[0m : Quit Scry \e[36m?\e[0m : Cycle help panels (1/2)
35
42
 
36
43
  BASIC NAVIGATION: · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
37
44
  · ·
38
- · UP / DOWN : Navigate (You can type a number first) ·
39
- · RIGHT : Expand current or selected row(s) ·
40
- · LEFT : Collapse current or selected row(s) ·
45
+ · \e[36mUP / DOWN\e[0m : Navigate (To move further, type a number first or use \e[36mSHIFT\e[0m) ·
46
+ · \e[36mRIGHT\e[0m : Expand current or selected row(s) ·
47
+ · \e[36mLEFT\e[0m : Collapse current or selected row(s) ·
48
+ · ·
49
+ · (\e[36mh/j/k/l\e[0m on the home row can also serve as arrow keys) ·
41
50
  · ·
42
- · ENTER : Close Scry, returning current or selected object(s) (Key or Value) ·
51
+ · \e[36mENTER\e[0m : Close Scry, returning current or selected object(s) (Key or Value) ·
43
52
  · ·
44
53
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
45
54
 
46
55
  INSPECTING WITH LENS VIEW: · · · · · · · · · · · · · ·
47
56
  · ·
48
- · SPACEBAR : Toggle Lens View ·
49
- · l : Cycle through lens types ·
50
- · L : Toggle subject (Key/Value of row) ·
57
+ · \e[36mSPACEBAR\e[0m : Toggle Lens View ·
58
+ · \e[36m > \e[0m : Cycle through lens types ·
59
+ · \e[36m < \e[0m : Toggle subject (Key/Value of row) ·
51
60
  · ·
52
61
  · · · · · · · · · · · · · · · · · · · · · · · · · · ·
53
62
 
54
63
  MORE NAVIGATION: · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
55
64
  · ·
56
- · [w] : Move view window 0 : Reset view location ·
57
- · [a][s][d] (ALT increases speed) (Press again: reset cursor) ·
65
+ · \e[36m [w] \e[0m : Move view window \e[36m0\e[0m : Reset view location ·
66
+ · \e[36m[a][s][d]\e[0m (\e[36mALT\e[0m increases speed) (Press again: reset cursor) ·
58
67
  · ·
59
68
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
60
69
  HELPSCREENPAGE
61
70
 
62
- HELP_SCREEN_ADVANCED = <<~'HELPSCREENADVANCEDPAGE'
63
- q : Quit Scry ? : Cycle help panels (2/2)
71
+ HELP_SCREEN_ADVANCED = <<~"HELPSCREENADVANCEDPAGE"
72
+ \e[36mq\e[0m : Quit Scry \e[36m?\e[0m : Cycle help panels (2/2)
64
73
 
65
74
  ADVANCED: · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
66
75
  · DIGGING DEEPER: ·
67
76
  · For current or selected row(s)... ·
68
- · @ : Build instance variable sub-rows ·
69
- · . : Build ActiveRecord association sub-rows ·
70
- · ( : Attempt to smart-build sub-rows, if Enumerable. Usually '@' is preferable. ·
77
+ · \e[36m@\e[0m : Build instance variable sub-rows ·
78
+ · \e[36m.\e[0m : Build ActiveRecord association sub-rows ·
79
+ · \e[36m(\e[0m : Attempt to smart-build sub-rows, if Enumerable. Usually '@' is preferable ·
80
+ · \e[36mo\e[0m : Quick Open: builds the most likely helpful sub-rows ( '.' || '@' || '(' ) ·
81
+ · · ·
82
+ · \e[36mc\e[0m : Enter method text to call on object(s), results becomes navigable sub-rows ·
71
83
  · ·
72
84
  · SELECTING ROWS: ·
73
- · * : Select/Deselect ALL rows ·
74
- · | : Select/Deselect every sibling row under the same parent row ·
75
- · - : Select/Deselect current row ·
85
+ · \e[36m*\e[0m : Select/Deselect ALL rows ·
86
+ · \e[36m|\e[0m : Select/Deselect every sibling row under the same parent row ·
87
+ · \e[36m-\e[0m : Select/Deselect current row ·
88
+ · ·
89
+ · MANAGING MULTIPLE SESSION TABS: ·
90
+ · \e[36mTab\e[0m : Change session tab (to the right) (\e[36mShift+Tab\e[0m moves left) ·
91
+ · \e[36mQ\e[0m : Close current session tab ·
92
+ · \e[36mt\e[0m : [Open new]... session tab with current or selected row(s) as the seed ·
93
+ · \e[36mT\e[0m : [Restart]... session tab with current or selected row(s) as the seed ·
76
94
  · ·
77
95
  · TEXT SEARCH: ·
78
- · / : Begin a text search (in tree view) ·
79
- · n : Move to next search result ·
96
+ · \e[36m/\e[0m : Begin a text search (in tree view) ·
97
+ · \e[36mn\e[0m : Move to next search result ·
98
+ · ·
99
+ · ·
100
+ · \e[36m=\e[0m : Open prompt to type a console handle for current or selected row(s) ·
80
101
  · ·
81
- · Esc : Resets selection, last search, and number-to-move. (or returns to Tree View) ·
102
+ · \e[36mEsc\e[0m : Resets selection, last search, and number-to-move. (or returns to Tree View) ·
82
103
  · ·
83
104
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
84
105
  HELPSCREENADVANCEDPAGE
@@ -97,7 +118,8 @@ module Scryglass
97
118
 
98
119
  def self.load_silently
99
120
  begin
100
- add_kernel_methods
121
+ add_kernel_method
122
+ create_scryglass_session_manager
101
123
  { success: true, error: nil }
102
124
  rescue => e
103
125
  { success: false, error: e }
@@ -130,8 +152,7 @@ module Scryglass
130
152
  | > my_object.scry
131
153
  |
132
154
  | To resume the previous session: (in same console session)
133
- | > scry OR
134
- | > scry_resume (if you're in a breakpoint pry)
155
+ | > scry
135
156
  \e[0m
136
157
  CONSOLE_HELP
137
158
 
@@ -140,39 +161,51 @@ module Scryglass
140
161
 
141
162
  private
142
163
 
143
- def self.add_kernel_methods
164
+ def self.create_scryglass_session_manager
165
+ $scry_session_manager = Scryglass::SessionManager.new
166
+ end
167
+
168
+ def self.add_kernel_method
144
169
  Kernel.module_eval do
145
- def scry(arg = nil, actions = nil) # `actions` can't be a keyword arg due
146
- # to this ruby issue: https://bugs.ruby-lang.org/issues/8316
170
+ def scry(arg = nil, _actions = nil)
171
+ # `actions` can't be a keyword arg due to this ruby issue:
172
+ # https://bugs.ruby-lang.org/issues/8316
147
173
 
148
- receiver = self unless to_s == 'main'
174
+ Scryglass.config.validate!
175
+
176
+ current_console_binding = binding.of_caller(1)
177
+
178
+ receiver_is_just_the_console = self == current_console_binding.receiver
179
+ receiver = self unless receiver_is_just_the_console
149
180
  # As in: `receiver.scry`,
150
181
  # and no receiver means scry was called on 'main', (unless self is
151
182
  # different in the because you've pry'd into something!)
152
183
 
153
184
  seed_object = arg || receiver
154
185
 
155
- $scry_session = Scryglass::Session.new(seed_object) if seed_object
156
- # If it's been given an arg or receiver, create new session!
157
- # The global variable is purposeful, and not accessible outside of
158
- # the one particular console instance.
159
-
160
- scry_resume(actions) # Pick up the new or previous session
161
- end
162
-
163
- # For the user, this is mainly just for pry sessions where `self` isn't `main`
164
- def scry_resume(actions = nil)
165
- Scryglass.config.validate!
186
+ if seed_object
187
+ # If it's been given an arg or receiver, create new session!
188
+ # The global variable is purposeful, and not accessible outside of
189
+ # the one particular console instance.
190
+ $scry_session_manager << Scryglass::Session.new(seed_object)
191
+ end
166
192
 
167
- no_previous_session = $scry_session.nil?
168
- if no_previous_session
193
+ unless $scry_session_manager.current_session
169
194
  raise ArgumentError,
170
- '`scry` requires either an argument, a receiver, or a past' \
195
+ '`scry` requires either an argument, a receiver, or a past ' \
171
196
  'session to reopen. try `Scryglass.help`'
172
197
  end
173
198
 
174
- Hexes.stdout_rescue do
175
- $scry_session.run_scry_ui(actions: actions)
199
+ $scry_session_manager.track_binding!(current_console_binding)
200
+
201
+ begin
202
+ Hexes.stdout_rescue do
203
+ $scry_session_manager.run_scry_ui
204
+ end
205
+ rescue => e # Here we ensure good visibility in case of errors
206
+ screen_height, _screen_width = $stdout.winsize
207
+ $stdout.write "\e[#{screen_height};1H\n" # (Moves console cursor to bottom left corner)
208
+ raise e
176
209
  end
177
210
  end
178
211
  end
@@ -0,0 +1,10 @@
1
+ module Scryglass
2
+ class BindingTracker
3
+ attr_accessor :console_binding, :user_named_variables
4
+
5
+ def initialize(console_binding:)
6
+ self.user_named_variables = []
7
+ self.console_binding = console_binding
8
+ end
9
+ end
10
+ end
@@ -26,16 +26,25 @@ module Scryglass
26
26
  self.lenses = [ # Custom lenses can easily be added as name+lambda hashes! Or comment some out to turn them off.
27
27
  { name: 'Pretty Print (`pp`)',
28
28
  lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { pp o } } },
29
+ { name: 'Amazing Print (`ap`)',
30
+ lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { ap o } } }, # This has colors!
29
31
  { name: 'Inspect (`.inspect`)',
30
32
  lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o.inspect } } },
31
33
  { name: 'Yaml Print (`y`)',
32
34
  lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { require 'yaml' ; y o } } }, # OR: `puts o.to_yaml`
33
35
  { name: 'Puts (`puts`)',
34
36
  lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o } } },
35
- # { name: 'Method Showcase',
36
- # lambda: ->(o) { Scryglass::LensHelper.method_showcase_for(o) } },
37
+ { name: 'Method Showcase',
38
+ lambda: ->(o) { Scryglass::LensHelper.method_showcase_for(o, char_limit: 20_000) } },
37
39
  ]
38
40
 
41
+ ## AmazingPrint defaults, if the user has not set their own:
42
+ ::AmazingPrint.defaults ||= {
43
+ index: false, # (Don't display array indices).
44
+ raw: true, # (Recursively format instance variables).
45
+ }
46
+ # See https://github.com/amazing-print/amazing_print
47
+
39
48
  ## Building ActiveRecord association sub-rows:
40
49
  self.include_empty_associations = true
41
50
  self.include_through_associations = false
@@ -2,21 +2,83 @@
2
2
 
3
3
  module Scryglass
4
4
  module LensHelper
5
- def method_showcase_for(object)
5
+ using ClipStringRefinement
6
+
7
+ def self.method_showcase_for(object, char_limit: nil)
8
+ # method_list = object.methods - Object.methods
6
9
  method_list = object.methods - Object.methods
10
+ return '' if method_list.empty?
11
+
7
12
  label_space = [method_list.map(&:length).max, 45].min
8
- method_list.sort.map do |method_name|
9
- label = method_name.to_s.ljust(label_space, ' ')
10
- begin
11
- method = object.method(method_name)
12
- label + ' : ' +
13
- method.source_location.to_a.join(':') + "\n" +
14
- Hexes.capture_io { puts method.source }
15
- rescue => e
16
- label + ' : Error: ' +
17
- e.message + "\n"
13
+ method_list.sort!
14
+ running_method_showcase = ''.dup
15
+
16
+ method_list.each do |method_name|
17
+ label = method_name.to_s
18
+ label_padding = ' ' * [(label_space - label.length), 0].max
19
+ label = "\e[1;34m#{label}\e[0m" # make blue and bold
20
+
21
+ if char_limit && running_method_showcase.length >= char_limit
22
+ break
18
23
  end
19
- end.join("\n")
24
+
25
+ running_method_showcase <<
26
+ begin
27
+ method = object.method(method_name)
28
+
29
+ method_source_location = method.source_location.to_a.join(':')
30
+ source_location_line =
31
+ unless method_source_location.empty?
32
+ " \e[36m\e[4m#{method_source_location}\e[0m\n" # Cyan, underlined
33
+ end
34
+
35
+ highlighted_space = "\e[7m\s\e[0m"
36
+ method_lines = Hexes.capture_io { puts method.source }.split("\n")
37
+ method_lines.prepend('')
38
+ method_source = method_lines.map do |line|
39
+ ' ' + highlighted_space + line
40
+ end.join("\n")
41
+
42
+ translated_parameters = method.parameters.map do |pair|
43
+ arg_type = pair[0]
44
+ arg_name = pair[1]
45
+
46
+ case arg_type
47
+ when :req
48
+ "#{arg_name}"
49
+ when :opt
50
+ "#{arg_name} = ?"
51
+ when :keyreq
52
+ "#{arg_name}:"
53
+ when :key
54
+ "#{arg_name}: ?"
55
+ when :rest
56
+ "*#{arg_name}"
57
+ when :keyrest
58
+ "**#{arg_name}"
59
+ when :block
60
+ "&#{arg_name}"
61
+ end
62
+ end
63
+
64
+ arg_preview = "(#{translated_parameters.join(', ')})"
65
+
66
+ "#{label} #{arg_preview}\n" +
67
+ source_location_line +
68
+ "#{method_source}\n\n"
69
+ rescue => e
70
+ "#{label}#{label_padding} : " \
71
+ "Error: \e[31m#{e.message}\n\e[0m" +
72
+ (source_location_line || '') +
73
+ "\n"
74
+ end
75
+ end
76
+
77
+ if char_limit
78
+ running_method_showcase.ansiless_clip_at(char_limit)
79
+ else
80
+ running_method_showcase
81
+ end
20
82
  end
21
83
  end
22
84
  end
@@ -51,29 +51,30 @@ module Scryglass
51
51
 
52
52
  ## Here we cut down the (rectangular) display array in both dimensions (into a smaller rectangle), as needed, to fit the view.
53
53
  sliced_lines = split_lines.map do |string|
54
- ansi_length = string.length - string.ansiless_length # Escape codes make `length` different from display length!
55
- slice_length = screen_width + ansi_length
56
- string[current_view_coords[:x], slice_length] || '' # If I don't want to
57
- # opacify here, I need to account for nils when the view is fully
58
- # beyond the shorter lines.
54
+ string.ansi_slice(current_view_coords[:x], screen_width) || '' # If I
55
+ # don't want to opacify here, I need to account for nils when the view
56
+ # is fully beyond the shorter lines.
59
57
  end
60
58
  sliced_list = sliced_lines[current_view_coords[:y], non_header_view_size]
61
59
 
62
- sliced_list.join("\n")
60
+ sliced_list
63
61
  end
64
62
 
65
63
  def recalculate_y_boundaries
66
- self.y_boundaries = 0...(uncut_body_string.count("\n") + 1)
64
+ number_of_lines = uncut_body_string.count("\n") + 1
65
+ preview_row = 1
66
+ self.y_boundaries = 0...(number_of_lines + preview_row)
67
67
  end
68
68
 
69
69
  def recalculate_x_boundaries
70
70
  _screen_height, screen_width = $stdout.winsize
71
71
 
72
72
  split_lines = uncut_body_string.split("\n")
73
- length_of_longest_line = split_lines.map(&:length).max || 0
73
+ length_of_longest_line = split_lines.map(&:ansiless_length).max || 0
74
74
  max_line_length = [length_of_longest_line, screen_width].max
75
+ preview_column = 1
75
76
 
76
- self.x_boundaries = 0...max_line_length
77
+ self.x_boundaries = 0...(max_line_length + preview_column)
77
78
  end
78
79
 
79
80
  def current_ro_subheader
@@ -85,10 +86,29 @@ module Scryglass
85
86
  row_below_string =
86
87
  current_ro.next_visible_ro_down.to_s if current_ro.next_visible_ro_down
87
88
 
88
- tree_preview_related_commands = ['A', 'B', 'C', 'D',
89
- '@', '.', '(', '*', '|', '-']
89
+ tree_preview_related_keys = ::Scryglass::Session::KEY_MAP.slice(
90
+ :move_cursor_up,
91
+ :move_cursor_down,
92
+ :homerow_move_cursor_up,
93
+ :homerow_move_cursor_down,
94
+ :homerow_move_cursor_up_fast,
95
+ :homerow_move_cursor_down_fast,
96
+ :open_bucket,
97
+ :close_bucket,
98
+ :homerow_open_bucket,
99
+ :homerow_close_bucket,
100
+ :build_instance_variables,
101
+ :build_ar_relations,
102
+ :build_enum_children,
103
+ :smart_open,
104
+ :select_siblings,
105
+ :select_all,
106
+ :select_current,
107
+ :continue_search,
108
+ ).values
109
+
90
110
  ro_view_label =
91
- if tree_preview_related_commands.include?(last_keypress)
111
+ if tree_preview_related_keys.include?(last_keypress)
92
112
  "\e[7mVIEWING:\e[00m" # Color reversed
93
113
  else
94
114
  'VIEWING:'
@@ -109,6 +129,7 @@ module Scryglass
109
129
  current_subject_type = scry_session.current_subject_type
110
130
  current_subject = scry_session.current_ro.current_subject
111
131
  last_keypress = scry_session.last_keypress
132
+ key_map = ::Scryglass::Session::KEY_MAP
112
133
 
113
134
  lens_count = LensPanel.lenses.count
114
135
  lens_id = current_lens % lens_count
@@ -117,23 +138,26 @@ module Scryglass
117
138
  longest_lens_name_length = LensPanel.lenses.map do |lens|
118
139
  lens[:name].length
119
140
  end.max
120
- lens_type_header_length = 9 + (lens_count.to_s.length * 2)
141
+ lens_type_header_length = 13 + (lens_count.to_s.length * 2)
121
142
  + longest_lens_name_length
122
- subject_type_header = "SUBJECT: #{current_subject_type}".ljust(14, ' ')
143
+ subject_type_header = "[<] SUBJECT: #{current_subject_type}".ljust(18, ' ')
123
144
  subject_class_header = " CLASS: #{current_subject.class}"
124
- lens_type_header = " LENS #{lens_id + 1}/#{lens_count}: #{lens[:name]}"
145
+ lens_type_header = " [>] LENS #{lens_id + 1}/#{lens_count}: #{lens[:name]}"
125
146
  .ljust(lens_type_header_length, ' ')
126
147
 
148
+ user_just_switched_lens = last_keypress == key_map[:switch_lens]
149
+ user_just_switched_subject_type = last_keypress == key_map[:switch_subject_type]
150
+
151
+ if user_just_switched_lens
152
+ lens_type_header = "\e[7m#{lens_type_header}\e[00m" # Color reversed
153
+ elsif user_just_switched_subject_type
154
+ subject_type_header = "\e[7m#{subject_type_header}\e[00m" # Color reversed
155
+ end
156
+
127
157
  fit_lens_header = [
128
158
  subject_type_header, subject_class_header, lens_type_header
129
159
  ].fit_to(screen_width)
130
160
 
131
- if last_keypress == 'l'
132
- fit_lens_header[4] = "\e[7m#{fit_lens_header[4]}" # Format to be ended by Hexes.opacify_screen_string() (using \e[00m)
133
- elsif last_keypress == 'L'
134
- fit_lens_header[0] = "\e[7m#{fit_lens_header[0]}\e[00m"
135
- end
136
-
137
161
  fit_lens_header.join('')
138
162
  end
139
163
  end