summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthankyouverycool <66646555+thankyouverycool@users.noreply.github.com>2021-02-21 19:01:26 -0500
committerAndreas Kling <kling@serenityos.org>2021-02-22 09:21:30 +0100
commit3e987eba2b39b137a78aafbfc6340eb375f219a6 (patch)
tree73a3c186e6df5e5c8ee8adba8e86cd264ed55936
parentb4c0314f1dae98fa5d206a097e29a30950d2624f (diff)
downloadserenity-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.cpp122
-rw-r--r--Userland/Applications/TextEditor/TextEditorWidget.h15
-rw-r--r--Userland/Applications/TextEditor/TextEditorWindow.gml51
-rw-r--r--Userland/Libraries/LibGUI/TextDocument.cpp10
-rw-r--r--Userland/Libraries/LibGUI/TextDocument.h4
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;