Browse Source

HackStudio: Handle crash of the LanguageServer gracefully

Previously, HackStudio exited whenever a LanguageServer crashed.

Now, we disconnect all clients from that language server instance and
show a nice notification.
Itamar 4 năm trước cách đây
mục cha
commit
715933ce8b

+ 70 - 7
Userland/DevTools/HackStudio/LanguageClient.cpp

@@ -28,6 +28,7 @@
 #include <AK/String.h>
 #include <AK/Vector.h>
 #include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
+#include <LibGUI/Notification.h>
 
 namespace HackStudio {
 
@@ -40,34 +41,54 @@ void ServerConnection::handle(const Messages::LanguageClient::AutoCompleteSugges
     m_language_client->provide_autocomplete_suggestions(message.suggestions());
 }
 
+void ServerConnection::die()
+{
+    dbgln("ServerConnection::die()");
+    if (!m_language_client)
+        return;
+    m_language_client->on_server_crash();
+}
+
 void LanguageClient::open_file(const String& path, int fd)
 {
-    m_connection.post_message(Messages::LanguageServer::FileOpened(path, fd));
+    if (!m_server_connection)
+        return;
+    m_server_connection->post_message(Messages::LanguageServer::FileOpened(path, fd));
 }
 
 void LanguageClient::set_file_content(const String& path, const String& content)
 {
-    m_connection.post_message(Messages::LanguageServer::SetFileContent(path, content));
+    if (!m_server_connection)
+        return;
+    m_server_connection->post_message(Messages::LanguageServer::SetFileContent(path, content));
 }
 
 void LanguageClient::insert_text(const String& path, const String& text, size_t line, size_t column)
 {
-    m_connection.post_message(Messages::LanguageServer::FileEditInsertText(path, text, line, column));
+    if (!m_server_connection)
+        return;
+    m_server_connection->post_message(Messages::LanguageServer::FileEditInsertText(path, text, line, column));
 }
 
 void LanguageClient::remove_text(const String& path, size_t from_line, size_t from_column, size_t to_line, size_t to_column)
 {
-    m_connection.post_message(Messages::LanguageServer::FileEditRemoveText(path, from_line, from_column, to_line, to_column));
+    if (!m_server_connection)
+        return;
+    m_server_connection->post_message(Messages::LanguageServer::FileEditRemoveText(path, from_line, from_column, to_line, to_column));
 }
 
 void LanguageClient::request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column)
 {
+    if (!m_server_connection)
+        return;
     set_active_client();
-    m_connection.post_message(Messages::LanguageServer::AutoCompleteSuggestions(path, cursor_line, cursor_column));
+    m_server_connection->post_message(Messages::LanguageServer::AutoCompleteSuggestions(path, cursor_line, cursor_column));
 }
 
 void LanguageClient::provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>& suggestions)
 {
+    if (!m_server_connection)
+        return;
     if (on_autocomplete_suggestions)
         on_autocomplete_suggestions(suggestions);
 
@@ -76,12 +97,54 @@ void LanguageClient::provide_autocomplete_suggestions(const Vector<GUI::Autocomp
 
 void LanguageClient::set_autocomplete_mode(const String& mode)
 {
-    m_connection.post_message(Messages::LanguageServer::SetAutoCompleteMode(mode));
+    if (!m_server_connection)
+        return;
+    m_server_connection->post_message(Messages::LanguageServer::SetAutoCompleteMode(mode));
 }
 
 void LanguageClient::set_active_client()
 {
-    m_connection.attach(*this);
+    if (!m_server_connection)
+        return;
+    m_server_connection->attach(*this);
+}
+
+void LanguageClient::on_server_crash()
+{
+    ASSERT(m_server_connection);
+    auto project_path = m_server_connection->projcet_path();
+    ServerConnection::remove_instance_for_project(project_path);
+    m_server_connection = nullptr;
+
+    auto notification = GUI::Notification::construct();
+
+    notification->set_icon(Gfx::Bitmap::load_from_file("/res/icons/32x32/app-hack-studio.png"));
+    notification->set_title("Oops!");
+    notification->set_text(String::formatted("LanguageServer for {} crashed", project_path));
+    notification->show();
+}
+
+HashMap<String, NonnullRefPtr<ServerConnection>> ServerConnection::s_instances_for_projects;
+
+RefPtr<ServerConnection> ServerConnection::instance_for_project(const String& project_path)
+{
+    auto key = LexicalPath { project_path }.string();
+    auto value = s_instances_for_projects.get(key);
+    if (!value.has_value())
+        return nullptr;
+    return *value.value();
+}
+
+void ServerConnection::set_instance_for_project(const String& project_path, NonnullRefPtr<ServerConnection>&& instance)
+{
+    auto key = LexicalPath { project_path }.string();
+    s_instances_for_projects.set(key, move(instance));
+}
+
+void ServerConnection::remove_instance_for_project(const String& project_path)
+{
+    auto key = LexicalPath { project_path }.string();
+    s_instances_for_projects.remove(key);
 }
 
 }

+ 18 - 10
Userland/DevTools/HackStudio/LanguageClient.h

@@ -67,45 +67,53 @@ public:
     }
 
     WeakPtr<LanguageClient> language_client() { return m_language_client; }
+    const String& projcet_path() const { return m_project_path; }
 
     template<typename ConcreteType>
     static NonnullRefPtr<ServerConnection> get_or_create(const String& project_path)
     {
-        static HashMap<String, NonnullRefPtr<ConcreteType>> s_instances_for_projects;
         auto key = LexicalPath { project_path }.string();
         if (auto instance = s_instances_for_projects.get(key); instance.has_value())
             return *instance.value();
 
         auto connection = ConcreteType::construct(project_path);
         connection->handshake();
-        s_instances_for_projects.set(key, *connection);
+        set_instance_for_project(project_path, *connection);
         return *connection;
     }
 
+    static RefPtr<ServerConnection> instance_for_project(const String& project_path);
+    static void set_instance_for_project(const String& project_path, NonnullRefPtr<ServerConnection>&&);
+    static void remove_instance_for_project(const String& project_path);
+
+    virtual void die();
+
 protected:
     virtual void handle(const Messages::LanguageClient::AutoCompleteSuggestions&) override;
 
     String m_project_path;
     WeakPtr<LanguageClient> m_language_client;
+
+private:
+    static HashMap<String, NonnullRefPtr<ServerConnection>> s_instances_for_projects;
 };
 
 class LanguageClient : public Weakable<LanguageClient> {
 public:
     explicit LanguageClient(NonnullRefPtr<ServerConnection>&& connection)
-        : m_connection(*connection)
-        , m_server_connection(move(connection))
+        : m_server_connection(move(connection))
     {
-        m_previous_client = m_connection.language_client();
+        m_previous_client = m_server_connection->language_client();
         ASSERT(m_previous_client.ptr() != this);
-        m_connection.attach(*this);
+        m_server_connection->attach(*this);
     }
 
     virtual ~LanguageClient()
     {
-        m_connection.detach();
+        m_server_connection->detach();
         ASSERT(m_previous_client.ptr() != this);
         if (m_previous_client)
-            m_connection.attach(*m_previous_client);
+            m_server_connection->attach(*m_previous_client);
     }
 
     void set_active_client();
@@ -117,12 +125,12 @@ public:
     virtual void set_autocomplete_mode(const String& mode);
 
     void provide_autocomplete_suggestions(const Vector<GUI::AutocompleteProvider::Entry>&);
+    void on_server_crash();
 
     Function<void(Vector<GUI::AutocompleteProvider::Entry>)> on_autocomplete_suggestions;
 
 private:
-    ServerConnection& m_connection;
-    NonnullRefPtr<ServerConnection> m_server_connection;
+    WeakPtr<ServerConnection> m_server_connection;
     WeakPtr<LanguageClient> m_previous_client;
 };