diff --git a/i18n/en/cosmic_edit.ftl b/i18n/en/cosmic_edit.ftl index 25e057f..68c6bf6 100644 --- a/i18n/en/cosmic_edit.ftl +++ b/i18n/en/cosmic_edit.ftl @@ -41,6 +41,9 @@ enable-vim-bindings = Enable Vim bindings find-placeholder = Find... find-previous = Find previous find-next = Find next +replace-placeholder = Replace... +replace = Replace +replace-all = Replace all # Menu diff --git a/res/icons/replace-all-symbolic.svg b/res/icons/replace-all-symbolic.svg new file mode 100644 index 0000000..dcaaada --- /dev/null +++ b/res/icons/replace-all-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/icons/replace-symbolic.svg b/res/icons/replace-symbolic.svg new file mode 100644 index 0000000..30876bd --- /dev/null +++ b/res/icons/replace-symbolic.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/icon_cache.rs b/src/icon_cache.rs index aeca7db..accdc2f 100644 --- a/src/icon_cache.rs +++ b/src/icon_cache.rs @@ -37,6 +37,8 @@ impl IconCache { bundle!("go-up-symbolic", 16); bundle!("list-add-symbolic", 16); bundle!("object-select-symbolic", 16); + bundle!("replace-symbolic", 16); + bundle!("replace-all-symbolic", 16); bundle!("window-close-symbolic", 16); Self { cache } diff --git a/src/main.rs b/src/main.rs index 798e45e..a8d8bf7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,6 +184,8 @@ pub enum Message { Find(Option), FindNext, FindPrevious, + FindReplace, + FindReplaceAll, FindReplaceValueChanged(String), FindSearchValueChanged(String), GitProjectStatus(Vec<(String, PathBuf, Vec)>), @@ -267,6 +269,7 @@ pub struct App { context_page: ContextPage, text_box_id: widget::Id, find_opt: Option, + find_replace_id: widget::Id, find_replace_value: String, find_search_id: widget::Id, find_search_value: String, @@ -976,6 +979,7 @@ impl Application for App { context_page: ContextPage::Settings, text_box_id: widget::Id::unique(), find_opt: None, + find_replace_id: widget::Id::unique(), find_replace_value: String::new(), find_search_id: widget::Id::unique(), find_search_value: String::new(), @@ -1242,6 +1246,22 @@ impl Application for App { // Focus correct input return self.update_focus(); } + Message::FindReplace => { + if !self.find_search_value.is_empty() { + if let Some(Tab::Editor(tab)) = self.active_tab() { + tab.replace(&self.find_search_value, &self.find_replace_value); + } + } + + // Focus correct input + return self.update_focus(); + } + Message::FindReplaceAll => { + log::warn!("FIND REPLACE ALL"); + + // Focus correct input + return self.update_focus(); + } Message::FindReplaceValueChanged(value) => { self.find_replace_value = value; } @@ -1876,7 +1896,7 @@ impl Application for App { } if let Some(replace) = &self.find_opt { - let text_input = + let find_input = widget::text_input::text_input(fl!("find-placeholder"), &self.find_search_value) .id(self.find_search_id.clone()) .on_input(Message::FindSearchValueChanged) @@ -1890,7 +1910,7 @@ impl Application for App { .into(), ); let find_widget = widget::row::with_children(vec![ - text_input.into(), + find_input.into(), widget::tooltip( button(icon_cache_get("go-up-symbolic", 16)) .on_press(Message::FindPrevious) @@ -1920,9 +1940,52 @@ impl Application for App { .padding(space_xxs) .spacing(space_xxs); + let mut column = widget::column::with_capacity(2).push(find_widget); + if *replace { + let replace_input = widget::text_input::text_input( + fl!("replace-placeholder"), + &self.find_replace_value, + ) + .id(self.find_replace_id.clone()) + .on_input(Message::FindReplaceValueChanged) + .on_submit(Message::FindReplace) + .width(Length::Fixed(320.0)) + .trailing_icon( + button(icon_cache_get("edit-clear-symbolic", 16)) + .on_press(Message::FindReplaceValueChanged(String::new())) + .style(style::Button::Icon) + .into(), + ); + let replace_widget = widget::row::with_children(vec![ + replace_input.into(), + widget::tooltip( + button(icon_cache_get("replace-symbolic", 16)) + .on_press(Message::FindReplace) + .padding(space_xxs) + .style(style::Button::Icon), + fl!("replace"), + widget::tooltip::Position::Top, + ) + .into(), + widget::tooltip( + button(icon_cache_get("replace-all-symbolic", 16)) + .on_press(Message::FindReplaceAll) + .padding(space_xxs) + .style(style::Button::Icon), + fl!("replace-all"), + widget::tooltip::Position::Top, + ) + .into(), + ]) + .align_items(Alignment::Center) + .padding(space_xxs) + .spacing(space_xxs); + + column = column.push(replace_widget); + } + tab_column = tab_column.push( - widget::cosmic_container::container(find_widget) - .layer(cosmic_theme::Layer::Primary), + widget::cosmic_container::container(column).layer(cosmic_theme::Layer::Primary), ); } diff --git a/src/tab.rs b/src/tab.rs index e61dcfb..1f77d40 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -200,6 +200,42 @@ impl EditorTab { } } + pub fn replace(&self, value: &str, replace: &str) -> bool { + let mut editor = self.editor.lock().unwrap(); + let mut cursor = editor.cursor(); + let start_line = cursor.line; + while cursor.line < editor.with_buffer(|buffer| buffer.lines.len()) { + if let Some(index) = editor.with_buffer(|buffer| { + buffer.lines[cursor.line] + .text() + .match_indices(value) + .filter_map(|(i, _)| { + if cursor.line != start_line || i >= cursor.index { + Some(i) + } else { + None + } + }) + .next() + }) { + cursor.index = index; + let mut end = cursor; + end.index = index + value.len(); + + editor.start_change(); + editor.delete_range(cursor, end); + cursor = editor.insert_at(cursor, replace, None); + editor.set_cursor(cursor); + editor.finish_change(); + + return true; + } + + cursor.line += 1; + } + false + } + // Code adapted from cosmic-text ViEditor search pub fn search(&self, value: &str, forwards: bool) -> bool { let mut editor = self.editor.lock().unwrap();