mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 19:37:35 +00:00
LibLine: Display suggestions and cycle between them
With extra color (tm) This commit also patches the users of LibLine to properly use the new API
This commit is contained in:
parent
b59a391a78
commit
2fdce695d6
4 changed files with 82 additions and 34 deletions
|
@ -280,25 +280,43 @@ String Editor::get_line(const String& prompt)
|
|||
}
|
||||
|
||||
String token = is_empty_token ? String() : String(&m_buffer[token_start], m_cursor - token_start);
|
||||
Vector<String> suggestions;
|
||||
|
||||
if (is_first_token)
|
||||
suggestions = on_tab_complete_first_token(token);
|
||||
else
|
||||
suggestions = on_tab_complete_other_token(token);
|
||||
// ask for completions only on the first tab
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (m_times_tab_pressed > 1 && !suggestions.is_empty()) {
|
||||
auto current_suggestion_index = m_next_suggestion_index;
|
||||
if (m_next_suggestion_index < m_suggestions.size()) {
|
||||
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)
|
||||
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));
|
||||
m_next_suggestion_index = (m_next_suggestion_index + 1) % m_suggestions.size();
|
||||
} else {
|
||||
m_next_suggestion_index = 0;
|
||||
}
|
||||
|
||||
if (m_times_tab_pressed > 1 && !m_suggestions.is_empty()) {
|
||||
size_t longest_suggestion_length = 0;
|
||||
|
||||
for (auto& suggestion : suggestions)
|
||||
for (auto& suggestion : m_suggestions)
|
||||
longest_suggestion_length = max(longest_suggestion_length, suggestion.length());
|
||||
|
||||
size_t num_printed = 0;
|
||||
size_t lines_used { 1 };
|
||||
size_t index { 0 };
|
||||
putchar('\n');
|
||||
// FIXME: what if we use more lines than the terminal has?
|
||||
// this would put the actual prompt out of view
|
||||
for (auto& suggestion : suggestions) {
|
||||
for (auto& suggestion : m_suggestions) {
|
||||
size_t next_column = num_printed + suggestion.length() + longest_suggestion_length + 2;
|
||||
|
||||
if (next_column > m_num_columns) {
|
||||
|
@ -307,9 +325,25 @@ String Editor::get_line(const String& prompt)
|
|||
num_printed = 0;
|
||||
}
|
||||
|
||||
// show just enough suggestions to fill up the screen
|
||||
// without moving the prompt out of view
|
||||
if (lines_used + num_lines() >= m_num_lines)
|
||||
break;
|
||||
|
||||
if (index == current_suggestion_index) {
|
||||
vt_apply_style({ Style::Foreground(Style::Color::Blue) });
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
num_printed += fprintf(stderr, "%-*s", static_cast<int>(longest_suggestion_length) + 2, suggestion.characters());
|
||||
m_lines_used_for_last_suggestions = lines_used;
|
||||
|
||||
if (index == current_suggestion_index) {
|
||||
vt_apply_style({});
|
||||
fflush(stdout);
|
||||
}
|
||||
++index;
|
||||
}
|
||||
m_lines_used_for_last_suggestions = lines_used;
|
||||
|
||||
// adjust for the case that we scroll up after writing the suggestions
|
||||
if (m_origin_x + lines_used >= m_num_lines) {
|
||||
|
@ -318,7 +352,6 @@ String Editor::get_line(const String& prompt)
|
|||
reposition_cursor();
|
||||
}
|
||||
|
||||
suggestions.clear_with_capacity();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -327,10 +360,13 @@ String Editor::get_line(const String& prompt)
|
|||
// let's clean them up
|
||||
if (m_lines_used_for_last_suggestions) {
|
||||
vt_clear_lines(0, m_lines_used_for_last_suggestions);
|
||||
vt_move_relative(-m_lines_used_for_last_suggestions, 0);
|
||||
reposition_cursor();
|
||||
m_refresh_needed = true;
|
||||
m_lines_used_for_last_suggestions = 0;
|
||||
}
|
||||
m_last_shown_suggestion = String::empty();
|
||||
m_suggestions.clear();
|
||||
suggest(0, 0);
|
||||
}
|
||||
m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB
|
||||
|
||||
|
|
|
@ -113,6 +113,11 @@ public:
|
|||
m_spans_ending.clear();
|
||||
m_refresh_needed = true;
|
||||
}
|
||||
void suggest(size_t invariant_offset = 0, size_t index = 0)
|
||||
{
|
||||
m_next_suggestion_index = index;
|
||||
m_next_suggestion_invariant_offset = invariant_offset;
|
||||
}
|
||||
|
||||
const struct termios& termios() const { return m_termios; }
|
||||
const struct termios& default_termios() const { return m_default_termios; }
|
||||
|
@ -190,6 +195,10 @@ private:
|
|||
size_t m_origin_y { 0 };
|
||||
|
||||
String m_new_prompt;
|
||||
Vector<String> m_suggestions;
|
||||
String m_last_shown_suggestion;
|
||||
size_t m_next_suggestion_index { 0 };
|
||||
size_t m_next_suggestion_invariant_offset { 0 };
|
||||
|
||||
HashMap<char, NonnullOwnPtr<KeyCallback>> m_key_callbacks;
|
||||
|
||||
|
|
|
@ -1041,7 +1041,7 @@ int main(int argc, char** argv)
|
|||
return strncmp(token.characters(), program.characters(), token.length());
|
||||
});
|
||||
if (!match)
|
||||
return Vector<String>();
|
||||
return {};
|
||||
|
||||
String completion = *match;
|
||||
Vector<String> suggestions;
|
||||
|
@ -1054,22 +1054,24 @@ int main(int argc, char** argv)
|
|||
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]);
|
||||
editor.cut_mismatching_chars(completion, cached_path[i], token.length());
|
||||
seen_others = true;
|
||||
}
|
||||
for (size_t i = index + 1; i < cached_path.size() && cached_path[i].starts_with(token); ++i) {
|
||||
editor.cut_mismatching_chars(completion, cached_path[i], token.length());
|
||||
suggestions.append(cached_path[i]);
|
||||
seen_others = true;
|
||||
}
|
||||
suggestions.append(cached_path[index]);
|
||||
|
||||
// If we have characters to add, add them.
|
||||
if (completion.length() > token.length())
|
||||
editor.insert(completion.substring(token.length(), completion.length() - token.length()));
|
||||
// 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()) != ' '))
|
||||
editor.insert(' ');
|
||||
if (!seen_others && (editor.cursor() == editor.buffer().size() || editor.buffer_at(editor.cursor()) != ' ')) {
|
||||
suggestions[0] = String::format("%s ", suggestions[0].characters());
|
||||
}
|
||||
|
||||
dbg() << "found " << suggestions.size() << " elements";
|
||||
for (auto& el : suggestions)
|
||||
dbg() << ">>> '" << el << "'";
|
||||
|
||||
editor.suggest(token.length(), 0);
|
||||
|
||||
return suggestions;
|
||||
};
|
||||
|
@ -1078,6 +1080,7 @@ int main(int argc, char** argv)
|
|||
String path;
|
||||
Vector<String> suggestions;
|
||||
|
||||
editor.suggest(token.length(), 0);
|
||||
ssize_t last_slash = token.length() - 1;
|
||||
while (last_slash >= 0 && token[last_slash] != '/')
|
||||
--last_slash;
|
||||
|
|
|
@ -544,6 +544,9 @@ int main(int argc, char** argv)
|
|||
};
|
||||
|
||||
auto complete = [&interpreter, &editor = *editor](const String& token) -> Vector<String> {
|
||||
if (token.length() == 0)
|
||||
return {}; // nyeh
|
||||
|
||||
StringView line { editor.buffer().data(), editor.cursor() };
|
||||
// we're only going to complete either
|
||||
// - <N>
|
||||
|
@ -556,23 +559,18 @@ int main(int argc, char** argv)
|
|||
Function<void(const JS::Shape&, const StringView&)> 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))
|
||||
if (!results.contains_slow(descriptor.key)) // hide duplicates
|
||||
results.append(descriptor.key);
|
||||
if (descriptor.key.view().starts_with(property_pattern)) {
|
||||
auto completion = descriptor.key;
|
||||
if (!results.contains_slow(completion)) { // hide duplicates
|
||||
results.append(completion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (const auto* prototype = shape.prototype()) {
|
||||
list_all_properties(prototype->shape(), property_pattern);
|
||||
}
|
||||
};
|
||||
auto complete = [&results, &editor](auto& property_pattern) {
|
||||
if (results.size()) {
|
||||
auto completion = results.first().view();
|
||||
completion = completion.substring_view(property_pattern.length(), completion.length() - property_pattern.length());
|
||||
if (completion.length())
|
||||
editor.insert(completion);
|
||||
}
|
||||
};
|
||||
|
||||
if (token.contains(".")) {
|
||||
auto parts = token.split('.', true);
|
||||
|
@ -597,12 +595,14 @@ int main(int argc, char** argv)
|
|||
const auto* object = variable.to_object(interpreter->heap());
|
||||
const auto& shape = object->shape();
|
||||
list_all_properties(shape, property_pattern);
|
||||
complete(property_pattern);
|
||||
if (results.size())
|
||||
editor.suggest(property_pattern.length());
|
||||
return results;
|
||||
}
|
||||
const auto& variable = interpreter->global_object();
|
||||
list_all_properties(variable.shape(), token);
|
||||
complete(token);
|
||||
if (results.size())
|
||||
editor.suggest(token.length());
|
||||
return results;
|
||||
};
|
||||
editor->on_tab_complete_first_token = [complete](auto& value) { return complete(value); };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue