123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- /*
- * 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/String.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<String> 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(String const& path, int fd)
- {
- if (!m_connection_wrapper.connection())
- return;
- m_connection_wrapper.connection()->async_file_opened(path, fd);
- }
- void LanguageClient::set_file_content(String const& path, String const& content)
- {
- if (!m_connection_wrapper.connection())
- return;
- m_connection_wrapper.connection()->async_set_file_content(path, content);
- }
- void LanguageClient::insert_text(String const& path, String 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(String 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(String 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<String, NonnullOwnPtr<ConnectionToServerWrapper>> ConnectionToServerInstances::s_instance_for_language;
- void ConnectionToServer::declarations_in_document(String const& filename, Vector<CodeComprehension::Declaration> const& declarations)
- {
- ProjectDeclarations::the().set_declared_symbols(filename, declarations);
- }
- void ConnectionToServer::todo_entries_in_document(String const& filename, Vector<CodeComprehension::TodoEntry> const& todo_entries)
- {
- ToDoEntries::the().set_entries(filename, move(todo_entries));
- }
- void LanguageClient::search_declaration(String 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(String 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(String 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(String 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<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);
- }
- void ConnectionToServerInstances::set_instance_for_language(String const& language_name, NonnullOwnPtr<ConnectionToServerWrapper>&& connection_wrapper)
- {
- s_instance_for_language.set(language_name, move(connection_wrapper));
- }
- void ConnectionToServerInstances::remove_instance_for_language(String const& language_name)
- {
- s_instance_for_language.remove(language_name);
- }
- ConnectionToServerWrapper* ConnectionToServerInstances::get_instance_wrapper(String 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()
- {
- show_crash_notification();
- m_connection.clear();
- static constexpr int max_crash_frequency_seconds = 10;
- 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;
- 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::try_load_from_file("/res/icons/32x32/app-hack-studio.png").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::try_load_from_file("/res/icons/32x32/app-hack-studio.png").release_value_but_fixme_should_propagate_errors());
- notification->set_title("Oops!");
- notification->set_text(String::formatted("LanguageServer has crashed"));
- notification->show();
- }
- ConnectionToServerWrapper::ConnectionToServerWrapper(String const& language_name, Function<NonnullRefPtr<ConnectionToServer>()> connection_creator)
- : m_language(language_from_name(language_name))
- , 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());
- });
- }
- }
|