use super::{App, FormatMode, SubstituteMatch};
impl App {
pub fn execute_substitute(&mut self, cmd: &str) {
if self.format_mode != FormatMode::Edit {
self.set_status("Substitute only works in Edit mode");
return;
}
let is_global_file = cmd.starts_with("%s/");
let cmd_prefix = if is_global_file { "%s/" } else { "s/" };
let cmd_rest = cmd.strip_prefix(cmd_prefix).unwrap_or("");
let parts: Vec<&str> = cmd_rest.splitn(3, '/').collect();
if parts.len() < 2 {
self.set_status("Invalid substitute syntax. Use :s/pattern/replacement/[flags]");
return;
}
let pattern = parts[0];
let replacement = parts[1];
let flags = if parts.len() == 3 { parts[2] } else { "" };
if pattern.is_empty() {
self.set_status("Empty pattern");
return;
}
let global_line = flags.contains('g');
let confirm = flags.contains('c');
self.save_undo_state();
if confirm {
self.build_substitute_confirmations(pattern, replacement, is_global_file, global_line);
if self.substitute_confirmations.is_empty() {
self.set_status(&format!("Pattern not found: {}", pattern));
} else {
self.current_substitute_index = 0;
self.set_status(&format!(
"Replace with '{}'? (y/n/a/q) [{}/{}]",
replacement,
self.current_substitute_index + 1,
self.substitute_confirmations.len()
));
}
} else {
let count = self.perform_substitute(pattern, replacement, is_global_file, global_line);
if count > 0 {
self.is_modified = true;
self.convert_json();
self.set_status(&format!("{} substitution{} made", count, if count == 1 { "" } else { "s" }));
} else {
self.set_status(&format!("Pattern not found: {}", pattern));
self.undo_stack.pop();
}
}
}
fn build_substitute_confirmations(&mut self, pattern: &str, replacement: &str, is_global_file: bool, global_line: bool) {
self.substitute_confirmations.clear();
let lines: Vec<String> = self.json_input.lines().map(|s| s.to_string()).collect();
let line_range = if is_global_file {
0..lines.len()
} else {
self.content_cursor_line..self.content_cursor_line + 1
};
for line_idx in line_range {
if line_idx >= lines.len() {
break;
}
let line = &lines[line_idx];
if global_line {
let mut search_start = 0;
while let Some(pos) = line[search_start..].find(pattern) {
let actual_pos = search_start + pos;
self.substitute_confirmations.push(SubstituteMatch {
line: line_idx,
col: actual_pos,
pattern: pattern.to_string(),
replacement: replacement.to_string(),
});
search_start = actual_pos + pattern.len();
}
} else {
if let Some(pos) = line.find(pattern) {
self.substitute_confirmations.push(SubstituteMatch {
line: line_idx,
col: pos,
pattern: pattern.to_string(),
replacement: replacement.to_string(),
});
}
}
}
}
fn perform_substitute(&mut self, pattern: &str, replacement: &str, is_global_file: bool, global_line: bool) -> usize {
let mut lines: Vec<String> = self.json_input.lines().map(|s| s.to_string()).collect();
let mut count = 0;
let line_range = if is_global_file {
0..lines.len()
} else {
self.content_cursor_line..self.content_cursor_line + 1
};
for line_idx in line_range {
if line_idx >= lines.len() {
break;
}
if global_line {
let original = lines[line_idx].clone();
lines[line_idx] = original.replace(pattern, replacement);
if lines[line_idx] != original {
count += original.matches(pattern).count();
}
} else {
if let Some(pos) = lines[line_idx].find(pattern) {
lines[line_idx].replace_range(pos..pos + pattern.len(), replacement);
count += 1;
}
}
}
if count > 0 {
self.json_input = lines.join("\n");
if self.json_input.chars().last() != Some('\n') && !self.json_input.is_empty() {
if let Some(last_char) = self.json_input.chars().last() {
if last_char != '\n' {
self.json_input.push('\n');
}
}
}
}
count
}
pub fn handle_substitute_confirmation(&mut self, answer: char) {
if self.substitute_confirmations.is_empty() {
return;
}
let mut should_substitute = false;
let mut quit = false;
let mut all = false;
match answer {
'y' => should_substitute = true,
'n' => should_substitute = false,
'a' => {
should_substitute = true;
all = true;
}
'q' => quit = true,
_ => return,
}
if quit {
self.substitute_confirmations.clear();
self.current_substitute_index = 0;
self.set_status("Substitute cancelled");
self.undo_stack.pop();
return;
}
if all {
let remaining_count = self.substitute_confirmations.len() - self.current_substitute_index;
let matches_to_apply: Vec<SubstituteMatch> = self.substitute_confirmations
[self.current_substitute_index..]
.to_vec();
let mut lines: Vec<String> = self.json_input.lines().map(|s| s.to_string()).collect();
for match_item in matches_to_apply.iter().rev() {
if match_item.line < lines.len() {
let line = &mut lines[match_item.line];
if match_item.col + match_item.pattern.len() <= line.len() {
line.replace_range(
match_item.col..match_item.col + match_item.pattern.len(),
&match_item.replacement,
);
}
}
}
self.json_input = lines.join("\n");
self.substitute_confirmations.clear();
self.current_substitute_index = 0;
self.is_modified = true;
self.convert_json();
self.set_status(&format!("{} substitution{} made", remaining_count, if remaining_count == 1 { "" } else { "s" }));
} else {
if should_substitute {
let match_item = &self.substitute_confirmations[self.current_substitute_index];
let mut lines: Vec<String> = self.json_input.lines().map(|s| s.to_string()).collect();
if match_item.line < lines.len() {
let line = &mut lines[match_item.line];
if match_item.col + match_item.pattern.len() <= line.len() {
line.replace_range(
match_item.col..match_item.col + match_item.pattern.len(),
&match_item.replacement,
);
self.json_input = lines.join("\n");
self.is_modified = true;
}
}
}
self.current_substitute_index += 1;
if self.current_substitute_index >= self.substitute_confirmations.len() {
let total = self.substitute_confirmations.len();
self.substitute_confirmations.clear();
self.current_substitute_index = 0;
self.convert_json();
self.set_status(&format!("{} substitution{} completed", total, if total == 1 { "" } else { "s" }));
} else {
let next_match = &self.substitute_confirmations[self.current_substitute_index];
self.set_status(&format!(
"Replace with '{}'? (y/n/a/q) [{}/{}]",
next_match.replacement,
self.current_substitute_index + 1,
self.substitute_confirmations.len()
));
}
}
}
}