Serenity Operating System
at master 156 lines 4.8 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "FindInFilesWidget.h" 8#include "HackStudio.h" 9#include "Project.h" 10#include <AK/StringBuilder.h> 11#include <LibGUI/BoxLayout.h> 12#include <LibGUI/Button.h> 13#include <LibGUI/TableView.h> 14#include <LibGUI/TextBox.h> 15#include <LibGfx/Font/FontDatabase.h> 16 17namespace HackStudio { 18 19struct Match { 20 DeprecatedString filename; 21 GUI::TextRange range; 22 DeprecatedString text; 23}; 24 25class SearchResultsModel final : public GUI::Model { 26public: 27 enum Column { 28 Filename, 29 Location, 30 MatchedText, 31 __Count 32 }; 33 34 explicit SearchResultsModel(Vector<Match> const&& matches) 35 : m_matches(move(matches)) 36 { 37 } 38 39 virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_matches.size(); } 40 virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Count; } 41 42 virtual DeprecatedString column_name(int column) const override 43 { 44 switch (column) { 45 case Column::Filename: 46 return "Filename"; 47 case Column::Location: 48 return "#"; 49 case Column::MatchedText: 50 return "Text"; 51 default: 52 VERIFY_NOT_REACHED(); 53 } 54 } 55 56 virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override 57 { 58 if (role == GUI::ModelRole::TextAlignment) 59 return Gfx::TextAlignment::CenterLeft; 60 if (role == GUI::ModelRole::Font) { 61 if (index.column() == Column::MatchedText) 62 return Gfx::FontDatabase::default_fixed_width_font(); 63 return {}; 64 } 65 if (role == GUI::ModelRole::Display) { 66 auto& match = m_matches.at(index.row()); 67 switch (index.column()) { 68 case Column::Filename: 69 return match.filename; 70 case Column::Location: 71 return (int)match.range.start().line(); 72 case Column::MatchedText: 73 return match.text; 74 } 75 } 76 return {}; 77 } 78 79 virtual GUI::ModelIndex index(int row, int column = 0, const GUI::ModelIndex& = GUI::ModelIndex()) const override 80 { 81 if (row < 0 || row >= (int)m_matches.size()) 82 return {}; 83 if (column < 0 || column >= Column::__Count) 84 return {}; 85 return create_index(row, column, &m_matches.at(row)); 86 } 87 88private: 89 Vector<Match> m_matches; 90}; 91 92static RefPtr<SearchResultsModel> find_in_files(StringView text) 93{ 94 Vector<Match> matches; 95 project().for_each_text_file([&](auto& file) { 96 auto matches_in_file = file.document().find_all(text); 97 for (auto& range : matches_in_file) { 98 auto whole_line_range = file.document().range_for_entire_line(range.start().line()); 99 auto whole_line_containing_match = file.document().text_in_range(whole_line_range); 100 auto left_part = whole_line_containing_match.substring(0, range.start().column()); 101 auto right_part = whole_line_containing_match.substring(range.end().column(), whole_line_containing_match.length() - range.end().column()); 102 StringBuilder builder; 103 builder.append(left_part); 104 builder.append(0x01); 105 builder.append(file.document().text_in_range(range)); 106 builder.append(0x02); 107 builder.append(right_part); 108 matches.append({ file.name(), range, builder.to_deprecated_string() }); 109 } 110 }); 111 112 return adopt_ref(*new SearchResultsModel(move(matches))); 113} 114 115FindInFilesWidget::FindInFilesWidget() 116{ 117 set_layout<GUI::VerticalBoxLayout>(); 118 119 auto& top_container = add<Widget>(); 120 top_container.set_layout<GUI::HorizontalBoxLayout>(); 121 top_container.set_fixed_height(22); 122 123 m_textbox = top_container.add<GUI::TextBox>(); 124 125 m_button = top_container.add<GUI::Button>("Find in files"_string.release_value_but_fixme_should_propagate_errors()); 126 m_button->set_fixed_width(100); 127 128 m_result_view = add<GUI::TableView>(); 129 130 m_result_view->on_activation = [](auto& index) { 131 auto& match = *(const Match*)index.internal_data(); 132 open_file(match.filename); 133 current_editor().set_selection(match.range); 134 current_editor().set_focus(true); 135 }; 136 137 m_button->on_click = [this](auto) { 138 auto results_model = find_in_files(m_textbox->text()); 139 m_result_view->set_model(results_model); 140 }; 141 m_textbox->on_return_pressed = [this] { 142 m_button->click(); 143 }; 144} 145 146void FindInFilesWidget::focus_textbox_and_select_all() 147{ 148 m_textbox->select_all(); 149 m_textbox->set_focus(true); 150} 151void FindInFilesWidget::reset() 152{ 153 m_result_view->set_model(nullptr); 154} 155 156}