diff options
-rw-r--r-- | Libraries/LibLine/Editor.cpp | 97 | ||||
-rw-r--r-- | Libraries/LibLine/Editor.h | 5 |
2 files changed, 88 insertions, 14 deletions
diff --git a/Libraries/LibLine/Editor.cpp b/Libraries/LibLine/Editor.cpp index 71e5f0a79d..3958bec31d 100644 --- a/Libraries/LibLine/Editor.cpp +++ b/Libraries/LibLine/Editor.cpp @@ -290,12 +290,38 @@ String Editor::get_line(const String& prompt) String token = is_empty_token ? String() : String(&m_buffer[token_start], m_cursor - token_start); // ask for completions only on the first tab + // and scan for the largest common prefix to display // further tabs simply show the cached completions if (m_times_tab_pressed == 1) { if (is_first_token) m_suggestions = on_tab_complete_first_token(token); else 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(); + } else if (m_suggestions.size()) { + char last_valid_suggestion_char; + for (;; ++common_suggestion_prefix) { + if (m_suggestions[0].length() <= common_suggestion_prefix) + goto no_more_commons; + + last_valid_suggestion_char = m_suggestions[0][common_suggestion_prefix]; + + for (const auto& suggestion : m_suggestions) { + if (suggestion.length() < common_suggestion_prefix || suggestion[common_suggestion_prefix] != last_valid_suggestion_char) { + goto no_more_commons; + } + } + } + no_more_commons:; + m_largest_common_suggestion_prefix_length = common_suggestion_prefix; + } else { + m_largest_common_suggestion_prefix_length = 0; + // there are no suggestions, beep~ + putchar('\a'); + fflush(stdout); + } m_prompt_lines_at_suggestion_initiation = num_lines(); } @@ -314,19 +340,57 @@ 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()) { - auto actual_offset = m_times_tab_pressed == 1 ? m_cursor : m_cursor - m_last_shown_suggestion.length() + m_next_suggestion_invariant_offset; - for (size_t i = m_next_suggestion_invariant_offset; i < m_last_shown_suggestion.length(); ++i) + size_t actual_offset; + size_t shown_length = m_last_shown_suggestion_display_length; + switch (m_times_tab_pressed) { + case 1: + actual_offset = m_cursor; + break; + case 2: + actual_offset = m_cursor - m_largest_common_suggestion_prefix_length + m_next_suggestion_invariant_offset; + if (can_complete) + shown_length = m_largest_common_suggestion_prefix_length; + break; + default: + if (m_last_shown_suggestion_display_length == 0) + actual_offset = m_cursor; + else + actual_offset = m_cursor - m_last_shown_suggestion_display_length + m_next_suggestion_invariant_offset; + break; + } + + for (size_t i = m_next_suggestion_invariant_offset; i < shown_length; ++i) m_buffer.remove(actual_offset); m_cursor = actual_offset; m_refresh_needed = true; } m_last_shown_suggestion = m_suggestions[m_next_suggestion_index]; - insert(m_last_shown_suggestion.substring_view(m_next_suggestion_invariant_offset, m_last_shown_suggestion.length() - m_next_suggestion_invariant_offset)); - if (m_tab_direction == TabDirection::Forward) - increment_suggestion_index(); - else - decrement_suggestion_index(); + m_last_shown_suggestion_display_length = m_last_shown_suggestion.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)); + 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; + } + } else { + m_last_shown_suggestion_display_length = 0; + } + ++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)); + if (m_tab_direction == TabDirection::Forward) + increment_suggestion_index(); + else + decrement_suggestion_index(); + } } else { m_next_suggestion_index = 0; } @@ -373,7 +437,8 @@ String Editor::get_line(const String& prompt) if (lines_used + m_prompt_lines_at_suggestion_initiation >= m_num_lines) break; - if (index == current_suggestion_index) { + // only apply colour to the selection if something is *actually* added to the buffer + if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) { vt_apply_style({ Style::Foreground(Style::Color::Blue) }); fflush(stdout); } @@ -385,7 +450,7 @@ String Editor::get_line(const String& prompt) num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.characters()); } - if (index == current_suggestion_index) { + if (m_last_shown_suggestion_was_complete && index == current_suggestion_index) { vt_apply_style({}); fflush(stdout); } @@ -405,6 +470,7 @@ String Editor::get_line(const String& prompt) // after it, as if it were auto-completed suggest(0, 0); m_last_shown_suggestion = String::empty(); + m_last_shown_suggestion_display_length = 0; m_suggestions.clear(); m_times_tab_pressed = 0; } @@ -420,6 +486,7 @@ String Editor::get_line(const String& prompt) m_refresh_needed = true; m_lines_used_for_last_suggestions = 0; } + m_last_shown_suggestion_display_length = 0; m_last_shown_suggestion = String::empty(); m_suggestions.clear(); suggest(0, 0); @@ -462,7 +529,8 @@ String Editor::get_line(const String& prompt) m_refresh_needed = true; continue; } - if (ch == 0xc) { // ^L + // ^L + if (ch == 0xc) { printf("\033[3J\033[H\033[2J"); // Clear screen. vt_move_absolute(1, 1); m_origin_x = 1; @@ -470,18 +538,21 @@ String Editor::get_line(const String& prompt) m_refresh_needed = true; continue; } - if (ch == 0x01) { // ^A + // ^A + if (ch == 0x01) { m_cursor = 0; continue; } - if (ch == m_termios.c_cc[VEOF]) { // Normally ^D + // Normally ^D + if (ch == m_termios.c_cc[VEOF]) { if (m_buffer.is_empty()) { printf("<EOF>\n"); exit(0); } continue; } - if (ch == 0x05) { // ^E + // ^E + if (ch == 0x05) { m_cursor = m_buffer.size(); continue; diff --git a/Libraries/LibLine/Editor.h b/Libraries/LibLine/Editor.h index 618e0f38f8..f5085cfcbd 100644 --- a/Libraries/LibLine/Editor.h +++ b/Libraries/LibLine/Editor.h @@ -196,9 +196,12 @@ private: String m_new_prompt; Vector<String> m_suggestions; - String m_last_shown_suggestion; + String 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 }; size_t m_next_suggestion_invariant_offset { 0 }; + size_t m_largest_common_suggestion_prefix_length { 0 }; enum class TabDirection { Forward, |