2025-07-05 14:42:45 -04:00
|
|
|
use super::editor::TextEditor;
|
2025-07-16 13:27:31 -04:00
|
|
|
use eframe::egui;
|
2025-07-05 14:42:45 -04:00
|
|
|
|
|
|
|
|
impl TextEditor {
|
|
|
|
|
pub fn update_find_matches(&mut self) {
|
2025-07-16 13:27:31 -04:00
|
|
|
let previous_match_index = self.current_match_index;
|
2025-07-05 14:42:45 -04:00
|
|
|
self.find_matches.clear();
|
|
|
|
|
self.current_match_index = None;
|
|
|
|
|
|
|
|
|
|
if self.find_query.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(tab) = self.get_active_tab() {
|
|
|
|
|
let content = &tab.content;
|
|
|
|
|
let query = if self.case_sensitive_search {
|
|
|
|
|
self.find_query.clone()
|
|
|
|
|
} else {
|
|
|
|
|
self.find_query.to_lowercase()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let search_content = if self.case_sensitive_search {
|
|
|
|
|
content.clone()
|
|
|
|
|
} else {
|
|
|
|
|
content.to_lowercase()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let mut start = 0;
|
|
|
|
|
while let Some(pos) = search_content[start..].find(&query) {
|
|
|
|
|
let absolute_pos = start + pos;
|
|
|
|
|
self.find_matches
|
|
|
|
|
.push((absolute_pos, absolute_pos + query.len()));
|
|
|
|
|
start = absolute_pos + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !self.find_matches.is_empty() {
|
2025-07-16 13:27:31 -04:00
|
|
|
if let Some(prev_index) = previous_match_index {
|
|
|
|
|
if prev_index < self.find_matches.len() {
|
|
|
|
|
self.current_match_index = Some(prev_index);
|
|
|
|
|
} else {
|
|
|
|
|
self.current_match_index = Some(0);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.current_match_index = Some(0);
|
|
|
|
|
}
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
pub fn find_next(&mut self, ctx: &egui::Context) {
|
2025-07-05 14:42:45 -04:00
|
|
|
if self.find_matches.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(current) = self.current_match_index {
|
|
|
|
|
self.current_match_index = Some((current + 1) % self.find_matches.len());
|
|
|
|
|
} else {
|
|
|
|
|
self.current_match_index = Some(0);
|
|
|
|
|
}
|
2025-07-16 13:27:31 -04:00
|
|
|
|
|
|
|
|
self.select_current_match(ctx);
|
|
|
|
|
self.should_select_current_match = true;
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
pub fn find_previous(&mut self, ctx: &egui::Context) {
|
2025-07-05 14:42:45 -04:00
|
|
|
if self.find_matches.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(current) = self.current_match_index {
|
|
|
|
|
self.current_match_index = Some(if current == 0 {
|
|
|
|
|
self.find_matches.len() - 1
|
|
|
|
|
} else {
|
|
|
|
|
current - 1
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
self.current_match_index = Some(0);
|
|
|
|
|
}
|
2025-07-16 13:27:31 -04:00
|
|
|
|
|
|
|
|
self.select_current_match(ctx);
|
|
|
|
|
self.should_select_current_match = true;
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_current_match_position(&self) -> Option<(usize, usize)> {
|
|
|
|
|
if let Some(index) = self.current_match_index {
|
|
|
|
|
self.find_matches.get(index).copied()
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-16 13:27:31 -04:00
|
|
|
|
|
|
|
|
pub fn select_current_match(&self, ctx: &egui::Context) {
|
|
|
|
|
if let Some((start_byte, end_byte)) = self.get_current_match_position() {
|
|
|
|
|
if let Some(active_tab) = self.get_active_tab() {
|
|
|
|
|
let content = &active_tab.content;
|
|
|
|
|
|
|
|
|
|
let start_char = content[..start_byte.min(content.len())].chars().count();
|
|
|
|
|
let end_char = content[..end_byte.min(content.len())].chars().count();
|
|
|
|
|
|
|
|
|
|
let text_edit_id = egui::Id::new("main_text_editor");
|
|
|
|
|
if let Some(mut state) = egui::TextEdit::load_state(ctx, text_edit_id) {
|
|
|
|
|
let selection_range = egui::text::CCursorRange::two(
|
|
|
|
|
egui::text::CCursor::new(start_char),
|
|
|
|
|
egui::text::CCursor::new(end_char),
|
|
|
|
|
);
|
|
|
|
|
state.cursor.set_char_range(Some(selection_range));
|
|
|
|
|
egui::TextEdit::store_state(ctx, text_edit_id, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn replace_current_match(&mut self, ctx: &egui::Context) {
|
|
|
|
|
if self.find_query.is_empty() || self.find_matches.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some((start_byte, end_byte)) = self.get_current_match_position() {
|
|
|
|
|
let replace_query = self.replace_query.clone();
|
|
|
|
|
let replacement_end = start_byte + replace_query.len();
|
|
|
|
|
|
|
|
|
|
if let Some(active_tab) = self.get_active_tab_mut() {
|
|
|
|
|
let content = &active_tab.content;
|
|
|
|
|
|
|
|
|
|
let mut new_content = content.clone();
|
|
|
|
|
new_content.replace_range(start_byte..end_byte, &replace_query);
|
|
|
|
|
|
|
|
|
|
active_tab.content = new_content;
|
|
|
|
|
active_tab.is_modified = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.update_find_matches();
|
|
|
|
|
|
|
|
|
|
if let Some(active_tab) = self.get_active_tab() {
|
|
|
|
|
let replacement_end_char = active_tab.content
|
|
|
|
|
[..replacement_end.min(active_tab.content.len())]
|
|
|
|
|
.chars()
|
|
|
|
|
.count();
|
|
|
|
|
|
|
|
|
|
let text_edit_id = egui::Id::new("main_text_editor");
|
|
|
|
|
if let Some(mut state) = egui::TextEdit::load_state(ctx, text_edit_id) {
|
|
|
|
|
state
|
|
|
|
|
.cursor
|
|
|
|
|
.set_char_range(Some(egui::text::CCursorRange::one(
|
|
|
|
|
egui::text::CCursor::new(replacement_end_char),
|
|
|
|
|
)));
|
|
|
|
|
egui::TextEdit::store_state(ctx, text_edit_id, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn replace_all(&mut self, ctx: &egui::Context) {
|
|
|
|
|
if self.find_query.is_empty() || self.find_matches.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let find_query = self.find_query.clone();
|
|
|
|
|
let replace_query = self.replace_query.clone();
|
|
|
|
|
let case_sensitive = self.case_sensitive_search;
|
|
|
|
|
let find_matches = self.find_matches.clone();
|
|
|
|
|
|
|
|
|
|
if let Some(active_tab) = self.get_active_tab_mut() {
|
|
|
|
|
let content = &active_tab.content;
|
|
|
|
|
|
|
|
|
|
let new_content = if case_sensitive {
|
|
|
|
|
content.replace(&find_query, &replace_query)
|
|
|
|
|
} else {
|
|
|
|
|
let mut result = String::new();
|
|
|
|
|
let mut last_end = 0;
|
|
|
|
|
|
|
|
|
|
for (start_byte, end_byte) in &find_matches {
|
|
|
|
|
result.push_str(&content[last_end..*start_byte]);
|
|
|
|
|
result.push_str(&replace_query);
|
|
|
|
|
last_end = *end_byte;
|
|
|
|
|
}
|
|
|
|
|
result.push_str(&content[last_end..]);
|
|
|
|
|
result
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
active_tab.content = new_content;
|
|
|
|
|
active_tab.is_modified = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.update_find_matches();
|
|
|
|
|
|
|
|
|
|
self.current_match_index = None;
|
|
|
|
|
|
|
|
|
|
let text_edit_id = egui::Id::new("main_text_editor");
|
|
|
|
|
if let Some(mut state) = egui::TextEdit::load_state(ctx, text_edit_id) {
|
|
|
|
|
state
|
|
|
|
|
.cursor
|
|
|
|
|
.set_char_range(Some(egui::text::CCursorRange::one(
|
|
|
|
|
egui::text::CCursor::new(0),
|
|
|
|
|
)));
|
|
|
|
|
egui::TextEdit::store_state(ctx, text_edit_id, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|