2020-10-02 03:01:33 +03:30
|
|
|
/*
|
|
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
|
|
*
|
2021-04-22 01:24:48 -07:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-10-02 03:01:33 +03:30
|
|
|
*/
|
|
|
|
|
|
|
|
#include "LanguageClient.h"
|
2021-02-27 09:50:02 +02:00
|
|
|
#include "HackStudio.h"
|
2021-04-09 08:49:48 +03:00
|
|
|
#include "ProjectDeclarations.h"
|
2021-05-17 22:19:50 +02:00
|
|
|
#include "ToDoEntries.h"
|
2020-10-02 03:01:33 +03:30
|
|
|
#include <AK/String.h>
|
|
|
|
#include <AK/Vector.h>
|
2021-02-13 09:47:08 +02:00
|
|
|
#include <LibGUI/Notification.h>
|
2020-10-02 03:01:33 +03:30
|
|
|
|
|
|
|
namespace HackStudio {
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void ConnectionToServer::auto_complete_suggestions(Vector<GUI::AutocompleteProvider::Entry> const& suggestions)
|
2020-10-02 03:01:33 +03:30
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_current_language_client) {
|
2021-01-23 19:48:42 +02:00
|
|
|
dbgln("Language Server connection has no attached language client");
|
|
|
|
return;
|
|
|
|
}
|
2021-05-02 19:54:34 +02:00
|
|
|
m_current_language_client->provide_autocomplete_suggestions(suggestions);
|
2020-10-02 03:01:33 +03:30
|
|
|
}
|
2021-02-27 09:31:05 +02:00
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServer::declaration_location(const GUI::AutocompleteProvider::ProjectLocation& location)
|
2021-02-20 12:27:39 +02:00
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_current_language_client) {
|
2021-02-20 12:27:39 +02:00
|
|
|
dbgln("Language Server connection has no attached language client");
|
|
|
|
return;
|
|
|
|
}
|
2021-05-02 19:54:34 +02:00
|
|
|
m_current_language_client->declaration_found(location.file, location.line, location.column);
|
2021-02-27 09:31:05 +02:00
|
|
|
}
|
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServer::parameters_hint_result(Vector<String> const& params, int argument_index)
|
2021-07-03 11:55:54 +03:00
|
|
|
{
|
|
|
|
if (!m_current_language_client) {
|
|
|
|
dbgln("Language Server connection has no attached language client");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
VERIFY(argument_index >= 0);
|
|
|
|
m_current_language_client->parameters_hint_result(params, static_cast<size_t>(argument_index));
|
|
|
|
}
|
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServer::tokens_info_result(Vector<GUI::AutocompleteProvider::TokenInfo> const& tokens_info)
|
2022-02-06 22:28:57 +02:00
|
|
|
{
|
|
|
|
if (!m_current_language_client) {
|
|
|
|
dbgln("Language Server connection has no attached language client");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
VERIFY(m_current_language_client->on_tokens_info_result);
|
|
|
|
m_current_language_client->on_tokens_info_result(tokens_info);
|
|
|
|
}
|
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServer::die()
|
2021-02-13 09:47:08 +02:00
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
VERIFY(m_wrapper);
|
|
|
|
// Wrapper destructs us here
|
|
|
|
m_wrapper->on_crash();
|
2021-02-13 09:47:08 +02:00
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::open_file(String const& path, int fd)
|
2020-10-02 03:01:33 +03:30
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_connection_wrapper.connection())
|
2021-02-13 09:47:08 +02:00
|
|
|
return;
|
2021-05-03 13:33:59 +02:00
|
|
|
m_connection_wrapper.connection()->async_file_opened(path, fd);
|
2020-10-02 03:01:33 +03:30
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::set_file_content(String const& path, String const& content)
|
2020-10-02 03:01:33 +03:30
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_connection_wrapper.connection())
|
2021-02-13 09:47:08 +02:00
|
|
|
return;
|
2021-05-03 13:33:59 +02:00
|
|
|
m_connection_wrapper.connection()->async_set_file_content(path, content);
|
2020-10-02 03:01:33 +03:30
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::insert_text(String const& path, String const& text, size_t line, size_t column)
|
2020-10-02 03:01:33 +03:30
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_connection_wrapper.connection())
|
2021-02-13 09:47:08 +02:00
|
|
|
return;
|
2021-05-03 13:33:59 +02:00
|
|
|
m_connection_wrapper.connection()->async_file_edit_insert_text(path, text, line, column);
|
2020-10-02 03:01:33 +03:30
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::remove_text(String const& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column)
|
2020-10-02 03:01:33 +03:30
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_connection_wrapper.connection())
|
2021-02-13 09:47:08 +02:00
|
|
|
return;
|
2021-05-03 13:33:59 +02:00
|
|
|
m_connection_wrapper.connection()->async_file_edit_remove_text(path, from_line, from_column, to_line, to_column);
|
2020-10-02 03:01:33 +03:30
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::request_autocomplete(String const& path, size_t cursor_line, size_t cursor_column)
|
2020-10-02 03:01:33 +03:30
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_connection_wrapper.connection())
|
2021-02-13 09:47:08 +02:00
|
|
|
return;
|
2021-02-06 15:58:02 +02:00
|
|
|
set_active_client();
|
2021-05-03 13:33:59 +02:00
|
|
|
m_connection_wrapper.connection()->async_auto_complete_suggestions(GUI::AutocompleteProvider::ProjectLocation { path, cursor_line, cursor_column });
|
2020-10-02 03:01:33 +03:30
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::provide_autocomplete_suggestions(Vector<GUI::AutocompleteProvider::Entry> const& suggestions) const
|
2020-10-02 03:01:33 +03:30
|
|
|
{
|
|
|
|
if (on_autocomplete_suggestions)
|
|
|
|
on_autocomplete_suggestions(suggestions);
|
|
|
|
|
|
|
|
// Otherwise, drop it on the floor :shrug:
|
|
|
|
}
|
|
|
|
|
2021-02-06 15:58:02 +02:00
|
|
|
void LanguageClient::set_active_client()
|
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
if (!m_connection_wrapper.connection())
|
2021-02-13 09:47:08 +02:00
|
|
|
return;
|
2021-03-05 21:01:42 +02:00
|
|
|
m_connection_wrapper.set_active_client(*this);
|
2021-02-13 09:47:08 +02:00
|
|
|
}
|
|
|
|
|
2022-02-06 22:28:57 +02:00
|
|
|
bool LanguageClient::is_active_client() const
|
|
|
|
{
|
|
|
|
if (!m_connection_wrapper.connection())
|
|
|
|
return false;
|
|
|
|
return m_connection_wrapper.connection()->active_client() == this;
|
|
|
|
}
|
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
HashMap<String, NonnullOwnPtr<ConnectionToServerWrapper>> ConnectionToServerInstances::s_instance_for_language;
|
2021-03-05 21:01:42 +02:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void ConnectionToServer::declarations_in_document(String const& filename, Vector<GUI::AutocompleteProvider::Declaration> const& declarations)
|
2021-02-13 09:47:08 +02:00
|
|
|
{
|
2021-05-02 19:54:34 +02:00
|
|
|
ProjectDeclarations::the().set_declared_symbols(filename, declarations);
|
2021-03-05 21:01:42 +02:00
|
|
|
}
|
2021-02-13 09:47:08 +02:00
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServer::todo_entries_in_document(String const& filename, Vector<Cpp::Parser::TodoEntry> const& todo_entries)
|
2021-05-17 22:19:50 +02:00
|
|
|
{
|
|
|
|
ToDoEntries::the().set_entries(filename, move(todo_entries));
|
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::search_declaration(String const& path, size_t line, size_t column)
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
if (!m_connection_wrapper.connection())
|
|
|
|
return;
|
|
|
|
set_active_client();
|
2021-05-03 13:33:59 +02:00
|
|
|
m_connection_wrapper.connection()->async_find_declaration(GUI::AutocompleteProvider::ProjectLocation { path, line, column });
|
2021-03-05 21:01:42 +02:00
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::get_parameters_hint(String const& path, size_t line, size_t column)
|
2021-07-03 11:55:54 +03:00
|
|
|
{
|
|
|
|
if (!m_connection_wrapper.connection())
|
|
|
|
return;
|
|
|
|
set_active_client();
|
|
|
|
m_connection_wrapper.connection()->async_get_parameters_hint(GUI::AutocompleteProvider::ProjectLocation { path, line, column });
|
|
|
|
}
|
2022-02-06 22:28:57 +02:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::get_tokens_info(String const& filename)
|
2022-02-06 22:28:57 +02:00
|
|
|
{
|
|
|
|
if (!m_connection_wrapper.connection())
|
|
|
|
return;
|
|
|
|
VERIFY(is_active_client());
|
|
|
|
m_connection_wrapper.connection()->async_get_tokens_info(filename);
|
|
|
|
}
|
2021-07-03 11:55:54 +03:00
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void LanguageClient::declaration_found(String const& file, size_t line, size_t column) const
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
if (!on_declaration_found) {
|
|
|
|
dbgln("on_declaration_found callback is not set");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
on_declaration_found(file, line, column);
|
|
|
|
}
|
|
|
|
|
2021-07-03 11:55:54 +03:00
|
|
|
void LanguageClient::parameters_hint_result(Vector<String> const& params, size_t argument_index) const
|
|
|
|
{
|
|
|
|
if (!on_function_parameters_hint_result) {
|
|
|
|
dbgln("on_function_parameters_hint_result callback is not set");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
on_function_parameters_hint_result(params, argument_index);
|
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void ConnectionToServerInstances::set_instance_for_language(String const& language_name, NonnullOwnPtr<ConnectionToServerWrapper>&& connection_wrapper)
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
s_instance_for_language.set(language_name, move(connection_wrapper));
|
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
void ConnectionToServerInstances::remove_instance_for_language(String const& language_name)
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
s_instance_for_language.remove(language_name);
|
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
ConnectionToServerWrapper* ConnectionToServerInstances::get_instance_wrapper(String const& language_name)
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
if (auto instance = s_instance_for_language.get(language_name); instance.has_value()) {
|
2022-02-25 13:04:59 +02:00
|
|
|
return const_cast<ConnectionToServerWrapper*>(instance.value());
|
2021-03-05 21:01:42 +02:00
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
2021-02-13 09:47:08 +02:00
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::on_crash()
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
show_crash_notification();
|
|
|
|
m_connection.clear();
|
|
|
|
|
2021-12-03 09:46:20 +02:00
|
|
|
static constexpr int max_crash_frequency_seconds = 10;
|
2021-03-05 21:01:42 +02:00
|
|
|
if (m_last_crash_timer.is_valid() && m_last_crash_timer.elapsed() / 1000 < max_crash_frequency_seconds) {
|
|
|
|
dbgln("LanguageServer crash frequency is too high");
|
|
|
|
m_respawn_allowed = false;
|
|
|
|
|
2022-01-06 07:07:15 -07:00
|
|
|
show_frequent_crashes_notification();
|
2021-03-05 21:01:42 +02:00
|
|
|
} else {
|
|
|
|
m_last_crash_timer.start();
|
|
|
|
try_respawn_connection();
|
|
|
|
}
|
|
|
|
}
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::show_frequent_crashes_notification() const
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
auto notification = GUI::Notification::construct();
|
2021-11-06 16:25:29 +01:00
|
|
|
notification->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/32x32/app-hack-studio.png").release_value_but_fixme_should_propagate_errors());
|
2021-03-05 21:01:42 +02:00
|
|
|
notification->set_title("LanguageServer Crashes too much!");
|
|
|
|
notification->set_text("LanguageServer aided features will not be available in this session");
|
|
|
|
notification->show();
|
|
|
|
}
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::show_crash_notification() const
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
auto notification = GUI::Notification::construct();
|
2021-11-06 16:25:29 +01:00
|
|
|
notification->set_icon(Gfx::Bitmap::try_load_from_file("/res/icons/32x32/app-hack-studio.png").release_value_but_fixme_should_propagate_errors());
|
2021-02-13 09:47:08 +02:00
|
|
|
notification->set_title("Oops!");
|
2021-03-05 21:01:42 +02:00
|
|
|
notification->set_text(String::formatted("LanguageServer has crashed"));
|
2021-02-13 09:47:08 +02:00
|
|
|
notification->show();
|
|
|
|
}
|
|
|
|
|
2022-04-01 20:58:27 +03:00
|
|
|
ConnectionToServerWrapper::ConnectionToServerWrapper(String const& language_name, Function<NonnullRefPtr<ConnectionToServer>()> connection_creator)
|
2021-03-05 21:01:42 +02:00
|
|
|
: m_language(language_from_name(language_name))
|
|
|
|
, m_connection_creator(move(connection_creator))
|
|
|
|
{
|
|
|
|
create_connection();
|
|
|
|
}
|
2021-02-13 09:47:08 +02:00
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::create_connection()
|
2021-02-13 09:47:08 +02:00
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
VERIFY(m_connection.is_null());
|
|
|
|
m_connection = m_connection_creator();
|
|
|
|
m_connection->set_wrapper(*this);
|
2021-02-13 09:47:08 +02:00
|
|
|
}
|
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
ConnectionToServer* ConnectionToServerWrapper::connection()
|
2021-02-13 09:47:08 +02:00
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
return m_connection.ptr();
|
2021-02-06 15:58:02 +02:00
|
|
|
}
|
2021-03-05 21:01:42 +02:00
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::attach(LanguageClient& client)
|
2021-02-27 09:50:02 +02:00
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
m_connection->m_current_language_client = &client;
|
2021-02-27 09:50:02 +02:00
|
|
|
}
|
2021-02-06 15:58:02 +02:00
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::detach()
|
2021-02-20 12:27:39 +02:00
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
m_connection->m_current_language_client.clear();
|
2021-02-20 12:27:39 +02:00
|
|
|
}
|
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::set_active_client(LanguageClient& client)
|
2021-02-20 12:27:39 +02:00
|
|
|
{
|
2021-03-05 21:01:42 +02:00
|
|
|
m_connection->m_current_language_client = &client;
|
|
|
|
}
|
|
|
|
|
2022-02-25 13:04:59 +02:00
|
|
|
void ConnectionToServerWrapper::try_respawn_connection()
|
2021-03-05 21:01:42 +02:00
|
|
|
{
|
|
|
|
if (!m_respawn_allowed)
|
2021-02-20 12:27:39 +02:00
|
|
|
return;
|
2021-03-05 21:01:42 +02:00
|
|
|
|
2022-02-25 12:27:37 +02:00
|
|
|
dbgln("Respawning ConnectionToServer");
|
2021-03-05 21:01:42 +02:00
|
|
|
create_connection();
|
|
|
|
|
2021-12-03 09:45:23 +02:00
|
|
|
// After respawning the language-server, we have to send the content of open project files
|
2021-03-05 21:01:42 +02:00
|
|
|
// so the server's FileDB will be up-to-date.
|
2022-04-01 20:58:27 +03:00
|
|
|
for_each_open_file([this](ProjectFile const& file) {
|
2021-03-05 21:01:42 +02:00
|
|
|
if (file.code_document().language() != m_language)
|
|
|
|
return;
|
2021-05-03 13:33:59 +02:00
|
|
|
m_connection->async_set_file_content(file.code_document().file_path(), file.document().text());
|
2021-03-05 21:01:42 +02:00
|
|
|
});
|
2021-02-20 12:27:39 +02:00
|
|
|
}
|
|
|
|
|
2020-10-02 03:01:33 +03:30
|
|
|
}
|