Serenity Operating System
at master 279 lines 9.6 kB view raw
1/* 2 * Copyright (c) 2020, the SerenityOS developers. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7#include "LanguageClient.h" 8#include "HackStudio.h" 9#include "ProjectDeclarations.h" 10#include "ToDoEntries.h" 11#include <AK/DeprecatedString.h> 12#include <AK/Vector.h> 13#include <LibGUI/Notification.h> 14 15namespace HackStudio { 16 17void ConnectionToServer::auto_complete_suggestions(Vector<CodeComprehension::AutocompleteResultEntry> const& suggestions) 18{ 19 if (!m_current_language_client) { 20 dbgln("Language Server connection has no attached language client"); 21 return; 22 } 23 m_current_language_client->provide_autocomplete_suggestions(suggestions); 24} 25 26void ConnectionToServer::declaration_location(CodeComprehension::ProjectLocation const& location) 27{ 28 if (!m_current_language_client) { 29 dbgln("Language Server connection has no attached language client"); 30 return; 31 } 32 m_current_language_client->declaration_found(location.file, location.line, location.column); 33} 34 35void ConnectionToServer::parameters_hint_result(Vector<DeprecatedString> const& params, int argument_index) 36{ 37 if (!m_current_language_client) { 38 dbgln("Language Server connection has no attached language client"); 39 return; 40 } 41 42 VERIFY(argument_index >= 0); 43 m_current_language_client->parameters_hint_result(params, static_cast<size_t>(argument_index)); 44} 45 46void ConnectionToServer::tokens_info_result(Vector<CodeComprehension::TokenInfo> const& tokens_info) 47{ 48 if (!m_current_language_client) { 49 dbgln("Language Server connection has no attached language client"); 50 return; 51 } 52 VERIFY(m_current_language_client->on_tokens_info_result); 53 m_current_language_client->on_tokens_info_result(tokens_info); 54} 55 56void ConnectionToServer::die() 57{ 58 VERIFY(m_wrapper); 59 // Wrapper destructs us here 60 m_wrapper->on_crash(); 61} 62 63void LanguageClient::open_file(DeprecatedString const& path, int fd) 64{ 65 if (!m_connection_wrapper.connection()) 66 return; 67 m_connection_wrapper.connection()->async_file_opened(path, fd); 68} 69 70void LanguageClient::set_file_content(DeprecatedString const& path, DeprecatedString const& content) 71{ 72 if (!m_connection_wrapper.connection()) 73 return; 74 m_connection_wrapper.connection()->async_set_file_content(path, content); 75} 76 77void LanguageClient::insert_text(DeprecatedString const& path, DeprecatedString const& text, size_t line, size_t column) 78{ 79 if (!m_connection_wrapper.connection()) 80 return; 81 m_connection_wrapper.connection()->async_file_edit_insert_text(path, text, line, column); 82} 83 84void LanguageClient::remove_text(DeprecatedString const& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column) 85{ 86 if (!m_connection_wrapper.connection()) 87 return; 88 m_connection_wrapper.connection()->async_file_edit_remove_text(path, from_line, from_column, to_line, to_column); 89} 90 91void LanguageClient::request_autocomplete(DeprecatedString const& path, size_t cursor_line, size_t cursor_column) 92{ 93 if (!m_connection_wrapper.connection()) 94 return; 95 set_active_client(); 96 m_connection_wrapper.connection()->async_auto_complete_suggestions(CodeComprehension::ProjectLocation { path, cursor_line, cursor_column }); 97} 98 99void LanguageClient::provide_autocomplete_suggestions(Vector<CodeComprehension::AutocompleteResultEntry> const& suggestions) const 100{ 101 if (on_autocomplete_suggestions) 102 on_autocomplete_suggestions(suggestions); 103 104 // Otherwise, drop it on the floor :shrug: 105} 106 107void LanguageClient::set_active_client() 108{ 109 if (!m_connection_wrapper.connection()) 110 return; 111 m_connection_wrapper.set_active_client(*this); 112} 113 114bool LanguageClient::is_active_client() const 115{ 116 if (!m_connection_wrapper.connection()) 117 return false; 118 return m_connection_wrapper.connection()->active_client() == this; 119} 120 121HashMap<DeprecatedString, NonnullOwnPtr<ConnectionToServerWrapper>> ConnectionToServerInstances::s_instance_for_language; 122 123void ConnectionToServer::declarations_in_document(DeprecatedString const& filename, Vector<CodeComprehension::Declaration> const& declarations) 124{ 125 ProjectDeclarations::the().set_declared_symbols(filename, declarations); 126} 127 128void ConnectionToServer::todo_entries_in_document(DeprecatedString const& filename, Vector<CodeComprehension::TodoEntry> const& todo_entries) 129{ 130 ToDoEntries::the().set_entries(filename, move(todo_entries)); 131} 132 133void LanguageClient::search_declaration(DeprecatedString const& path, size_t line, size_t column) 134{ 135 if (!m_connection_wrapper.connection()) 136 return; 137 set_active_client(); 138 m_connection_wrapper.connection()->async_find_declaration(CodeComprehension::ProjectLocation { path, line, column }); 139} 140 141void LanguageClient::get_parameters_hint(DeprecatedString const& path, size_t line, size_t column) 142{ 143 if (!m_connection_wrapper.connection()) 144 return; 145 set_active_client(); 146 m_connection_wrapper.connection()->async_get_parameters_hint(CodeComprehension::ProjectLocation { path, line, column }); 147} 148 149void LanguageClient::get_tokens_info(DeprecatedString const& filename) 150{ 151 if (!m_connection_wrapper.connection()) 152 return; 153 VERIFY(is_active_client()); 154 m_connection_wrapper.connection()->async_get_tokens_info(filename); 155} 156 157void LanguageClient::declaration_found(DeprecatedString const& file, size_t line, size_t column) const 158{ 159 if (!on_declaration_found) { 160 dbgln("on_declaration_found callback is not set"); 161 return; 162 } 163 on_declaration_found(file, line, column); 164} 165 166void LanguageClient::parameters_hint_result(Vector<DeprecatedString> const& params, size_t argument_index) const 167{ 168 if (!on_function_parameters_hint_result) { 169 dbgln("on_function_parameters_hint_result callback is not set"); 170 return; 171 } 172 on_function_parameters_hint_result(params, argument_index); 173} 174 175void ConnectionToServerInstances::set_instance_for_language(DeprecatedString const& language_name, NonnullOwnPtr<ConnectionToServerWrapper>&& connection_wrapper) 176{ 177 s_instance_for_language.set(language_name, move(connection_wrapper)); 178} 179 180void ConnectionToServerInstances::remove_instance_for_language(DeprecatedString const& language_name) 181{ 182 s_instance_for_language.remove(language_name); 183} 184 185ConnectionToServerWrapper* ConnectionToServerInstances::get_instance_wrapper(DeprecatedString const& language_name) 186{ 187 if (auto instance = s_instance_for_language.get(language_name); instance.has_value()) { 188 return const_cast<ConnectionToServerWrapper*>(instance.value()); 189 } 190 return nullptr; 191} 192 193void ConnectionToServerWrapper::on_crash() 194{ 195 using namespace AK::TimeLiterals; 196 197 show_crash_notification(); 198 m_connection.clear(); 199 200 static constexpr Time max_crash_frequency = 10_sec; 201 if (m_last_crash_timer.is_valid() && m_last_crash_timer.elapsed_time() < max_crash_frequency) { 202 dbgln("LanguageServer crash frequency is too high"); 203 m_respawn_allowed = false; 204 205 show_frequent_crashes_notification(); 206 } else { 207 m_last_crash_timer.start(); 208 try_respawn_connection(); 209 } 210} 211void ConnectionToServerWrapper::show_frequent_crashes_notification() const 212{ 213 auto notification = GUI::Notification::construct(); 214 notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"sv).release_value_but_fixme_should_propagate_errors()); 215 notification->set_title("LanguageServer Crashes too much!"); 216 notification->set_text("LanguageServer aided features will not be available in this session"); 217 notification->show(); 218} 219void ConnectionToServerWrapper::show_crash_notification() const 220{ 221 auto notification = GUI::Notification::construct(); 222 notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"sv).release_value_but_fixme_should_propagate_errors()); 223 notification->set_title("Oops!"); 224 notification->set_text(DeprecatedString::formatted("LanguageServer has crashed")); 225 notification->show(); 226} 227 228ConnectionToServerWrapper::ConnectionToServerWrapper(DeprecatedString const& language_name, Function<NonnullRefPtr<ConnectionToServer>()> connection_creator) 229 : m_language(Syntax::language_from_name(language_name).value()) 230 , m_connection_creator(move(connection_creator)) 231{ 232 create_connection(); 233} 234 235void ConnectionToServerWrapper::create_connection() 236{ 237 VERIFY(m_connection.is_null()); 238 m_connection = m_connection_creator(); 239 m_connection->set_wrapper(*this); 240} 241 242ConnectionToServer* ConnectionToServerWrapper::connection() 243{ 244 return m_connection.ptr(); 245} 246 247void ConnectionToServerWrapper::attach(LanguageClient& client) 248{ 249 m_connection->m_current_language_client = &client; 250} 251 252void ConnectionToServerWrapper::detach() 253{ 254 m_connection->m_current_language_client.clear(); 255} 256 257void ConnectionToServerWrapper::set_active_client(LanguageClient& client) 258{ 259 m_connection->m_current_language_client = &client; 260} 261 262void ConnectionToServerWrapper::try_respawn_connection() 263{ 264 if (!m_respawn_allowed) 265 return; 266 267 dbgln("Respawning ConnectionToServer"); 268 create_connection(); 269 270 // After respawning the language-server, we have to send the content of open project files 271 // so the server's FileDB will be up-to-date. 272 for_each_open_file([this](ProjectFile const& file) { 273 if (file.code_document().language() != m_language) 274 return; 275 m_connection->async_set_file_content(file.code_document().file_path(), file.document().text()); 276 }); 277} 278 279}