diff --git a/Libraries/LibLine/Editor.cpp b/Libraries/LibLine/Editor.cpp index 3958bec31d6..071678d03f0 100644 --- a/Libraries/LibLine/Editor.cpp +++ b/Libraries/LibLine/Editor.cpp @@ -299,17 +299,17 @@ String Editor::get_line(const String& prompt) m_suggestions = on_tab_complete_other_token(token); size_t common_suggestion_prefix { 0 }; if (m_suggestions.size() == 1) { - m_largest_common_suggestion_prefix_length = m_suggestions[0].length(); + m_largest_common_suggestion_prefix_length = m_suggestions[0].text.length(); } else if (m_suggestions.size()) { char last_valid_suggestion_char; for (;; ++common_suggestion_prefix) { - if (m_suggestions[0].length() <= common_suggestion_prefix) + if (m_suggestions[0].text.length() <= common_suggestion_prefix) goto no_more_commons; - last_valid_suggestion_char = m_suggestions[0][common_suggestion_prefix]; + last_valid_suggestion_char = m_suggestions[0].text[common_suggestion_prefix]; for (const auto& suggestion : m_suggestions) { - if (suggestion.length() < common_suggestion_prefix || suggestion[common_suggestion_prefix] != last_valid_suggestion_char) { + if (suggestion.text.length() < common_suggestion_prefix || suggestion.text[common_suggestion_prefix] != last_valid_suggestion_char) { goto no_more_commons; } } @@ -341,7 +341,7 @@ String Editor::get_line(const String& prompt) auto current_suggestion_index = m_next_suggestion_index; if (m_next_suggestion_index < m_suggestions.size()) { auto can_complete = m_next_suggestion_invariant_offset < m_largest_common_suggestion_prefix_length; - if (!m_last_shown_suggestion.is_null()) { + if (!m_last_shown_suggestion.text.is_null()) { size_t actual_offset; size_t shown_length = m_last_shown_suggestion_display_length; switch (m_times_tab_pressed) { @@ -367,17 +367,20 @@ String Editor::get_line(const String& prompt) m_refresh_needed = true; } m_last_shown_suggestion = m_suggestions[m_next_suggestion_index]; - m_last_shown_suggestion_display_length = m_last_shown_suggestion.length(); + m_last_shown_suggestion_display_length = m_last_shown_suggestion.text.length(); m_last_shown_suggestion_was_complete = true; if (m_times_tab_pressed == 1) { // This is the first time, so only auto-complete *if possible* if (can_complete) { - insert(m_last_shown_suggestion.substring_view(m_next_suggestion_invariant_offset, m_largest_common_suggestion_prefix_length - m_next_suggestion_invariant_offset)); + insert(m_last_shown_suggestion.text.substring_view(m_next_suggestion_invariant_offset, m_largest_common_suggestion_prefix_length - m_next_suggestion_invariant_offset)); m_last_shown_suggestion_display_length = m_largest_common_suggestion_prefix_length; // do not increment the suggestion index, as the first tab should only be a *peek* if (m_suggestions.size() == 1) { // if there's one suggestion, commit and forget m_times_tab_pressed = 0; + // add in the trivia of the last selected suggestion + insert(m_last_shown_suggestion.trailing_trivia); + m_last_shown_suggestion_display_length += m_last_shown_suggestion.trailing_trivia.length(); } } else { m_last_shown_suggestion_display_length = 0; @@ -385,7 +388,9 @@ String Editor::get_line(const String& prompt) ++m_times_tab_pressed; m_last_shown_suggestion_was_complete = false; } else { - insert(m_last_shown_suggestion.substring_view(m_next_suggestion_invariant_offset, m_last_shown_suggestion.length() - m_next_suggestion_invariant_offset)); + insert(m_last_shown_suggestion.text.substring_view(m_next_suggestion_invariant_offset, m_last_shown_suggestion.text.length() - m_next_suggestion_invariant_offset)); + // add in the trivia of the last selected suggestion + insert(m_last_shown_suggestion.trailing_trivia); if (m_tab_direction == TabDirection::Forward) increment_suggestion_index(); else @@ -399,7 +404,7 @@ String Editor::get_line(const String& prompt) size_t longest_suggestion_length = 0; for (auto& suggestion : m_suggestions) { - longest_suggestion_length = max(longest_suggestion_length, suggestion.length()); + longest_suggestion_length = max(longest_suggestion_length, suggestion.text.length()); } size_t num_printed = 0; @@ -423,10 +428,10 @@ String Editor::get_line(const String& prompt) } vt_move_absolute(max_line_count + m_origin_x, 1); for (auto& suggestion : m_suggestions) { - size_t next_column = num_printed + suggestion.length() + longest_suggestion_length + 2; + size_t next_column = num_printed + suggestion.text.length() + longest_suggestion_length + 2; if (next_column > m_num_columns) { - auto lines = (suggestion.length() + m_num_columns - 1) / m_num_columns; + auto lines = (suggestion.text.length() + m_num_columns - 1) / m_num_columns; lines_used += lines; putchar('\n'); num_printed = 0; @@ -445,9 +450,9 @@ String Editor::get_line(const String& prompt) if (spans_entire_line) { num_printed += m_num_columns; - fprintf(stderr, "%s", suggestion.characters()); + fprintf(stderr, "%s", suggestion.text.characters()); } else { - num_printed += fprintf(stderr, "%-*s", static_cast(longest_suggestion_length) + 2, suggestion.characters()); + num_printed += fprintf(stderr, "%-*s", static_cast(longest_suggestion_length) + 2, suggestion.text.characters()); } if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) { diff --git a/Libraries/LibLine/Editor.h b/Libraries/LibLine/Editor.h index f5085cfcbd0..ce7813d31ac 100644 --- a/Libraries/LibLine/Editor.h +++ b/Libraries/LibLine/Editor.h @@ -53,6 +53,28 @@ struct KeyCallback { Function callback; }; +struct CompletionSuggestion { + // intentionally not explicit (allows suggesting bare strings) + CompletionSuggestion(const String& completion) + : text(completion) + , trailing_trivia("") + { + } + CompletionSuggestion(const StringView& completion, const StringView& trailing_trivia) + : text(completion) + , trailing_trivia(trailing_trivia) + { + } + + bool operator==(const CompletionSuggestion& suggestion) const + { + return suggestion.text == text; + } + + String text; + String trailing_trivia; +}; + class Editor { public: Editor(); @@ -79,8 +101,8 @@ public: void register_character_input_callback(char ch, Function callback); - Function(const String&)> on_tab_complete_first_token; - Function(const String&)> on_tab_complete_other_token; + Function(const String&)> on_tab_complete_first_token; + Function(const String&)> on_tab_complete_other_token; Function on_display_refresh; // FIXME: we will have to kindly ask our instantiators to set our signal handlers @@ -195,8 +217,8 @@ private: size_t m_origin_y { 0 }; String m_new_prompt; - Vector m_suggestions; - String m_last_shown_suggestion { String::empty() }; + Vector m_suggestions; + CompletionSuggestion m_last_shown_suggestion { String::empty() }; size_t m_last_shown_suggestion_display_length { 0 }; bool m_last_shown_suggestion_was_complete { false }; size_t m_next_suggestion_index { 0 }; diff --git a/Shell/main.cpp b/Shell/main.cpp index 576b1e373c7..0e70e5a14b7 100644 --- a/Shell/main.cpp +++ b/Shell/main.cpp @@ -1042,7 +1042,7 @@ int main(int argc, char** argv) g.termios = editor.termios(); g.default_termios = editor.default_termios(); - editor.on_tab_complete_first_token = [&](const String& token) -> Vector { + editor.on_tab_complete_first_token = [&](const String& token) -> Vector { auto match = binary_search(cached_path.data(), cached_path.size(), token, [](const String& token, const String& program) -> int { return strncmp(token.characters(), program.characters(), token.length()); }); @@ -1052,7 +1052,7 @@ int main(int argc, char** argv) // Suggest local executables and directories auto mut_token = token; // copy it :( String path; - Vector local_suggestions; + Vector local_suggestions; bool suggest_executables = true; ssize_t last_slash = token.length() - 1; @@ -1090,6 +1090,7 @@ int main(int argc, char** argv) // manually skip `.' and `..' if (file == "." || file == "..") continue; + auto trivia = " "; if (file.starts_with(mut_token)) { String file_path = String::format("%s/%s", path.characters(), file.characters()); struct stat program_status; @@ -1098,25 +1099,14 @@ int main(int argc, char** argv) continue; if (!(program_status.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) continue; - if (!S_ISDIR(program_status.st_mode) && !suggest_executables) - continue; + if (S_ISDIR(program_status.st_mode)) { + if (suggest_executables) + continue; + else + trivia = "/"; + } - local_suggestions.append(file); - } - } - - // If we have a single match and it's a directory, we add a slash. If it's - // a regular file, we add a space, unless we already have one. - if (local_suggestions.size() == 1) { - auto& completion = local_suggestions[0]; - String file_path = String::format("%s/%s", path.characters(), completion.characters()); - struct stat program_status; - int stat_error = stat(file_path.characters(), &program_status); - if (!stat_error) { - if (S_ISDIR(program_status.st_mode)) - completion = String::format("%s/", local_suggestions[0].characters()); - else if (editor.cursor() == editor.buffer().size() || editor.buffer_at(editor.cursor()) != ' ') - completion = String::format("%s ", local_suggestions[0].characters()); + local_suggestions.append({ file, trivia }); } } @@ -1124,37 +1114,29 @@ int main(int argc, char** argv) } String completion = *match; - Vector suggestions; + Vector suggestions; // Now that we have a program name starting with our token, we look at // other program names starting with our token and cut off any mismatching // characters. - bool seen_others = false; int index = match - cached_path.data(); for (int i = index - 1; i >= 0 && cached_path[i].starts_with(token); --i) { - suggestions.append(cached_path[i]); - seen_others = true; + suggestions.append({ cached_path[i], " " }); } for (size_t i = index + 1; i < cached_path.size() && cached_path[i].starts_with(token); ++i) { - suggestions.append(cached_path[i]); - seen_others = true; - } - suggestions.append(cached_path[index]); - - // If we have a single match, we add a space, unless we already have one. - if (!seen_others && (editor.cursor() == editor.buffer().size() || editor.buffer_at(editor.cursor()) != ' ')) { - suggestions[0] = String::format("%s ", suggestions[0].characters()); + suggestions.append({ cached_path[i], " " }); } + suggestions.append({ cached_path[index], " " }); editor.suggest(token.length(), 0); return suggestions; }; - editor.on_tab_complete_other_token = [&](const String& vtoken) -> Vector { + editor.on_tab_complete_other_token = [&](const String& vtoken) -> Vector { auto token = vtoken; // copy it :( String path; - Vector suggestions; + Vector suggestions; ssize_t last_slash = token.length() - 1; while (last_slash >= 0 && token[last_slash] != '/') @@ -1190,22 +1172,15 @@ int main(int argc, char** argv) if (file == "." || file == "..") continue; if (file.starts_with(token)) { - suggestions.append(file); - } - } - - // If we have a single match and it's a directory, we add a slash. If it's - // a regular file, we add a space, unless we already have one. - if (suggestions.size() == 1) { - auto& completion = suggestions[0]; - String file_path = String::format("%s/%s", path.characters(), completion.characters()); - struct stat program_status; - int stat_error = stat(file_path.characters(), &program_status); - if (!stat_error) { - if (S_ISDIR(program_status.st_mode)) - completion = String::format("%s/", suggestions[0].characters()); - else if (editor.cursor() == editor.buffer().size() || editor.buffer_at(editor.cursor()) != ' ') - completion = String::format("%s ", suggestions[0].characters()); + struct stat program_status; + String file_path = String::format("%s/%s", path.characters(), file.characters()); + int stat_error = stat(file_path.characters(), &program_status); + if (!stat_error) { + if (S_ISDIR(program_status.st_mode)) + suggestions.append({ file, "/" }); + else + suggestions.append({ file, " " }); + } } } diff --git a/Userland/js.cpp b/Userland/js.cpp index 06e11ac6de2..0806089381e 100644 --- a/Userland/js.cpp +++ b/Userland/js.cpp @@ -553,7 +553,7 @@ int main(int argc, char** argv) editor.set_prompt(prompt_for_level(open_indents)); }; - auto complete = [&interpreter, &editor = *editor](const String& token) -> Vector { + auto complete = [&interpreter, &editor = *editor](const String& token) -> Vector { if (token.length() == 0) return {}; // nyeh @@ -564,13 +564,13 @@ int main(int argc, char** argv) // - .

// where N is the complete name of a variable and // P is part of the name of one of its properties - Vector results; + Vector results; Function list_all_properties = [&results, &list_all_properties](const JS::Shape& shape, auto& property_pattern) { for (const auto& descriptor : shape.property_table()) { if (descriptor.value.attributes & JS::Attribute::Enumerable) { if (descriptor.key.view().starts_with(property_pattern)) { - auto completion = descriptor.key; + Line::CompletionSuggestion completion { descriptor.key }; if (!results.contains_slow(completion)) { // hide duplicates results.append(completion); }