mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 19:44:58 +00:00

The one behavior difference here is that the statusbar used to display "Unknown" for unknown file types, and "Markdown" for md, but we now display "Plain Text" for all file types without syntax highlighters.
279 lines
9.3 KiB
C++
279 lines
9.3 KiB
C++
/*
|
|
* Copyright (c) 2020, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "LanguageClient.h"
|
|
#include "HackStudio.h"
|
|
#include "ProjectDeclarations.h"
|
|
#include "ToDoEntries.h"
|
|
#include <AK/DeprecatedString.h>
|
|
#include <AK/Vector.h>
|
|
#include <LibGUI/Notification.h>
|
|
|
|
namespace HackStudio {
|
|
|
|
void ConnectionToServer::auto_complete_suggestions(Vector<CodeComprehension::AutocompleteResultEntry> const& suggestions)
|
|
{
|
|
if (!m_current_language_client) {
|
|
dbgln("Language Server connection has no attached language client");
|
|
return;
|
|
}
|
|
m_current_language_client->provide_autocomplete_suggestions(suggestions);
|
|
}
|
|
|
|
void ConnectionToServer::declaration_location(CodeComprehension::ProjectLocation const& location)
|
|
{
|
|
if (!m_current_language_client) {
|
|
dbgln("Language Server connection has no attached language client");
|
|
return;
|
|
}
|
|
m_current_language_client->declaration_found(location.file, location.line, location.column);
|
|
}
|
|
|
|
void ConnectionToServer::parameters_hint_result(Vector<DeprecatedString> const& params, int argument_index)
|
|
{
|
|
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));
|
|
}
|
|
|
|
void ConnectionToServer::tokens_info_result(Vector<CodeComprehension::TokenInfo> const& tokens_info)
|
|
{
|
|
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);
|
|
}
|
|
|
|
void ConnectionToServer::die()
|
|
{
|
|
VERIFY(m_wrapper);
|
|
// Wrapper destructs us here
|
|
m_wrapper->on_crash();
|
|
}
|
|
|
|
void LanguageClient::open_file(DeprecatedString const& path, int fd)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
m_connection_wrapper.connection()->async_file_opened(path, fd);
|
|
}
|
|
|
|
void LanguageClient::set_file_content(DeprecatedString const& path, DeprecatedString const& content)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
m_connection_wrapper.connection()->async_set_file_content(path, content);
|
|
}
|
|
|
|
void LanguageClient::insert_text(DeprecatedString const& path, DeprecatedString const& text, size_t line, size_t column)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
m_connection_wrapper.connection()->async_file_edit_insert_text(path, text, line, column);
|
|
}
|
|
|
|
void LanguageClient::remove_text(DeprecatedString const& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
m_connection_wrapper.connection()->async_file_edit_remove_text(path, from_line, from_column, to_line, to_column);
|
|
}
|
|
|
|
void LanguageClient::request_autocomplete(DeprecatedString const& path, size_t cursor_line, size_t cursor_column)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
set_active_client();
|
|
m_connection_wrapper.connection()->async_auto_complete_suggestions(CodeComprehension::ProjectLocation { path, cursor_line, cursor_column });
|
|
}
|
|
|
|
void LanguageClient::provide_autocomplete_suggestions(Vector<CodeComprehension::AutocompleteResultEntry> const& suggestions) const
|
|
{
|
|
if (on_autocomplete_suggestions)
|
|
on_autocomplete_suggestions(suggestions);
|
|
|
|
// Otherwise, drop it on the floor :shrug:
|
|
}
|
|
|
|
void LanguageClient::set_active_client()
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
m_connection_wrapper.set_active_client(*this);
|
|
}
|
|
|
|
bool LanguageClient::is_active_client() const
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return false;
|
|
return m_connection_wrapper.connection()->active_client() == this;
|
|
}
|
|
|
|
HashMap<DeprecatedString, NonnullOwnPtr<ConnectionToServerWrapper>> ConnectionToServerInstances::s_instance_for_language;
|
|
|
|
void ConnectionToServer::declarations_in_document(DeprecatedString const& filename, Vector<CodeComprehension::Declaration> const& declarations)
|
|
{
|
|
ProjectDeclarations::the().set_declared_symbols(filename, declarations);
|
|
}
|
|
|
|
void ConnectionToServer::todo_entries_in_document(DeprecatedString const& filename, Vector<CodeComprehension::TodoEntry> const& todo_entries)
|
|
{
|
|
ToDoEntries::the().set_entries(filename, move(todo_entries));
|
|
}
|
|
|
|
void LanguageClient::search_declaration(DeprecatedString const& path, size_t line, size_t column)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
set_active_client();
|
|
m_connection_wrapper.connection()->async_find_declaration(CodeComprehension::ProjectLocation { path, line, column });
|
|
}
|
|
|
|
void LanguageClient::get_parameters_hint(DeprecatedString const& path, size_t line, size_t column)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
set_active_client();
|
|
m_connection_wrapper.connection()->async_get_parameters_hint(CodeComprehension::ProjectLocation { path, line, column });
|
|
}
|
|
|
|
void LanguageClient::get_tokens_info(DeprecatedString const& filename)
|
|
{
|
|
if (!m_connection_wrapper.connection())
|
|
return;
|
|
VERIFY(is_active_client());
|
|
m_connection_wrapper.connection()->async_get_tokens_info(filename);
|
|
}
|
|
|
|
void LanguageClient::declaration_found(DeprecatedString const& file, size_t line, size_t column) const
|
|
{
|
|
if (!on_declaration_found) {
|
|
dbgln("on_declaration_found callback is not set");
|
|
return;
|
|
}
|
|
on_declaration_found(file, line, column);
|
|
}
|
|
|
|
void LanguageClient::parameters_hint_result(Vector<DeprecatedString> 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);
|
|
}
|
|
|
|
void ConnectionToServerInstances::set_instance_for_language(DeprecatedString const& language_name, NonnullOwnPtr<ConnectionToServerWrapper>&& connection_wrapper)
|
|
{
|
|
s_instance_for_language.set(language_name, move(connection_wrapper));
|
|
}
|
|
|
|
void ConnectionToServerInstances::remove_instance_for_language(DeprecatedString const& language_name)
|
|
{
|
|
s_instance_for_language.remove(language_name);
|
|
}
|
|
|
|
ConnectionToServerWrapper* ConnectionToServerInstances::get_instance_wrapper(DeprecatedString const& language_name)
|
|
{
|
|
if (auto instance = s_instance_for_language.get(language_name); instance.has_value()) {
|
|
return const_cast<ConnectionToServerWrapper*>(instance.value());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ConnectionToServerWrapper::on_crash()
|
|
{
|
|
using namespace AK::TimeLiterals;
|
|
|
|
show_crash_notification();
|
|
m_connection.clear();
|
|
|
|
static constexpr Time max_crash_frequency = 10_sec;
|
|
if (m_last_crash_timer.is_valid() && m_last_crash_timer.elapsed_time() < max_crash_frequency) {
|
|
dbgln("LanguageServer crash frequency is too high");
|
|
m_respawn_allowed = false;
|
|
|
|
show_frequent_crashes_notification();
|
|
} else {
|
|
m_last_crash_timer.start();
|
|
try_respawn_connection();
|
|
}
|
|
}
|
|
void ConnectionToServerWrapper::show_frequent_crashes_notification() const
|
|
{
|
|
auto notification = GUI::Notification::construct();
|
|
notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"sv).release_value_but_fixme_should_propagate_errors());
|
|
notification->set_title("LanguageServer Crashes too much!");
|
|
notification->set_text("LanguageServer aided features will not be available in this session");
|
|
notification->show();
|
|
}
|
|
void ConnectionToServerWrapper::show_crash_notification() const
|
|
{
|
|
auto notification = GUI::Notification::construct();
|
|
notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"sv).release_value_but_fixme_should_propagate_errors());
|
|
notification->set_title("Oops!");
|
|
notification->set_text(DeprecatedString::formatted("LanguageServer has crashed"));
|
|
notification->show();
|
|
}
|
|
|
|
ConnectionToServerWrapper::ConnectionToServerWrapper(DeprecatedString const& language_name, Function<NonnullRefPtr<ConnectionToServer>()> connection_creator)
|
|
: m_language(Syntax::language_from_name(language_name).value())
|
|
, m_connection_creator(move(connection_creator))
|
|
{
|
|
create_connection();
|
|
}
|
|
|
|
void ConnectionToServerWrapper::create_connection()
|
|
{
|
|
VERIFY(m_connection.is_null());
|
|
m_connection = m_connection_creator();
|
|
m_connection->set_wrapper(*this);
|
|
}
|
|
|
|
ConnectionToServer* ConnectionToServerWrapper::connection()
|
|
{
|
|
return m_connection.ptr();
|
|
}
|
|
|
|
void ConnectionToServerWrapper::attach(LanguageClient& client)
|
|
{
|
|
m_connection->m_current_language_client = &client;
|
|
}
|
|
|
|
void ConnectionToServerWrapper::detach()
|
|
{
|
|
m_connection->m_current_language_client.clear();
|
|
}
|
|
|
|
void ConnectionToServerWrapper::set_active_client(LanguageClient& client)
|
|
{
|
|
m_connection->m_current_language_client = &client;
|
|
}
|
|
|
|
void ConnectionToServerWrapper::try_respawn_connection()
|
|
{
|
|
if (!m_respawn_allowed)
|
|
return;
|
|
|
|
dbgln("Respawning ConnectionToServer");
|
|
create_connection();
|
|
|
|
// After respawning the language-server, we have to send the content of open project files
|
|
// so the server's FileDB will be up-to-date.
|
|
for_each_open_file([this](ProjectFile const& file) {
|
|
if (file.code_document().language() != m_language)
|
|
return;
|
|
m_connection->async_set_file_content(file.code_document().file_path(), file.document().text());
|
|
});
|
|
}
|
|
|
|
}
|