diff options
author | AnotherTest <ali.mpfard@gmail.com> | 2020-10-03 17:25:51 +0330 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-10-04 23:12:28 +0200 |
commit | 6b55b007dda31777507a3ec75b369f7406fd1d21 (patch) | |
tree | 8949c9c12c4b61cdeb4c4cd19808ec5253fbc6e4 /DevTools/HackStudio | |
parent | 7f3e1fa826d51bba80bddd71a68fff25f03fdbac (diff) | |
download | serenity-6b55b007dda31777507a3ec75b369f7406fd1d21.zip |
HackStudio: Add a Shell language server
Diffstat (limited to 'DevTools/HackStudio')
10 files changed, 451 insertions, 4 deletions
diff --git a/DevTools/HackStudio/Editor.cpp b/DevTools/HackStudio/Editor.cpp index 13e07f4188..da06015855 100644 --- a/DevTools/HackStudio/Editor.cpp +++ b/DevTools/HackStudio/Editor.cpp @@ -483,6 +483,7 @@ void Editor::set_document(GUI::TextDocument& doc) break; case Language::Shell: set_syntax_highlighter(make<GUI::ShellSyntaxHighlighter>()); + m_language_client = get_language_client<LanguageClients::Shell::ServerConnection>(project().root_directory()); break; default: set_syntax_highlighter(nullptr); diff --git a/DevTools/HackStudio/LanguageClients/CMakeLists.txt b/DevTools/HackStudio/LanguageClients/CMakeLists.txt index 8ffd97ebb7..e06c45f98c 100644 --- a/DevTools/HackStudio/LanguageClients/CMakeLists.txt +++ b/DevTools/HackStudio/LanguageClients/CMakeLists.txt @@ -1,4 +1,4 @@ - set(GENERATED_SOURCES - ../../LanguageServers/LanguageServerEndpoint.h - ../../LanguageServers/LanguageClientEndpoint.h - ) +set(GENERATED_SOURCES + ../../LanguageServers/LanguageServerEndpoint.h + ../../LanguageServers/LanguageClientEndpoint.h +) diff --git a/DevTools/HackStudio/LanguageClients/ServerConnections.h b/DevTools/HackStudio/LanguageClients/ServerConnections.h index cab9404f66..89266e37ca 100644 --- a/DevTools/HackStudio/LanguageClients/ServerConnections.h +++ b/DevTools/HackStudio/LanguageClients/ServerConnections.h @@ -47,6 +47,7 @@ namespace LanguageClients { LANGUAGE_CLIENT(Cpp, cpp) +LANGUAGE_CLIENT(Shell, shell) } diff --git a/DevTools/HackStudio/LanguageServers/CMakeLists.txt b/DevTools/HackStudio/LanguageServers/CMakeLists.txt index 156dbe15c1..afcc0118d0 100644 --- a/DevTools/HackStudio/LanguageServers/CMakeLists.txt +++ b/DevTools/HackStudio/LanguageServers/CMakeLists.txt @@ -2,3 +2,4 @@ compile_ipc(LanguageServer.ipc LanguageServerEndpoint.h) compile_ipc(LanguageClient.ipc LanguageClientEndpoint.h) add_subdirectory(Cpp) +add_subdirectory(Shell) diff --git a/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp new file mode 100644 index 0000000000..fb39ca415a --- /dev/null +++ b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * 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 "AutoComplete.h" +#include <AK/HashTable.h> +#include <LibLine/SuggestionManager.h> +#include <Shell/AST.h> +#include <Shell/Parser.h> +#include <Shell/Shell.h> + +// #define DEBUG_AUTOCOMPLETE + +namespace LanguageServers::Shell { + +Vector<AutoCompleteResponse> AutoComplete::get_suggestions(const String& code, size_t offset) +{ + // FIXME: No need to reparse this every time! + auto ast = ::Shell::Parser { code }.parse(); + if (!ast) + return {}; + +#ifdef DEBUG_AUTOCOMPLETE + dbg() << "Complete '" << code << "': "; + ast->dump(1); + dbg() << "At offset " << offset; +#endif + + auto result = ast->complete_for_editor(m_shell, offset); + Vector<AutoCompleteResponse> completions; + for (auto& entry : result) { +#ifdef DEBUG_AUTOCOMPLETE + dbg() << "Suggestion: '" << entry.text_string << "' starting at " << entry.input_offset; +#endif + completions.append({ entry.text_string, entry.input_offset }); + } + + return completions; +} + +} diff --git a/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h new file mode 100644 index 0000000000..924864bcf4 --- /dev/null +++ b/DevTools/HackStudio/LanguageServers/Shell/AutoComplete.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * 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 <AK/String.h> +#include <AK/Vector.h> +#include <DevTools/HackStudio/AutoCompleteResponse.h> +#include <LibGUI/TextPosition.h> +#include <Shell/Shell.h> + +namespace LanguageServers::Shell { + +using namespace ::HackStudio; + +class AutoComplete { +public: + AutoComplete() + : m_shell(::Shell::Shell::construct()) + { + } + + Vector<AutoCompleteResponse> get_suggestions(const String& code, size_t autocomplete_position); + +private: + NonnullRefPtr<::Shell::Shell> m_shell; +}; + +} diff --git a/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt b/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt new file mode 100644 index 0000000000..144a4526a3 --- /dev/null +++ b/DevTools/HackStudio/LanguageServers/Shell/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SOURCES + ClientConnection.cpp + main.cpp + AutoComplete.cpp +) + +set(GENERATED_SOURCES + ../LanguageServerEndpoint.h + ../LanguageClientEndpoint.h) + +serenity_bin(ShellLanguageServer) + +# We link with LibGUI because we use GUI::TextDocument to update +# the content of files according to the edit actions we receive over IPC. +target_link_libraries(ShellLanguageServer LibIPC LibShell LibGUI) diff --git a/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.cpp b/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.cpp new file mode 100644 index 0000000000..5e0aba2b35 --- /dev/null +++ b/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.cpp @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * 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 "ClientConnection.h" +#include "AutoComplete.h" +#include <AK/HashMap.h> +#include <LibCore/File.h> +#include <LibGUI/TextDocument.h> + +// #define DEBUG_SH_LANGUAGE_SERVER +// #define DEBUG_FILE_CONTENT + +namespace LanguageServers::Shell { + +static HashMap<int, RefPtr<ClientConnection>> s_connections; + +ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> socket, int client_id) + : IPC::ClientConnection<LanguageClientEndpoint, LanguageServerEndpoint>(*this, move(socket), client_id) +{ + s_connections.set(client_id, *this); +} + +ClientConnection::~ClientConnection() +{ +} + +void ClientConnection::die() +{ + s_connections.remove(client_id()); + exit(0); +} + +OwnPtr<Messages::LanguageServer::GreetResponse> ClientConnection::handle(const Messages::LanguageServer::Greet& message) +{ + m_project_root = LexicalPath(message.project_root()); +#ifdef DEBUG_SH_LANGUAGE_SERVER + dbg() << "project_root: " << m_project_root.string(); +#endif + return make<Messages::LanguageServer::GreetResponse>(client_id()); +} + +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 true; } + virtual int soft_tab_width() const override { return 4; } +}; + +static DefaultDocumentClient s_default_document_client; + +void ClientConnection::handle(const Messages::LanguageServer::FileOpened& message) +{ + LexicalPath file_path(String::format("%s/%s", m_project_root.string().characters(), message.file_name().characters())); +#ifdef DEBUG_SH_LANGUAGE_SERVER + dbg() << "FileOpened: " << file_path.string(); +#endif + + auto file = Core::File::construct(file_path.string()); + if (!file->open(Core::IODevice::ReadOnly)) { + errno = file->error(); + perror("open"); + dbg() << "Failed to open project file: " << file_path.string(); + return; + } + auto content = file->read_all(); + StringView content_view(content); + auto document = GUI::TextDocument::create(&s_default_document_client); + document->set_text(content_view); + m_open_files.set(message.file_name(), document); +#ifdef DEBUG_FILE_CONTENT + dbg() << document->text(); +#endif +} + +void ClientConnection::handle(const Messages::LanguageServer::FileEditInsertText& message) +{ +#ifdef DEBUG_SH_LANGUAGE_SERVER + dbg() << "InsertText for file: " << message.file_name(); + dbg() << "Text: " << message.text(); + dbg() << "[" << message.start_line() << ":" << message.start_column() << "]"; +#endif + auto document = document_for(message.file_name()); + if (!document) { + dbg() << "file " << message.file_name() << " has not been opened"; + return; + } + GUI::TextPosition start_position { (size_t)message.start_line(), (size_t)message.start_column() }; + document->insert_at(start_position, message.text(), &s_default_document_client); +#ifdef DEBUG_FILE_CONTENT + dbg() << document->text(); +#endif +} + +void ClientConnection::handle(const Messages::LanguageServer::FileEditRemoveText& message) +{ +#ifdef DEBUG_SH_LANGUAGE_SERVER + dbg() << "RemoveText for file: " << message.file_name(); + dbg() << "[" << message.start_line() << ":" << message.start_column() << " - " << message.end_line() << ":" << message.end_column() << "]"; +#endif + auto document = document_for(message.file_name()); + if (!document) { + dbg() << "file " << message.file_name() << " has not been opened"; + return; + } + GUI::TextPosition start_position { (size_t)message.start_line(), (size_t)message.start_column() }; + GUI::TextRange range { + GUI::TextPosition { (size_t)message.start_line(), + (size_t)message.start_column() }, + GUI::TextPosition { (size_t)message.end_line(), + (size_t)message.end_column() } + }; + + document->remove(range); +#ifdef DEBUG_FILE_CONTENT + dbg() << document->text(); +#endif +} + +void ClientConnection::handle(const Messages::LanguageServer::AutoCompleteSuggestions& message) +{ +#ifdef DEBUG_SH_LANGUAGE_SERVER + dbg() << "AutoCompleteSuggestions for: " << message.file_name() << " " << message.cursor_line() << ":" << message.cursor_column(); +#endif + + auto document = document_for(message.file_name()); + if (!document) { + dbg() << "file " << message.file_name() << " has not been opened"; + return; + } + + auto& lines = document->lines(); + size_t offset = 0; + + if (message.cursor_line() > 0) { + for (auto i = 0; i < message.cursor_line(); ++i) + offset += lines[i].length() + 1; + } + offset += message.cursor_column(); + + auto suggestions = m_autocomplete.get_suggestions(document->text(), offset); + post_message(Messages::LanguageClient::AutoCompleteSuggestions(move(suggestions))); +} + +RefPtr<GUI::TextDocument> ClientConnection::document_for(const String& file_name) +{ + auto document_optional = m_open_files.get(file_name); + if (!document_optional.has_value()) + return nullptr; + + return document_optional.value(); +} + +void ClientConnection::handle(const Messages::LanguageServer::SetFileContent& message) +{ + auto document = document_for(message.file_name()); + if (!document) { + dbg() << "file " << message.file_name() << " has not been opened"; + return; + } + auto content = message.content(); + document->set_text(content.view()); +} + +} diff --git a/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h b/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h new file mode 100644 index 0000000000..27f53857b3 --- /dev/null +++ b/DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * 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 "AutoComplete.h" +#include <AK/HashMap.h> +#include <AK/LexicalPath.h> +#include <DevTools/HackStudio/AutoCompleteResponse.h> +#include <LibGUI/TextDocument.h> +#include <LibIPC/ClientConnection.h> + +#include <DevTools/HackStudio/LanguageServers/LanguageClientEndpoint.h> +#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h> + +namespace LanguageServers::Shell { + +class ClientConnection final + : public IPC::ClientConnection<LanguageClientEndpoint, LanguageServerEndpoint> + , public LanguageServerEndpoint { + C_OBJECT(ClientConnection); + +public: + explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id); + ~ClientConnection() override; + + virtual void die() override; + +private: + virtual OwnPtr<Messages::LanguageServer::GreetResponse> handle(const Messages::LanguageServer::Greet&) override; + virtual void handle(const Messages::LanguageServer::FileOpened&) override; + virtual void handle(const Messages::LanguageServer::FileEditInsertText&) override; + virtual void handle(const Messages::LanguageServer::FileEditRemoveText&) override; + virtual void handle(const Messages::LanguageServer::SetFileContent&) override; + virtual void handle(const Messages::LanguageServer::AutoCompleteSuggestions&) override; + + RefPtr<GUI::TextDocument> document_for(const String& file_name); + + LexicalPath m_project_root; + HashMap<String, NonnullRefPtr<GUI::TextDocument>> m_open_files; + + AutoComplete m_autocomplete; +}; + +} diff --git a/DevTools/HackStudio/LanguageServers/Shell/main.cpp b/DevTools/HackStudio/LanguageServers/Shell/main.cpp new file mode 100644 index 0000000000..a5e1417e67 --- /dev/null +++ b/DevTools/HackStudio/LanguageServers/Shell/main.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * 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 <AK/LexicalPath.h> +#include <DevTools/HackStudio/LanguageServers/Shell/ClientConnection.h> +#include <LibCore/EventLoop.h> +#include <LibCore/File.h> +#include <LibCore/LocalServer.h> +#include <LibIPC/ClientConnection.h> +#include <sys/stat.h> +#include <unistd.h> + +int main(int, char**) +{ + Core::EventLoop event_loop; + if (pledge("stdio unix rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto socket = Core::LocalSocket::take_over_accepted_socket_from_system_server(); + IPC::new_client_connection<LanguageServers::Shell::ClientConnection>(socket.release_nonnull(), 1); + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + return event_loop.exec(); +} |