@@ -38,35 +38,64 @@ def initialize(code_lines:, block:)
3838 @before_array = [ ]
3939 @stop_after_kw = false
4040
41- @skip_hidden = false
42- @skip_empty = false
41+ @force_add_hidden = false
42+ @force_add_empty = false
4343 end
4444
45- def skip ( name )
46- case name
47- when :hidden?
48- @skip_hidden = true
49- when :empty?
50- @skip_empty = true
51- else
52- raise "Unsupported skip #{ name } "
53- end
45+ # When using this flag, `scan_while` will
46+ # bypass the block it's given and always add a
47+ # line that responds truthy to `CodeLine#hidden?`
48+ #
49+ # Lines are hidden when they've been evaluated by
50+ # the parser as part of a block and found to contain
51+ # valid code.
52+ def force_add_hidden
53+ @force_add_hidden = true
54+ self
55+ end
56+
57+ # When using this flag, `scan_while` will
58+ # bypass the block it's given and always add a
59+ # line that responds truthy to `CodeLine#empty?`
60+ #
61+ # Empty lines contain no code, only whitespace such
62+ # as leading spaces a newline.
63+ def force_add_empty
64+ @force_add_empty = true
5465 self
5566 end
5667
68+ # Tells `scan_while` to look for mismatched keyword/end-s
69+ #
70+ # When scanning up, if we see more keywords then end-s it will
71+ # stop. This might happen when scanning outside of a method body.
72+ # the first scan line up would be a keyword and this setting would
73+ # trigger a stop.
74+ #
75+ # When scanning down, stop if there are more end-s than keywords.
5776 def stop_after_kw
5877 @stop_after_kw = true
5978 self
6079 end
6180
81+ # Main work method
82+ #
83+ # The scan_while method takes a block that yields lines above and
84+ # below the block. If the yield returns true, the @before_index
85+ # or @after_index are modified to include the matched line.
86+ #
87+ # In addition to yielding individual lines, the internals of this
88+ # object give a mini DSL to handle common situations such as
89+ # stopping if we've found a keyword/end mis-match in one direction
90+ # or the other.
6291 def scan_while
6392 stop_next = false
6493 kw_count = 0
6594 end_count = 0
6695 index = before_lines . reverse_each . take_while do |line |
6796 next false if stop_next
68- next true if @skip_hidden && line . hidden?
69- next true if @skip_empty && line . empty?
97+ next true if @force_add_hidden && line . hidden?
98+ next true if @force_add_empty && line . empty?
7099
71100 kw_count += 1 if line . is_kw?
72101 end_count += 1 if line . is_end?
@@ -86,8 +115,8 @@ def scan_while
86115 end_count = 0
87116 index = after_lines . take_while do |line |
88117 next false if stop_next
89- next true if @skip_hidden && line . hidden?
90- next true if @skip_empty && line . empty?
118+ next true if @force_add_hidden && line . hidden?
119+ next true if @force_add_empty && line . empty?
91120
92121 kw_count += 1 if line . is_kw?
93122 end_count += 1 if line . is_end?
@@ -104,6 +133,33 @@ def scan_while
104133 self
105134 end
106135
136+ # Shows surrounding kw/end pairs
137+ #
138+ # The purpose of showing these extra pairs is due to cases
139+ # of ambiguity when only one visible line is matched.
140+ #
141+ # For example:
142+ #
143+ # 1 class Dog
144+ # 2 def bark
145+ # 4 def eat
146+ # 5 end
147+ # 6 end
148+ #
149+ # In this case either line 2 could be missing an `end` or
150+ # line 4 was an extra line added by mistake (it happens).
151+ #
152+ # When we detect the above problem it shows the issue
153+ # as only being on line 2
154+ #
155+ # 2 def bark
156+ #
157+ # Showing "neighbor" keyword pairs gives extra context:
158+ #
159+ # 2 def bark
160+ # 4 def eat
161+ # 5 end
162+ #
107163 def capture_neighbor_context
108164 lines = [ ]
109165 kw_count = 0
@@ -145,6 +201,20 @@ def capture_neighbor_context
145201 lines
146202 end
147203
204+ # Shows the context around code provided by "falling" indentation
205+ #
206+ # Converts:
207+ #
208+ # it "foo" do
209+ #
210+ # into:
211+ #
212+ # class OH
213+ # def hello
214+ # it "foo" do
215+ # end
216+ # end
217+ #
148218 def on_falling_indent
149219 last_indent = @orig_indent
150220 before_lines . reverse_each do |line |
@@ -213,18 +283,31 @@ def lookahead_balance_one_line
213283 self
214284 end
215285
286+ # Finds code lines at the same or greater indentation and adds them
287+ # to the block
216288 def scan_neighbors_not_empty
217289 scan_while { |line | line . not_empty? && line . indent >= @orig_indent }
218290 end
219291
292+ # Returns the next line to be scanned above the current block.
293+ # Returns `nil` if at the top of the document already
220294 def next_up
221295 @code_lines [ before_index . pred ]
222296 end
223297
298+ # Returns the next line to be scanned below the current block.
299+ # Returns `nil` if at the bottom of the document already
224300 def next_down
225301 @code_lines [ after_index . next ]
226302 end
227303
304+ # Scan blocks based on indentation of next line above/below block
305+ #
306+ # Determines indentaion of the next line above/below the current block.
307+ #
308+ # Normally this is called when a block has expanded to capture all "neighbors"
309+ # at the same (or greater) indentation and needs to expand out. For example
310+ # the `def/end` lines surrounding a method.
228311 def scan_adjacent_indent
229312 before_after_indent = [ ]
230313 before_after_indent << ( next_up &.indent || 0 )
@@ -236,6 +319,16 @@ def scan_adjacent_indent
236319 self
237320 end
238321
322+ # TODO: Doc or delete
323+ #
324+ # I don't remember why this is needed, but it's called in code_context.
325+ # It's related to the implementation of `capture_neighbor_context` somehow
326+ # and that display improvement is only triggered when there's one visible line
327+ #
328+ # I think the primary purpose is to not include the current line in the
329+ # logic evaluation of `capture_neighbor_context`. If that's true, then
330+ # we should fix that method to handle this logic instead of only using
331+ # it in one place and together.
239332 def start_at_next_line
240333 before_index
241334 after_index
@@ -244,26 +337,39 @@ def start_at_next_line
244337 self
245338 end
246339
340+ # Return the currently matched lines as a `CodeBlock`
341+ #
342+ # When a `CodeBlock` is created it will gather metadata about
343+ # itself, so this is not a free conversion. Avoid allocating
344+ # more CodeBlock's than needed
247345 def code_block
248346 CodeBlock . new ( lines : lines )
249347 end
250348
349+ # Returns the lines matched by the current scan as an
350+ # array of CodeLines
251351 def lines
252352 @code_lines [ before_index ..after_index ]
253353 end
254354
355+ # Gives the index of the first line currently scanned
255356 def before_index
256357 @before_index ||= @orig_before_index
257358 end
258359
360+ # Gives the index of the last line currently scanned
259361 def after_index
260362 @after_index ||= @orig_after_index
261363 end
262364
365+ # Returns an array of all the CodeLines that exist before
366+ # the currently scanned block
263367 private def before_lines
264368 @code_lines [ 0 ...before_index ] || [ ]
265369 end
266370
371+ # Returns an array of all the CodeLines that exist after
372+ # the currently scanned block
267373 private def after_lines
268374 @code_lines [ after_index . next ..-1 ] || [ ]
269375 end
0 commit comments