use anyhow::Result;use serde::{Serialize, de::DeserializeOwned};use serde_json::Value;use std::{ops::Range, sync::LazyLock};use tree_sitter::{Query, StreamingIterator as _};use util::RangeExt; pub fn update_value_in_json_text<'a>( text: &mut String, key_path: &mut Vec<&'a str>, tab_size: usize, old_value: &'a Value, new_value: &'a Value, edits: &mut Vec<(Range<usize>, String)>,) { // If the old and new values are both objects, then compare them key by key, // preserving the comments and formatting of the unchanged parts. Otherwise, // replace the old value with the new value. if let (Value::Object(old_object), Value::Object(new_object)) = (old_value, new_value) { for (key, old_sub_value) in old_object.iter() { key_path.push(key); if let Some(new_sub_value) = new_object.get(key) { // Key exists in both old and new, recursively update update_value_in_json_text( text, key_path, tab_size, old_sub_value, new_sub_value, edits, ); } else { // Key was removed from new object, remove the entire key-value pair let (range, replacement) = replace_value_in_json_text(text, key_path, 0, None, None); text.replace_range(range.clone(), &replacement); edits.push((range, replacement)); } key_path.pop(); } for (key, new_sub_value) in new_object.iter() { key_path.push(key); if !old_object.contains_key(key) { update_value_in_json_text( text, key_path, tab_size, &Value::Null, new_sub_value, edits, ); } key_path.pop(); } } else if old_value != new_value { let mut new_value = new_value.clone(); if let Some(new_object) = new_value.as_object_mut() { new_object.retain(|_, v| !v.is_null()); } let (range, replacement) = replace_value_in_json_text(text, key_path, tab_size, Some(&new_value), None); text.replace_range(range.clone(), &replacement); edits.push((range, replacement)); }} /// * `replace_key` - When an exact key match according to `key_path` is found, replace the key with `replace_key` if `Some`.pub fn replace_value_in_json_text<T: AsRef<str>>( text: &str, key_path: &[T], tab_size: usize, new_value: Option<&Value>, replace_key: Option<&str>,) -> (Range<usize>, String) { static PAIR_QUERY: LazyLock<Query> = LazyLock::new(|| { Query::new( &tree_sitter_json::LANGUAGE.into(), "(pair key: (string) @key value: (_) @value)", ) .expect("Failed to create PAIR_QUERY") }); let mut parser = tree_sitter::Parser::new(); parser .set_language(&tree_sitter_json::LANGUAGE.into()) .unwrap(); let syntax_tree = parser.parse(text, None).unwrap(); let mut cursor = tree_sitter::QueryCursor::new(); let mut depth = 0; let mut last_value_range = 0..0; let mut first_key_start = None; let mut existing_value_range = 0..text.len(); let mut matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); while let Some(mat) = matches.next() { if mat.captures.len() != 2 { continue; } let key_range = mat.captures[0].node.byte_range(); let value_range = mat.captures[1].node.byte_range(); // Don't enter sub objects until we find an exact // match for the current keypath if last_value_range.contains_inclusive(&value_range) { continue; } last_value_range = value_range.clone(); if key_range.start > existing_value_range.end { break; } first_key_start.get_or_insert(key_range.start); let found_key = text .get(key_range.clone()) .zip(key_path.get(depth)) .and_then(|(key_text, key_path_value)| { serde_json::to_string(key_path_value.as_ref()) .ok() .map(|key_path| depth < key_path.len() && key_text == key_path) }) .unwrap_or(false); if found_key { existing_value_range = value_range; // Reset last value range when increasing in depth last_value_range = existing_value_range.start..existing_value_range.start; depth += 1; if depth == key_path.len() { break; } if let Some(array_replacement) = handle_possible_array_value( &mat.captures[0].node, &mat.captures[1].node, text, &key_path[depth..], new_value, replace_key, tab_size, ) { return array_replacement; } first_key_start = None; } } // We found the exact key we want if depth == key_path.len() { if let Some(new_value) = new_value { let new_val = to_pretty_json(new_value, tab_size, tab_size * depth); if let Some(replace_key) = replace_key.and_then(|str| serde_json::to_string(str).ok()) { let new_key = format!("{}: ", replace_key); if let Some(key_start) = text[..existing_value_range.start].rfind('"') { if let Some(prev_key_start) = text[..key_start].rfind('"') { existing_value_range.start = prev_key_start; } else { existing_value_range.start = key_start; } } (existing_value_range, new_key + &new_val) } else { (existing_value_range, new_val) } } else { let mut removal_start = first_key_start.unwrap_or(existing_value_range.start); let mut removal_end = existing_value_range.end; // Find the actual key position by looking for the key in the pair // We need to extend the range to include the key, not just the value if let Some(key_start) = text[..existing_value_range.start].rfind('"') { if let Some(prev_key_start) = text[..key_start].rfind('"') { removal_start = prev_key_start; } else { removal_start = key_start; } } let mut removed_comma = false; // Look backward for a preceding comma first let preceding_text = text.get(0..removal_start).unwrap_or(""); if let Some(comma_pos) = preceding_text.rfind(',') { // Check if there are only whitespace characters between the comma and our key let between_comma_and_key = text.get(comma_pos + 1..removal_start).unwrap_or(""); if between_comma_and_key.trim().is_empty() { removal_start = comma_pos; removed_comma = true; } } if let Some(remaining_text) = text.get(existing_value_range.end..) && !removed_comma { let mut chars = remaining_text.char_indices(); while let Some((offset, ch)) = chars.next() { if ch == ',' { removal_end = existing_value_range.end + offset + 1; // Also consume whitespace after the comma for (_, next_ch) in chars.by_ref() { if next_ch.is_whitespace() { removal_end += next_ch.len_utf8(); } else { break; } } break; } else if !ch.is_whitespace() { break; } } } (removal_start..removal_end, String::new()) } } else { if let Some(first_key_start) = first_key_start { // We have key paths, construct the sub objects let new_key = key_path[depth].as_ref(); // We don't have the key, construct the nested objects let new_value = construct_json_value(&key_path[(depth + 1)..], new_value); let mut row = 0; let mut column = 0; for (ix, char) in text.char_indices() { if ix == first_key_start { break; } if char == '\n' { row += 1; column = 0; } else { column += char.len_utf8(); } } if row > 0 { // depth is 0 based, but division needs to be 1 based. let new_val = to_pretty_json(&new_value, column / (depth + 1), column); let space = ' '; let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); (first_key_start..first_key_start, content) } else { let new_val = serde_json::to_string(&new_value).unwrap(); let mut content = format!(r#""{new_key}": {new_val},"#); content.push(' '); (first_key_start..first_key_start, content) } } else { // We don't have the key, construct the nested objects let new_value = construct_json_value(&key_path[depth..], new_value); let indent_prefix_len = tab_size * depth; let mut new_val = to_pretty_json(&new_value, tab_size, indent_prefix_len); if depth == 0 { new_val.push('\n'); } // best effort to keep comments with best effort indentation let mut replace_text = &text[existing_value_range.clone()]; while let Some(comment_start) = replace_text.rfind("//") { if let Some(comment_end) = replace_text[comment_start..].find('\n') { let mut comment_with_indent_start = replace_text[..comment_start] .rfind('\n') .unwrap_or(comment_start); if !replace_text[comment_with_indent_start..comment_start] .trim() .is_empty() { comment_with_indent_start = comment_start; } new_val.insert_str( 1, &replace_text[comment_with_indent_start..comment_start + comment_end], ); } replace_text = &replace_text[..comment_start]; } (existing_value_range, new_val) } }} fn construct_json_value( key_path: &[impl AsRef<str>], new_value: Option<&serde_json::Value>,) -> serde_json::Value { let mut new_value = serde_json::to_value(new_value.unwrap_or(&serde_json::Value::Null)).unwrap(); for key in key_path.iter().rev() { if parse_index_key(key.as_ref()).is_some() { new_value = serde_json::json!([new_value]); } else { new_value = serde_json::json!({ key.as_ref().to_string(): new_value }); } } return new_value;} fn parse_index_key(index_key: &str) -> Option<usize> { index_key.strip_prefix('#')?.parse().ok()} fn handle_possible_array_value( key_node: &tree_sitter::Node, value_node: &tree_sitter::Node, text: &str, remaining_key_path: &[impl AsRef<str>], new_value: Option<&Value>, replace_key: Option<&str>, tab_size: usize,) -> Option<(Range<usize>, String)> { if remaining_key_path.is_empty() { return None; } let key_path = remaining_key_path; let index = parse_index_key(key_path[0].as_ref())?; let value_is_array = value_node.kind() == TS_ARRAY_KIND; let array_str = if value_is_array { &text[value_node.byte_range()] } else { "" }; let (mut replace_range, mut replace_value) = replace_top_level_array_value_in_json_text( array_str, &key_path[1..], new_value, replace_key, index, tab_size, ); if value_is_array { replace_range.start += value_node.start_byte(); replace_range.end += value_node.start_byte(); } else { // replace the full value if it wasn't an array replace_range = value_node.byte_range(); } let non_whitespace_char_count = replace_value.len() - replace_value .chars() .filter(char::is_ascii_whitespace) .count(); let needs_indent = replace_value.ends_with('\n') || (replace_value .chars() .zip(replace_value.chars().skip(1)) .any(|(c, next_c)| c == '\n' && !next_c.is_ascii_whitespace())); let contains_comment = (replace_value.contains("//") && replace_value.contains('\n')) || (replace_value.contains("/*") && replace_value.contains("*/")); if needs_indent { let indent_width = key_node.start_position().column; let increased_indent = format!("\n{space:width$}", space = ' ', width = indent_width); replace_value = replace_value.replace('\n', &increased_indent); } else if non_whitespace_char_count < 32 && !contains_comment { // remove indentation while let Some(idx) = replace_value.find("\n ") { replace_value.remove(idx); } while let Some(idx) = replace_value.find(" ") { replace_value.remove(idx); } } return Some((replace_range, replace_value));} const TS_DOCUMENT_KIND: &str = "document";const TS_ARRAY_KIND: &str = "array";const TS_COMMENT_KIND: &str = "comment"; pub fn replace_top_level_array_value_in_json_text( text: &str, key_path: &[impl AsRef<str>], new_value: Option<&Value>, replace_key: Option<&str>, array_index: usize, tab_size: usize,) -> (Range<usize>, String) { let mut parser = tree_sitter::Parser::new(); parser .set_language(&tree_sitter_json::LANGUAGE.into()) .unwrap(); let syntax_tree = parser.parse(text, None).unwrap(); let mut cursor = syntax_tree.walk(); if cursor.node().kind() == TS_DOCUMENT_KIND { cursor.goto_first_child(); } while cursor.node().kind() != TS_ARRAY_KIND { if !cursor.goto_next_sibling() { let json_value = construct_json_value(key_path, new_value); let json_value = serde_json::json!([json_value]); return (0..text.len(), to_pretty_json(&json_value, tab_size, 0)); } } // false if no children // cursor.goto_first_child(); debug_assert_eq!(cursor.node().kind(), "["); let mut index = 0; while index <= array_index { let node = cursor.node(); if !matches!(node.kind(), "[" | "]" | TS_COMMENT_KIND | ",") && !node.is_extra() && !node.is_missing() { if index == array_index { break; } index += 1; } if !cursor.goto_next_sibling() { if let Some(new_value) = new_value { return append_top_level_array_value_in_json_text(text, new_value, tab_size); } else { return (0..0, String::new()); } } } let range = cursor.node().range(); let indent_width = range.start_point.column; let offset = range.start_byte; let text_range = range.start_byte..range.end_byte; let value_str = &text[text_range.clone()]; let needs_indent = range.start_point.row > 0; if new_value.is_none() && key_path.is_empty() { let mut remove_range = text_range; if index == 0 { while cursor.goto_next_sibling() && (cursor.node().is_extra() || cursor.node().is_missing()) {} if cursor.node().kind() == "," { remove_range.end = cursor.node().range().end_byte; } if let Some(next_newline) = &text[remove_range.end + 1..].find('\n') && text[remove_range.end + 1..remove_range.end + next_newline] .chars() .all(|c| c.is_ascii_whitespace()) { remove_range.end = remove_range.end + next_newline; } } else { while cursor.goto_previous_sibling() && (cursor.node().is_extra() || cursor.node().is_missing()) {} if cursor.node().kind() == "," { remove_range.start = cursor.node().range().start_byte; } } (remove_range, String::new()) } else { if let Some(array_replacement) = handle_possible_array_value( &cursor.node(), &cursor.node(), text, key_path, new_value, replace_key, tab_size, ) { return array_replacement; } let (mut replace_range, mut replace_value) = replace_value_in_json_text(value_str, key_path, tab_size, new_value, replace_key); replace_range.start += offset; replace_range.end += offset; if needs_indent { let increased_indent = format!("\n{space:width$}", space = ' ', width = indent_width); replace_value = replace_value.replace('\n', &increased_indent); } else { while let Some(idx) = replace_value.find("\n ") { replace_value.remove(idx + 1); } while let Some(idx) = replace_value.find("\n") { replace_value.replace_range(idx..idx + 1, " "); } } (replace_range, replace_value) }} pub fn append_top_level_array_value_in_json_text(Lines 1–500 of 2635. Load more
use anyhow::Result; use serde::{Serialize, de::DeserializeOwned}; use serde_json::Value; use std::{ops::Range, sync::LazyLock}; use tree_sitter::{Query, StreamingIterator as _}; use util::RangeExt; pub fn update_value_in_json_text<'a>( text: &mut String, key_path: &mut Vec<&'a str>, tab_size: usize, old_value: &'a Value, new_value: &'a Value, edits: &mut Vec<(Range<usize>, String)>, ) { // If the old and new values are both objects, then compare them key by key, // preserving the comments and formatting of the unchanged parts. Otherwise, // replace the old value with the new value. if let (Value::Object(old_object), Value::Object(new_object)) = (old_value, new_value) { for (key, old_sub_value) in old_object.iter() { key_path.push(key); if let Some(new_sub_value) = new_object.get(key) { // Key exists in both old and new, recursively update update_value_in_json_text( text, key_path, tab_size, old_sub_value, new_sub_value, edits, ); } else { // Key was removed from new object, remove the entire key-value pair let (range, replacement) = replace_value_in_json_text(text, key_path, 0, None, None); text.replace_range(range.clone(), &replacement); edits.push((range, replacement)); } key_path.pop(); } for (key, new_sub_value) in new_object.iter() { key_path.push(key); if !old_object.contains_key(key) { update_value_in_json_text( text, key_path, tab_size, &Value::Null, new_sub_value, edits, ); } key_path.pop(); } } else if old_value != new_value { let mut new_value = new_value.clone(); if let Some(new_object) = new_value.as_object_mut() { new_object.retain(|_, v| !v.is_null()); } let (range, replacement) = replace_value_in_json_text(text, key_path, tab_size, Some(&new_value), None); text.replace_range(range.clone(), &replacement); edits.push((range, replacement)); } } /// * `replace_key` - When an exact key match according to `key_path` is found, replace the key with `replace_key` if `Some`. pub fn replace_value_in_json_text<T: AsRef<str>>( text: &str, key_path: &[T], tab_size: usize, new_value: Option<&Value>, replace_key: Option<&str>, ) -> (Range<usize>, String) { static PAIR_QUERY: LazyLock<Query> = LazyLock::new(|| { Query::new( &tree_sitter_json::LANGUAGE.into(), "(pair key: (string) @key value: (_) @value)", ) .expect("Failed to create PAIR_QUERY") }); let mut parser = tree_sitter::Parser::new(); parser .set_language(&tree_sitter_json::LANGUAGE.into()) .unwrap(); let syntax_tree = parser.parse(text, None).unwrap(); let mut cursor = tree_sitter::QueryCursor::new(); let mut depth = 0; let mut last_value_range = 0..0; let mut first_key_start = None; let mut existing_value_range = 0..text.len(); let mut matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes()); while let Some(mat) = matches.next() { if mat.captures.len() != 2 { continue; } let key_range = mat.captures[0].node.byte_range(); let value_range = mat.captures[1].node.byte_range(); // Don't enter sub objects until we find an exact // match for the current keypath if last_value_range.contains_inclusive(&value_range) { continue; } last_value_range = value_range.clone(); if key_range.start > existing_value_range.end { break; } first_key_start.get_or_insert(key_range.start); let found_key = text .get(key_range.clone()) .zip(key_path.get(depth)) .and_then(|(key_text, key_path_value)| { serde_json::to_string(key_path_value.as_ref()) .ok() .map(|key_path| depth < key_path.len() && key_text == key_path) }) .unwrap_or(false); if found_key { existing_value_range = value_range; // Reset last value range when increasing in depth last_value_range = existing_value_range.start..existing_value_range.start; depth += 1; if depth == key_path.len() { break; } if let Some(array_replacement) = handle_possible_array_value( &mat.captures[0].node, &mat.captures[1].node, text, &key_path[depth..], new_value, replace_key, tab_size, ) { return array_replacement; } first_key_start = None; } } // We found the exact key we want if depth == key_path.len() { if let Some(new_value) = new_value { let new_val = to_pretty_json(new_value, tab_size, tab_size * depth); if let Some(replace_key) = replace_key.and_then(|str| serde_json::to_string(str).ok()) { let new_key = format!("{}: ", replace_key); if let Some(key_start) = text[..existing_value_range.start].rfind('"') { if let Some(prev_key_start) = text[..key_start].rfind('"') { existing_value_range.start = prev_key_start; } else { existing_value_range.start = key_start; } } (existing_value_range, new_key + &new_val) } else { (existing_value_range, new_val) } } else { let mut removal_start = first_key_start.unwrap_or(existing_value_range.start); let mut removal_end = existing_value_range.end; // Find the actual key position by looking for the key in the pair // We need to extend the range to include the key, not just the value if let Some(key_start) = text[..existing_value_range.start].rfind('"') { if let Some(prev_key_start) = text[..key_start].rfind('"') { removal_start = prev_key_start; } else { removal_start = key_start; } } let mut removed_comma = false; // Look backward for a preceding comma first let preceding_text = text.get(0..removal_start).unwrap_or(""); if let Some(comma_pos) = preceding_text.rfind(',') { // Check if there are only whitespace characters between the comma and our key let between_comma_and_key = text.get(comma_pos + 1..removal_start).unwrap_or(""); if between_comma_and_key.trim().is_empty() { removal_start = comma_pos; removed_comma = true; } } if let Some(remaining_text) = text.get(existing_value_range.end..) && !removed_comma { let mut chars = remaining_text.char_indices(); while let Some((offset, ch)) = chars.next() { if ch == ',' { removal_end = existing_value_range.end + offset + 1; // Also consume whitespace after the comma for (_, next_ch) in chars.by_ref() { if next_ch.is_whitespace() { removal_end += next_ch.len_utf8(); } else { break; } } break; } else if !ch.is_whitespace() { break; } } } (removal_start..removal_end, String::new()) } } else { if let Some(first_key_start) = first_key_start { // We have key paths, construct the sub objects let new_key = key_path[depth].as_ref(); // We don't have the key, construct the nested objects let new_value = construct_json_value(&key_path[(depth + 1)..], new_value); let mut row = 0; let mut column = 0; for (ix, char) in text.char_indices() { if ix == first_key_start { break; } if char == '\n' { row += 1; column = 0; } else { column += char.len_utf8(); } } if row > 0 { // depth is 0 based, but division needs to be 1 based. let new_val = to_pretty_json(&new_value, column / (depth + 1), column); let space = ' '; let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column); (first_key_start..first_key_start, content) } else { let new_val = serde_json::to_string(&new_value).unwrap(); let mut content = format!(r#""{new_key}": {new_val},"#); content.push(' '); (first_key_start..first_key_start, content) } } else { // We don't have the key, construct the nested objects let new_value = construct_json_value(&key_path[depth..], new_value); let indent_prefix_len = tab_size * depth; let mut new_val = to_pretty_json(&new_value, tab_size, indent_prefix_len); if depth == 0 { new_val.push('\n'); } // best effort to keep comments with best effort indentation let mut replace_text = &text[existing_value_range.clone()]; while let Some(comment_start) = replace_text.rfind("//") { if let Some(comment_end) = replace_text[comment_start..].find('\n') { let mut comment_with_indent_start = replace_text[..comment_start] .rfind('\n') .unwrap_or(comment_start); if !replace_text[comment_with_indent_start..comment_start] .trim() .is_empty() { comment_with_indent_start = comment_start; } new_val.insert_str( 1, &replace_text[comment_with_indent_start..comment_start + comment_end], ); } replace_text = &replace_text[..comment_start]; } (existing_value_range, new_val) } } } fn construct_json_value( key_path: &[impl AsRef<str>], new_value: Option<&serde_json::Value>, ) -> serde_json::Value { let mut new_value = serde_json::to_value(new_value.unwrap_or(&serde_json::Value::Null)).unwrap(); for key in key_path.iter().rev() { if parse_index_key(key.as_ref()).is_some() { new_value = serde_json::json!([new_value]); } else { new_value = serde_json::json!({ key.as_ref().to_string(): new_value }); } } return new_value; } fn parse_index_key(index_key: &str) -> Option<usize> { index_key.strip_prefix('#')?.parse().ok() } fn handle_possible_array_value( key_node: &tree_sitter::Node, value_node: &tree_sitter::Node, text: &str, remaining_key_path: &[impl AsRef<str>], new_value: Option<&Value>, replace_key: Option<&str>, tab_size: usize, ) -> Option<(Range<usize>, String)> { if remaining_key_path.is_empty() { return None; } let key_path = remaining_key_path; let index = parse_index_key(key_path[0].as_ref())?; let value_is_array = value_node.kind() == TS_ARRAY_KIND; let array_str = if value_is_array { &text[value_node.byte_range()] } else { "" }; let (mut replace_range, mut replace_value) = replace_top_level_array_value_in_json_text( array_str, &key_path[1..], new_value, replace_key, index, tab_size, ); if value_is_array { replace_range.start += value_node.start_byte(); replace_range.end += value_node.start_byte(); } else { // replace the full value if it wasn't an array replace_range = value_node.byte_range(); } let non_whitespace_char_count = replace_value.len() - replace_value .chars() .filter(char::is_ascii_whitespace) .count(); let needs_indent = replace_value.ends_with('\n') || (replace_value .chars() .zip(replace_value.chars().skip(1)) .any(|(c, next_c)| c == '\n' && !next_c.is_ascii_whitespace())); let contains_comment = (replace_value.contains("//") && replace_value.contains('\n')) || (replace_value.contains("/*") && replace_value.contains("*/")); if needs_indent { let indent_width = key_node.start_position().column; let increased_indent = format!("\n{space:width$}", space = ' ', width = indent_width); replace_value = replace_value.replace('\n', &increased_indent); } else if non_whitespace_char_count < 32 && !contains_comment { // remove indentation while let Some(idx) = replace_value.find("\n ") { replace_value.remove(idx); } while let Some(idx) = replace_value.find(" ") { replace_value.remove(idx); } } return Some((replace_range, replace_value)); } const TS_DOCUMENT_KIND: &str = "document"; const TS_ARRAY_KIND: &str = "array"; const TS_COMMENT_KIND: &str = "comment"; pub fn replace_top_level_array_value_in_json_text( text: &str, key_path: &[impl AsRef<str>], new_value: Option<&Value>, replace_key: Option<&str>, array_index: usize, tab_size: usize, ) -> (Range<usize>, String) { let mut parser = tree_sitter::Parser::new(); parser .set_language(&tree_sitter_json::LANGUAGE.into()) .unwrap(); let syntax_tree = parser.parse(text, None).unwrap(); let mut cursor = syntax_tree.walk(); if cursor.node().kind() == TS_DOCUMENT_KIND { cursor.goto_first_child(); } while cursor.node().kind() != TS_ARRAY_KIND { if !cursor.goto_next_sibling() { let json_value = construct_json_value(key_path, new_value); let json_value = serde_json::json!([json_value]); return (0..text.len(), to_pretty_json(&json_value, tab_size, 0)); } } // false if no children // cursor.goto_first_child(); debug_assert_eq!(cursor.node().kind(), "["); let mut index = 0; while index <= array_index { let node = cursor.node(); if !matches!(node.kind(), "[" | "]" | TS_COMMENT_KIND | ",") && !node.is_extra() && !node.is_missing() { if index == array_index { break; } index += 1; } if !cursor.goto_next_sibling() { if let Some(new_value) = new_value { return append_top_level_array_value_in_json_text(text, new_value, tab_size); } else { return (0..0, String::new()); } } } let range = cursor.node().range(); let indent_width = range.start_point.column; let offset = range.start_byte; let text_range = range.start_byte..range.end_byte; let value_str = &text[text_range.clone()]; let needs_indent = range.start_point.row > 0; if new_value.is_none() && key_path.is_empty() { let mut remove_range = text_range; if index == 0 { while cursor.goto_next_sibling() && (cursor.node().is_extra() || cursor.node().is_missing()) {} if cursor.node().kind() == "," { remove_range.end = cursor.node().range().end_byte; } if let Some(next_newline) = &text[remove_range.end + 1..].find('\n') && text[remove_range.end + 1..remove_range.end + next_newline] .chars() .all(|c| c.is_ascii_whitespace()) { remove_range.end = remove_range.end + next_newline; } } else { while cursor.goto_previous_sibling() && (cursor.node().is_extra() || cursor.node().is_missing()) {} if cursor.node().kind() == "," { remove_range.start = cursor.node().range().start_byte; } } (remove_range, String::new()) } else { if let Some(array_replacement) = handle_possible_array_value( &cursor.node(), &cursor.node(), text, key_path, new_value, replace_key, tab_size, ) { return array_replacement; } let (mut replace_range, mut replace_value) = replace_value_in_json_text(value_str, key_path, tab_size, new_value, replace_key); replace_range.start += offset; replace_range.end += offset; if needs_indent { let increased_indent = format!("\n{space:width$}", space = ' ', width = indent_width); replace_value = replace_value.replace('\n', &increased_indent); } else { while let Some(idx) = replace_value.find("\n ") { replace_value.remove(idx + 1); } while let Some(idx) = replace_value.find("\n") { replace_value.replace_range(idx..idx + 1, " "); } } (replace_range, replace_value) } } pub fn append_top_level_array_value_in_json_text(