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.
@@ -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
@@ -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
- help_key_reminder = "Press '?' for controls"
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
- ansi_length = string.length - string.ansiless.length
95
- slice_length = screen_width + ansi_length
96
- string[current_view_coords[:x], slice_length] || '' # If I don't want to
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.join("\n")
101
+ sliced_lines
102
102
  end
103
103
 
104
104
  def recalculate_y_boundaries
105
- self.y_boundaries = 0...scry_session.all_ros.select(&:visible?).count
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(&:length).max
114
+ length_of_longest_line = split_lines.map(&:ansiless_length).max
113
115
  max_line_length = [length_of_longest_line, screen_width].max
114
- self.x_boundaries = 0...max_line_length
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
@@ -1,3 +1,3 @@
1
1
  module Scryglass
2
- VERSION = "1.1.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -72,11 +72,53 @@ module Scryglass
72
72
  end
73
73
 
74
74
  def visible_body_string
75
- visible_body_slice(uncut_body_string)
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
@@ -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: 1.1.0
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: 2020-09-21 00:00:00.000000000 Z
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