Class: AttributedString::FilterResult

Inherits:
String
  • Object
show all
Defined in:
lib/attributed_string/filter_result.rb

Instance Method Summary collapse

Constructor Details

#initialize(attr_string, &block) ⇒ FilterResult

Returns a new instance of FilterResult.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/attributed_string/filter_result.rb', line 18

def initialize(attr_string, &block)
  filtered_positions = []
  cached_block_calls = {}

  # TODO: this can be optimized to use the same method that inspect uses which doesn't go through
  # every character (it goes through each substring span with different ranges)
  # A presenter type architecture that inspect, rainbow print, and filter can share would be ideal
  attr_string.each_char.with_index do |char, index|
    attrs = attr_string.attrs_at(index)
    # Use the attrs object ID as the cache key to handle different attribute hashes
    cache_key = attrs.hash
    cached_result = cached_block_calls.fetch(cache_key) do
      result = block.call(attrs)
      cached_block_calls[cache_key] = result
      result
    end
    if cached_result
      filtered_positions << index
    end
  end

  # Group adjacent positions into ranges to minimize allocations
  ranges = []
  unless filtered_positions.empty?
    start_pos = filtered_positions.first
    prev_pos = start_pos
    filtered_positions.each_with_index do |pos, idx|
      next if idx == 0
      if pos == prev_pos + 1
        # Continue the current range
        prev_pos = pos
      else
        # End the current range and start a new one
        ranges << (start_pos..prev_pos)
        start_pos = pos
        prev_pos = pos
      end
    end
    # Add the final range
    ranges << (start_pos..prev_pos)
  end

  # Concatenate substrings from the original string based on the ranges
  result_string = ranges.map { |range| attr_string.send(:original_slice,range) }.join

  # Build the list of original positions
  original_positions = ranges.flat_map { |range| range.to_a }

  super(result_string)
  @original_positions = original_positions
  freeze
end

Instance Method Details

#original_position_at(index) ⇒ Object



71
72
73
# File 'lib/attributed_string/filter_result.rb', line 71

def original_position_at(index)
  @original_positions.fetch(index)
end

#original_ranges_for(filtered_range) ⇒ Object

Raises:

  • (ArgumentError)


75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/attributed_string/filter_result.rb', line 75

def original_ranges_for(filtered_range)
  # TODO: this doesn't work for excluded end range
  raise ArgumentError, "Invalid range" unless filtered_range.is_a?(Range)
  raise ArgumentError, "Range out of bounds" if filtered_range.end >= length
  if filtered_range.begin > filtered_range.end
    raise ArgumentError, "Reverse range is not allowed"
  end
  if filtered_range.begin == filtered_range.end && filtered_range.exclude_end?
    return []
  end

  original_positions = @original_positions[filtered_range]
  ranges = []
  start_pos = original_positions.first
  prev_pos = start_pos

  original_positions.each_with_index do |pos, idx|
    next if idx == 0
    if pos == prev_pos + 1
      # Continue the current range
      prev_pos = pos
    else
      # End the current range and start a new one
      ranges << (start_pos..prev_pos)
      start_pos = pos
      prev_pos = pos
    end
  end
  # Add the final range
  ranges << (start_pos..prev_pos)
  ranges
end