@@ -378,39 +378,52 @@ fn apply_complex_replace(
378378 let start_byte = feature. location . byte_span . 0 - ws_len;
379379 let end_byte = feature. location . byte_span . 1 ;
380380
381- // Find the colon separating key from value
382- let colon_pos = find_key_colon ( content_with_ws) ;
381+ // Use query_exact to locate the value's byte span independently.
382+ // This avoids string-searching for the colon separator, which breaks
383+ // on quoted keys containing colons (e.g. "http://example.com": 8080).
384+ let value_feature = doc
385+ . query_exact ( route)
386+ . map_err ( |e| format ! ( "Query failed: {e}" ) ) ?;
383387
384- let key_part = match colon_pos {
385- Some ( pos) => {
386- let key = & content_with_ws[ ..pos + 1 ] ; // through the colon
387- key. to_string ( )
388+ let key_part = match value_feature {
389+ Some ( vf) => {
390+ let vf_start = vf. location . byte_span . 0 ;
391+ // Note: no reversed-span check needed; tree-sitter nodes guarantee start <= end.
392+ if vf_start > source. len ( ) || !source. is_char_boundary ( vf_start) {
393+ return Err ( "Value feature span is not valid in source" . to_string ( ) ) ;
394+ }
395+ let prefix = source[ start_byte..vf_start] . trim_end ( ) ;
396+ if prefix. is_empty ( ) {
397+ // Bare value (e.g. sequence item) — no key prefix
398+ let serialized = serde_yaml:: to_string ( value)
399+ . map_err ( |e| format ! ( "Failed to serialize YAML: {e}" ) ) ?;
400+ let trimmed = serialized. trim_end_matches ( '\n' ) ;
401+
402+ let line_start = source[ ..feature. location . byte_span . 0 ]
403+ . rfind ( '\n' )
404+ . map ( |nl| nl + 1 )
405+ . unwrap_or ( 0 ) ;
406+ let base_indent = feature. location . byte_span . 0 - line_start;
407+ let indent_str = " " . repeat ( base_indent) ;
408+
409+ let indented = indent_block ( trimmed, & indent_str) ;
410+
411+ let mut result = source. to_string ( ) ;
412+ result. replace_range (
413+ feature. location . byte_span . 0 ..feature. location . byte_span . 1 ,
414+ & indented,
415+ ) ;
416+ if !result. ends_with ( '\n' ) {
417+ result. push ( '\n' ) ;
418+ }
419+ return yamlpath:: Document :: new ( result)
420+ . map_err ( |e| format ! ( "Failed to re-parse YAML: {e}" ) ) ;
421+ }
422+ prefix. to_string ( )
388423 }
389424 None => {
390- // No colon found — bare value (e.g. sequence item)
391- let serialized = serde_yaml:: to_string ( value)
392- . map_err ( |e| format ! ( "Failed to serialize YAML: {e}" ) ) ?;
393- let trimmed = serialized. trim_end_matches ( '\n' ) ;
394-
395- let line_start = source[ ..feature. location . byte_span . 0 ]
396- . rfind ( '\n' )
397- . map ( |nl| nl + 1 )
398- . unwrap_or ( 0 ) ;
399- let base_indent = feature. location . byte_span . 0 - line_start;
400- let indent_str = " " . repeat ( base_indent) ;
401-
402- let indented = indent_block ( trimmed, & indent_str) ;
403-
404- let mut result = source. to_string ( ) ;
405- result. replace_range (
406- feature. location . byte_span . 0 ..feature. location . byte_span . 1 ,
407- & indented,
408- ) ;
409- if !result. ends_with ( '\n' ) {
410- result. push ( '\n' ) ;
411- }
412- return yamlpath:: Document :: new ( result)
413- . map_err ( |e| format ! ( "Failed to re-parse YAML: {e}" ) ) ;
425+ // Absent value (e.g. `key:\n`) — content is just key+colon
426+ content_with_ws. trim_end ( ) . to_string ( )
414427 }
415428 } ;
416429
@@ -449,16 +462,6 @@ fn apply_complex_replace(
449462 yamlpath:: Document :: new ( result) . map_err ( |e| format ! ( "Failed to re-parse YAML: {e}" ) )
450463}
451464
452- /// Find the first colon (key-value separator) in a YAML fragment.
453- ///
454- /// Uses a naive `find(':')`, consistent with yamlpatch's own Replace
455- /// implementation. This means colons inside quoted keys will be
456- /// misidentified — a known yamlpatch limitation that will be fixed
457- /// uniformly when yamlpatch addresses it.
458- fn find_key_colon ( content : & str ) -> Option < usize > {
459- content. find ( ':' )
460- }
461-
462465fn indent_block ( content : & str , indent : & str ) -> String {
463466 let mut result = String :: new ( ) ;
464467 for ( i, line) in content. lines ( ) . enumerate ( ) {
0 commit comments