| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2021-04-28 22:46:44 +02:00
										 |  |  |  * Copyright (c) 2020-2021, the SerenityOS developers. | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2021-04-22 01:24:48 -07:00
										 |  |  |  * SPDX-License-Identifier: BSD-2-Clause | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | #include <AK/BinarySearch.h>
 | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  | #include <AK/FileStream.h>
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | #include <AK/Function.h>
 | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | #include <AK/StringBuilder.h>
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | #include <LibLine/SuggestionDisplay.h>
 | 
					
						
							|  |  |  | #include <LibLine/VT.h>
 | 
					
						
							|  |  |  | #include <stdio.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Line { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-14 14:41:18 +03:30
										 |  |  | void XtermSuggestionDisplay::display(SuggestionManager const& manager) | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-04-19 12:43:36 +04:30
										 |  |  |     did_display(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |     OutputFileStream stderr_stream { stderr }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     size_t longest_suggestion_length = 0; | 
					
						
							|  |  |  |     size_t longest_suggestion_byte_length = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-19 23:10:18 +04:30
										 |  |  |     manager.set_start_index(0); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     manager.for_each_suggestion([&](auto& suggestion, auto) { | 
					
						
							|  |  |  |         longest_suggestion_length = max(longest_suggestion_length, suggestion.text_view.length()); | 
					
						
							|  |  |  |         longest_suggestion_byte_length = max(longest_suggestion_byte_length, suggestion.text_string.length()); | 
					
						
							|  |  |  |         return IterationDecision::Continue; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     size_t num_printed = 0; | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  |     size_t lines_used = 1; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |     VT::save_cursor(stderr_stream); | 
					
						
							|  |  |  |     VT::clear_lines(0, m_lines_used_for_last_suggestions, stderr_stream); | 
					
						
							|  |  |  |     VT::restore_cursor(stderr_stream); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     auto spans_entire_line { false }; | 
					
						
							| 
									
										
										
										
											2021-01-10 16:23:04 +03:30
										 |  |  |     Vector<StringMetrics::LineMetrics> lines; | 
					
						
							| 
									
										
										
										
											2020-06-29 20:08:02 +04:30
										 |  |  |     for (size_t i = 0; i < m_prompt_lines_at_suggestion_initiation - 1; ++i) | 
					
						
							| 
									
										
										
										
											2021-01-10 16:23:04 +03:30
										 |  |  |         lines.append({ {}, 0 }); | 
					
						
							|  |  |  |     lines.append({ {}, longest_suggestion_length }); | 
					
						
							|  |  |  |     auto max_line_count = StringMetrics { move(lines) }.lines_with_addition({ { { {}, 0 } } }, m_num_columns); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     if (longest_suggestion_length >= m_num_columns - 2) { | 
					
						
							|  |  |  |         spans_entire_line = true; | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |         // We should make enough space for the biggest entry in
 | 
					
						
							|  |  |  |         // the suggestion list to fit in the prompt line.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         auto start = max_line_count - m_prompt_lines_at_suggestion_initiation; | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |         for (size_t i = start; i < max_line_count; ++i) | 
					
						
							|  |  |  |             stderr_stream.write("\n"sv.bytes()); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         lines_used += max_line_count; | 
					
						
							|  |  |  |         longest_suggestion_length = 0; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |     VT::move_absolute(max_line_count + m_origin_row, 1, stderr_stream); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  |     if (m_pages.is_empty()) { | 
					
						
							|  |  |  |         size_t num_printed = 0; | 
					
						
							|  |  |  |         size_t lines_used = 1; | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |         // Cache the pages.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  |         manager.set_start_index(0); | 
					
						
							|  |  |  |         size_t page_start = 0; | 
					
						
							|  |  |  |         manager.for_each_suggestion([&](auto& suggestion, auto index) { | 
					
						
							|  |  |  |             size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2; | 
					
						
							|  |  |  |             if (next_column > m_num_columns) { | 
					
						
							|  |  |  |                 auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns; | 
					
						
							|  |  |  |                 lines_used += lines; | 
					
						
							|  |  |  |                 num_printed = 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines) { | 
					
						
							|  |  |  |                 m_pages.append({ page_start, index }); | 
					
						
							|  |  |  |                 page_start = index; | 
					
						
							|  |  |  |                 lines_used = 1; | 
					
						
							|  |  |  |                 num_printed = 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (spans_entire_line) | 
					
						
							|  |  |  |                 num_printed += m_num_columns; | 
					
						
							|  |  |  |             else | 
					
						
							|  |  |  |                 num_printed += longest_suggestion_length + 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return IterationDecision::Continue; | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |         // Append the last page.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  |         m_pages.append({ page_start, manager.count() }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto page_index = fit_to_page_boundary(manager.next_index()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     manager.set_start_index(m_pages[page_index].start); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     manager.for_each_suggestion([&](auto& suggestion, auto index) { | 
					
						
							|  |  |  |         size_t next_column = num_printed + suggestion.text_view.length() + longest_suggestion_length + 2; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (next_column > m_num_columns) { | 
					
						
							|  |  |  |             auto lines = (suggestion.text_view.length() + m_num_columns - 1) / m_num_columns; | 
					
						
							|  |  |  |             lines_used += lines; | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |             stderr_stream.write("\n"sv.bytes()); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |             num_printed = 0; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |         // Show just enough suggestions to fill up the screen
 | 
					
						
							|  |  |  |         // without moving the prompt out of view.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines) | 
					
						
							|  |  |  |             return IterationDecision::Break; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-09 13:44:11 +01:00
										 |  |  |         // Only apply color to the selection if something is *actually* added to the buffer.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         if (manager.is_current_suggestion_complete() && index == manager.next_index()) { | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |             VT::apply_style({ Style::Foreground(Style::XtermColor::Blue) }, stderr_stream); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (spans_entire_line) { | 
					
						
							|  |  |  |             num_printed += m_num_columns; | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |             stderr_stream.write(suggestion.text_string.bytes()); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |             stderr_stream.write(String::formatted("{: <{}}", suggestion.text_string, longest_suggestion_byte_length + 2).bytes()); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |             num_printed += longest_suggestion_length + 2; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |         if (manager.is_current_suggestion_complete() && index == manager.next_index()) | 
					
						
							|  |  |  |             VT::apply_style(Style::reset_style(), stderr_stream); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         return IterationDecision::Continue; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     m_lines_used_for_last_suggestions = lines_used; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-14 14:03:57 +03:30
										 |  |  |     // The last line of the prompt is the same line as the first line of the buffer, so we need to subtract one here.
 | 
					
						
							|  |  |  |     lines_used += m_prompt_lines_at_suggestion_initiation - 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |     // If we filled the screen, move back the origin.
 | 
					
						
							| 
									
										
										
										
											2020-06-08 00:48:40 +04:30
										 |  |  |     if (m_origin_row + lines_used >= m_num_lines) { | 
					
						
							|  |  |  |         m_origin_row = m_num_lines - lines_used; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     if (m_pages.size() > 1) { | 
					
						
							|  |  |  |         auto left_arrow = page_index > 0 ? '<' : ' '; | 
					
						
							|  |  |  |         auto right_arrow = page_index < m_pages.size() - 1 ? '>' : ' '; | 
					
						
							| 
									
										
										
										
											2021-04-21 22:45:45 +02:00
										 |  |  |         auto string = String::formatted("{:c} page {} of {} {:c}", left_arrow, page_index + 1, m_pages.size(), right_arrow); | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |         if (string.length() > m_num_columns - 1) { | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |             // This would overflow into the next line, so just don't print an indicator.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |         VT::move_absolute(m_origin_row + lines_used, m_num_columns - string.length() - 1, stderr_stream); | 
					
						
							|  |  |  |         VT::apply_style({ Style::Background(Style::XtermColor::Green) }, stderr_stream); | 
					
						
							|  |  |  |         stderr_stream.write(string.bytes()); | 
					
						
							|  |  |  |         VT::apply_style(Style::reset_style(), stderr_stream); | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool XtermSuggestionDisplay::cleanup() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-04-19 12:43:36 +04:30
										 |  |  |     did_cleanup(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     if (m_lines_used_for_last_suggestions) { | 
					
						
							| 
									
										
										
										
											2021-07-19 23:12:28 +04:30
										 |  |  |         OutputFileStream stderr_stream { stderr }; | 
					
						
							|  |  |  |         VT::clear_lines(0, m_lines_used_for_last_suggestions, stderr_stream); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         m_lines_used_for_last_suggestions = 0; | 
					
						
							|  |  |  |         return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | size_t XtermSuggestionDisplay::fit_to_page_boundary(size_t selection_index) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-02-23 20:42:32 +01:00
										 |  |  |     VERIFY(m_pages.size() > 0); | 
					
						
							| 
									
										
										
										
											2020-08-02 22:10:35 +03:00
										 |  |  |     size_t index = 0; | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     auto* match = binary_search( | 
					
						
							| 
									
										
										
										
											2020-12-29 16:13:16 +01:00
										 |  |  |         m_pages.span(), | 
					
						
							|  |  |  |         PageRange { selection_index, selection_index }, | 
					
						
							|  |  |  |         &index, | 
					
						
							|  |  |  |         [](auto& a, auto& b) -> int { | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  |             if (a.start >= b.start && a.start < b.end) | 
					
						
							|  |  |  |                 return 0; | 
					
						
							|  |  |  |             return a.start - b.start; | 
					
						
							| 
									
										
										
										
											2020-12-29 16:13:16 +01:00
										 |  |  |         }); | 
					
						
							| 
									
										
										
										
											2020-05-22 14:02:17 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!match) | 
					
						
							|  |  |  |         return m_pages.size() - 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return index; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | } |