scryglass 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,67 @@ 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 ( '.' || '@' || '(' ) ·
71
81
  · ·
72
82
  · SELECTING ROWS: ·
73
- · * : Select/Deselect ALL rows ·
74
- · | : Select/Deselect every sibling row under the same parent row ·
75
- · - : Select/Deselect current row ·
83
+ · \e[36m*\e[0m : Select/Deselect ALL rows ·
84
+ · \e[36m|\e[0m : Select/Deselect every sibling row under the same parent row ·
85
+ · \e[36m-\e[0m : Select/Deselect current row ·
86
+ · ·
87
+ · MANAGING MULTIPLE SESSION TABS: ·
88
+ · \e[36mTab\e[0m : Change session tab (to the right) (\e[36mShift+Tab\e[0m moves left) ·
89
+ · \e[36mQ\e[0m : Close current session tab ·
76
90
  · ·
77
91
  · TEXT SEARCH: ·
78
- · / : Begin a text search (in tree view) ·
79
- · n : Move to next search result ·
92
+ · \e[36m/\e[0m : Begin a text search (in tree view) ·
93
+ · \e[36mn\e[0m : Move to next search result ·
94
+ · ·
95
+ · ·
96
+ · \e[36m=\e[0m : Open prompt to type a console handle for current or selected row(s) ·
80
97
  · ·
81
- · Esc : Resets selection, last search, and number-to-move. (or returns to Tree View) ·
98
+ · \e[36mEsc\e[0m : Resets selection, last search, and number-to-move. (or returns to Tree View) ·
82
99
  · ·
83
100
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
84
101
  HELPSCREENADVANCEDPAGE
@@ -97,7 +114,8 @@ module Scryglass
97
114
 
98
115
  def self.load_silently
99
116
  begin
100
- add_kernel_methods
117
+ add_kernel_method
118
+ create_scryglass_session_manager
101
119
  { success: true, error: nil }
102
120
  rescue => e
103
121
  { success: false, error: e }
@@ -130,8 +148,7 @@ module Scryglass
130
148
  | > my_object.scry
131
149
  |
132
150
  | To resume the previous session: (in same console session)
133
- | > scry OR
134
- | > scry_resume (if you're in a breakpoint pry)
151
+ | > scry
135
152
  \e[0m
136
153
  CONSOLE_HELP
137
154
 
@@ -140,39 +157,51 @@ module Scryglass
140
157
 
141
158
  private
142
159
 
143
- def self.add_kernel_methods
160
+ def self.create_scryglass_session_manager
161
+ $scry_session_manager = Scryglass::SessionManager.new
162
+ end
163
+
164
+ def self.add_kernel_method
144
165
  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
166
+ def scry(arg = nil, _actions = nil)
167
+ # `actions` can't be a keyword arg due to this ruby issue:
168
+ # https://bugs.ruby-lang.org/issues/8316
147
169
 
148
- receiver = self unless to_s == 'main'
170
+ Scryglass.config.validate!
171
+
172
+ current_console_binding = binding.of_caller(1)
173
+
174
+ receiver_is_just_the_console = self == current_console_binding.receiver
175
+ receiver = self unless receiver_is_just_the_console
149
176
  # As in: `receiver.scry`,
150
177
  # and no receiver means scry was called on 'main', (unless self is
151
178
  # different in the because you've pry'd into something!)
152
179
 
153
180
  seed_object = arg || receiver
154
181
 
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!
182
+ if seed_object
183
+ # If it's been given an arg or receiver, create new session!
184
+ # The global variable is purposeful, and not accessible outside of
185
+ # the one particular console instance.
186
+ $scry_session_manager << Scryglass::Session.new(seed_object)
187
+ end
166
188
 
167
- no_previous_session = $scry_session.nil?
168
- if no_previous_session
189
+ unless $scry_session_manager.current_session
169
190
  raise ArgumentError,
170
- '`scry` requires either an argument, a receiver, or a past' \
191
+ '`scry` requires either an argument, a receiver, or a past ' \
171
192
  'session to reopen. try `Scryglass.help`'
172
193
  end
173
194
 
174
- Hexes.stdout_rescue do
175
- $scry_session.run_scry_ui(actions: actions)
195
+ $scry_session_manager.track_binding!(current_console_binding)
196
+
197
+ begin
198
+ Hexes.stdout_rescue do
199
+ $scry_session_manager.run_scry_ui
200
+ end
201
+ rescue => e # Here we ensure good visibility in case of errors
202
+ screen_height, _screen_width = $stdout.winsize
203
+ $stdout.write "\e[#{screen_height};1H\n" # (Moves console cursor to bottom left corner)
204
+ raise e
176
205
  end
177
206
  end
178
207
  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) } },
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,19 +2,64 @@
2
2
 
3
3
  module Scryglass
4
4
  module LensHelper
5
- def method_showcase_for(object)
5
+ def self.method_showcase_for(object)
6
+ # method_list = object.methods - Object.methods
6
7
  method_list = object.methods - Object.methods
8
+ return '' if method_list.empty?
9
+
7
10
  label_space = [method_list.map(&:length).max, 45].min
8
11
  method_list.sort.map do |method_name|
9
- label = method_name.to_s.ljust(label_space, ' ')
12
+ label = method_name.to_s
13
+ label_padding = ' ' * (label_space - label.length)
14
+ label = "\e[1;34m#{label}\e[0m" # make blue and bold
15
+
10
16
  begin
11
17
  method = object.method(method_name)
12
- label + ' : ' +
13
- method.source_location.to_a.join(':') + "\n" +
14
- Hexes.capture_io { puts method.source }
18
+
19
+ method_source_location = method.source_location.to_a.join(':')
20
+ source_location_line =
21
+ unless method_source_location.empty?
22
+ " \e[36m\e[4m#{method_source_location}\e[0m\n" # Cyan, underlined
23
+ end
24
+
25
+ highlighted_space = "\e[7m\s\e[0m"
26
+ method_lines = Hexes.capture_io { puts method.source }.split("\n")
27
+ method_lines.prepend('')
28
+ method_source = method_lines.map do |line|
29
+ ' ' + highlighted_space + line
30
+ end.join("\n")
31
+
32
+ translated_parameters = method.parameters.map do |pair|
33
+ arg_type = pair[0]
34
+ arg_name = pair[1]
35
+
36
+ case arg_type
37
+ when :req
38
+ "#{arg_name}"
39
+ when :opt
40
+ "#{arg_name} = ?"
41
+ when :keyreq
42
+ "#{arg_name}:"
43
+ when :key
44
+ "#{arg_name}: ?"
45
+ when :rest
46
+ "*#{arg_name}"
47
+ when :keyrest
48
+ "**#{arg_name}"
49
+ when :block
50
+ "&#{arg_name}"
51
+ end
52
+ end
53
+
54
+ arg_preview = "(#{translated_parameters.join(', ')})"
55
+
56
+ "#{label} #{arg_preview}\n" +
57
+ source_location_line +
58
+ "#{method_source}\n"
15
59
  rescue => e
16
- label + ' : Error: ' +
17
- e.message + "\n"
60
+ "#{label}#{label_padding} : " \
61
+ "Error: \e[31m#{e.message}\n\e[0m" +
62
+ (source_location_line || '')
18
63
  end
19
64
  end.join("\n")
20
65
  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