LanguageClient.cpp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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::auto_complete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>& suggestions)
  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(suggestions);
  21. }
  22. void ServerConnection::declaration_location(const GUI::AutocompleteProvider::ProjectLocation& location)
  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(location.file, location.line, 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()->async_file_opened(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()->async_set_file_content(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()->async_file_edit_insert_text(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()->async_file_edit_remove_text(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()->async_auto_complete_suggestions(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_active_client()
  75. {
  76. if (!m_connection_wrapper.connection())
  77. return;
  78. m_connection_wrapper.set_active_client(*this);
  79. }
  80. HashMap<String, NonnullOwnPtr<ServerConnectionWrapper>> ServerConnectionInstances::s_instance_for_language;
  81. void ServerConnection::declarations_in_document(const String& filename, const Vector<GUI::AutocompleteProvider::Declaration>& declarations)
  82. {
  83. ProjectDeclarations::the().set_declared_symbols(filename, declarations);
  84. }
  85. void LanguageClient::search_declaration(const String& path, size_t line, size_t column)
  86. {
  87. if (!m_connection_wrapper.connection())
  88. return;
  89. set_active_client();
  90. m_connection_wrapper.connection()->async_find_declaration(GUI::AutocompleteProvider::ProjectLocation { path, line, column });
  91. }
  92. void LanguageClient::declaration_found(const String& file, size_t line, size_t column) const
  93. {
  94. if (!on_declaration_found) {
  95. dbgln("on_declaration_found callback is not set");
  96. return;
  97. }
  98. on_declaration_found(file, line, column);
  99. }
  100. void ServerConnectionInstances::set_instance_for_language(const String& language_name, NonnullOwnPtr<ServerConnectionWrapper>&& connection_wrapper)
  101. {
  102. s_instance_for_language.set(language_name, move(connection_wrapper));
  103. }
  104. void ServerConnectionInstances::remove_instance_for_language(const String& language_name)
  105. {
  106. s_instance_for_language.remove(language_name);
  107. }
  108. ServerConnectionWrapper* ServerConnectionInstances::get_instance_wrapper(const String& language_name)
  109. {
  110. if (auto instance = s_instance_for_language.get(language_name); instance.has_value()) {
  111. return const_cast<ServerConnectionWrapper*>(instance.value());
  112. }
  113. return nullptr;
  114. }
  115. void ServerConnectionWrapper::on_crash()
  116. {
  117. show_crash_notification();
  118. m_connection.clear();
  119. static constexpr int max_crash_frequency_seconds = 3;
  120. if (m_last_crash_timer.is_valid() && m_last_crash_timer.elapsed() / 1000 < max_crash_frequency_seconds) {
  121. dbgln("LanguageServer crash frequency is too high");
  122. m_respawn_allowed = false;
  123. show_frequenct_crashes_notification();
  124. } else {
  125. m_last_crash_timer.start();
  126. try_respawn_connection();
  127. }
  128. }
  129. void ServerConnectionWrapper::show_frequenct_crashes_notification() const
  130. {
  131. auto notification = GUI::Notification::construct();
  132. notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"));
  133. notification->set_title("LanguageServer Crashes too much!");
  134. notification->set_text("LanguageServer aided features will not be available in this session");
  135. notification->show();
  136. }
  137. void ServerConnectionWrapper::show_crash_notification() const
  138. {
  139. auto notification = GUI::Notification::construct();
  140. notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"));
  141. notification->set_title("Oops!");
  142. notification->set_text(String::formatted("LanguageServer has crashed"));
  143. notification->show();
  144. }
  145. ServerConnectionWrapper::ServerConnectionWrapper(const String& language_name, Function<NonnullRefPtr<ServerConnection>()> connection_creator)
  146. : m_language(language_from_name(language_name))
  147. , m_connection_creator(move(connection_creator))
  148. {
  149. create_connection();
  150. }
  151. void ServerConnectionWrapper::create_connection()
  152. {
  153. VERIFY(m_connection.is_null());
  154. m_connection = m_connection_creator();
  155. m_connection->set_wrapper(*this);
  156. m_connection->handshake();
  157. }
  158. ServerConnection* ServerConnectionWrapper::connection()
  159. {
  160. return m_connection.ptr();
  161. }
  162. void ServerConnectionWrapper::attach(LanguageClient& client)
  163. {
  164. m_connection->m_current_language_client = &client;
  165. }
  166. void ServerConnectionWrapper::detach()
  167. {
  168. m_connection->m_current_language_client.clear();
  169. }
  170. void ServerConnectionWrapper::set_active_client(LanguageClient& client)
  171. {
  172. m_connection->m_current_language_client = &client;
  173. }
  174. void ServerConnectionWrapper::try_respawn_connection()
  175. {
  176. if (!m_respawn_allowed)
  177. return;
  178. dbgln("Respawning ServerConnection");
  179. create_connection();
  180. // After respawning the language-server, we have to flush the content of the project files
  181. // so the server's FileDB will be up-to-date.
  182. project().for_each_text_file([this](const ProjectFile& file) {
  183. if (file.code_document().language() != m_language)
  184. return;
  185. m_connection->async_set_file_content(file.code_document().file_path(), file.document().text());
  186. });
  187. }
  188. }