1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-16 23:05:08 +00:00

LibLine: Unify completion hooks and adapt its users

LibLine should ultimately not care about what a "token" means in the
context of its user, so force the user to split the buffer itself.
This also allows the users to pick up contextual clues as well, since
they have to lex the line themselves.

This commit pacthes Shell and the JS repl to better handle completions,
so certain wrong behaviours are now corrected as well:
- JS repl can now complete "Object . getOw<tab>"
- Shell can now complete "echo | ca<tab>" and paths inside strings
This commit is contained in:
AnotherTest 2020-05-19 08:42:01 +04:30 committed by Andreas Kling
parent d18f6e82eb
commit 7fba21aefc
6 changed files with 179 additions and 171 deletions

View file

@ -655,17 +655,75 @@ int main(int argc, char** argv)
editor.set_prompt(prompt_for_level(open_indents));
};
auto complete = [&interpreter, &editor = *s_editor](const String& token) -> Vector<Line::CompletionSuggestion> {
if (token.length() == 0)
return {}; // nyeh
auto complete = [&interpreter](const Line::Editor& editor) -> Vector<Line::CompletionSuggestion> {
auto line = editor.line(editor.cursor());
JS::Lexer lexer { line };
enum {
Initial,
CompleteVariable,
CompleteNullProperty,
CompleteProperty,
} mode { Initial };
StringView variable_name;
StringView property_name;
auto line = editor.line();
// we're only going to complete either
// - <N>
// where N is part of the name of a variable
// - <N>.<P>
// where N is the complete name of a variable and
// P is part of the name of one of its properties
auto js_token = lexer.next();
for (; js_token.type() != JS::TokenType::Eof; js_token = lexer.next()) {
switch (mode) {
case CompleteVariable:
switch (js_token.type()) {
case JS::TokenType::Period:
// ...<name> <dot>
mode = CompleteNullProperty;
break;
default:
// not a dot, reset back to initial
mode = Initial;
break;
}
break;
case CompleteNullProperty:
if (js_token.is_identifier_name()) {
// ...<name> <dot> <name>
mode = CompleteProperty;
property_name = js_token.value();
} else {
mode = Initial;
}
break;
case CompleteProperty:
// something came after the property access, reset to initial
case Initial:
if (js_token.is_identifier_name()) {
// ...<name>...
mode = CompleteVariable;
variable_name = js_token.value();
} else {
mode = Initial;
}
break;
}
}
bool last_token_has_trivia = js_token.trivia().length() > 0;
if (mode == CompleteNullProperty) {
mode = CompleteProperty;
property_name = "";
last_token_has_trivia = false; // <name> <dot> [tab] is sensible to complete.
}
if (mode == Initial || last_token_has_trivia)
return {}; // we do not know how to complete this
Vector<Line::CompletionSuggestion> results;
Function<void(const JS::Shape&, const StringView&)> list_all_properties = [&results, &list_all_properties](const JS::Shape& shape, auto& property_pattern) {
@ -682,41 +740,40 @@ int main(int argc, char** argv)
}
};
if (token.contains(".")) {
auto parts = token.split('.', true);
// refuse either `.` or `a.b.c`
if (parts.size() > 2 || parts.size() == 0)
return {};
auto name = parts[0];
auto property_pattern = parts[1];
auto maybe_variable = interpreter->get_variable(name);
switch (mode) {
case CompleteProperty: {
auto maybe_variable = interpreter->get_variable(variable_name);
if (maybe_variable.is_empty()) {
maybe_variable = interpreter->global_object().get(name);
maybe_variable = interpreter->global_object().get(variable_name);
if (maybe_variable.is_empty())
return {};
break;
}
auto variable = maybe_variable;
if (!variable.is_object())
return {};
break;
const auto* object = variable.to_object(*interpreter);
const auto& shape = object->shape();
list_all_properties(shape, property_pattern);
list_all_properties(shape, property_name);
if (results.size())
editor.suggest(property_pattern.length());
return results;
editor.suggest(property_name.length());
break;
}
const auto& variable = interpreter->global_object();
list_all_properties(variable.shape(), token);
if (results.size())
editor.suggest(token.length());
case CompleteVariable: {
const auto& variable = interpreter->global_object();
list_all_properties(variable.shape(), variable_name);
if (results.size())
editor.suggest(variable_name.length());
break;
}
default:
ASSERT_NOT_REACHED();
}
return results;
};
s_editor->on_tab_complete_first_token = [complete](auto& value) { return complete(value); };
s_editor->on_tab_complete_other_token = [complete](auto& value) { return complete(value); };
s_editor->on_tab_complete = move(complete);
repl(*interpreter);
} else {
interpreter = JS::Interpreter::create<JS::GlobalObject>();