diff options
author | thankyouverycool <66646555+thankyouverycool@users.noreply.github.com> | 2021-02-21 19:01:26 -0500 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-02-22 09:21:30 +0100 |
commit | 3e987eba2b39b137a78aafbfc6340eb375f219a6 (patch) | |
tree | 73a3c186e6df5e5c8ee8adba8e86cd264ed55936 | |
parent | b4c0314f1dae98fa5d206a097e29a30950d2624f (diff) | |
download | serenity-3e987eba2b39b137a78aafbfc6340eb375f219a6.zip |
TextEditor+LibGUI: Add case matching and wrap around optionality
Adds simple ASCII case matching and wrap around toggles to
TextEditor's find/replace widget and reorganizes its layout
-rw-r--r-- | Userland/Applications/TextEditor/TextEditorWidget.cpp | 122 | ||||
-rw-r--r-- | Userland/Applications/TextEditor/TextEditorWidget.h | 15 | ||||
-rw-r--r-- | Userland/Applications/TextEditor/TextEditorWindow.gml | 51 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/TextDocument.cpp | 10 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/TextDocument.h | 4 |
5 files changed, 97 insertions, 105 deletions
diff --git a/Userland/Applications/TextEditor/TextEditorWidget.cpp b/Userland/Applications/TextEditor/TextEditorWidget.cpp index 63688b29f4..3b24f001b3 100644 --- a/Userland/Applications/TextEditor/TextEditorWidget.cpp +++ b/Userland/Applications/TextEditor/TextEditorWidget.cpp @@ -40,9 +40,11 @@ #include <LibGUI/ActionGroup.h> #include <LibGUI/BoxLayout.h> #include <LibGUI/Button.h> +#include <LibGUI/CheckBox.h> #include <LibGUI/FilePicker.h> #include <LibGUI/FontPicker.h> #include <LibGUI/GMLSyntaxHighlighter.h> +#include <LibGUI/GroupBox.h> #include <LibGUI/INISyntaxHighlighter.h> #include <LibGUI/Menu.h> #include <LibGUI/MenuBar.h> @@ -60,7 +62,6 @@ #include <LibMarkdown/Document.h> #include <LibWeb/OutOfProcessWebView.h> #include <Shell/SyntaxHighlighter.h> -#include <string.h> TextEditorWidget::TextEditorWidget() { @@ -108,59 +109,43 @@ TextEditorWidget::TextEditorWidget() } }; - m_find_replace_widget = *find_descendant_of_type_named<GUI::Widget>("find_replace_widget"); - + m_find_replace_widget = *find_descendant_of_type_named<GUI::GroupBox>("find_replace_widget"); m_find_widget = *find_descendant_of_type_named<GUI::Widget>("find_widget"); - m_replace_widget = *find_descendant_of_type_named<GUI::Widget>("replace_widget"); - m_find_textbox = m_find_widget->add<GUI::TextBox>(); - m_replace_textbox = m_replace_widget->add<GUI::TextBox>(); + m_find_textbox = *find_descendant_of_type_named<GUI::TextBox>("find_textbox"); + m_find_textbox->set_placeholder("Find"); - m_find_next_action = GUI::Action::create("Find next", { Mod_Ctrl, Key_G }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find-next.png"), [&](auto&) { - auto needle = m_find_textbox->text(); - if (needle.is_empty()) { - dbgln("find_next(\"\")"); - return; - } + m_replace_textbox = *find_descendant_of_type_named<GUI::TextBox>("replace_textbox"); + m_replace_textbox->set_placeholder("Replace"); - if (m_find_use_regex) - m_editor->document().update_regex_matches(needle); + m_match_case_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("match_case_checkbox"); + m_match_case_checkbox->on_checked = [this] { + m_match_case = m_match_case_checkbox->is_checked(); + }; + m_match_case_checkbox->set_checked(true); - auto found_range = m_editor->document().find_next(needle, m_editor->normalized_selection().end(), GUI::TextDocument::SearchShouldWrap::Yes, m_find_use_regex); - dbgln("find_next('{}') returned {}", needle, found_range); - if (found_range.is_valid()) { - m_editor->set_selection(found_range); - } else { - GUI::MessageBox::show(window(), - String::formatted("Not found: \"{}\"", needle), - "Not found", - GUI::MessageBox::Type::Information); - } - }); + m_regex_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("regex_checkbox"); + m_regex_checkbox->on_checked = [this] { + m_use_regex = m_regex_checkbox->is_checked(); + }; + m_regex_checkbox->set_checked(false); - m_find_regex_action = GUI::Action::create("Find regex", { Mod_Ctrl | Mod_Shift, Key_R }, [&](auto&) { - m_find_regex_button->set_checked(!m_find_regex_button->is_checked()); - m_find_use_regex = m_find_regex_button->is_checked(); - }); + m_wrap_around_checkbox = *find_descendant_of_type_named<GUI::CheckBox>("wrap_around_checkbox"); + m_wrap_around_checkbox->on_checked = [this] { + m_should_wrap = m_wrap_around_checkbox->is_checked(); + }; + m_wrap_around_checkbox->set_checked(true); - m_find_previous_action = GUI::Action::create("Find previous", { Mod_Ctrl | Mod_Shift, Key_G }, [&](auto&) { + m_find_next_action = GUI::Action::create("Find next", { Mod_Ctrl, Key_G }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find-next.png"), [&](auto&) { auto needle = m_find_textbox->text(); - if (needle.is_empty()) { - dbgln("find_prev(\"\")"); + if (needle.is_empty()) return; - } - - auto selection_start = m_editor->normalized_selection().start(); - if (!selection_start.is_valid()) - selection_start = m_editor->normalized_selection().end(); - - if (m_find_use_regex) + if (m_use_regex) m_editor->document().update_regex_matches(needle); - auto found_range = m_editor->document().find_previous(needle, selection_start, GUI::TextDocument::SearchShouldWrap::Yes, m_find_use_regex); - - dbgln("find_prev(\"{}\") returned {}", needle, found_range); + auto found_range = m_editor->document().find_next(needle, m_editor->normalized_selection().end(), m_should_wrap ? GUI::TextDocument::SearchShouldWrap::Yes : GUI::TextDocument::SearchShouldWrap::No, m_use_regex, m_match_case); + dbgln("find_next('{}') returned {}", needle, found_range); if (found_range.is_valid()) { m_editor->set_selection(found_range); } else { @@ -171,25 +156,21 @@ TextEditorWidget::TextEditorWidget() } }); - m_replace_next_action = GUI::Action::create("Replace next", { Mod_Ctrl, Key_F1 }, [&](auto&) { + m_find_previous_action = GUI::Action::create("Find previous", { Mod_Ctrl | Mod_Shift, Key_G }, Gfx::Bitmap::load_from_file("/res/icons/16x16/find-previous.png"), [&](auto&) { auto needle = m_find_textbox->text(); - auto substitute = m_replace_textbox->text(); - if (needle.is_empty()) return; + if (m_use_regex) + m_editor->document().update_regex_matches(needle); auto selection_start = m_editor->normalized_selection().start(); if (!selection_start.is_valid()) - selection_start = m_editor->normalized_selection().start(); - - if (m_find_use_regex) - m_editor->document().update_regex_matches(needle); - - auto found_range = m_editor->document().find_next(needle, selection_start, GUI::TextDocument::SearchShouldWrap::Yes, m_find_use_regex); + selection_start = m_editor->normalized_selection().end(); + auto found_range = m_editor->document().find_previous(needle, selection_start, m_should_wrap ? GUI::TextDocument::SearchShouldWrap::Yes : GUI::TextDocument::SearchShouldWrap::No, m_use_regex, m_match_case); + dbgln("find_prev(\"{}\") returned {}", needle, found_range); if (found_range.is_valid()) { m_editor->set_selection(found_range); - m_editor->insert_at_cursor_or_replace_selection(substitute); } else { GUI::MessageBox::show(window(), String::formatted("Not found: \"{}\"", needle), @@ -198,21 +179,15 @@ TextEditorWidget::TextEditorWidget() } }); - m_replace_previous_action = GUI::Action::create("Replace previous", { Mod_Ctrl | Mod_Shift, Key_F1 }, [&](auto&) { + m_replace_action = GUI::Action::create("Replace", { Mod_Ctrl, Key_F1 }, [&](auto&) { auto needle = m_find_textbox->text(); auto substitute = m_replace_textbox->text(); if (needle.is_empty()) return; - - auto selection_start = m_editor->normalized_selection().start(); - if (!selection_start.is_valid()) - selection_start = m_editor->normalized_selection().start(); - - if (m_find_use_regex) + if (m_use_regex) m_editor->document().update_regex_matches(needle); - auto found_range = m_editor->document().find_previous(needle, selection_start); - + auto found_range = m_editor->document().find_next(needle, m_editor->normalized_selection().start(), m_should_wrap ? GUI::TextDocument::SearchShouldWrap::Yes : GUI::TextDocument::SearchShouldWrap::No, m_use_regex, m_match_case); if (found_range.is_valid()) { m_editor->set_selection(found_range); m_editor->insert_at_cursor_or_replace_selection(substitute); @@ -229,47 +204,42 @@ TextEditorWidget::TextEditorWidget() auto substitute = m_replace_textbox->text(); if (needle.is_empty()) return; - if (m_find_use_regex) + if (m_use_regex) m_editor->document().update_regex_matches(needle); - auto found_range = m_editor->document().find_next(needle, {}, GUI::TextDocument::SearchShouldWrap::Yes, m_find_use_regex); + auto found_range = m_editor->document().find_next(needle, {}, GUI::TextDocument::SearchShouldWrap::Yes, m_use_regex, m_match_case); while (found_range.is_valid()) { m_editor->set_selection(found_range); m_editor->insert_at_cursor_or_replace_selection(substitute); - found_range = m_editor->document().find_next(needle, {}, GUI::TextDocument::SearchShouldWrap::Yes, m_find_use_regex); + found_range = m_editor->document().find_next(needle, {}, GUI::TextDocument::SearchShouldWrap::Yes, m_use_regex, m_match_case); } }); m_find_previous_button = *find_descendant_of_type_named<GUI::Button>("find_previous_button"); m_find_previous_button->set_action(*m_find_previous_action); + m_find_previous_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/find-previous.png")); m_find_next_button = *find_descendant_of_type_named<GUI::Button>("find_next_button"); m_find_next_button->set_action(*m_find_next_action); + m_find_next_button->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/find-next.png")); m_find_textbox->on_return_pressed = [this] { m_find_next_button->click(); }; - m_find_regex_button = m_find_widget->add<GUI::Button>(".*"); - m_find_regex_button->set_fixed_width(20); - m_find_regex_button->set_action(*m_find_regex_action); - m_find_textbox->on_escape_pressed = [this] { m_find_replace_widget->set_visible(false); m_editor->set_focus(true); }; - m_replace_previous_button = *find_descendant_of_type_named<GUI::Button>("replace_previous_button"); - m_replace_previous_button->set_action(*m_replace_previous_action); - - m_replace_next_button = *find_descendant_of_type_named<GUI::Button>("replace_next_button"); - m_replace_next_button->set_action(*m_replace_next_action); + m_replace_button = *find_descendant_of_type_named<GUI::Button>("replace_button"); + m_replace_button->set_action(*m_replace_action); m_replace_all_button = *find_descendant_of_type_named<GUI::Button>("replace_all_button"); m_replace_all_button->set_action(*m_replace_all_action); m_replace_textbox->on_return_pressed = [this] { - m_replace_next_button->click(); + m_replace_button->click(); }; m_replace_textbox->on_escape_pressed = [this] { @@ -393,10 +363,8 @@ TextEditorWidget::TextEditorWidget() edit_menu.add_separator(); edit_menu.add_action(*m_find_replace_action); edit_menu.add_action(*m_find_next_action); - edit_menu.add_action(*m_find_regex_action); edit_menu.add_action(*m_find_previous_action); - edit_menu.add_action(*m_replace_next_action); - edit_menu.add_action(*m_replace_previous_action); + edit_menu.add_action(*m_replace_action); edit_menu.add_action(*m_replace_all_action); m_no_preview_action = GUI::Action::create_checkable( diff --git a/Userland/Applications/TextEditor/TextEditorWidget.h b/Userland/Applications/TextEditor/TextEditorWidget.h index 20f7b3a9ba..637e890e7d 100644 --- a/Userland/Applications/TextEditor/TextEditorWidget.h +++ b/Userland/Applications/TextEditor/TextEditorWidget.h @@ -78,10 +78,8 @@ private: RefPtr<GUI::Action> m_vim_emulation_setting_action; RefPtr<GUI::Action> m_find_next_action; - RefPtr<GUI::Action> m_find_regex_action; RefPtr<GUI::Action> m_find_previous_action; - RefPtr<GUI::Action> m_replace_next_action; - RefPtr<GUI::Action> m_replace_previous_action; + RefPtr<GUI::Action> m_replace_action; RefPtr<GUI::Action> m_replace_all_action; RefPtr<GUI::Action> m_layout_toolbar_action; @@ -99,13 +97,14 @@ private: RefPtr<GUI::TextBox> m_replace_textbox; RefPtr<GUI::Button> m_find_previous_button; RefPtr<GUI::Button> m_find_next_button; - RefPtr<GUI::Button> m_find_regex_button; - RefPtr<GUI::Button> m_replace_previous_button; - RefPtr<GUI::Button> m_replace_next_button; + RefPtr<GUI::Button> m_replace_button; RefPtr<GUI::Button> m_replace_all_button; RefPtr<GUI::Widget> m_find_replace_widget; RefPtr<GUI::Widget> m_find_widget; RefPtr<GUI::Widget> m_replace_widget; + RefPtr<GUI::CheckBox> m_regex_checkbox; + RefPtr<GUI::CheckBox> m_match_case_checkbox; + RefPtr<GUI::CheckBox> m_wrap_around_checkbox; GUI::ActionGroup m_wrapping_mode_actions; RefPtr<GUI::Action> m_no_wrapping_action; @@ -126,7 +125,9 @@ private: bool m_document_dirty { false }; bool m_document_opening { false }; bool m_auto_detect_preview_mode { false }; - bool m_find_use_regex { false }; + bool m_use_regex { false }; + bool m_match_case { true }; + bool m_should_wrap { true }; PreviewMode m_preview_mode { PreviewMode::None }; }; diff --git a/Userland/Applications/TextEditor/TextEditorWindow.gml b/Userland/Applications/TextEditor/TextEditorWindow.gml index ad02320c50..97a50c348e 100644 --- a/Userland/Applications/TextEditor/TextEditorWindow.gml +++ b/Userland/Applications/TextEditor/TextEditorWindow.gml @@ -25,14 +25,15 @@ } } - @GUI::Widget { + @GUI::GroupBox { name: "find_replace_widget" visible: false fill_with_background_color: true - fixed_height: 48 + fixed_height: 56 layout: @GUI::VerticalBoxLayout { - margins: [2, 2, 2, 4] + spacing: 2 + margins: [5, 5, 5, 5] } @GUI::Widget { @@ -41,18 +42,33 @@ fixed_height: 22 layout: @GUI::HorizontalBoxLayout { + spacing: 4 } @GUI::Button { name: "find_previous_button" - text: "Find previous" - fixed_width: 150 + fixed_width: 38 } @GUI::Button { name: "find_next_button" - text: "Find next" - fixed_width: 150 + fixed_width: 38 + } + + @GUI::TextBox { + name: "find_textbox" + } + + @GUI::CheckBox { + name: "regex_checkbox" + text: "Use RegEx" + fixed_width: 80 + } + + @GUI::CheckBox { + name: "match_case_checkbox" + text: "Match case" + fixed_width: 85 } } @@ -62,24 +78,29 @@ fixed_height: 22 layout: @GUI::HorizontalBoxLayout { + spacing: 4 } @GUI::Button { - name: "replace_previous_button" - text: "Replace previous" - fixed_width: 100 + name: "replace_button" + text: "Replace" + fixed_width: 80 } - @GUI::Button { - name: "replace_next_button" - text: "Replace next" - fixed_width: 100 + @GUI::TextBox { + name: "replace_textbox" } @GUI::Button { name: "replace_all_button" text: "Replace all" - fixed_width: 100 + fixed_width: 80 + } + + @GUI::CheckBox { + name: "wrap_around_checkbox" + text: "Wrap around" + fixed_width: 85 } } } diff --git a/Userland/Libraries/LibGUI/TextDocument.cpp b/Userland/Libraries/LibGUI/TextDocument.cpp index 3c1836184e..a523b18157 100644 --- a/Userland/Libraries/LibGUI/TextDocument.cpp +++ b/Userland/Libraries/LibGUI/TextDocument.cpp @@ -387,7 +387,7 @@ void TextDocument::update_regex_matches(const StringView& needle) } } -TextRange TextDocument::find_next(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch) +TextRange TextDocument::find_next(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch, bool match_case) { if (needle.is_empty()) return {}; @@ -450,7 +450,7 @@ TextRange TextDocument::find_next(const StringView& needle, const TextPosition& do { auto ch = code_point_at(position); // FIXME: This is not the right way to use a Unicode needle! - if (ch == (u32)needle[needle_index]) { + if (match_case ? ch == (u32)needle[needle_index] : tolower(ch) == tolower((u32)needle[needle_index])) { if (needle_index == 0) start_of_potential_match = position; ++needle_index; @@ -467,7 +467,7 @@ TextRange TextDocument::find_next(const StringView& needle, const TextPosition& return {}; } -TextRange TextDocument::find_previous(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch) +TextRange TextDocument::find_previous(const StringView& needle, const TextPosition& start, SearchShouldWrap should_wrap, bool regmatch, bool match_case) { if (needle.is_empty()) return {}; @@ -524,6 +524,8 @@ TextRange TextDocument::find_previous(const StringView& needle, const TextPositi TextPosition position = start.is_valid() ? start : TextPosition(0, 0); position = previous_position_before(position, should_wrap); + if (position.line() >= line_count()) + return {}; TextPosition original_position = position; TextPosition end_of_potential_match; @@ -532,7 +534,7 @@ TextRange TextDocument::find_previous(const StringView& needle, const TextPositi do { auto ch = code_point_at(position); // FIXME: This is not the right way to use a Unicode needle! - if (ch == (u32)needle[needle_index]) { + if (match_case ? ch == (u32)needle[needle_index] : tolower(ch) == tolower((u32)needle[needle_index])) { if (needle_index == needle.length() - 1) end_of_potential_match = position; if (needle_index == 0) diff --git a/Userland/Libraries/LibGUI/TextDocument.h b/Userland/Libraries/LibGUI/TextDocument.h index ba00d34e0d..d559373f9b 100644 --- a/Userland/Libraries/LibGUI/TextDocument.h +++ b/Userland/Libraries/LibGUI/TextDocument.h @@ -109,8 +109,8 @@ public: Vector<TextRange> find_all(const StringView& needle, bool regmatch = false); void update_regex_matches(const StringView&); - TextRange find_next(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes, bool regmatch = false); - TextRange find_previous(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes, bool regmatch = false); + TextRange find_next(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes, bool regmatch = false, bool match_case = true); + TextRange find_previous(const StringView&, const TextPosition& start = {}, SearchShouldWrap = SearchShouldWrap::Yes, bool regmatch = false, bool match_case = true); TextPosition next_position_after(const TextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const; TextPosition previous_position_before(const TextPosition&, SearchShouldWrap = SearchShouldWrap::Yes) const; |