@@ -23,31 +23,38 @@ def apply(content, search, replace)
2323 lines = content . split ( "\n " )
2424 search_lines = search . split ( "\n " )
2525
26- # First try exact matching
26+ # 1. Try exact matching
2727 match_positions =
2828 find_matches ( lines , search_lines ) { |line , search_line | line == search_line }
2929
30- # sripped match
30+ # 2. Try stripped matching
3131 if match_positions . empty?
3232 match_positions =
3333 find_matches ( lines , search_lines ) do |line , search_line |
3434 line . strip == search_line . strip
3535 end
3636 end
3737
38- # Fallback to fuzzy matching if no exact matches found
38+ # 3. Try fuzzy matching
3939 if match_positions . empty?
4040 match_positions =
4141 find_matches ( lines , search_lines ) do |line , search_line |
4242 fuzzy_match? ( line , search_line )
4343 end
4444 end
4545
46+ # 4. Try block matching as last resort
47+ if match_positions . empty?
48+ if block_matches = find_block_matches ( content , search )
49+ return replace_blocks ( content , block_matches , replace )
50+ end
51+ end
52+
4653 if match_positions . empty?
4754 raise NoMatchError , "Could not find a match for the search content"
4855 end
4956
50- # Replace every occurrence (process in descending order to avoid shifting indices)
57+ # Replace matches in reverse order
5158 match_positions . sort . reverse . each do |pos |
5259 lines . slice! ( pos , search_lines . length )
5360 lines . insert ( pos , *replace . split ( "\n " ) )
@@ -61,11 +68,13 @@ def apply(content, search, replace)
6168 def find_matches ( lines , search_lines )
6269 matches = [ ]
6370 max_index = lines . length - search_lines . length
71+
6472 ( 0 ..max_index ) . each do |i |
6573 if ( 0 ...search_lines . length ) . all? { |j | yield ( lines [ i + j ] , search_lines [ j ] ) }
6674 matches << i
6775 end
6876 end
77+
6978 matches
7079 end
7180
@@ -80,16 +89,89 @@ def levenshtein_distance(s1, s2)
8089 m = s1 . length
8190 n = s2 . length
8291 d = Array . new ( m + 1 ) { Array . new ( n + 1 , 0 ) }
92+
8393 ( 0 ..m ) . each { |i | d [ i ] [ 0 ] = i }
8494 ( 0 ..n ) . each { |j | d [ 0 ] [ j ] = j }
95+
8596 ( 1 ..m ) . each do |i |
8697 ( 1 ..n ) . each do |j |
8798 cost = s1 [ i - 1 ] == s2 [ j - 1 ] ? 0 : 1
8899 d [ i ] [ j ] = [ d [ i - 1 ] [ j ] + 1 , d [ i ] [ j - 1 ] + 1 , d [ i - 1 ] [ j - 1 ] + cost ] . min
89100 end
90101 end
102+
91103 d [ m ] [ n ]
92104 end
105+
106+ def find_block_matches ( content , search )
107+ content_blocks = extract_blocks ( content )
108+ search_blocks = extract_blocks ( search )
109+
110+ return nil if content_blocks . empty? || search_blocks . empty?
111+
112+ matches = [ ]
113+ search_blocks . each do |search_block |
114+ content_blocks . each do |content_block |
115+ matches << content_block if content_block [ :text ] == search_block [ :text ]
116+ end
117+ end
118+
119+ matches . empty? ? nil : matches
120+ end
121+
122+ def extract_blocks ( text )
123+ lines = text . split ( "\n " )
124+ blocks = [ ]
125+ current_block = [ ]
126+ block_start = nil
127+
128+ lines . each_with_index do |line , index |
129+ if line =~ /^[^\s ]/
130+ # Save previous block if exists
131+ if !current_block . empty?
132+ current_block << line
133+ blocks << {
134+ start : block_start ,
135+ length : current_block . length ,
136+ text : current_block . join ( "\n " ) . strip ,
137+ }
138+ current_block = [ ]
139+ else
140+ current_block = [ line ]
141+ block_start = index
142+ end
143+ else
144+ # Continue current block
145+ current_block << line if current_block . any?
146+ end
147+ end
148+
149+ # Add final block
150+ if !current_block . empty?
151+ blocks << {
152+ start : block_start ,
153+ length : current_block . length ,
154+ text : current_block . join ( "\n " ) . strip ,
155+ }
156+ end
157+
158+ blocks
159+ end
160+
161+ def replace_blocks ( content , blocks , replace )
162+ lines = content . split ( "\n " )
163+
164+ # Sort blocks in reverse order to maintain correct positions
165+ blocks
166+ . sort_by { |b | -b [ :start ] }
167+ . each_with_index do |block , index |
168+ replacement = index . zero? ? replace : ""
169+ lines . slice! ( block [ :start ] , block [ :length ] )
170+ lines . insert ( block [ :start ] , *replacement . split ( "\n " ) )
171+ end
172+
173+ lines . join ( "\n " )
174+ end
93175 end
94176 end
95177 end
0 commit comments