LanguageClient.cpp 8.3 KB

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