diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2020-05-19 08:42:01 +0430 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-05-20 13:41:37 +0200 |
commit | 7fba21aefce6ce69ea9f44918a156cf3d281591c (patch) | |
tree | 6387724c5d2933a4d52f7aa58572ae7280eec9dd /Userland | |
parent | d18f6e82eb95054108114843ef824bf36026c758 (diff) | |
download | serenity-7fba21aefce6ce69ea9f44918a156cf3d281591c.zip |
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
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/js.cpp | 109 |
1 files changed, 83 insertions, 26 deletions
diff --git a/Userland/js.cpp b/Userland/js.cpp index a3cd05c5ec..4c6fbe080b 100644 --- a/Userland/js.cpp +++ b/Userland/js.cpp @@ -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>(); |