XtermSuggestionDisplay.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * Copyright (c) 2020-2021, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/BinarySearch.h>
  7. #include <AK/FileStream.h>
  8. #include <AK/Function.h>
  9. #include <AK/StringBuilder.h>
  10. #include <LibLine/SuggestionDisplay.h>
  11. #include <LibLine/VT.h>
  12. #include <stdio.h>
  13. namespace Line {
  14. void XtermSuggestionDisplay::display(const SuggestionManager& manager)
  15. {
  16. did_display();
  17. OutputFileStream stderr_stream { stderr };
  18. size_t longest_suggestion_length = 0;
  19. size_t longest_suggestion_byte_length = 0;
  20. manager.set_start_index(0);
  21. manager.for_each_suggestion([&](auto& suggestion, auto) {
  22. longest_suggestion_length = max(longest_suggestion_length, suggestion.text_view.length());
  23. longest_suggestion_byte_length = max(longest_suggestion_byte_length, suggestion.text_string.length());
  24. return IterationDecision::Continue;
  25. });
  26. size_t num_printed = 0;
  27. size_t lines_used = 1;
  28. VT::save_cursor(stderr_stream);
  29. VT::clear_lines(0, m_lines_used_for_last_suggestions, stderr_stream);
  30. VT::restore_cursor(stderr_stream);
  31. auto spans_entire_line { false };
  32. Vector<StringMetrics::LineMetrics> lines;
  33. for (size_t i = 0; i < m_prompt_lines_at_suggestion_initiation - 1; ++i)
  34. lines.append({ {}, 0 });
  35. lines.append({ {}, longest_suggestion_length });
  36. auto max_line_count = StringMetrics { move(lines) }.lines_with_addition({ { { {}, 0 } } }, m_num_columns);
  37. if (longest_suggestion_length >= m_num_columns - 2) {
  38. spans_entire_line = true;
  39. // We should make enough space for the biggest entry in
  40. // the suggestion list to fit in the prompt line.
  41. auto start = max_line_count - m_prompt_lines_at_suggestion_initiation;
  42. for (size_t i = start; i < max_line_count; ++i)
  43. stderr_stream.write("\n"sv.bytes());
  44. lines_used += max_line_count;
  45. longest_suggestion_length = 0;
  46. }
  47. VT::move_absolute(max_line_count + m_origin_row, 1, stderr_stream);
  48. if (m_pages.is_empty()) {
  49. size_t num_printed = 0;
  50. size_t lines_used = 1;
  51. // Cache the pages.
  52. manager.set_start_index(0);
  53. size_t page_start = 0;
  54. manager.for_each_suggestion([&](auto& suggestion, auto index) {
  55. size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2;
  56. if (next_column > m_num_columns) {
  57. auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns;
  58. lines_used += lines;
  59. num_printed = 0;
  60. }
  61. if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines) {
  62. m_pages.append({ page_start, index });
  63. page_start = index;
  64. lines_used = 1;
  65. num_printed = 0;
  66. }
  67. if (spans_entire_line)
  68. num_printed += m_num_columns;
  69. else
  70. num_printed += longest_suggestion_length + 2;
  71. return IterationDecision::Continue;
  72. });
  73. // Append the last page.
  74. m_pages.append({ page_start, manager.count() });
  75. }
  76. auto page_index = fit_to_page_boundary(manager.next_index());
  77. manager.set_start_index(m_pages[page_index].start);
  78. manager.for_each_suggestion([&](auto& suggestion, auto index) {
  79. size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2;
  80. if (next_column > m_num_columns) {
  81. auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns;
  82. lines_used += lines;
  83. stderr_stream.write("\n"sv.bytes());
  84. num_printed = 0;
  85. }
  86. // Show just enough suggestions to fill up the screen
  87. // without moving the prompt out of view.
  88. if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines)
  89. return IterationDecision::Break;
  90. // Only apply color to the selection if something is *actually* added to the buffer.
  91. if (manager.is_current_suggestion_complete() && index == manager.next_index()) {
  92. VT::apply_style({ Style::Foreground(Style::XtermColor::Blue) }, stderr_stream);
  93. }
  94. if (spans_entire_line) {
  95. num_printed += m_num_columns;
  96. stderr_stream.write(suggestion.text_string.bytes());
  97. } else {
  98. stderr_stream.write(String::formatted("{: <{}}", suggestion.text_string, longest_suggestion_byte_length + 2).bytes());
  99. num_printed += longest_suggestion_length + 2;
  100. }
  101. if (manager.is_current_suggestion_complete() && index == manager.next_index())
  102. VT::apply_style(Style::reset_style(), stderr_stream);
  103. return IterationDecision::Continue;
  104. });
  105. m_lines_used_for_last_suggestions = lines_used;
  106. // If we filled the screen, move back the origin.
  107. if (m_origin_row + lines_used >= m_num_lines) {
  108. m_origin_row = m_num_lines - lines_used;
  109. }
  110. if (m_pages.size() > 1) {
  111. auto left_arrow = page_index > 0 ? '<' : ' ';
  112. auto right_arrow = page_index < m_pages.size() - 1 ? '>' : ' ';
  113. auto string = String::formatted("{:c} page {} of {} {:c}", left_arrow, page_index + 1, m_pages.size(), right_arrow);
  114. if (string.length() > m_num_columns - 1) {
  115. // This would overflow into the next line, so just don't print an indicator.
  116. return;
  117. }
  118. VT::move_absolute(m_origin_row + lines_used, m_num_columns - string.length() - 1, stderr_stream);
  119. VT::apply_style({ Style::Background(Style::XtermColor::Green) }, stderr_stream);
  120. stderr_stream.write(string.bytes());
  121. VT::apply_style(Style::reset_style(), stderr_stream);
  122. }
  123. }
  124. bool XtermSuggestionDisplay::cleanup()
  125. {
  126. did_cleanup();
  127. if (m_lines_used_for_last_suggestions) {
  128. OutputFileStream stderr_stream { stderr };
  129. VT::clear_lines(0, m_lines_used_for_last_suggestions, stderr_stream);
  130. m_lines_used_for_last_suggestions = 0;
  131. return true;
  132. }
  133. return false;
  134. }
  135. size_t XtermSuggestionDisplay::fit_to_page_boundary(size_t selection_index)
  136. {
  137. VERIFY(m_pages.size() > 0);
  138. size_t index = 0;
  139. auto* match = binary_search(
  140. m_pages.span(),
  141. PageRange { selection_index, selection_index },
  142. &index,
  143. [](auto& a, auto& b) -> int {
  144. if (a.start >= b.start && a.start < b.end)
  145. return 0;
  146. return a.start - b.start;
  147. });
  148. if (!match)
  149. return m_pages.size() - 1;
  150. return index;
  151. }
  152. }