Class: AttributedString::FilterResult
- Inherits:
-
String
- Object
- String
- AttributedString::FilterResult
- Defined in:
- lib/attributed_string/filter_result.rb
Instance Method Summary collapse
-
#initialize(attr_string, &block) ⇒ FilterResult
constructor
A new instance of FilterResult.
- #original_position_at(index) ⇒ Object
- #original_ranges_for(filtered_range) ⇒ Object
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
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 |