LanguageClient.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /*
  2. * Copyright (c) 2020, the SerenityOS developers.
  3. * All rights reserved.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. *
  8. * 1. Redistributions of source code must retain the above copyright notice, this
  9. * list of conditions and the following disclaimer.
  10. *
  11. * 2. Redistributions in binary form must reproduce the above copyright notice,
  12. * this list of conditions and the following disclaimer in the documentation
  13. * and/or other materials provided with the distribution.
  14. *
  15. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  16. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  18. * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  19. * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  21. * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  22. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  23. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. #include "LanguageClient.h"
  27. #include "HackStudio.h"
  28. #include "ProjectDeclarations.h"
  29. #include <AK/String.h>
  30. #include <AK/Vector.h>
  31. #include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
  32. #include <LibGUI/Notification.h>
  33. namespace HackStudio {
  34. void ServerConnection::handle(const Messages::LanguageClient::AutoCompleteSuggestions& message)
  35. {
  36. if (!m_current_language_client) {
  37. dbgln("Language Server connection has no attached language client");
  38. return;
  39. }
  40. m_current_language_client->provide_autocomplete_suggestions(message.suggestions());
  41. }
  42. void ServerConnection::handle(const Messages::LanguageClient::DeclarationLocation& message)
  43. {
  44. if (!m_current_language_client) {
  45. dbgln("Language Server connection has no attached language client");
  46. return;
  47. }
  48. m_current_language_client->declaration_found(message.location().file, message.location().line, message.location().column);
  49. }
  50. void ServerConnection::die()
  51. {
  52. VERIFY(m_wrapper);
  53. // Wrapper destructs us here
  54. m_wrapper->on_crash();
  55. }
  56. void LanguageClient::open_file(const String& path, int fd)
  57. {
  58. if (!m_connection_wrapper.connection())
  59. return;
  60. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FileOpened(path, fd));
  61. }
  62. void LanguageClient::set_file_content(const String& path, const String& content)
  63. {
  64. if (!m_connection_wrapper.connection())
  65. return;
  66. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::SetFileContent(path, content));
  67. }
  68. void LanguageClient::insert_text(const String& path, const String& text, size_t line, size_t column)
  69. {
  70. if (!m_connection_wrapper.connection())
  71. return;
  72. // set_active_client();
  73. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FileEditInsertText(path, text, line, column));
  74. }
  75. void LanguageClient::remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column)
  76. {
  77. if (!m_connection_wrapper.connection())
  78. return;
  79. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FileEditRemoveText(path, from_line, from_column, to_line, to_column));
  80. }
  81. void LanguageClient::request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column)
  82. {
  83. if (!m_connection_wrapper.connection())
  84. return;
  85. set_active_client();
  86. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::AutoCompleteSuggestions(GUI::AutocompleteProvider::ProjectLocation { path, cursor_line, cursor_column }));
  87. }
  88. void LanguageClient::provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>& suggestions) const
  89. {
  90. if (on_autocomplete_suggestions)
  91. on_autocomplete_suggestions(suggestions);
  92. // Otherwise, drop it on the floor :shrug:
  93. }
  94. void LanguageClient::set_autocomplete_mode(const String& mode)
  95. {
  96. if (!m_connection_wrapper.connection())
  97. return;
  98. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::SetAutoCompleteMode(mode));
  99. }
  100. void LanguageClient::set_active_client()
  101. {
  102. if (!m_connection_wrapper.connection())
  103. return;
  104. m_connection_wrapper.set_active_client(*this);
  105. }
  106. HashMap<String, NonnullOwnPtr<ServerConnectionWrapper>> ServerConnectionInstances::s_instance_for_language;
  107. void ServerConnection::handle(const Messages::LanguageClient::DeclarationsInDocument& message)
  108. {
  109. ProjectDeclarations::the().set_declared_symbols(message.filename(), message.declarations());
  110. }
  111. void LanguageClient::search_declaration(const String& path, size_t line, size_t column)
  112. {
  113. if (!m_connection_wrapper.connection())
  114. return;
  115. set_active_client();
  116. m_connection_wrapper.connection()->post_message(Messages::LanguageServer::FindDeclaration(GUI::AutocompleteProvider::ProjectLocation { path, line, column }));
  117. }
  118. void LanguageClient::declaration_found(const String& file, size_t line, size_t column) const
  119. {
  120. if (!on_declaration_found) {
  121. dbgln("on_declaration_found callback is not set");
  122. return;
  123. }
  124. on_declaration_found(file, line, column);
  125. }
  126. void ServerConnectionInstances::set_instance_for_language(const String& language_name, NonnullOwnPtr<ServerConnectionWrapper>&& connection_wrapper)
  127. {
  128. s_instance_for_language.set(language_name, move(connection_wrapper));
  129. }
  130. void ServerConnectionInstances::remove_instance_for_language(const String& language_name)
  131. {
  132. s_instance_for_language.remove(language_name);
  133. }
  134. ServerConnectionWrapper* ServerConnectionInstances::get_instance_wrapper(const String& language_name)
  135. {
  136. if (auto instance = s_instance_for_language.get(language_name); instance.has_value()) {
  137. return const_cast<ServerConnectionWrapper*>(instance.value());
  138. }
  139. return nullptr;
  140. }
  141. void ServerConnectionWrapper::on_crash()
  142. {
  143. show_crash_notification();
  144. m_connection.clear();
  145. static constexpr int max_crash_frequency_seconds = 3;
  146. if (m_last_crash_timer.is_valid() && m_last_crash_timer.elapsed() / 1000 < max_crash_frequency_seconds) {
  147. dbgln("LanguageServer crash frequency is too high");
  148. m_respawn_allowed = false;
  149. show_frequenct_crashes_notification();
  150. } else {
  151. m_last_crash_timer.start();
  152. try_respawn_connection();
  153. }
  154. }
  155. void ServerConnectionWrapper::show_frequenct_crashes_notification() const
  156. {
  157. auto notification = GUI::Notification::construct();
  158. notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"));
  159. notification->set_title("LanguageServer Crashes too much!");
  160. notification->set_text("LanguageServer aided features will not be available in this session");
  161. notification->show();
  162. }
  163. void ServerConnectionWrapper::show_crash_notification() const
  164. {
  165. auto notification = GUI::Notification::construct();
  166. notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"));
  167. notification->set_title("Oops!");
  168. notification->set_text(String::formatted("LanguageServer has crashed"));
  169. notification->show();
  170. }
  171. ServerConnectionWrapper::ServerConnectionWrapper(const String& language_name, Function<NonnullRefPtr<ServerConnection>()> connection_creator)
  172. : m_language(language_from_name(language_name))
  173. , m_connection_creator(move(connection_creator))
  174. {
  175. create_connection();
  176. }
  177. void ServerConnectionWrapper::create_connection()
  178. {
  179. VERIFY(m_connection.is_null());
  180. m_connection = m_connection_creator();
  181. m_connection->set_wrapper(*this);
  182. m_connection->handshake();
  183. }
  184. ServerConnection* ServerConnectionWrapper::connection()
  185. {
  186. return m_connection.ptr();
  187. }
  188. void ServerConnectionWrapper::attach(LanguageClient& client)
  189. {
  190. m_connection->m_current_language_client = &client;
  191. }
  192. void ServerConnectionWrapper::detach()
  193. {
  194. m_connection->m_current_language_client.clear();
  195. }
  196. void ServerConnectionWrapper::set_active_client(LanguageClient& client)
  197. {
  198. m_connection->m_current_language_client = &client;
  199. }
  200. void ServerConnectionWrapper::try_respawn_connection()
  201. {
  202. if (!m_respawn_allowed)
  203. return;
  204. dbgln("Respawning ServerConnection");
  205. create_connection();
  206. // After respawning the language-server, we have to flush the content of the project files
  207. // so the server's FileDB will be up-to-date.
  208. project().for_each_text_file([this](const ProjectFile& file) {
  209. if (file.code_document().language() != m_language)
  210. return;
  211. m_connection->post_message(Messages::LanguageServer::SetFileContent(file.code_document().file_path(), file.document().text()));
  212. });
  213. }
  214. }