scryglass 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 812af5768c446597a262d5db01b4b560510ee2223e13b5884ed456f54d7c626c
4
- data.tar.gz: aaf8d7344d82b2ea03528fc66ba6c5c58ea61254f554656c42509c8f2e36c0ab
3
+ metadata.gz: 75f5dd1ffed556a9b5d06dc18925e3c728bb2e49742fee1d7b5705c5f8be0aac
4
+ data.tar.gz: 3076092cf60d7359fc51552199584af40b23e1a71c4291ee8853738a75bc1c8d
5
5
  SHA512:
6
- metadata.gz: 667d43b1bfd0855f7e64e0d3f8f24f8d187b4d0ffa164b24631dc2e6a3682a5365b10f8283a980dcc82b0566a6e4601eb1f25782c7c6940ca2cb65602bb06051
7
- data.tar.gz: 578f61b052af0fc5004b11f7a634465c6412cb5a84446845cc403fea73f84449ec9bf4c2e7c92b67369f934492fb01c74d7474fd0c3e3593749aa6c6634e69c6
6
+ metadata.gz: cd72f233a5afe293c135ab3ed638d147397dc3400bcd37a7dc6b724f125af7484a8036ee9f1bd0fb492025ff30ee06a9c04cddb92c954912a33d7a17aa71b9f4
7
+ data.tar.gz: 3fc0b51496476cf08bc9ef79573198133265a15eeac5cac1fb573e27767aa788f1901be0887dec7dcf1341e8eedd1c0d45c12f3ddf9ba5a60f662a2b94ee0e7d
Binary file
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.1.0] - 2020-09-21
11
+
12
+ ## Added
13
+
14
+ - Added ability to distinguish genuine escape key presses, and added escape key functionality.
15
+ - Added ability (AnsiSliceStringRefinement) to slice strings while effectively maintaining their ANSI formatting, as our eyes would expect.
16
+ - Added some dynamic header items to Tree View that track the following:
17
+ - Multiple targets count and message
18
+ - Last search text (what will be searched again by hitting 'n')
19
+ - Number-to-move, if digits are typed
20
+ - ('?' controls reminder now only displays when header is otherwise empty)
21
+
22
+ ## Changed
23
+
24
+ - Now inputs check and screen redraws every 0.1 seconds even without user keypresses.
25
+ - (ArrayFitToRefinement now allows non-plural array counts)
26
+
27
+ ## Removed
28
+
29
+ - Temporarily removed record/playback functionality
30
+ - Removed all dependency on activesupport
31
+
32
+ ## Fixed
33
+
34
+ - Fixed issue where shrinking the console screen size enough would create a visual glitch until one of the boundary-resizing commands was received.
35
+
10
36
  ## [1.0.1] - 2020-09-18
11
37
 
12
38
  ### Added
@@ -1,30 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- scryglass (1.0.1)
4
+ scryglass (1.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (5.2.4.4)
10
- concurrent-ruby (~> 1.0, >= 1.0.2)
11
- i18n (>= 0.7, < 2)
12
- minitest (~> 5.1)
13
- tzinfo (~> 1.1)
14
- concurrent-ruby (1.1.7)
15
- i18n (1.8.5)
16
- concurrent-ruby (~> 1.0)
17
- minitest (5.14.2)
18
9
  rake (12.3.3)
19
- thread_safe (0.3.6)
20
- tzinfo (1.2.7)
21
- thread_safe (~> 0.1)
22
10
 
23
11
  PLATFORMS
24
12
  ruby
25
13
 
26
14
  DEPENDENCIES
27
- activesupport (~> 5.0)
28
15
  bundler (~> 2.1)
29
16
  rake (~> 12.0)
30
17
  scryglass!
data/README.md CHANGED
@@ -1,3 +1,17 @@
1
+ # 🔮 Scryglass
2
+
3
+ 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
4
+ relations, and unknown Enumerable types like an expandable/collapsable file tree in an intuitive UI.
5
+
6
+ Objects and child objects can also be inspected through a variety of display lenses, returned directly to the console, and more!
7
+
8
+ `scry` is quick to use and useful for both experienced developers and those very new to ruby, rails, or coding.
9
+ It facilitates:
10
+ - Debugging/Investigating
11
+ - Education, learning the structure of objects and their relationships
12
+ - Comparing/Scanning sub-items in an Enumerable (e.g. Person.first.library_records.scry)
13
+
14
+
1
15
  # Table of Contents
2
16
 
3
17
  [🔮 Scryglass Intro Summary](#-scryglass)
@@ -18,18 +32,6 @@
18
32
  - [Miscellaneous Troubleshooting Notes](#miscellaneous-troubleshooting-notes)
19
33
  - [Contributing](#contributing)
20
34
 
21
- # 🔮 Scryglass
22
-
23
- 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
24
- relations, and unknown Enumerable types like an expandable/collapsable file tree in an intuitive UI.
25
-
26
- Objects and child objects can also be inspected through a variety of display lenses, returned directly to the console, and more!
27
-
28
- `scry` is quick to use and useful for both experienced developers and those very new to ruby, rails, or coding.
29
- It facilitates:
30
- - Debugging/Investigating
31
- - Education, learning the structure of objects and their relationships
32
- - Comparing/Scanning sub-items in an Enumerable (e.g. Person.first.library_records.scry)
33
35
 
34
36
  ## ⚡️ tl;dr SUPER Quick Start
35
37
 
@@ -58,7 +60,7 @@ $ gem install scryglass
58
60
  ```
59
61
  ## Enabling Scryglass
60
62
 
61
- For the `scry` method syntax to work as cleanly as it does, Scryglass needs to add the method to the Kernel module. While this is safe, it was safest to have this only happen on a console session basis. To enable the `scry` method, call `Scryglass.load`. Thus, to automatically enable Scryglass when opening a console, you add one of the following lines to your `./irbrc` (and `./pryrc` for rails or pry sessions):
63
+ For the `scry` method syntax to work as cleanly as it does, Scryglass needs to add the method to the Kernel module. While this is safe, it was safest to have this only happen on a console session basis. To enable the `scry` method, call `Scryglass.load`. Thus, to automatically enable Scryglass when opening a console, you add one of the following lines to your `./.irbrc` (and `./.pryrc` for rails or pry sessions):
62
64
  ```ruby
63
65
  Scryglass.load
64
66
  ```
@@ -162,6 +164,7 @@ Scryglass has two features to make wait time a little easier:
162
164
  | `-` | Select/Deselect current row | If these objects are later returned, the order in which they were selected will determine their order in the returned array. |
163
165
  | `/` | Begin a text search (in tree view) | Begins a case-sensitive regex search of all items, in a loop, starting with just below the current row. For a matching object to be found, the search must match its *truncated sample string in tree view* (regardless of what is on or off screen) (it must match either the key or the value, not the full line they create) (known enumerable types, like `[•••]`, may count as a match if they contain the string in the backend). |
164
166
  | `n` | Move to next search result | Will, using the most recent search entry, move the cursor on to the next match downward, cycling through all rows. This follows the same matching rules as the original search. |
167
+ | `Esc` | Resets selection, last search, and number-to-move. (or returns to Tree View) | (Essentially, clears the values represented in the Tree View header if you're in the Tree View; otherwise it returns you to the Tree View) |
165
168
 
166
169
  ## Configuration / Customization (all optional)
167
170
 
@@ -192,7 +195,7 @@ Scryglass.configure do |config|
192
195
  # { name: 'Inspect (`.inspect`)',
193
196
  # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o.inspect } } },
194
197
  # { name: 'Yaml Print (`y`)',
195
- # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { y o } } }, # OR: `puts o.to_yaml`
198
+ # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { require 'yaml' ; y o } } }, # OR: `puts o.to_yaml`
196
199
  # { name: 'Puts (`puts`)',
197
200
  # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o } } },
198
201
  # # { name: 'Method Showcase', # Not included by default
@@ -15,7 +15,7 @@ Scryglass.configure do |config|
15
15
  # { name: 'Inspect (`.inspect`)',
16
16
  # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o.inspect } } },
17
17
  # { name: 'Yaml Print (`y`)',
18
- # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { y o } } }, # OR: `puts o.to_yaml`
18
+ # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { require 'yaml' ; y o } } }, # OR: `puts o.to_yaml`
19
19
  # { name: 'Puts (`puts`)',
20
20
  # lambda: ->(o) { Hexes.capture_io(char_limit: 20_000) { puts o } } },
21
21
  # # { name: 'Method Showcase', # Not included by default
@@ -107,7 +107,7 @@ module Hexes
107
107
  necessary_constants_defined = necessary_constants.all?(&:constant_defined?)
108
108
  return yield unless necessary_constants_defined
109
109
 
110
- rails_logger_defined = 'Rails'.constant_defined? && Rails.try(:logger).present?
110
+ rails_logger_defined = 'Rails'.constant_defined? && !!Rails.try(:logger)
111
111
 
112
112
  ## These are purposefully preserved as global variables so retrieval, in
113
113
  ## debugging or errored usage, is as easy as possible.
@@ -0,0 +1,65 @@
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
@@ -8,6 +8,9 @@ module ArrayFitToRefinement
8
8
  length_method = ignore_ansi_codes ? :ansiless_length : :length
9
9
  length_result = string_array.join('').send(length_method)
10
10
 
11
+ if string_array.count < 2
12
+ return nonplural_solution(string_length_goal, length_result, fill: fill)
13
+ end
11
14
 
12
15
  if length_result > string_length_goal
13
16
  string_array.compress_to(string_length_goal, ignore_ansi_codes: ignore_ansi_codes)
@@ -63,5 +66,14 @@ module ArrayFitToRefinement
63
66
 
64
67
  working_array.zip(spacers).flatten.compact
65
68
  end
69
+
70
+ private
71
+
72
+ def nonplural_solution(string_length_goal, length_result, fill:)
73
+ return [fill * string_length_goal] if self.empty?
74
+
75
+ remaining_space = string_length_goal - length_result
76
+ return self.map(&:to_s).append(fill * remaining_space)
77
+ end
66
78
  end
67
79
  end
@@ -1,13 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
  require 'stringio'
3
+
3
4
  ## Bookkeeping and external tools:
4
5
  require "scryglass/version"
5
- require 'active_support/core_ext/object/blank' # This gives us `.present?` and `.blank?` # https://stackoverflow.com/questions/4648684/how-to-use-present-in-ruby-projects
6
6
  require 'io/console'
7
7
  require 'pp'
8
+ require 'timeout'
8
9
 
9
10
  ## Refinements and sub-tools:
10
11
  require 'refinements/ansiless_string_refinement'
12
+ # require 'refinements/ansi_slice_string_refinement' # Employed soon
11
13
  require 'refinements/clip_string_refinement'
12
14
  require 'refinements/constant_defined_string_refinement'
13
15
  require 'refinements/array_fit_to_refinement'
@@ -76,6 +78,8 @@ module Scryglass
76
78
  · / : Begin a text search (in tree view) ·
77
79
  · n : Move to next search result ·
78
80
  · ·
81
+ · Esc : Resets selection, last search, and number-to-move. (or returns to Tree View) ·
82
+ · ·
79
83
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
80
84
  HELPSCREENADVANCEDPAGE
81
85
 
@@ -78,7 +78,7 @@ module Scryglass
78
78
 
79
79
  def current_ro_subheader
80
80
  current_ro = scry_session.current_ro
81
- user_input = scry_session.user_input
81
+ last_keypress = scry_session.last_keypress
82
82
 
83
83
  row_above_string =
84
84
  current_ro.next_visible_ro_up.to_s if current_ro.next_visible_ro_up
@@ -88,7 +88,7 @@ module Scryglass
88
88
  tree_preview_related_commands = ['A', 'B', 'C', 'D',
89
89
  '@', '.', '(', '*', '|', '-']
90
90
  ro_view_label =
91
- if tree_preview_related_commands.include?(user_input)
91
+ if tree_preview_related_commands.include?(last_keypress)
92
92
  "\e[7mVIEWING:\e[00m" # Color reversed
93
93
  else
94
94
  'VIEWING:'
@@ -108,7 +108,7 @@ module Scryglass
108
108
  current_lens = scry_session.current_lens
109
109
  current_subject_type = scry_session.current_subject_type
110
110
  current_subject = scry_session.current_ro.current_subject
111
- user_input = scry_session.user_input
111
+ last_keypress = scry_session.last_keypress
112
112
 
113
113
  lens_count = LensPanel.lenses.count
114
114
  lens_id = current_lens % lens_count
@@ -128,9 +128,9 @@ module Scryglass
128
128
  subject_type_header, subject_class_header, lens_type_header
129
129
  ].fit_to(screen_width)
130
130
 
131
- if user_input == 'l'
131
+ if last_keypress == 'l'
132
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 user_input == 'L'
133
+ elsif last_keypress == 'L'
134
134
  fit_lens_header[0] = "\e[7m#{fit_lens_header[0]}\e[00m"
135
135
  end
136
136
 
@@ -114,19 +114,15 @@ module Scryglass
114
114
 
115
115
  # (Used for recalculate_indeces after new Ros have been injected)
116
116
  def next_ro_without_using_index
117
- if sub_ros.any?
118
- sub_ros.first
119
- elsif top_ro?
120
- nil
121
- elsif sibling_down.present?
122
- sibling_down
123
- else
124
- upward_feeler_ro = self
125
- until upward_feeler_ro.sibling_down.present? || upward_feeler_ro.top_ro?
126
- upward_feeler_ro = upward_feeler_ro.parent_ro
127
- end
128
- upward_feeler_ro.sibling_down
117
+ return sub_ros.first if sub_ros.first
118
+ return nil if top_ro?
119
+ return sibling_down if sibling_down
120
+
121
+ upward_feeler_ro = self
122
+ until upward_feeler_ro.sibling_down || upward_feeler_ro.top_ro?
123
+ upward_feeler_ro = upward_feeler_ro.parent_ro
129
124
  end
125
+ upward_feeler_ro.sibling_down
130
126
  end
131
127
 
132
128
  def sibling_down
@@ -310,7 +310,7 @@ module Scryglass
310
310
  through_indicator = is_through ? '(t)' : ' '
311
311
  end
312
312
 
313
- is_scoped = info.scope.present?
313
+ is_scoped = !!info.scope
314
314
  if include_scoped_associations
315
315
  scoped_indicator = is_scoped ? '(s)' : ' '
316
316
  end
@@ -322,7 +322,7 @@ module Scryglass
322
322
  relation_name.to_s
323
323
  end
324
324
 
325
- if ar_value.present? || include_empty_associations
325
+ if (!ar_value || ar_value.empty?) || include_empty_associations
326
326
  ar_key = Scryglass::ViewWrapper.new(
327
327
  relation_name,
328
328
  string: relation_representation
@@ -11,7 +11,7 @@ class Scryglass::Session
11
11
  :view_panels, :current_panel_type,
12
12
  :progress_bar
13
13
 
14
- attr_accessor :user_input, :last_search, :number_to_move
14
+ attr_accessor :user_signals, :last_search, :number_to_move
15
15
 
16
16
  CURSOR_CHARACTER = '–' # These are en dashes (alt+dash), not hyphens or em dashes.
17
17
 
@@ -33,7 +33,7 @@ class Scryglass::Session
33
33
  self.current_panel_type = :tree
34
34
  self.special_command_targets = []
35
35
  self.number_to_move = ''
36
- self.user_input = nil
36
+ self.user_signals = []
37
37
  self.progress_bar = Prog::Pipe.new
38
38
 
39
39
  top_ro = roify(seed, parent_ro: nil, depth: 1)
@@ -52,15 +52,16 @@ class Scryglass::Session
52
52
  in_scry_session = true
53
53
  redraw = true
54
54
 
55
- case actions
56
- when :record
57
- $scry_session_actions_performed = []
58
- when :playback
59
- if $scry_session_actions_performed.blank?
60
- raise 'Could not find recording of previous session\'s actions'
61
- end
62
- @input_stack = $scry_session_actions_performed.dup
63
- end
55
+ ## On hold: Record/Playback Functionality:
56
+ # case actions
57
+ # when :record
58
+ # $scry_session_actions_performed = []
59
+ # when :playback
60
+ # if $scry_session_actions_performed.blank?
61
+ # raise 'Could not find recording of previous session\'s actions'
62
+ # end
63
+ # @input_stack = $scry_session_actions_performed.dup
64
+ # end
64
65
 
65
66
  # We print a full screen of lines so the first call of draw_screen doesn't
66
67
  # write over any previous valuable content the user had in the console.
@@ -70,24 +71,35 @@ class Scryglass::Session
70
71
  draw_screen if redraw
71
72
  redraw = true
72
73
 
73
- case actions
74
- when :record
75
- self.user_input = $stdin.getch
76
- $scry_session_actions_performed << user_input
77
- when :playback
78
- if @input_stack.any? # (IV to be easily accessible for debugging)
79
- self.user_input = @input_stack.shift
80
- sleep 0.05
81
- else
82
- self.user_input = $stdin.getch
83
- end
84
- else
85
- self.user_input = $stdin.getch
86
- end
74
+ ## On hold: Record/Playback Functionality:
75
+ # case actions
76
+ # when :record
77
+ # self.user_input = $stdin.getch
78
+ # $scry_session_actions_performed << user_input
79
+ # when :playback
80
+ # if @input_stack.any? # (IV to be easily accessible for debugging)
81
+ # self.user_input = @input_stack.shift
82
+ # sleep 0.05
83
+ # else
84
+ # self.user_input = $stdin.getch
85
+ # end
86
+ # else
87
+ # self.user_input = $stdin.getch
88
+ # end
89
+
90
+ new_signal = fetch_user_signal
87
91
 
88
92
  wait_start_time = Time.now
89
93
 
90
- case user_input
94
+ case new_signal
95
+ when nil
96
+ when 'esc'
97
+ case current_panel_type
98
+ when :lens
99
+ self.current_panel_type = :tree
100
+ when :tree
101
+ clear_tracked_values
102
+ end
91
103
  when "\u0003"
92
104
  set_console_cursor_below_content
93
105
  raise IRB::Abort, 'Ctrl+C Detected'
@@ -123,40 +135,34 @@ class Scryglass::Session
123
135
  self.number_to_move += '9'
124
136
  redraw = false
125
137
  when '0'
126
- if number_to_move.present? # You can append zeros to number_to_move...
138
+ if number_to_move[0] # You can append zeros to existing number_to_move...
127
139
  self.number_to_move += '0'
128
140
  redraw = false
129
141
  else # ...but otherwise it's understood to be a view||cursor reset.
130
142
  reset_the_view_or_cursor
131
143
  end
132
144
  when 'A' # Up arrow
133
- action_count = number_to_move.present? ? number_to_move.to_i : 1
145
+ action_count = !number_to_move.empty? ? number_to_move.to_i : 1
134
146
  navigate_up_multiple(action_count)
135
147
 
136
148
  self.number_to_move = ''
137
- lens_view.recalculate_boundaries if current_panel_type == :lens
138
149
  tree_view.slide_view_to_cursor
139
150
  when 'B' # Down arrow
140
- action_count = number_to_move.present? ? number_to_move.to_i : 1
151
+ action_count = !number_to_move.empty? ? number_to_move.to_i : 1
141
152
  navigate_down_multiple(action_count)
142
153
 
143
154
  self.number_to_move = ''
144
- lens_view.recalculate_boundaries if current_panel_type == :lens
145
155
  tree_view.slide_view_to_cursor
146
156
  when 'C' # Right arrow
147
157
  expand_targets
148
158
  when 'D' # Left arrow
149
159
  collapse_targets
150
- lens_view.recalculate_boundaries if current_panel_type == :lens
151
160
  when ' '
152
161
  toggle_view_panel
153
- lens_view.recalculate_boundaries if current_panel_type == :lens
154
162
  when 'l'
155
163
  scroll_lens_type
156
- lens_view.recalculate_boundaries if current_panel_type == :lens
157
164
  when 'L'
158
165
  toggle_current_subject_type
159
- lens_view.recalculate_boundaries if current_panel_type == :lens
160
166
  when 'w'
161
167
  current_view_panel.move_view_up(5)
162
168
  when 's'
@@ -177,15 +183,12 @@ class Scryglass::Session
177
183
  in_scry_session = run_help_screen_ui
178
184
  when '@'
179
185
  build_instance_variables_for_target_ros
180
- tree_view.recalculate_boundaries
181
186
  tree_view.slide_view_to_cursor # Just a nice-to-have
182
187
  when '.'
183
188
  build_activerecord_relations_for_target_ros
184
- tree_view.recalculate_boundaries
185
189
  tree_view.slide_view_to_cursor # Just a nice-to-have
186
190
  when '('
187
191
  build_enum_children_for_target_ros
188
- tree_view.recalculate_boundaries
189
192
  tree_view.slide_view_to_cursor # Just a nice-to-have
190
193
  when '|'
191
194
  sibling_ros = if current_ro.top_ro?
@@ -214,12 +217,15 @@ class Scryglass::Session
214
217
  special_command_targets << current_ro
215
218
  end
216
219
  when '/'
220
+ _screen_height, screen_width = $stdout.winsize
217
221
  $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
218
- $stdout.write SEARCH_PROMPT
222
+ $stdout.print ' ' * screen_width
223
+ $stdout.write "#{CSI}1;1H" # (Moves console cursor to top left corner)
224
+ $stdout.print SEARCH_PROMPT
219
225
  $stdout.write "#{CSI}1;#{SEARCH_PROMPT.ansiless_length + 1}H" # (Moves
220
226
  # console cursor to just after the search prompt, before user types)
221
227
  query = $stdin.gets.chomp
222
- if query.present?
228
+ unless query.empty?
223
229
  self.last_search = query
224
230
  go_to_next_search_result
225
231
  end
@@ -236,7 +242,7 @@ class Scryglass::Session
236
242
  return subjects_of_target_ros
237
243
  end
238
244
 
239
- print "\a" if Time.now - wait_start_time > 4 && user_input != '?' # (Audio 'beep')
245
+ print "\a" if Time.now - wait_start_time > 4 && last_keypress != '?' # (Audio 'beep')
240
246
  end
241
247
  end
242
248
 
@@ -244,13 +250,24 @@ class Scryglass::Session
244
250
  all_ros.first
245
251
  end
246
252
 
253
+ def last_keypress
254
+ last_two_signals = user_signals.last(2)
255
+ last_two_signals.last || last_two_signals.first
256
+ end
257
+
247
258
  private
248
259
 
260
+ def clear_tracked_values
261
+ self.special_command_targets = []
262
+ self.last_search = nil
263
+ self.number_to_move = ''
264
+ end
265
+
249
266
  def print_progress_bar
250
267
  screen_height, _screen_width = $stdout.winsize
251
268
  bar = progress_bar.to_s
252
269
  $stdout.write "#{CSI}#{screen_height};1H" # (Moves console cursor to bottom left corner)
253
- print bar if bar.present?
270
+ print bar unless bar.tr(' ', '').empty?
254
271
  end
255
272
 
256
273
  def current_view_panel
@@ -325,6 +342,29 @@ class Scryglass::Session
325
342
  end
326
343
  end
327
344
 
345
+ def fetch_user_signal
346
+ previous_signal = user_signals.last
347
+ new_signal =
348
+ begin
349
+ Timeout.timeout(0.1) { $stdin.getch }
350
+ rescue Timeout::Error
351
+ nil
352
+ end
353
+
354
+ ## Since many keys, including arrow keys, result in several signals being
355
+ ## sent (e.g. DOWN: "\e" then "[" then "B" in RAPID succession), the
356
+ ## *pause* after a genuine escape key press (also "\e") is the only way
357
+ ## to distinguish it precisely.
358
+ genuine_escape_key_press = new_signal.nil? && previous_signal == "\e"
359
+ if genuine_escape_key_press
360
+ new_signal = 'esc'
361
+ end
362
+
363
+ user_signals << new_signal unless new_signal.nil? && previous_signal.nil?
364
+
365
+ new_signal
366
+ end
367
+
328
368
  def run_help_screen_ui
329
369
  screen_height, _screen_width = $stdout.winsize
330
370
 
@@ -337,9 +377,12 @@ class Scryglass::Session
337
377
  sliced_help_screen = Hexes.simple_screen_slice(current_help_screen)
338
378
  help_screen_string = Hexes.opacify_screen_string(sliced_help_screen)
339
379
  Hexes.overwrite_screen(help_screen_string)
340
- help_screen_user_input = $stdin.getch
341
380
 
342
- case help_screen_user_input
381
+ new_signal = fetch_user_signal
382
+
383
+ case new_signal
384
+ when 'esc'
385
+ return true
343
386
  when '?'
344
387
  current_help_screen_index += 1
345
388
  when 'q'
@@ -375,7 +418,6 @@ class Scryglass::Session
375
418
 
376
419
  move_cursor_to(current_ro.parent_ro) until current_ro.visible?
377
420
  tree_view.slide_view_to_cursor
378
- tree_view.recalculate_boundaries # TODO: should these be conditional? If they are, I might need a potential tree view recalc after toggling lens view to tree view.
379
421
  end
380
422
 
381
423
  def expand_targets
@@ -388,7 +430,6 @@ class Scryglass::Session
388
430
  else
389
431
  expand!(current_ro)
390
432
  end
391
- tree_view.recalculate_boundaries
392
433
  end
393
434
 
394
435
  def reset_the_view_or_cursor
@@ -400,6 +441,8 @@ class Scryglass::Session
400
441
  end
401
442
 
402
443
  def draw_screen
444
+ current_view_panel.recalculate_boundaries # This now happens at every screen
445
+ # draw to account for the user changing the screen size. Otherwise glitch.
403
446
  current_view_panel.ensure_correct_view_coords
404
447
  screen_string = current_view_panel.screen_string
405
448
 
@@ -4,6 +4,7 @@ module Scryglass
4
4
  class TreePanel < Scryglass::ViewPanel
5
5
  using ClipStringRefinement
6
6
  using AnsilessStringRefinement
7
+ using ArrayFitToRefinement
7
8
 
8
9
  def slide_view_to_cursor
9
10
  cursor_tracking = Scryglass.config.cursor_tracking
@@ -54,13 +55,35 @@ module Scryglass
54
55
 
55
56
  def uncut_header_string
56
57
  _screen_height, screen_width = $stdout.winsize
57
- header_string = "Press '?' for controls\n".rjust(screen_width, ' ') +
58
- '·' * screen_width
59
- if scry_session.special_command_targets.any?
60
- special_targets_message = " (Next command will apply to all selected rows)"
61
- header_string[0...special_targets_message.length] = special_targets_message
58
+ dotted_line = '·' * screen_width
59
+
60
+ number_to_move = scry_session.number_to_move
61
+ last_search = scry_session.last_search
62
+ special_command_targets = scry_session.special_command_targets
63
+
64
+ if special_command_targets.any?
65
+ special_targets_message = "(Next command will apply to all (#{special_command_targets.count}) selected rows)"
66
+ end
67
+ if !number_to_move.empty?
68
+ number_to_move_message = " Move distance: #{number_to_move}"
69
+ end
70
+ if last_search
71
+ last_search_message = " Last search: #{last_search}"
62
72
  end
63
- header_string
73
+ if [special_targets_message, number_to_move_message, last_search].none?
74
+ help_key_reminder = "Press '?' for controls"
75
+ end
76
+
77
+ tree_header_items = [
78
+ special_targets_message,
79
+ last_search_message,
80
+ number_to_move_message,
81
+ help_key_reminder
82
+ ]
83
+
84
+ fit_tree_header_array = tree_header_items.fit_to(screen_width)
85
+
86
+ fit_tree_header_array.join('') + "\n" + dotted_line
64
87
  end
65
88
 
66
89
  def visible_body_slice(uncut_body_string)
@@ -1,3 +1,3 @@
1
1
  module Scryglass
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -41,7 +41,6 @@ Gem::Specification.new do |spec|
41
41
 
42
42
  spec.required_ruby_version = '>= 2.5.3'
43
43
 
44
- spec.add_development_dependency 'activesupport', '~> 5.0'
45
44
  spec.add_development_dependency 'bundler', '~> 2.1'
46
45
  spec.add_development_dependency 'rake', '~> 12.0'
47
46
 
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scryglass
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.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-18 00:00:00.000000000 Z
11
+ date: 2020-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '5.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '5.0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: bundler
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -62,6 +48,7 @@ executables: []
62
48
  extensions: []
63
49
  extra_rdoc_files: []
64
50
  files:
51
+ - ".DS_Store"
65
52
  - ".gitignore"
66
53
  - ".tool-versions"
67
54
  - CHANGELOG.md
@@ -76,6 +63,7 @@ files:
76
63
  - lib/example_material.rb
77
64
  - lib/hexes.rb
78
65
  - lib/prog.rb
66
+ - lib/refinements/ansi_slice_string_refinement.rb
79
67
  - lib/refinements/ansiless_string_refinement.rb
80
68
  - lib/refinements/array_fit_to_refinement.rb
81
69
  - lib/refinements/clip_string_refinement.rb