LanguageClient.cpp 9.1 KB

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