FindInFilesWidget.cpp 4.6 KB

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