Serenity Operating System
at master 197 lines 8.1 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include <AK/Function.h> 8#include <LibLine/SuggestionManager.h> 9 10namespace Line { 11 12CompletionSuggestion::CompletionSuggestion(StringView completion, StringView trailing_trivia, StringView display_trivia, Style style) 13 : style(style) 14 , text_string(completion) 15 , display_trivia_string(display_trivia) 16 , is_valid(true) 17{ 18 Utf8View text_u8 { completion }; 19 Utf8View trivia_u8 { trailing_trivia }; 20 Utf8View display_u8 { display_trivia }; 21 22 for (auto cp : text_u8) 23 text.append(cp); 24 25 for (auto cp : trivia_u8) 26 this->trailing_trivia.append(cp); 27 28 for (auto cp : display_u8) 29 this->display_trivia.append(cp); 30 31 text_view = Utf32View { text.data(), text.size() }; 32 trivia_view = Utf32View { this->trailing_trivia.data(), this->trailing_trivia.size() }; 33 display_trivia_view = Utf32View { this->display_trivia.data(), this->display_trivia.size() }; 34} 35 36void SuggestionManager::set_suggestions(Vector<CompletionSuggestion>&& suggestions) 37{ 38 m_suggestions = move(suggestions); 39 40 // Set the views and make sure we were not given invalid suggestions 41 for (auto& suggestion : m_suggestions) { 42 VERIFY(suggestion.is_valid); 43 suggestion.text_view = { suggestion.text.data(), suggestion.text.size() }; 44 suggestion.trivia_view = { suggestion.trailing_trivia.data(), suggestion.trailing_trivia.size() }; 45 suggestion.display_trivia_view = { suggestion.display_trivia.data(), suggestion.display_trivia.size() }; 46 } 47 48 size_t common_suggestion_prefix { 0 }; 49 if (m_suggestions.size() == 1) { 50 m_largest_common_suggestion_prefix_length = m_suggestions[0].text_view.length(); 51 } else if (m_suggestions.size()) { 52 u32 last_valid_suggestion_code_point; 53 54 for (;; ++common_suggestion_prefix) { 55 if (m_suggestions[0].text_view.length() <= common_suggestion_prefix) 56 goto no_more_commons; 57 58 last_valid_suggestion_code_point = m_suggestions[0].text_view.code_points()[common_suggestion_prefix]; 59 60 for (auto& suggestion : m_suggestions) { 61 if (suggestion.text_view.length() <= common_suggestion_prefix || suggestion.text_view.code_points()[common_suggestion_prefix] != last_valid_suggestion_code_point) { 62 goto no_more_commons; 63 } 64 } 65 } 66 no_more_commons:; 67 m_largest_common_suggestion_prefix_length = common_suggestion_prefix; 68 } else { 69 m_largest_common_suggestion_prefix_length = 0; 70 } 71} 72 73void SuggestionManager::next() 74{ 75 if (m_suggestions.size()) 76 m_next_suggestion_index = (m_next_suggestion_index + 1) % m_suggestions.size(); 77 else 78 m_next_suggestion_index = 0; 79} 80 81void SuggestionManager::previous() 82{ 83 if (m_next_suggestion_index == 0) 84 m_next_suggestion_index = m_suggestions.size(); 85 m_next_suggestion_index--; 86} 87 88CompletionSuggestion const& SuggestionManager::suggest() 89{ 90 m_last_shown_suggestion = m_suggestions[m_next_suggestion_index]; 91 m_selected_suggestion_index = m_next_suggestion_index; 92 return m_last_shown_suggestion; 93} 94 95void SuggestionManager::set_current_suggestion_initiation_index(size_t index) 96{ 97 auto& suggestion = m_suggestions[m_next_suggestion_index]; 98 99 if (m_last_shown_suggestion_display_length) 100 m_last_shown_suggestion.start_index = index - suggestion.static_offset - m_last_shown_suggestion_display_length; 101 else 102 m_last_shown_suggestion.start_index = index - suggestion.static_offset - suggestion.invariant_offset; 103 104 m_last_shown_suggestion_display_length = m_last_shown_suggestion.text_view.length(); 105 m_last_shown_suggestion_was_complete = true; 106} 107 108SuggestionManager::CompletionAttemptResult SuggestionManager::attempt_completion(CompletionMode mode, size_t initiation_start_index) 109{ 110 CompletionAttemptResult result { mode }; 111 112 if (m_next_suggestion_index < m_suggestions.size()) { 113 auto& next_suggestion = m_suggestions[m_next_suggestion_index]; 114 115 if (mode == CompletePrefix && !next_suggestion.allow_commit_without_listing) { 116 result.new_completion_mode = CompletionMode::ShowSuggestions; 117 result.avoid_committing_to_single_suggestion = true; 118 m_last_shown_suggestion_display_length = 0; 119 m_last_shown_suggestion_was_complete = false; 120 m_last_shown_suggestion = DeprecatedString::empty(); 121 return result; 122 } 123 124 auto can_complete = next_suggestion.invariant_offset <= m_largest_common_suggestion_prefix_length; 125 ssize_t actual_offset; 126 size_t shown_length = m_last_shown_suggestion_display_length; 127 switch (mode) { 128 case CompletePrefix: 129 actual_offset = 0; 130 break; 131 case ShowSuggestions: 132 actual_offset = 0 - m_largest_common_suggestion_prefix_length + next_suggestion.invariant_offset; 133 if (can_complete && next_suggestion.allow_commit_without_listing) 134 shown_length = m_largest_common_suggestion_prefix_length + m_last_shown_suggestion.trivia_view.length(); 135 break; 136 default: 137 if (m_last_shown_suggestion_display_length == 0) 138 actual_offset = 0; 139 else 140 actual_offset = 0 - m_last_shown_suggestion_display_length + next_suggestion.invariant_offset; 141 break; 142 } 143 144 auto& suggestion = suggest(); 145 set_current_suggestion_initiation_index(initiation_start_index); 146 147 result.offset_region_to_remove = { next_suggestion.invariant_offset, shown_length }; 148 result.new_cursor_offset = actual_offset; 149 result.static_offset_from_cursor = next_suggestion.static_offset; 150 151 if (mode == CompletePrefix) { 152 // Only auto-complete *if possible*. 153 if (can_complete) { 154 result.insert.append(suggestion.text_view.substring_view(suggestion.invariant_offset, m_largest_common_suggestion_prefix_length - suggestion.invariant_offset)); 155 m_last_shown_suggestion_display_length = m_largest_common_suggestion_prefix_length; 156 // Do not increment the suggestion index, as the first tab should only be a *peek*. 157 if (m_suggestions.size() == 1) { 158 // If there's one suggestion, commit and forget. 159 result.new_completion_mode = DontComplete; 160 // Add in the trivia of the last selected suggestion. 161 result.insert.append(suggestion.trivia_view); 162 m_last_shown_suggestion_display_length = 0; 163 result.style_to_apply = suggestion.style; 164 m_last_shown_suggestion_was_complete = true; 165 return result; 166 } 167 } else { 168 m_last_shown_suggestion_display_length = 0; 169 } 170 result.new_completion_mode = CompletionMode::ShowSuggestions; 171 m_last_shown_suggestion_was_complete = false; 172 m_last_shown_suggestion = DeprecatedString::empty(); 173 } else { 174 result.insert.append(suggestion.text_view.substring_view(suggestion.invariant_offset, suggestion.text_view.length() - suggestion.invariant_offset)); 175 // Add in the trivia of the last selected suggestion. 176 result.insert.append(suggestion.trivia_view); 177 m_last_shown_suggestion_display_length += suggestion.trivia_view.length(); 178 } 179 } else { 180 m_next_suggestion_index = 0; 181 } 182 return result; 183} 184 185ErrorOr<size_t> SuggestionManager::for_each_suggestion(Function<ErrorOr<IterationDecision>(CompletionSuggestion const&, size_t)> callback) const 186{ 187 size_t start_index { 0 }; 188 for (auto& suggestion : m_suggestions) { 189 if (start_index++ < m_last_displayed_suggestion_index) 190 continue; 191 if (TRY(callback(suggestion, start_index - 1)) == IterationDecision::Break) 192 break; 193 } 194 return start_index; 195} 196 197}