LanguageClient.cpp 8.3 KB

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