diff --git a/Userland/DevTools/HackStudio/CMakeLists.txt b/Userland/DevTools/HackStudio/CMakeLists.txt index f86e4e02e5..ee4a8ddd2d 100644 --- a/Userland/DevTools/HackStudio/CMakeLists.txt +++ b/Userland/DevTools/HackStudio/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(LanguageClients) compile_gml(Dialogs/NewProjectDialog.gml Dialogs/NewProjectDialogGML.h new_project_dialog_gml) compile_gml(Dialogs/Git/GitCommitDialog.gml Dialogs/Git/GitCommitDialogGML.h git_commit_dialog_gml) +compile_gml(FindWidget.gml FindWidgetGML.h find_widget_gml) set(SOURCES CodeDocument.cpp @@ -32,6 +33,8 @@ set(SOURCES Editor.cpp EditorWrapper.cpp FindInFilesWidget.cpp + FindWidget.cpp + FindWidgetGML.h Git/DiffViewer.cpp Git/GitFilesModel.cpp Git/GitFilesView.cpp diff --git a/Userland/DevTools/HackStudio/EditorWrapper.cpp b/Userland/DevTools/HackStudio/EditorWrapper.cpp index 0ca9ff0fc7..b3eda59f42 100644 --- a/Userland/DevTools/HackStudio/EditorWrapper.cpp +++ b/Userland/DevTools/HackStudio/EditorWrapper.cpp @@ -20,11 +20,13 @@ namespace HackStudio { EditorWrapper::EditorWrapper() { set_layout(); - m_filename_title = untitled_label; // FIXME: Propagate errors instead of giving up - m_editor = MUST(try_add()); + m_editor = MUST(Editor::try_create()); + m_find_widget = add(*m_editor); + + add_child(*m_editor); m_editor->set_ruler_visible(true); m_editor->set_automatic_indentation_enabled(true); @@ -115,4 +117,12 @@ void EditorWrapper::set_debug_mode(bool enabled) m_editor->set_debug_mode(enabled); } +void EditorWrapper::search_action() +{ + if (m_find_widget->visible()) + m_find_widget->hide(); + else + m_find_widget->show(); +} + } diff --git a/Userland/DevTools/HackStudio/EditorWrapper.h b/Userland/DevTools/HackStudio/EditorWrapper.h index 3c7a677f3b..62c5121d4d 100644 --- a/Userland/DevTools/HackStudio/EditorWrapper.h +++ b/Userland/DevTools/HackStudio/EditorWrapper.h @@ -8,6 +8,7 @@ #pragma once #include "Debugger/BreakpointCallback.h" +#include "FindWidget.h" #include "Git/GitRepo.h" #include "LanguageClient.h" #include @@ -52,6 +53,9 @@ public: Function on_change; Function on_tab_close_request; + void search_action(); + FindWidget const& find_widget() const { return *m_find_widget; } + private: static constexpr auto untitled_label = "(Untitled)"; @@ -62,6 +66,7 @@ private: String m_filename; String m_filename_title; RefPtr m_editor; + RefPtr m_find_widget; Optional m_project_root; RefPtr m_git_repo; diff --git a/Userland/DevTools/HackStudio/FindWidget.cpp b/Userland/DevTools/HackStudio/FindWidget.cpp new file mode 100644 index 0000000000..ebb6639bf5 --- /dev/null +++ b/Userland/DevTools/HackStudio/FindWidget.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022, Itamar S. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "FindWidget.h" +#include "Editor.h" +#include +#include +#include +#include + +namespace HackStudio { + +FindWidget::FindWidget(NonnullRefPtr editor) + : m_editor(move(editor)) +{ + load_from_gml(find_widget_gml); + set_fixed_height(widget_height); + m_input_field = find_descendant_of_type_named("input_field"); + m_index_label = find_descendant_of_type_named("index_label"); + m_next = find_descendant_of_type_named("next"); + m_previous = find_descendant_of_type_named("previous"); + + VERIFY(m_input_field); + VERIFY(m_next); + VERIFY(m_previous); + + m_next->on_click = [this](auto) { + find_next(GUI::TextEditor::SearchDirection::Forward); + }; + m_previous->on_click = [this](auto) { + find_next(GUI::TextEditor::SearchDirection::Backward); + }; + + m_input_field->on_change = [this]() { + m_editor->reset_search_results(); + find_next(GUI::TextEditor::SearchDirection::Forward); + }; + + m_input_field->on_return_pressed = [this]() { + find_next(GUI::TextEditor::SearchDirection::Forward); + }; +} + +void FindWidget::show() +{ + set_visible(true); + set_focus(true); + m_input_field->set_focus(true); + // Adjust scroll value to smooth the appearance of the FindWidget. + m_editor->vertical_scrollbar().set_value(m_editor->vertical_scrollbar().value() + widget_height, GUI::AllowCallback::Yes, GUI::Scrollbar::DoClamp::No); + m_visible = !m_visible; +} + +void FindWidget::hide() +{ + set_visible(false); + set_focus(false); + m_visible = !m_visible; + m_editor->vertical_scrollbar().set_value(m_editor->vertical_scrollbar().value() - widget_height, GUI::AllowCallback::Yes, GUI::Scrollbar::DoClamp::No); + m_editor->set_focus(true); + m_editor->reset_search_results(); +} + +void FindWidget::find_next(GUI::TextEditor::SearchDirection direction) +{ + auto needle = m_input_field->text(); + if (needle.is_empty()) { + m_editor->reset_search_results(); + m_index_label->set_text(String::empty()); + return; + } + auto result = m_editor->find_text(needle, direction, GUI::TextDocument::SearchShouldWrap::Yes, false, false); + + if (result.is_valid()) + m_index_label->set_text(String::formatted("{}/{}", m_editor->search_result_index().value_or(0) + 1, m_editor->search_results().size())); + else + m_index_label->set_text(String::empty()); +} + +} diff --git a/Userland/DevTools/HackStudio/FindWidget.gml b/Userland/DevTools/HackStudio/FindWidget.gml new file mode 100644 index 0000000000..190e43d5aa --- /dev/null +++ b/Userland/DevTools/HackStudio/FindWidget.gml @@ -0,0 +1,32 @@ +@GUI::Widget { + name: "find_widget" + fill_with_background_color: true + shrink_to_fit: true + visible: false + layout: @GUI::HorizontalBoxLayout { + margins: [0, 0] + } + + @GUI::TextBox { + name: "input_field" + max_width: 250 + } + + @GUI::Label { + name: "index_label" + max_width: 30 + text: "" + } + + @GUI::Button { + name: "next" + icon: "/res/icons/16x16/go-down.png" + max_width: 15 + } + + @GUI::Button { + name: "previous" + icon: "/res/icons/16x16/go-up.png" + max_width: 15 + } +} diff --git a/Userland/DevTools/HackStudio/FindWidget.h b/Userland/DevTools/HackStudio/FindWidget.h new file mode 100644 index 0000000000..246ad7a4b9 --- /dev/null +++ b/Userland/DevTools/HackStudio/FindWidget.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022, Itamar S. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "Editor.h" +#include +#include +#include +#include + +namespace HackStudio { + +class Editor; +class EditorWrapper; + +class FindWidget final : public GUI::Widget { + C_OBJECT(FindWidget) +public: + ~FindWidget() = default; + + void show(); + void hide(); + bool visible() const { return m_visible; } + +private: + FindWidget(NonnullRefPtr); + + void find_next(GUI::TextEditor::SearchDirection); + + static constexpr auto widget_height = 25; + + NonnullRefPtr m_editor; + RefPtr m_input_field; + RefPtr m_index_label; + RefPtr m_next; + RefPtr m_previous; + bool m_visible { false }; +}; + +} diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.cpp b/Userland/DevTools/HackStudio/HackStudioWidget.cpp index 0ba73ea25b..df1b66220b 100644 --- a/Userland/DevTools/HackStudio/HackStudioWidget.cpp +++ b/Userland/DevTools/HackStudio/HackStudioWidget.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2020, Itamar S. + * Copyright (c) 2020-2022, Itamar S. * Copyright (c) 2020-2022, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause @@ -1445,6 +1445,11 @@ void HackStudioWidget::create_view_menu(GUI::Window& window) create_location_history_actions(); view_menu.add_action(*m_locations_history_back_action); view_menu.add_action(*m_locations_history_forward_action); + + auto search_action = GUI::Action::create("&Search", { Mod_Ctrl, Key_F }, [this](auto&) { + current_editor_wrapper().search_action(); + }); + view_menu.add_action(search_action); } void HackStudioWidget::create_help_menu(GUI::Window& window) diff --git a/Userland/DevTools/HackStudio/HackStudioWidget.h b/Userland/DevTools/HackStudio/HackStudioWidget.h index 45e8d8c555..29f76a148c 100644 --- a/Userland/DevTools/HackStudio/HackStudioWidget.h +++ b/Userland/DevTools/HackStudio/HackStudioWidget.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2020, Itamar S. + * Copyright (c) 2020-2022, Itamar S. * Copyright (c) 2020-2021, the SerenityOS developers. * * SPDX-License-Identifier: BSD-2-Clause