1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 15:48:12 +00:00

HackStudio: Relay completions requests to the language server unfiltered

Previously, the client would decide when to ask the server for
completions, and it would only do so for identifiers that had spans
(determined via the highlighter!).
Swap this around and make the server decide if it wants to complete
something.
This commit also adds a CompletionKind (which only has one value:
Identifier), to work with other kinds of completions as well.
This commit is contained in:
AnotherTest 2020-10-03 16:30:16 +03:30 committed by Andreas Kling
parent b42c6ea281
commit 9e73b0b696
12 changed files with 125 additions and 50 deletions

View file

@ -37,7 +37,7 @@ static RefPtr<Gfx::Bitmap> s_cplusplus_icon;
class AutoCompleteSuggestionModel final : public GUI::Model {
public:
explicit AutoCompleteSuggestionModel(Vector<String>&& suggestions)
explicit AutoCompleteSuggestionModel(Vector<AutoCompleteResponse>&& suggestions)
: m_suggestions(move(suggestions))
{
}
@ -48,6 +48,12 @@ public:
__Column_Count,
};
enum InternalRole {
__ModelRoleCustom = (int)GUI::ModelRole::Custom,
PartialInputLength,
Kind,
};
virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return m_suggestions.size(); }
virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override { return Column::__Column_Count; }
virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override
@ -55,21 +61,29 @@ public:
auto& suggestion = m_suggestions.at(index.row());
if (role == GUI::ModelRole::Display) {
if (index.column() == Column::Name) {
return suggestion;
return suggestion.completion;
}
if (index.column() == Column::Icon) {
// TODO: Have separate icons for fields, functions, methods etc
if (suggestion.ends_with(".cpp"))
// FIXME: Probably should have different icons for the different kinds, rather than for "c++".
if (suggestion.kind == CompletionKind::Identifier)
return *s_cplusplus_icon;
return *s_cplusplus_icon;
}
}
if ((int)role == InternalRole::Kind)
return (u32)suggestion.kind;
if ((int)role == InternalRole::PartialInputLength)
return (i64)suggestion.partial_input_length;
return {};
}
virtual void update() override {};
private:
Vector<String> m_suggestions;
Vector<AutoCompleteResponse> m_suggestions;
};
AutoCompleteBox::~AutoCompleteBox() { }
@ -89,9 +103,8 @@ AutoCompleteBox::AutoCompleteBox(WeakPtr<Editor> editor)
m_suggestion_view->set_column_headers_visible(false);
}
void AutoCompleteBox::update_suggestions(String partial_input, Vector<String>&& suggestions)
void AutoCompleteBox::update_suggestions(Vector<AutoCompleteResponse>&& suggestions)
{
m_partial_input = partial_input;
if (suggestions.is_empty())
return;
@ -112,7 +125,6 @@ void AutoCompleteBox::show(Gfx::IntPoint suggstion_box_location)
void AutoCompleteBox::close()
{
m_partial_input.empty();
m_popup_window->hide();
}
@ -155,11 +167,10 @@ void AutoCompleteBox::apply_suggestion()
auto suggestion_index = m_suggestion_view->model()->index(selected_index.row(), AutoCompleteSuggestionModel::Column::Name);
auto suggestion = suggestion_index.data().to_string();
size_t partial_length = suggestion_index.data((GUI::ModelRole)AutoCompleteSuggestionModel::InternalRole::PartialInputLength).to_i64();
ASSERT(!m_partial_input.is_null());
ASSERT(suggestion.starts_with(m_partial_input));
auto completion = suggestion.substring(m_partial_input.length(), suggestion.length() - m_partial_input.length());
ASSERT(suggestion.length() >= partial_length);
auto completion = suggestion.substring_view(partial_length, suggestion.length() - partial_length);
m_editor->insert_at_cursor_or_replace_selection(completion);
}

View file

@ -26,18 +26,20 @@
#pragma once
#include "AutoCompleteResponse.h"
#include <AK/WeakPtr.h>
#include <LibGUI/Widget.h>
namespace HackStudio {
class Editor;
class AutoCompleteBox final {
public:
explicit AutoCompleteBox(WeakPtr<Editor>);
~AutoCompleteBox();
void update_suggestions(const String partial_input, Vector<String>&& suggestions);
void update_suggestions(Vector<AutoCompleteResponse>&& suggestions);
void show(Gfx::IntPoint suggstion_box_location);
void close();
@ -51,6 +53,5 @@ private:
WeakPtr<Editor> m_editor;
RefPtr<GUI::Window> m_popup_window;
RefPtr<GUI::TableView> m_suggestion_view;
String m_partial_input;
};
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <AK/String.h>
#include <AK/Types.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
namespace HackStudio {
enum class CompletionKind {
Identifier,
};
struct AutoCompleteResponse {
String completion;
size_t partial_input_length { 0 };
CompletionKind kind { CompletionKind::Identifier };
};
}
namespace IPC {
template<>
inline bool encode(IPC::Encoder& encoder, const HackStudio::AutoCompleteResponse& response)
{
encoder << response.completion;
encoder << (u64)response.partial_input_length;
encoder << (u32)response.kind;
return true;
}
template<>
inline bool decode(IPC::Decoder& decoder, HackStudio::AutoCompleteResponse& response)
{
u32 kind = 0;
u64 partial_input_length = 0;
bool ok = decoder.decode(response.completion)
&& decoder.decode(partial_input_length)
&& decoder.decode(kind);
if (ok) {
response.kind = static_cast<HackStudio::CompletionKind>(kind);
response.partial_input_length = partial_input_length;
}
return ok;
}
}

View file

@ -494,28 +494,10 @@ void Editor::set_document(GUI::TextDocument& doc)
Optional<Editor::AutoCompleteRequestData> Editor::get_autocomplete_request_data()
{
auto highlighter = wrapper().editor().syntax_highlighter();
if (!highlighter)
if (!wrapper().editor().m_language_client)
return {};
auto& spans = document().spans();
for (size_t span_index = 2; span_index < spans.size(); ++span_index) {
auto& span = spans[span_index];
if (!span.range.contains(cursor())) {
continue;
}
if (highlighter->is_identifier(spans[span_index - 1].data)) {
auto completion_span = spans[span_index - 1];
auto adjusted_range = completion_span.range;
auto end_line_length = document().line(completion_span.range.end().line()).length();
adjusted_range.end().set_column(min(end_line_length, adjusted_range.end().column() + 1));
auto text_in_span = document().text_in_range(adjusted_range);
return AutoCompleteRequestData { completion_span.range.end(), text_in_span };
}
}
return {};
return Editor::AutoCompleteRequestData { { cursor().line(), cursor().column() > 0 ? cursor().column() - 1 : 0 } };
}
void Editor::update_autocomplete(const AutoCompleteRequestData& data)
@ -531,7 +513,7 @@ void Editor::update_autocomplete(const AutoCompleteRequestData& data)
show_autocomplete(data);
m_autocomplete_box->update_suggestions(data.partial_input, move(suggestions));
m_autocomplete_box->update_suggestions(move(suggestions));
m_autocomplete_in_focus = true;
};

View file

@ -86,7 +86,6 @@ private:
struct AutoCompleteRequestData {
GUI::TextPosition position;
String partial_input;
};
Optional<AutoCompleteRequestData> get_autocomplete_request_data();

View file

@ -61,7 +61,7 @@ void LanguageClient::request_autocomplete(const String& path, size_t cursor_line
m_connection.post_message(Messages::LanguageServer::AutoCompleteSuggestions(path, cursor_line, cursor_column));
}
void LanguageClient::provide_autocomplete_suggestions(const Vector<String>& suggestions)
void LanguageClient::provide_autocomplete_suggestions(const Vector<AutoCompleteResponse>& suggestions)
{
if (on_autocomplete_suggestions)
on_autocomplete_suggestions(suggestions);

View file

@ -26,12 +26,14 @@
#pragma once
#include "AutoCompleteResponse.h"
#include <AK/Forward.h>
#include <AK/LexicalPath.h>
#include <AK/Types.h>
#include <LibIPC/ServerConnection.h>
#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h>
#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
#include <LibIPC/ServerConnection.h>
namespace HackStudio {
@ -104,9 +106,9 @@ public:
virtual void remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column);
virtual void request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column);
void provide_autocomplete_suggestions(const Vector<String>&);
void provide_autocomplete_suggestions(const Vector<AutoCompleteResponse>&);
Function<void(Vector<String>)> on_autocomplete_suggestions;
Function<void(Vector<AutoCompleteResponse>)> on_autocomplete_suggestions;
private:
ServerConnection& m_connection;

View file

@ -32,7 +32,7 @@
namespace LanguageServers::Cpp {
Vector<String> AutoComplete::get_suggestions(const String& code, GUI::TextPosition autocomplete_position)
Vector<AutoCompleteResponse> AutoComplete::get_suggestions(const String& code, GUI::TextPosition autocomplete_position)
{
auto lines = code.split('\n', true);
Lexer lexer(code);
@ -46,7 +46,7 @@ Vector<String> AutoComplete::get_suggestions(const String& code, GUI::TextPositi
#ifdef DEBUG_AUTOCOMPLETE
for (auto& suggestion : suggestions) {
dbg() << "suggestion: " << suggestion;
dbg() << "suggestion: " << suggestion.completion;
}
#endif
@ -71,10 +71,10 @@ Optional<size_t> AutoComplete::token_in_position(const Vector<Cpp::Token>& token
return {};
}
Vector<String> AutoComplete::identifier_prefixes(const Vector<String> lines, const Vector<Cpp::Token>& tokens, size_t target_token_index)
Vector<AutoCompleteResponse> AutoComplete::identifier_prefixes(const Vector<String> lines, const Vector<Cpp::Token>& tokens, size_t target_token_index)
{
auto partial_input = text_of_token(lines, tokens[target_token_index]);
Vector<String> suggestions;
Vector<AutoCompleteResponse> suggestions;
HashTable<String> suggestions_lookup; // To avoid duplicate results
@ -85,7 +85,7 @@ Vector<String> AutoComplete::identifier_prefixes(const Vector<String> lines, con
auto text = text_of_token(lines, token);
if (text.starts_with(partial_input) && !suggestions_lookup.contains(text)) {
suggestions_lookup.set(text);
suggestions.append(text);
suggestions.append({ text, partial_input.length(), HackStudio::CompletionKind::Identifier });
}
}
return suggestions;

View file

@ -28,23 +28,25 @@
#include <AK/String.h>
#include <AK/Vector.h>
#include <DevTools/HackStudio/AutoCompleteResponse.h>
#include <LibCpp/Lexer.h>
#include <LibGUI/TextPosition.h>
namespace LanguageServers::Cpp {
using namespace ::Cpp;
using ::HackStudio::AutoCompleteResponse;
class AutoComplete {
public:
AutoComplete() = delete;
static Vector<String> get_suggestions(const String& code, GUI::TextPosition autocomplete_position);
static Vector<AutoCompleteResponse> get_suggestions(const String& code, GUI::TextPosition autocomplete_position);
private:
static Optional<size_t> token_in_position(const Vector<Cpp::Token>&, GUI::TextPosition);
static String text_of_token(const Vector<String> lines, const Cpp::Token&);
static Vector<String> identifier_prefixes(const Vector<String> lines, const Vector<Cpp::Token>&, size_t target_token_index);
static Vector<AutoCompleteResponse> identifier_prefixes(const Vector<String> lines, const Vector<Cpp::Token>&, size_t target_token_index);
};
}

View file

@ -159,7 +159,7 @@ void ClientConnection::handle(const Messages::LanguageServer::AutoCompleteSugges
return;
}
Vector<String> suggestions = AutoComplete::get_suggestions(document->text(), { (size_t)message.cursor_line(), (size_t)message.cursor_column() });
auto suggestions = AutoComplete::get_suggestions(document->text(), { (size_t)message.cursor_line(), (size_t)message.cursor_column() });
post_message(Messages::LanguageClient::AutoCompleteSuggestions(move(suggestions)));
}

View file

@ -28,11 +28,13 @@
#include <AK/HashMap.h>
#include <AK/LexicalPath.h>
#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h>
#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
#include <DevTools/HackStudio/AutoCompleteResponse.h>
#include <LibGUI/TextDocument.h>
#include <LibIPC/ClientConnection.h>
#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h>
#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
namespace LanguageServers::Cpp {
class ClientConnection final

View file

@ -1,4 +1,4 @@
endpoint LanguageClient = 8002
{
AutoCompleteSuggestions(Vector<String> suggestions) =|
AutoCompleteSuggestions(Vector<HackStudio::AutoCompleteResponse> suggestions) =|
}