LanguageClient.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "LanguageClient.h"
  7. #include "HackStudio.h"
  8. #include "ProjectDeclarations.h"
  9. #include <AK/String.h>
  10. #include <AK/Vector.h>
  11. #include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
  12. #include <LibGUI/Notification.h>
  13. namespace HackStudio {
  14. void ServerConnection::handle(const Messages::LanguageClient::AutoCompleteSuggestions& message)
  15. {
  16. if (!m_current_language_client) {
  17. dbgln("Language Server connection has no attached language client");
  18. return;
  19. }
  20. m_current_language_client->provide_autocomplete_suggestions(message.suggestions());
  21. }
  22. void ServerConnection::handle(const Messages::LanguageClient::DeclarationLocation& message)
  23. {
  24. if (!m_current_language_client) {
  25. dbgln("Language Server connection has no attached language client");
  26. return;
  27. }
  28. m_current_language_client->declaration_found(message.location().file, message.location().line, message.location().column);
  29. }
  30. void ServerConnection::die()
  31. {
  32. VERIFY(m_wrapper);
  33. // Wrapper destructs us here
  34. m_wrapper->on_crash();
  35. }
  36. void LanguageClient::open_file(const String& path, int fd)
  37. {
  38. if (!m_connection_wrapper.connection())
  39. return;
  40. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FileOpened(path, fd));
  41. }
  42. void LanguageClient::set_file_content(const String& path, const String& content)
  43. {
  44. if (!m_connection_wrapper.connection())
  45. return;
  46. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::SetFileContent(path, content));
  47. }
  48. void LanguageClient::insert_text(const String& path, const String& text, size_t line, size_t column)
  49. {
  50. if (!m_connection_wrapper.connection())
  51. return;
  52. // set_active_client();
  53. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FileEditInsertText(path, text, line, column));
  54. }
  55. void LanguageClient::remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column)
  56. {
  57. if (!m_connection_wrapper.connection())
  58. return;
  59. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FileEditRemoveText(path, from_line, from_column, to_line, to_column));
  60. }
  61. void LanguageClient::request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column)
  62. {
  63. if (!m_connection_wrapper.connection())
  64. return;
  65. set_active_client();
  66. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::AutoCompleteSuggestions(GUI::AutocompleteProvider::ProjectLocation { path, cursor_line, cursor_column }));
  67. }
  68. void LanguageClient::provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>& suggestions) const
  69. {
  70. if (on_autocomplete_suggestions)
  71. on_autocomplete_suggestions(suggestions);
  72. // Otherwise, drop it on the floor :shrug:
  73. }
  74. void LanguageClient::set_autocomplete_mode(const String& mode)
  75. {
  76. if (!m_connection_wrapper.connection())
  77. return;
  78. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::SetAutoCompleteMode(mode));
  79. }
  80. void LanguageClient::set_active_client()
  81. {
  82. if (!m_connection_wrapper.connection())
  83. return;
  84. m_connection_wrapper.set_active_client(*this);
  85. }
  86. HashMap<String, NonnullOwnPtr<ServerConnectionWrapper>> ServerConnectionInstances::s_instance_for_language;
  87. void ServerConnection::handle(const Messages::LanguageClient::DeclarationsInDocument& message)
  88. {
  89. ProjectDeclarations::the().set_declared_symbols(message.filename(), message.declarations());
  90. }
  91. void LanguageClient::search_declaration(const String& path, size_t line, size_t column)
  92. {
  93. if (!m_connection_wrapper.connection())
  94. return;
  95. set_active_client();
  96. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FindDeclaration(GUI::AutocompleteProvider::ProjectLocation { path, line, column }));
  97. }
  98. void LanguageClient::declaration_found(const String& file, size_t line, size_t column) const
  99. {
  100. if (!on_declaration_found) {
  101. dbgln("on_declaration_found callback is not set");
  102. return;
  103. }
  104. on_declaration_found(file, line, column);
  105. }
  106. void ServerConnectionInstances::set_instance_for_language(const String& language_name, NonnullOwnPtr<ServerConnectionWrapper>&& connection_wrapper)
  107. {
  108. s_instance_for_language.set(language_name, move(connection_wrapper));
  109. }
  110. void ServerConnectionInstances::remove_instance_for_language(const String& language_name)
  111. {
  112. s_instance_for_language.remove(language_name);
  113. }
  114. ServerConnectionWrapper* ServerConnectionInstances::get_instance_wrapper(const String& language_name)
  115. {
  116. if (auto instance = s_instance_for_language.get(language_name); instance.has_value()) {
  117. return const_cast<ServerConnectionWrapper*>(instance.value());
  118. }
  119. return nullptr;
  120. }
  121. void ServerConnectionWrapper::on_crash()
  122. {
  123. show_crash_notification();
  124. m_connection.clear();
  125. static constexpr int max_crash_frequency_seconds = 3;
  126. if (m_last_crash_timer.is_valid() && m_last_crash_timer.elapsed() / 1000 < max_crash_frequency_seconds) {
  127. dbgln("LanguageServer crash frequency is too high");
  128. m_respawn_allowed = false;
  129. show_frequenct_crashes_notification();
  130. } else {
  131. m_last_crash_timer.start();
  132. try_respawn_connection();
  133. }
  134. }
  135. void ServerConnectionWrapper::show_frequenct_crashes_notification() const
  136. {
  137. auto notification = GUI::Notification::construct();
  138. notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"));
  139. notification->set_title("LanguageServer Crashes too much!");
  140. notification->set_text("LanguageServer aided features will not be available in this session");
  141. notification->show();
  142. }
  143. void ServerConnectionWrapper::show_crash_notification() const
  144. {
  145. auto notification = GUI::Notification::construct();
  146. notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"));
  147. notification->set_title("Oops!");
  148. notification->set_text(String::formatted("LanguageServer has crashed"));
  149. notification->show();
  150. }
  151. ServerConnectionWrapper::ServerConnectionWrapper(const String& language_name, Function<NonnullRefPtr<ServerConnection>()> connection_creator)
  152. : m_language(language_from_name(language_name))
  153. , m_connection_creator(move(connection_creator))
  154. {
  155. create_connection();
  156. }
  157. void ServerConnectionWrapper::create_connection()
  158. {
  159. VERIFY(m_connection.is_null());
  160. m_connection = m_connection_creator();
  161. m_connection->set_wrapper(*this);
  162. m_connection->handshake();
  163. }
  164. ServerConnection* ServerConnectionWrapper::connection()
  165. {
  166. return m_connection.ptr();
  167. }
  168. void ServerConnectionWrapper::attach(LanguageClient& client)
  169. {
  170. m_connection->m_current_language_client = &client;
  171. }
  172. void ServerConnectionWrapper::detach()
  173. {
  174. m_connection->m_current_language_client.clear();
  175. }
  176. void ServerConnectionWrapper::set_active_client(LanguageClient& client)
  177. {
  178. m_connection->m_current_language_client = &client;
  179. }
  180. void ServerConnectionWrapper::try_respawn_connection()
  181. {
  182. if (!m_respawn_allowed)
  183. return;
  184. dbgln("Respawning ServerConnection");
  185. create_connection();
  186. // After respawning the language-server, we have to flush the content of the project files
  187. // so the server's FileDB will be up-to-date.
  188. project().for_each_text_file([this](const ProjectFile& file) {
  189. if (file.code_document().language() != m_language)
  190. return;
  191. m_connection->post_message(Messages::LanguageServer::SetFileContent(file.code_document().file_path(), file.document().text()));
  192. });
  193. }
  194. }