[rubygems/rubygems] Use a constant empty tar header to avoid extra allocations
[ruby.git] / lib / syntax_suggest / code_block.rb
blobd842890300457d63124daa1dd934242dcb9fb4ae
1 # frozen_string_literal: true
3 module SyntaxSuggest
4   # Multiple lines form a singular CodeBlock
5   #
6   # Source code is made of multiple CodeBlocks.
7   #
8   # Example:
9   #
10   #   code_block.to_s # =>
11   #     #   def foo
12   #     #     puts "foo"
13   #     #   end
14   #
15   #   code_block.valid? # => true
16   #   code_block.in_valid? # => false
17   #
18   #
19   class CodeBlock
20     UNSET = Object.new.freeze
21     attr_reader :lines, :starts_at, :ends_at
23     def initialize(lines: [])
24       @lines = Array(lines)
25       @valid = UNSET
26       @deleted = false
27       @starts_at = @lines.first.number
28       @ends_at = @lines.last.number
29     end
31     def delete
32       @deleted = true
33     end
35     def deleted?
36       @deleted
37     end
39     def visible_lines
40       @lines.select(&:visible?).select(&:not_empty?)
41     end
43     def mark_invisible
44       @lines.map(&:mark_invisible)
45     end
47     def is_end?
48       to_s.strip == "end"
49     end
51     def hidden?
52       @lines.all?(&:hidden?)
53     end
55     # This is used for frontier ordering, we are searching from
56     # the largest indentation to the smallest. This allows us to
57     # populate an array with multiple code blocks then call `sort!`
58     # on it without having to specify the sorting criteria
59     def <=>(other)
60       out = current_indent <=> other.current_indent
61       return out if out != 0
63       # Stable sort
64       starts_at <=> other.starts_at
65     end
67     def current_indent
68       @current_indent ||= lines.select(&:not_empty?).map(&:indent).min || 0
69     end
71     def invalid?
72       !valid?
73     end
75     def valid?
76       if @valid == UNSET
77         # Performance optimization
78         #
79         # If all the lines were previously hidden
80         # and we expand to capture additional empty
81         # lines then the result cannot be invalid
82         #
83         # That means there's no reason to re-check all
84         # lines with the parser (which is expensive).
85         # Benchmark in commit message
86         @valid = if lines.all? { |l| l.hidden? || l.empty? }
87           true
88         else
89           SyntaxSuggest.valid?(lines.map(&:original).join)
90         end
91       else
92         @valid
93       end
94     end
96     def to_s
97       @lines.join
98     end
99   end