Skip to content

Commit 1185903

Browse files
alimpfardawesomekling
authored andcommitted
LibLine+Userland: Make suggestion offsets per-suggestion
This allows the user to modify different parts of the input with different suggestions.
1 parent 1fcef99 commit 1185903

File tree

6 files changed

+79
-51
lines changed

6 files changed

+79
-51
lines changed

Userland/Libraries/LibLine/Editor.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -489,7 +489,7 @@ void Editor::stylize(Span const& span, Style const& style)
489489
ending_map.set(start, style);
490490
}
491491

492-
void Editor::suggest(size_t invariant_offset, size_t static_offset, Span::Mode offset_mode) const
492+
void Editor::transform_suggestion_offsets(size_t& invariant_offset, size_t& static_offset, Span::Mode offset_mode) const
493493
{
494494
auto internal_static_offset = static_offset;
495495
auto internal_invariant_offset = invariant_offset;
@@ -501,7 +501,8 @@ void Editor::suggest(size_t invariant_offset, size_t static_offset, Span::Mode o
501501
internal_static_offset = offsets.start;
502502
internal_invariant_offset = offsets.end - offsets.start;
503503
}
504-
m_suggestion_manager.set_suggestion_variants(internal_static_offset, internal_invariant_offset, 0);
504+
invariant_offset = internal_invariant_offset;
505+
static_offset = internal_static_offset;
505506
}
506507

507508
void Editor::initialize()
@@ -1141,7 +1142,6 @@ void Editor::handle_read_event()
11411142
// We have none, or just one suggestion,
11421143
// we should just commit that and continue
11431144
// after it, as if it were auto-completed.
1144-
suggest(0, 0, Span::CodepointOriented);
11451145
m_times_tab_pressed = 0;
11461146
m_suggestion_manager.reset();
11471147
m_suggestion_display->finish();
@@ -1180,7 +1180,6 @@ void Editor::cleanup_suggestions()
11801180
m_refresh_needed = true;
11811181
}
11821182
m_suggestion_manager.reset();
1183-
suggest(0, 0, Span::CodepointOriented);
11841183
m_suggestion_display->finish();
11851184
}
11861185
m_times_tab_pressed = 0; // Safe to say if we get here, the user didn't press TAB

Userland/Libraries/LibLine/Editor.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ class Editor : public Core::Object {
221221
// +-|- static offset: the suggestions start here
222222
// +- invariant offset: the suggestions do not change up to here
223223
//
224-
void suggest(size_t invariant_offset = 0, size_t static_offset = 0, Span::Mode offset_mode = Span::ByteOriented) const;
224+
void transform_suggestion_offsets(size_t& invariant_offset, size_t& static_offset, Span::Mode offset_mode = Span::ByteOriented) const;
225225

226226
const struct termios& termios() const { return m_termios; }
227227
const struct termios& default_termios() const { return m_default_termios; }

Userland/Libraries/LibLine/SuggestionManager.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ CompletionSuggestion const& SuggestionManager::suggest()
8484

8585
void SuggestionManager::set_current_suggestion_initiation_index(size_t index)
8686
{
87+
auto& suggestion = m_suggestions[m_next_suggestion_index];
88+
8789
if (m_last_shown_suggestion_display_length)
88-
m_last_shown_suggestion.start_index = index - m_next_suggestion_static_offset - m_last_shown_suggestion_display_length;
90+
m_last_shown_suggestion.start_index = index - suggestion.static_offset - m_last_shown_suggestion_display_length;
8991
else
90-
m_last_shown_suggestion.start_index = index - m_next_suggestion_static_offset - m_next_suggestion_invariant_offset;
92+
m_last_shown_suggestion.start_index = index - suggestion.static_offset - suggestion.invariant_offset;
9193

9294
m_last_shown_suggestion_display_length = m_last_shown_suggestion.text_view.length();
9395
m_last_shown_suggestion_was_complete = true;
@@ -98,27 +100,29 @@ SuggestionManager::CompletionAttemptResult SuggestionManager::attempt_completion
98100
CompletionAttemptResult result { mode };
99101

100102
if (m_next_suggestion_index < m_suggestions.size()) {
101-
auto can_complete = m_next_suggestion_invariant_offset <= m_largest_common_suggestion_prefix_length;
103+
auto& next_suggestion = m_suggestions[m_next_suggestion_index];
104+
105+
auto can_complete = next_suggestion.invariant_offset <= m_largest_common_suggestion_prefix_length;
102106
ssize_t actual_offset;
103107
size_t shown_length = m_last_shown_suggestion_display_length;
104108
switch (mode) {
105109
case CompletePrefix:
106110
actual_offset = 0;
107111
break;
108112
case ShowSuggestions:
109-
actual_offset = 0 - m_largest_common_suggestion_prefix_length + m_next_suggestion_invariant_offset;
113+
actual_offset = 0 - m_largest_common_suggestion_prefix_length + next_suggestion.invariant_offset;
110114
if (can_complete)
111115
shown_length = m_largest_common_suggestion_prefix_length + m_last_shown_suggestion.trivia_view.length();
112116
break;
113117
default:
114118
if (m_last_shown_suggestion_display_length == 0)
115119
actual_offset = 0;
116120
else
117-
actual_offset = 0 - m_last_shown_suggestion_display_length + m_next_suggestion_invariant_offset;
121+
actual_offset = 0 - m_last_shown_suggestion_display_length + next_suggestion.invariant_offset;
118122
break;
119123
}
120124

121-
result.offset_region_to_remove = { m_next_suggestion_invariant_offset, shown_length };
125+
result.offset_region_to_remove = { next_suggestion.invariant_offset, shown_length };
122126
result.new_cursor_offset = actual_offset;
123127

124128
auto& suggestion = suggest();
@@ -127,7 +131,7 @@ SuggestionManager::CompletionAttemptResult SuggestionManager::attempt_completion
127131
if (mode == CompletePrefix) {
128132
// Only auto-complete *if possible*.
129133
if (can_complete) {
130-
result.insert.append(suggestion.text_view.substring_view(m_next_suggestion_invariant_offset, m_largest_common_suggestion_prefix_length - m_next_suggestion_invariant_offset));
134+
result.insert.append(suggestion.text_view.substring_view(suggestion.invariant_offset, m_largest_common_suggestion_prefix_length - suggestion.invariant_offset));
131135
m_last_shown_suggestion_display_length = m_largest_common_suggestion_prefix_length;
132136
// Do not increment the suggestion index, as the first tab should only be a *peek*.
133137
if (m_suggestions.size() == 1) {
@@ -147,7 +151,7 @@ SuggestionManager::CompletionAttemptResult SuggestionManager::attempt_completion
147151
m_last_shown_suggestion_was_complete = false;
148152
m_last_shown_suggestion = String::empty();
149153
} else {
150-
result.insert.append(suggestion.text_view.substring_view(m_next_suggestion_invariant_offset, suggestion.text_view.length() - m_next_suggestion_invariant_offset));
154+
result.insert.append(suggestion.text_view.substring_view(suggestion.invariant_offset, suggestion.text_view.length() - suggestion.invariant_offset));
151155
// Add in the trivia of the last selected suggestion.
152156
result.insert.append(suggestion.trivia_view);
153157
m_last_shown_suggestion_display_length += suggestion.trivia_view.length();

Userland/Libraries/LibLine/SuggestionManager.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ struct CompletionSuggestion {
5353
Style style;
5454
size_t start_index { 0 };
5555
size_t input_offset { 0 };
56+
size_t static_offset { 0 };
57+
size_t invariant_offset { 0 };
5658

5759
Utf32View text_view;
5860
Utf32View trivia_view;
@@ -102,12 +104,6 @@ class SuggestionManager {
102104

103105
void next();
104106
void previous();
105-
void set_suggestion_variants(size_t static_offset, size_t invariant_offset, size_t suggestion_index) const
106-
{
107-
m_next_suggestion_index = suggestion_index;
108-
m_next_suggestion_static_offset = static_offset;
109-
m_next_suggestion_invariant_offset = invariant_offset;
110-
}
111107

112108
CompletionSuggestion const& suggest();
113109
CompletionSuggestion const& current_suggestion() const { return m_last_shown_suggestion; }
@@ -131,8 +127,6 @@ class SuggestionManager {
131127
size_t m_last_shown_suggestion_display_length { 0 };
132128
bool m_last_shown_suggestion_was_complete { false };
133129
mutable size_t m_next_suggestion_index { 0 };
134-
mutable size_t m_next_suggestion_invariant_offset { 0 };
135-
mutable size_t m_next_suggestion_static_offset { 0 };
136130
size_t m_largest_common_suggestion_prefix_length { 0 };
137131
mutable size_t m_last_displayed_suggestion_index { 0 };
138132
size_t m_selected_suggestion_index { 0 };

Userland/Shell/Shell.cpp

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1416,8 +1416,10 @@ Vector<Line::CompletionSuggestion> Shell::complete_path(StringView base,
14161416
// since we are not suggesting anything starting with
14171417
// `/foo/', but rather just `bar...'
14181418
auto token_length = escape_token(token).length();
1419+
size_t static_offset = last_slash + 1;
1420+
auto invariant_offset = token_length;
14191421
if (m_editor)
1420-
m_editor->suggest(token_length, last_slash + 1);
1422+
m_editor->transform_suggestion_offsets(invariant_offset, static_offset);
14211423

14221424
// only suggest dot-files if path starts with a dot
14231425
Core::DirIterator files(path,
@@ -1440,6 +1442,8 @@ Vector<Line::CompletionSuggestion> Shell::complete_path(StringView base,
14401442
suggestions.append({ escape_token(file), " " });
14411443
}
14421444
suggestions.last().input_offset = token_length;
1445+
suggestions.last().invariant_offset = invariant_offset;
1446+
suggestions.last().static_offset = static_offset;
14431447
}
14441448
}
14451449
}
@@ -1465,8 +1469,10 @@ Vector<Line::CompletionSuggestion> Shell::complete_program_name(StringView name,
14651469

14661470
String completion = *match;
14671471
auto token_length = escape_token(name).length();
1472+
auto invariant_offset = token_length;
1473+
size_t static_offset = 0;
14681474
if (m_editor)
1469-
m_editor->suggest(token_length, 0);
1475+
m_editor->transform_suggestion_offsets(invariant_offset, static_offset);
14701476

14711477
// Now that we have a program name starting with our token, we look at
14721478
// other program names starting with our token and cut off any mismatching
@@ -1475,16 +1481,17 @@ Vector<Line::CompletionSuggestion> Shell::complete_program_name(StringView name,
14751481
Vector<Line::CompletionSuggestion> suggestions;
14761482

14771483
int index = match - cached_path.data();
1478-
for (int i = index - 1; i >= 0 && cached_path[i].starts_with(name); --i) {
1484+
for (int i = index - 1; i >= 0 && cached_path[i].starts_with(name); --i)
14791485
suggestions.append({ cached_path[i], " " });
1480-
suggestions.last().input_offset = token_length;
1481-
}
1482-
for (size_t i = index + 1; i < cached_path.size() && cached_path[i].starts_with(name); ++i) {
1486+
for (size_t i = index + 1; i < cached_path.size() && cached_path[i].starts_with(name); ++i)
14831487
suggestions.append({ cached_path[i], " " });
1484-
suggestions.last().input_offset = token_length;
1485-
}
14861488
suggestions.append({ cached_path[index], " " });
1487-
suggestions.last().input_offset = token_length;
1489+
1490+
for (auto& entry : suggestions) {
1491+
entry.input_offset = token_length;
1492+
entry.invariant_offset = invariant_offset;
1493+
entry.static_offset = static_offset;
1494+
}
14881495

14891496
return suggestions;
14901497
}
@@ -1494,8 +1501,10 @@ Vector<Line::CompletionSuggestion> Shell::complete_variable(StringView name, siz
14941501
Vector<Line::CompletionSuggestion> suggestions;
14951502
auto pattern = offset ? name.substring_view(0, offset) : "";
14961503

1504+
auto invariant_offset = offset;
1505+
size_t static_offset = 0;
14971506
if (m_editor)
1498-
m_editor->suggest(offset);
1507+
m_editor->transform_suggestion_offsets(invariant_offset, static_offset);
14991508

15001509
// Look at local variables.
15011510
for (auto& frame : m_local_frames) {
@@ -1516,10 +1525,15 @@ Vector<Line::CompletionSuggestion> Shell::complete_variable(StringView name, siz
15161525
if (suggestions.contains_slow(name))
15171526
continue;
15181527
suggestions.append(move(name));
1519-
suggestions.last().input_offset = offset;
15201528
}
15211529
}
15221530

1531+
for (auto& entry : suggestions) {
1532+
entry.input_offset = offset;
1533+
entry.invariant_offset = invariant_offset;
1534+
entry.static_offset = static_offset;
1535+
}
1536+
15231537
return suggestions;
15241538
}
15251539

@@ -1528,8 +1542,10 @@ Vector<Line::CompletionSuggestion> Shell::complete_user(StringView name, size_t
15281542
Vector<Line::CompletionSuggestion> suggestions;
15291543
auto pattern = offset ? name.substring_view(0, offset) : "";
15301544

1545+
auto invariant_offset = offset;
1546+
size_t static_offset = 0;
15311547
if (m_editor)
1532-
m_editor->suggest(offset);
1548+
m_editor->transform_suggestion_offsets(invariant_offset, static_offset);
15331549

15341550
Core::DirIterator di("/home", Core::DirIterator::SkipParentAndBaseDir);
15351551

@@ -1540,7 +1556,10 @@ Vector<Line::CompletionSuggestion> Shell::complete_user(StringView name, size_t
15401556
String name = di.next_path();
15411557
if (name.starts_with(pattern)) {
15421558
suggestions.append(name);
1543-
suggestions.last().input_offset = offset;
1559+
auto& suggestion = suggestions.last();
1560+
suggestion.input_offset = offset;
1561+
suggestion.invariant_offset = invariant_offset;
1562+
suggestion.static_offset = static_offset;
15441563
}
15451564
}
15461565

@@ -1553,8 +1572,10 @@ Vector<Line::CompletionSuggestion> Shell::complete_option(StringView program_nam
15531572
while (start < option.length() && option[start] == '-' && start < 2)
15541573
++start;
15551574
auto option_pattern = offset > start ? option.substring_view(start, offset - start) : "";
1575+
auto invariant_offset = offset;
1576+
size_t static_offset = 0;
15561577
if (m_editor)
1557-
m_editor->suggest(offset);
1578+
m_editor->transform_suggestion_offsets(invariant_offset, static_offset);
15581579

15591580
Vector<Line::CompletionSuggestion> suggestions;
15601581

@@ -1579,13 +1600,18 @@ Vector<Line::CompletionSuggestion> Shell::complete_option(StringView program_nam
15791600
return builder.to_string();
15801601
};
15811602
#define __ENUMERATE_SHELL_OPTION(name, d_, descr_) \
1582-
if (#name##sv.starts_with(option_pattern)) { \
1583-
suggestions.append(maybe_negate(#name)); \
1584-
suggestions.last().input_offset = offset; \
1585-
}
1603+
if (#name##sv.starts_with(option_pattern)) \
1604+
suggestions.append(maybe_negate(#name));
15861605

15871606
ENUMERATE_SHELL_OPTIONS();
15881607
#undef __ENUMERATE_SHELL_OPTION
1608+
1609+
for (auto& entry : suggestions) {
1610+
entry.input_offset = offset;
1611+
entry.invariant_offset = invariant_offset;
1612+
entry.static_offset = static_offset;
1613+
}
1614+
15891615
return suggestions;
15901616
}
15911617
}
@@ -1596,18 +1622,24 @@ Vector<Line::CompletionSuggestion> Shell::complete_immediate_function_name(Strin
15961622
{
15971623
Vector<Line::CompletionSuggestion> suggestions;
15981624

1599-
#define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(fn_name) \
1600-
if (auto name_view = #fn_name##sv; name_view.starts_with(name)) { \
1601-
suggestions.append({ name_view, " " }); \
1602-
suggestions.last().input_offset = offset; \
1603-
}
1625+
auto invariant_offset = offset;
1626+
size_t static_offset = 0;
1627+
if (m_editor)
1628+
m_editor->transform_suggestion_offsets(invariant_offset, static_offset);
1629+
1630+
#define __ENUMERATE_SHELL_IMMEDIATE_FUNCTION(fn_name) \
1631+
if (auto name_view = #fn_name##sv; name_view.starts_with(name)) \
1632+
suggestions.append({ name_view, " " });
16041633

16051634
ENUMERATE_SHELL_IMMEDIATE_FUNCTIONS();
16061635

16071636
#undef __ENUMERATE_SHELL_IMMEDIATE_FUNCTION
16081637

1609-
if (m_editor)
1610-
m_editor->suggest(offset);
1638+
for (auto& entry : suggestions) {
1639+
entry.input_offset = offset;
1640+
entry.invariant_offset = invariant_offset;
1641+
entry.static_offset = static_offset;
1642+
}
16111643

16121644
return suggestions;
16131645
}

Userland/Utilities/js.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
15801580
Line::CompletionSuggestion completion { key, Line::CompletionSuggestion::ForSearch };
15811581
if (!results.contains_slow(completion)) { // hide duplicates
15821582
results.append(String(key));
1583+
results.last().invariant_offset = property_pattern.length();
15831584
}
15841585
}
15851586
}
@@ -1605,21 +1606,19 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
16051606
auto const* object = MUST(variable.to_object(interpreter->global_object()));
16061607
auto const& shape = object->shape();
16071608
list_all_properties(shape, property_name);
1608-
if (results.size())
1609-
editor.suggest(property_name.length());
16101609
break;
16111610
}
16121611
case CompleteVariable: {
16131612
auto const& variable = interpreter->global_object();
16141613
list_all_properties(variable.shape(), variable_name);
16151614

16161615
for (String& name : global_environment.declarative_record().bindings()) {
1617-
if (name.starts_with(variable_name))
1616+
if (name.starts_with(variable_name)) {
16181617
results.empend(name);
1618+
results.last().invariant_offset = variable_name.length();
1619+
}
16191620
}
16201621

1621-
if (results.size())
1622-
editor.suggest(variable_name.length());
16231622
break;
16241623
}
16251624
default:

0 commit comments

Comments
 (0)