| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | /*
 | 
					
						
							| 
									
										
										
										
											2021-04-28 22:46:44 +02:00
										 |  |  |  * Copyright (c) 2020, 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
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <AK/Function.h>
 | 
					
						
							|  |  |  | #include <LibLine/SuggestionManager.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace Line { | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-23 17:56:48 +04:30
										 |  |  | CompletionSuggestion::CompletionSuggestion(StringView completion, StringView trailing_trivia, StringView display_trivia, Style style) | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     : style(style) | 
					
						
							|  |  |  |     , text_string(completion) | 
					
						
							| 
									
										
										
										
											2022-03-23 17:56:48 +04:30
										 |  |  |     , display_trivia_string(display_trivia) | 
					
						
							| 
									
										
										
										
											2020-05-22 22:40:42 +04:30
										 |  |  |     , is_valid(true) | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     Utf8View text_u8 { completion }; | 
					
						
							|  |  |  |     Utf8View trivia_u8 { trailing_trivia }; | 
					
						
							| 
									
										
										
										
											2022-03-23 17:56:48 +04:30
										 |  |  |     Utf8View display_u8 { display_trivia }; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     for (auto cp : text_u8) | 
					
						
							|  |  |  |         text.append(cp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (auto cp : trivia_u8) | 
					
						
							|  |  |  |         this->trailing_trivia.append(cp); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-23 17:56:48 +04:30
										 |  |  |     for (auto cp : display_u8) | 
					
						
							|  |  |  |         this->display_trivia.append(cp); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     text_view = Utf32View { text.data(), text.size() }; | 
					
						
							|  |  |  |     trivia_view = Utf32View { this->trailing_trivia.data(), this->trailing_trivia.size() }; | 
					
						
							| 
									
										
										
										
											2022-03-23 17:56:48 +04:30
										 |  |  |     display_trivia_view = Utf32View { this->display_trivia.data(), this->display_trivia.size() }; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SuggestionManager::set_suggestions(Vector<CompletionSuggestion>&& suggestions) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     m_suggestions = move(suggestions); | 
					
						
							| 
									
										
										
										
											2020-05-22 22:40:42 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-06 17:59:12 +03:30
										 |  |  |     // Set the views and make sure we were not given invalid suggestions
 | 
					
						
							|  |  |  |     for (auto& suggestion : m_suggestions) { | 
					
						
							| 
									
										
										
										
											2021-02-23 20:42:32 +01:00
										 |  |  |         VERIFY(suggestion.is_valid); | 
					
						
							| 
									
										
										
										
											2022-03-06 17:59:12 +03:30
										 |  |  |         suggestion.text_view = { suggestion.text.data(), suggestion.text.size() }; | 
					
						
							|  |  |  |         suggestion.trivia_view = { suggestion.trailing_trivia.data(), suggestion.trailing_trivia.size() }; | 
					
						
							| 
									
										
										
										
											2022-03-23 17:56:48 +04:30
										 |  |  |         suggestion.display_trivia_view = { suggestion.display_trivia.data(), suggestion.display_trivia.size() }; | 
					
						
							| 
									
										
										
										
											2022-03-06 17:59:12 +03:30
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-05-22 22:40:42 +04:30
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     size_t common_suggestion_prefix { 0 }; | 
					
						
							|  |  |  |     if (m_suggestions.size() == 1) { | 
					
						
							|  |  |  |         m_largest_common_suggestion_prefix_length = m_suggestions[0].text_view.length(); | 
					
						
							|  |  |  |     } else if (m_suggestions.size()) { | 
					
						
							| 
									
										
										
										
											2020-08-05 16:31:20 -04:00
										 |  |  |         u32 last_valid_suggestion_code_point; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |         for (;; ++common_suggestion_prefix) { | 
					
						
							|  |  |  |             if (m_suggestions[0].text_view.length() <= common_suggestion_prefix) | 
					
						
							|  |  |  |                 goto no_more_commons; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-05 16:31:20 -04:00
										 |  |  |             last_valid_suggestion_code_point = m_suggestions[0].text_view.code_points()[common_suggestion_prefix]; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |             for (auto& suggestion : m_suggestions) { | 
					
						
							| 
									
										
										
										
											2020-08-05 16:31:20 -04:00
										 |  |  |                 if (suggestion.text_view.length() <= common_suggestion_prefix || suggestion.text_view.code_points()[common_suggestion_prefix] != last_valid_suggestion_code_point) { | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |                     goto no_more_commons; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     no_more_commons:; | 
					
						
							|  |  |  |         m_largest_common_suggestion_prefix_length = common_suggestion_prefix; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         m_largest_common_suggestion_prefix_length = 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SuggestionManager::next() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_suggestions.size()) | 
					
						
							|  |  |  |         m_next_suggestion_index = (m_next_suggestion_index + 1) % m_suggestions.size(); | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         m_next_suggestion_index = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SuggestionManager::previous() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (m_next_suggestion_index == 0) | 
					
						
							|  |  |  |         m_next_suggestion_index = m_suggestions.size(); | 
					
						
							|  |  |  |     m_next_suggestion_index--; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-14 14:41:18 +03:30
										 |  |  | CompletionSuggestion const& SuggestionManager::suggest() | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     m_last_shown_suggestion = m_suggestions[m_next_suggestion_index]; | 
					
						
							|  |  |  |     m_selected_suggestion_index = m_next_suggestion_index; | 
					
						
							|  |  |  |     return m_last_shown_suggestion; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void SuggestionManager::set_current_suggestion_initiation_index(size_t index) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |     auto& suggestion = m_suggestions[m_next_suggestion_index]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     if (m_last_shown_suggestion_display_length) | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |         m_last_shown_suggestion.start_index = index - suggestion.static_offset - m_last_shown_suggestion_display_length; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |     else | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |         m_last_shown_suggestion.start_index = index - suggestion.static_offset - suggestion.invariant_offset; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | 
 | 
					
						
							|  |  |  |     m_last_shown_suggestion_display_length = m_last_shown_suggestion.text_view.length(); | 
					
						
							|  |  |  |     m_last_shown_suggestion_was_complete = true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SuggestionManager::CompletionAttemptResult SuggestionManager::attempt_completion(CompletionMode mode, size_t initiation_start_index) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     CompletionAttemptResult result { mode }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (m_next_suggestion_index < m_suggestions.size()) { | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |         auto& next_suggestion = m_suggestions[m_next_suggestion_index]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 08:10:27 +04:30
										 |  |  |         if (mode == CompletePrefix && !next_suggestion.allow_commit_without_listing) { | 
					
						
							|  |  |  |             result.new_completion_mode = CompletionMode::ShowSuggestions; | 
					
						
							|  |  |  |             result.avoid_committing_to_single_suggestion = true; | 
					
						
							|  |  |  |             m_last_shown_suggestion_display_length = 0; | 
					
						
							|  |  |  |             m_last_shown_suggestion_was_complete = false; | 
					
						
							| 
									
										
										
										
											2022-12-04 18:02:33 +00:00
										 |  |  |             m_last_shown_suggestion = DeprecatedString::empty(); | 
					
						
							| 
									
										
										
										
											2022-04-15 08:10:27 +04:30
										 |  |  |             return result; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |         auto can_complete = next_suggestion.invariant_offset <= m_largest_common_suggestion_prefix_length; | 
					
						
							| 
									
										
										
										
											2021-05-16 08:47:13 +02:00
										 |  |  |         ssize_t actual_offset; | 
					
						
							|  |  |  |         size_t shown_length = m_last_shown_suggestion_display_length; | 
					
						
							|  |  |  |         switch (mode) { | 
					
						
							|  |  |  |         case CompletePrefix: | 
					
						
							|  |  |  |             actual_offset = 0; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         case ShowSuggestions: | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |             actual_offset = 0 - m_largest_common_suggestion_prefix_length + next_suggestion.invariant_offset; | 
					
						
							| 
									
										
										
										
											2022-04-15 08:10:27 +04:30
										 |  |  |             if (can_complete && next_suggestion.allow_commit_without_listing) | 
					
						
							| 
									
										
										
										
											2021-05-16 08:47:13 +02:00
										 |  |  |                 shown_length = m_largest_common_suggestion_prefix_length + m_last_shown_suggestion.trivia_view.length(); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         default: | 
					
						
							|  |  |  |             if (m_last_shown_suggestion_display_length == 0) | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |                 actual_offset = 0; | 
					
						
							| 
									
										
										
										
											2021-05-16 08:47:13 +02:00
										 |  |  |             else | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |                 actual_offset = 0 - m_last_shown_suggestion_display_length + next_suggestion.invariant_offset; | 
					
						
							| 
									
										
										
										
											2021-05-16 08:47:13 +02:00
										 |  |  |             break; | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         auto& suggestion = suggest(); | 
					
						
							|  |  |  |         set_current_suggestion_initiation_index(initiation_start_index); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 01:48:56 +04:30
										 |  |  |         result.offset_region_to_remove = { next_suggestion.invariant_offset, shown_length }; | 
					
						
							|  |  |  |         result.new_cursor_offset = actual_offset; | 
					
						
							|  |  |  |         result.static_offset_from_cursor = next_suggestion.static_offset; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         if (mode == CompletePrefix) { | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |             // Only auto-complete *if possible*.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |             if (can_complete) { | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |                 result.insert.append(suggestion.text_view.substring_view(suggestion.invariant_offset, m_largest_common_suggestion_prefix_length - suggestion.invariant_offset)); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |                 m_last_shown_suggestion_display_length = m_largest_common_suggestion_prefix_length; | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |                 // Do not increment the suggestion index, as the first tab should only be a *peek*.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |                 if (m_suggestions.size() == 1) { | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |                     // If there's one suggestion, commit and forget.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |                     result.new_completion_mode = DontComplete; | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |                     // Add in the trivia of the last selected suggestion.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |                     result.insert.append(suggestion.trivia_view); | 
					
						
							|  |  |  |                     m_last_shown_suggestion_display_length = 0; | 
					
						
							|  |  |  |                     result.style_to_apply = suggestion.style; | 
					
						
							|  |  |  |                     m_last_shown_suggestion_was_complete = true; | 
					
						
							|  |  |  |                     return result; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 m_last_shown_suggestion_display_length = 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             result.new_completion_mode = CompletionMode::ShowSuggestions; | 
					
						
							|  |  |  |             m_last_shown_suggestion_was_complete = false; | 
					
						
							| 
									
										
										
										
											2022-12-04 18:02:33 +00:00
										 |  |  |             m_last_shown_suggestion = DeprecatedString::empty(); | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2022-02-28 17:28:47 +03:30
										 |  |  |             result.insert.append(suggestion.text_view.substring_view(suggestion.invariant_offset, suggestion.text_view.length() - suggestion.invariant_offset)); | 
					
						
							| 
									
										
										
										
											2020-05-23 03:19:48 +04:30
										 |  |  |             // Add in the trivia of the last selected suggestion.
 | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |             result.insert.append(suggestion.trivia_view); | 
					
						
							|  |  |  |             m_last_shown_suggestion_display_length += suggestion.trivia_view.length(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         m_next_suggestion_index = 0; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  | ErrorOr<size_t> SuggestionManager::for_each_suggestion(Function<ErrorOr<IterationDecision>(CompletionSuggestion const&, size_t)> callback) const | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  | { | 
					
						
							|  |  |  |     size_t start_index { 0 }; | 
					
						
							|  |  |  |     for (auto& suggestion : m_suggestions) { | 
					
						
							|  |  |  |         if (start_index++ < m_last_displayed_suggestion_index) | 
					
						
							|  |  |  |             continue; | 
					
						
							| 
									
										
										
										
											2023-01-13 12:29:46 +01:00
										 |  |  |         if (TRY(callback(suggestion, start_index - 1)) == IterationDecision::Break) | 
					
						
							| 
									
										
										
										
											2020-05-22 03:52:34 +04:30
										 |  |  |             break; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return start_index; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |