diff --git a/Userland/DevTools/HackStudio/LanguageClient.cpp b/Userland/DevTools/HackStudio/LanguageClient.cpp index 1afc59bd60..f320bf08e4 100644 --- a/Userland/DevTools/HackStudio/LanguageClient.cpp +++ b/Userland/DevTools/HackStudio/LanguageClient.cpp @@ -62,6 +62,7 @@ void LanguageClient::remove_text(const String& path, size_t from_line, size_t fr void LanguageClient::request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column) { + set_active_client(); m_connection.post_message(Messages::LanguageServer::AutoCompleteSuggestions(path, cursor_line, cursor_column)); } @@ -78,4 +79,9 @@ void LanguageClient::set_autocomplete_mode(const String& mode) m_connection.post_message(Messages::LanguageServer::SetAutoCompleteMode(mode)); } +void LanguageClient::set_active_client() +{ + m_connection.attach(*this); +} + } diff --git a/Userland/DevTools/HackStudio/LanguageClient.h b/Userland/DevTools/HackStudio/LanguageClient.h index 51804d8d0f..b5a4085bf5 100644 --- a/Userland/DevTools/HackStudio/LanguageClient.h +++ b/Userland/DevTools/HackStudio/LanguageClient.h @@ -45,9 +45,10 @@ class ServerConnection : public IPC::ServerConnection , public LanguageClientEndpoint { public: - ServerConnection(const StringView& socket) + ServerConnection(const StringView& socket, const String& project_path) : IPC::ServerConnection(*this, socket) { + m_project_path = project_path; } void attach(LanguageClient& client) @@ -62,7 +63,7 @@ public: virtual void handshake() override { - send_sync(); + send_sync(m_project_path); } WeakPtr language_client() { return m_language_client; } @@ -75,7 +76,7 @@ public: if (auto instance = s_instances_for_projects.get(key); instance.has_value()) return *instance.value(); - auto connection = ConcreteType::construct(); + auto connection = ConcreteType::construct(project_path); connection->handshake(); s_instances_for_projects.set(key, *connection); return *connection; @@ -84,6 +85,7 @@ public: protected: virtual void handle(const Messages::LanguageClient::AutoCompleteSuggestions&) override; + String m_project_path; WeakPtr m_language_client; }; @@ -94,16 +96,19 @@ public: , m_server_connection(move(connection)) { m_previous_client = m_connection.language_client(); + ASSERT(m_previous_client.ptr() != this); m_connection.attach(*this); } virtual ~LanguageClient() { m_connection.detach(); + ASSERT(m_previous_client.ptr() != this); if (m_previous_client) m_connection.attach(*m_previous_client); } + void set_active_client(); virtual void open_file(const String& path, int fd); virtual void set_file_content(const String& path, const String& content); virtual void insert_text(const String& path, const String& text, size_t line, size_t column); diff --git a/Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h b/Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h index bcebdb419e..89266e37ca 100644 --- a/Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h +++ b/Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h @@ -32,16 +32,16 @@ #include #include -#define LANGUAGE_CLIENT(namespace_, socket_name) \ - namespace namespace_ { \ - class ServerConnection : public HackStudio::ServerConnection { \ - C_OBJECT(ServerConnection) \ - private: \ - ServerConnection() \ - : HackStudio::ServerConnection("/tmp/portal/language/" #socket_name) \ - { \ - } \ - }; \ +#define LANGUAGE_CLIENT(namespace_, socket_name) \ + namespace namespace_ { \ + class ServerConnection : public HackStudio::ServerConnection { \ + C_OBJECT(ServerConnection) \ + private: \ + ServerConnection(const String& project_path) \ + : HackStudio::ServerConnection("/tmp/portal/language/" #socket_name, project_path) \ + { \ + } \ + }; \ } namespace LanguageClients { diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp index 4f68c06f07..c6dd71cf48 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp @@ -52,8 +52,17 @@ void ClientConnection::die() exit(0); } -OwnPtr ClientConnection::handle(const Messages::LanguageServer::Greet&) +OwnPtr ClientConnection::handle(const Messages::LanguageServer::Greet& message) { + m_filedb.set_project_root(message.project_root()); + if (unveil(message.project_root().characters(), "r") < 0) { + perror("unveil"); + exit(1); + } + if (unveil(nullptr, nullptr) < 0) { + perror("unveil"); + exit(1); + } return make(); } diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/FileDB.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/FileDB.cpp new file mode 100644 index 0000000000..f354a2beb8 --- /dev/null +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/FileDB.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2021, Itamar S. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "FileDB.h" + +#include +#include + +RefPtr FileDB::get(const String& file_name) const +{ + auto absolute_path = to_absolute_path(file_name); + auto document_optional = m_open_files.get(absolute_path); + if (!document_optional.has_value()) + return create_from_filesystem(absolute_path); + + return document_optional.value(); +} + +RefPtr FileDB::get(const String& file_name) +{ + auto absolute_path = to_absolute_path(file_name); + return adopt(*const_cast(reinterpret_cast(this)->get(absolute_path).leak_ref())); +} + +bool FileDB::is_open(String file_name) const +{ + return m_open_files.contains(to_absolute_path(file_name)); +} + +bool FileDB::add(const String& file_name, int fd) +{ + auto document = create_from_fd(fd); + if (!document) + return false; + + m_open_files.set(to_absolute_path(file_name), document.release_nonnull()); + return true; +} + +String FileDB::to_absolute_path(const String& file_name) const +{ + if (LexicalPath { file_name }.is_absolute()) { + return file_name; + } + ASSERT(!m_project_root.is_null()); + return LexicalPath { String::formatted("{}/{}", m_project_root, file_name) }.string(); +} + +RefPtr FileDB::create_from_filesystem(const String& file_name) const +{ + auto file = Core::File::open(to_absolute_path(file_name), Core::IODevice::ReadOnly); + if (file.is_error()) { + dbgln("failed to create document for {} from filesystem", file_name); + return nullptr; + } + return create_from_file(*file.value()); +} + +RefPtr FileDB::create_from_fd(int fd) const +{ + auto file = Core::File::construct(); + if (!file->open(fd, Core::IODevice::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes)) { + errno = file->error(); + perror("open"); + dbgln("Failed to open project file"); + return nullptr; + } + return create_from_file(*file); +} + +class DefaultDocumentClient final : public GUI::TextDocument::Client { +public: + virtual ~DefaultDocumentClient() override = default; + virtual void document_did_append_line() override {}; + virtual void document_did_insert_line(size_t) override {}; + virtual void document_did_remove_line(size_t) override {}; + virtual void document_did_remove_all_lines() override {}; + virtual void document_did_change() override {}; + virtual void document_did_set_text() override {}; + virtual void document_did_set_cursor(const GUI::TextPosition&) override {}; + + virtual bool is_automatic_indentation_enabled() const override { return false; } + virtual int soft_tab_width() const override { return 4; } +}; +static DefaultDocumentClient s_default_document_client; + +RefPtr FileDB::create_from_file(Core::File& file) const +{ + auto content = file.read_all(); + StringView content_view(content); + auto document = GUI::TextDocument::create(&s_default_document_client); + document->set_text(content_view); + return document; +} + +void FileDB::on_file_edit_insert_text(const String& file_name, const String& inserted_text, size_t start_line, size_t start_column) +{ + auto document = get(file_name); + if (!document) { + dbgln("file {} has not been opened", file_name); + return; + } + GUI::TextPosition start_position { start_line, start_column }; + document->insert_at(start_position, inserted_text, &s_default_document_client); + +#if FILE_CONTENT_DEBUG + dbgln("{}", document->text()); +#endif +} + +void FileDB::on_file_edit_remove_text(const String& file_name, size_t start_line, size_t start_column, size_t end_line, size_t end_column) +{ + auto document = get(file_name); + if (!document) { + dbgln("file {} has not been opened", file_name); + return; + } + GUI::TextPosition start_position { start_line, start_column }; + GUI::TextRange range { + GUI::TextPosition { start_line, start_column }, + GUI::TextPosition { end_line, end_column } + }; + + document->remove(range); +#if FILE_CONTENT_DEBUG + dbgln("{}", document->text()); +#endif +} diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/FileDB.h b/Userland/DevTools/HackStudio/LanguageServers/Cpp/FileDB.h new file mode 100644 index 0000000000..7cace78db8 --- /dev/null +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/FileDB.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2021, Itamar S. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include +#include + +class FileDB final { +public: + RefPtr get(const String& file_name) const; + RefPtr get(const String& file_name); + bool add(const String& file_name, int fd); + + void set_project_root(const String& root_path) { m_project_root = root_path; } + + void on_file_edit_insert_text(const String& file_name, const String& inserted_text, size_t start_line, size_t start_column); + void on_file_edit_remove_text(const String& file_name, size_t start_line, size_t start_column, size_t end_line, size_t end_column); + String to_absolute_path(const String& file_name) const; + bool is_open(String file_name) const; + +private: + RefPtr create_from_filesystem(const String& file_name) const; + RefPtr create_from_fd(int fd) const; + RefPtr create_from_file(Core::File&) const; + +private: + HashMap> m_open_files; + String m_project_root; +}; diff --git a/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp b/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp index 524d6d6f50..a15bd30fd0 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp +++ b/Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Itamar S. + * Copyright (c) 2021, Itamar S. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,20 +36,21 @@ int main(int, char**) { Core::EventLoop event_loop; - if (pledge("stdio unix recvfd", nullptr) < 0) { + if (pledge("stdio unix recvfd rpath ", nullptr) < 0) { perror("pledge"); return 1; } auto socket = Core::LocalSocket::take_over_accepted_socket_from_system_server(); IPC::new_client_connection(socket.release_nonnull(), 1); - if (pledge("stdio recvfd", nullptr) < 0) { + if (pledge("stdio recvfd rpath", nullptr) < 0) { perror("pledge"); return 1; } - if (unveil(nullptr, nullptr) < 0) { + + if (unveil("/usr/include", "r") < 0) perror("unveil"); - return 1; - } + + // unveil will be sealed later, when we know the project's root path. return event_loop.exec(); } diff --git a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc index ae4f55cf89..1eada48bf0 100644 --- a/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc +++ b/Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc @@ -1,6 +1,6 @@ endpoint LanguageServer = 8001 { - Greet() => () + Greet(String project_root) => () FileOpened(String file_name, IPC::File file) =| FileEditInsertText(String file_name, String text, i32 start_line, i32 start_column) =|