소스 검색

HackStudio: Integrate with C++ Language Server

Editors now communicate with the c++ language server when openning and
editing c++ source files, and go through the language server to get
autocomplete suggestions.
Itamar 4 년 전
부모
커밋
a39c4cc340

+ 19 - 0
DevTools/HackStudio/CodeDocument.cpp

@@ -28,11 +28,30 @@
 
 namespace HackStudio {
 
+NonnullRefPtr<CodeDocument> CodeDocument::create(const LexicalPath& file_path, Client* client)
+{
+    return adopt(*new CodeDocument(file_path, client));
+}
+
 NonnullRefPtr<CodeDocument> CodeDocument::create(Client* client)
 {
     return adopt(*new CodeDocument(client));
 }
 
+CodeDocument::CodeDocument(const LexicalPath& file_path, Client* client)
+    : TextDocument(client)
+    , m_file_path(file_path)
+{
+    if (file_path.basename().ends_with(".cpp") || file_path.basename().ends_with(".h"))
+        m_language = Language::Cpp;
+    else if (file_path.basename().ends_with(".js"))
+        m_language = Language::JavaScript;
+    else if (file_path.basename().ends_with(".ini"))
+        m_language = Language::Ini;
+    else if (file_path.basename().ends_with(".sh"))
+        m_language = Language::Shell;
+}
+
 CodeDocument::CodeDocument(Client* client)
     : TextDocument(client)
 {

+ 9 - 1
DevTools/HackStudio/CodeDocument.h

@@ -26,6 +26,8 @@
 
 #pragma once
 
+#include "Language.h"
+#include <AK/LexicalPath.h>
 #include <LibGUI/TextDocument.h>
 
 namespace HackStudio {
@@ -33,6 +35,7 @@ namespace HackStudio {
 class CodeDocument final : public GUI::TextDocument {
 public:
     virtual ~CodeDocument() override;
+    static NonnullRefPtr<CodeDocument> create(const LexicalPath& file_path, Client* client = nullptr);
     static NonnullRefPtr<CodeDocument> create(Client* client = nullptr);
 
     const Vector<size_t>& breakpoint_lines() const { return m_breakpoint_lines; }
@@ -40,12 +43,17 @@ public:
     Optional<size_t> execution_position() const { return m_execution_position; }
     void set_execution_position(size_t line) { m_execution_position = line; }
     void clear_execution_position() { m_execution_position.clear(); }
+    const LexicalPath& file_path() const { return m_file_path; }
+    Language language() const { return m_language; }
 
     virtual bool is_code_document() const override final { return true; }
 
 private:
-    explicit CodeDocument(Client* client);
+    explicit CodeDocument(const LexicalPath& file_path, Client* client = nullptr);
+    explicit CodeDocument(Client* client = nullptr);
 
+    LexicalPath m_file_path;
+    Language m_language { Language::Unknown };
     Vector<size_t> m_breakpoint_lines;
     Optional<size_t> m_execution_position;
 };

+ 91 - 5
DevTools/HackStudio/Editor.cpp

@@ -25,16 +25,22 @@
  */
 
 #include "Editor.h"
-#include "CppAutoComplete.h"
+#include "Debugger/Debugger.h"
 #include "EditorWrapper.h"
+#include "HackStudio.h"
+#include "Language.h"
 #include <AK/ByteBuffer.h>
 #include <AK/LexicalPath.h>
 #include <LibCore/DirIterator.h>
 #include <LibCore/File.h>
 #include <LibGUI/Application.h>
+#include <LibGUI/CppSyntaxHighlighter.h>
+#include <LibGUI/INISyntaxHighlighter.h>
+#include <LibGUI/JSSyntaxHighlighter.h>
 #include <LibGUI/Label.h>
 #include <LibGUI/Painter.h>
 #include <LibGUI/ScrollBar.h>
+#include <LibGUI/ShellSyntaxHighlighter.h>
 #include <LibGUI/SyntaxHighlighter.h>
 #include <LibGUI/Window.h>
 #include <LibMarkdown/Document.h>
@@ -269,10 +275,10 @@ void Editor::mousedown_event(GUI::MouseEvent& event)
     if (event.button() == GUI::MouseButton::Left && event.position().x() < ruler_line_rect.width()) {
         if (!breakpoint_lines().contains_slow(text_position.line())) {
             breakpoint_lines().append(text_position.line());
-            on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Added);
+            Debugger::on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Added);
         } else {
             breakpoint_lines().remove_first_matching([&](size_t line) { return line == text_position.line(); });
-            on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Removed);
+            Debugger::on_breakpoint_change(wrapper().filename_label().text(), text_position.line(), BreakpointChange::Removed);
         }
     }
 
@@ -462,6 +468,25 @@ void Editor::set_document(GUI::TextDocument& doc)
 {
     ASSERT(doc.is_code_document());
     GUI::TextEditor::set_document(doc);
+
+    CodeDocument& code_document = static_cast<CodeDocument&>(doc);
+    switch (code_document.language()) {
+    case Language::Cpp:
+        set_syntax_highlighter(make<GUI::CppSyntaxHighlighter>());
+        cpp_Language_server_connection().post_message(Messages::CppLanguageServer::FileOpened(code_document.file_path().string()));
+        break;
+    case Language::JavaScript:
+        set_syntax_highlighter(make<GUI::JSSyntaxHighlighter>());
+        break;
+    case Language::Ini:
+        set_syntax_highlighter(make<GUI::IniSyntaxHighlighter>());
+        break;
+    case Language::Shell:
+        set_syntax_highlighter(make<GUI::ShellSyntaxHighlighter>());
+        break;
+    default:
+        set_syntax_highlighter(nullptr);
+    }
 }
 
 Optional<Editor::AutoCompleteRequestData> Editor::get_autocomplete_request_data()
@@ -492,8 +517,15 @@ Optional<Editor::AutoCompleteRequestData> Editor::get_autocomplete_request_data(
 
 void Editor::update_autocomplete(const AutoCompleteRequestData& data)
 {
-    // TODO: Move this part to a language server component :)
-    auto suggestions = CppAutoComplete::get_suggestions(text(), data.position);
+    if (code_document().language() != Language::Cpp)
+        return;
+    auto autocomplete_response = cpp_Language_server_connection().send_sync<Messages::CppLanguageServer::AutoCompleteSuggestions>(
+        code_document().file_path().string(),
+        data.position.line(),
+        data.position.column());
+    ASSERT(autocomplete_response);
+
+    auto suggestions = autocomplete_response->suggestions();
     if (suggestions.is_empty()) {
         close_autocomplete();
         return;
@@ -515,4 +547,58 @@ void Editor::close_autocomplete()
     m_autocomplete_in_focus = false;
 }
 
+void Editor::on_edit_action(const GUI::Command& command)
+{
+    if (code_document().language() != Language::Cpp)
+        return;
+
+    if (command.is_insert_text()) {
+        const GUI::InsertTextCommand& insert_command = static_cast<const GUI::InsertTextCommand&>(command);
+        cpp_Language_server_connection().post_message(
+            Messages::CppLanguageServer::FileEditInsertText(
+                code_document().file_path().string(),
+                insert_command.text(),
+                insert_command.range().start().line(),
+                insert_command.range().start().column()));
+        return;
+    }
+
+    if (command.is_remove_text()) {
+        const GUI::RemoveTextCommand& remove_command = static_cast<const GUI::RemoveTextCommand&>(command);
+        cpp_Language_server_connection().post_message(
+            Messages::CppLanguageServer::FileEditRemoveText(
+                code_document().file_path().string(),
+                remove_command.range().start().line(),
+                remove_command.range().start().column(),
+                remove_command.range().end().line(),
+                remove_command.range().end().column()));
+        return;
+    }
+
+    ASSERT_NOT_REACHED();
+}
+
+void Editor::undo()
+{
+    TextEditor::undo();
+    flush_file_content_to_langauge_server();
+}
+
+void Editor::redo()
+{
+    TextEditor::redo();
+    flush_file_content_to_langauge_server();
+}
+
+void Editor::flush_file_content_to_langauge_server()
+{
+    if (code_document().language() != Language::Cpp)
+        return;
+
+    cpp_Language_server_connection().post_message(
+        Messages::CppLanguageServer::SetFileContent(
+            code_document().file_path().string(),
+            document().text()));
+}
+
 }

+ 7 - 2
DevTools/HackStudio/Editor.h

@@ -55,13 +55,16 @@ public:
     void set_execution_position(size_t line_number);
     void clear_execution_position();
 
-    BreakpointChangeCallback on_breakpoint_change;
-
     const CodeDocument& code_document() const;
     CodeDocument& code_document();
 
     virtual void set_document(GUI::TextDocument&) override;
 
+    virtual void on_edit_action(const GUI::Command&) override;
+
+    virtual void undo() override;
+    virtual void redo() override;
+
 private:
     virtual void focusin_event(GUI::FocusEvent&) override;
     virtual void focusout_event(GUI::FocusEvent&) override;
@@ -91,6 +94,8 @@ private:
     void show_autocomplete(const AutoCompleteRequestData&);
     void close_autocomplete();
 
+    void flush_file_content_to_langauge_server();
+
     explicit Editor();
 
     RefPtr<GUI::Window> m_documentation_tooltip_window;

+ 1 - 3
DevTools/HackStudio/EditorWrapper.cpp

@@ -35,7 +35,7 @@
 
 namespace HackStudio {
 
-EditorWrapper::EditorWrapper(BreakpointChangeCallback breakpoint_change_callback)
+EditorWrapper::EditorWrapper()
 {
     set_layout<GUI::VerticalBoxLayout>();
 
@@ -72,8 +72,6 @@ EditorWrapper::EditorWrapper(BreakpointChangeCallback breakpoint_change_callback
     m_editor->on_open = [](String path) {
         open_file(path);
     };
-
-    m_editor->on_breakpoint_change = move(breakpoint_change_callback);
 }
 
 EditorWrapper::~EditorWrapper()

+ 1 - 1
DevTools/HackStudio/EditorWrapper.h

@@ -50,7 +50,7 @@ public:
     void set_editor_has_focus(Badge<Editor>, bool);
 
 private:
-    explicit EditorWrapper(BreakpointChangeCallback);
+    EditorWrapper();
 
     RefPtr<GUI::Label> m_filename_label;
     RefPtr<GUI::Label> m_cursor_label;

+ 2 - 0
DevTools/HackStudio/HackStudio.h

@@ -27,6 +27,7 @@
 #pragma once
 
 #include "EditorWrapper.h"
+#include "LanguageClients/Cpp/ServerConnection.h"
 #include "Project.h"
 #include <AK/String.h>
 #include <LibGUI/TextEditor.h>
@@ -40,5 +41,6 @@ void open_file(const String&);
 Project& project();
 String currently_open_file();
 void set_current_editor_wrapper(RefPtr<EditorWrapper>);
+LanguageClients::Cpp::ServerConnection& cpp_Language_server_connection();
 
 }

+ 1 - 16
DevTools/HackStudio/HackStudioWidget.cpp

@@ -57,16 +57,12 @@
 #include <LibGUI/Application.h>
 #include <LibGUI/BoxLayout.h>
 #include <LibGUI/Button.h>
-#include <LibGUI/CppSyntaxHighlighter.h>
 #include <LibGUI/FilePicker.h>
-#include <LibGUI/INISyntaxHighlighter.h>
 #include <LibGUI/InputBox.h>
-#include <LibGUI/JSSyntaxHighlighter.h>
 #include <LibGUI/Label.h>
 #include <LibGUI/Menu.h>
 #include <LibGUI/MenuBar.h>
 #include <LibGUI/MessageBox.h>
-#include <LibGUI/ShellSyntaxHighlighter.h>
 #include <LibGUI/Splitter.h>
 #include <LibGUI/StackWidget.h>
 #include <LibGUI/TabWidget.h>
@@ -211,17 +207,6 @@ void HackStudioWidget::open_file(const String& filename)
         current_editor().set_mode(GUI::TextEditor::ReadOnly);
     }
 
-    if (filename.ends_with(".cpp") || filename.ends_with(".h"))
-        current_editor().set_syntax_highlighter(make<GUI::CppSyntaxHighlighter>());
-    else if (filename.ends_with(".js"))
-        current_editor().set_syntax_highlighter(make<GUI::JSSyntaxHighlighter>());
-    else if (filename.ends_with(".ini"))
-        current_editor().set_syntax_highlighter(make<GUI::IniSyntaxHighlighter>());
-    else if (filename.ends_with(".sh"))
-        current_editor().set_syntax_highlighter(make<GUI::ShellSyntaxHighlighter>());
-    else
-        current_editor().set_syntax_highlighter(nullptr);
-
     if (filename.ends_with(".frm")) {
         set_edit_mode(EditMode::Form);
     } else {
@@ -366,7 +351,7 @@ NonnullRefPtr<GUI::Action> HackStudioWidget::create_delete_action()
 
 void HackStudioWidget::add_new_editor(GUI::Widget& parent)
 {
-    auto wrapper = EditorWrapper::construct(Debugger::on_breakpoint_change);
+    auto wrapper = EditorWrapper::construct();
     if (m_action_tab_widget) {
         parent.insert_child_before(wrapper, *m_action_tab_widget);
     } else {

+ 1 - 0
DevTools/HackStudio/HackStudioWidget.h

@@ -123,6 +123,7 @@ private:
 
     String m_currently_open_file;
     OwnPtr<Project> m_project;
+
     RefPtr<GUI::TreeView> m_project_tree_view;
     RefPtr<GUI::VerticalSplitter> m_right_hand_splitter;
     RefPtr<GUI::StackWidget> m_right_hand_stack;

+ 37 - 0
DevTools/HackStudio/Language.h

@@ -0,0 +1,37 @@
+/*
+ * 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
+
+namespace HackStudio {
+enum class Language {
+    Unknown,
+    Cpp,
+    JavaScript,
+    Ini,
+    Shell,
+};
+}

+ 1 - 1
DevTools/HackStudio/ProjectFile.cpp

@@ -38,7 +38,7 @@ ProjectFile::ProjectFile(const String& name)
 const GUI::TextDocument& ProjectFile::document() const
 {
     if (!m_document) {
-        m_document = CodeDocument::create(nullptr);
+        m_document = CodeDocument::create(LexicalPath(m_name));
         auto file = Core::File::construct(m_name);
         if (!file->open(Core::File::ReadOnly)) {
             ASSERT_NOT_REACHED();

+ 22 - 3
DevTools/HackStudio/main.cpp

@@ -26,6 +26,7 @@
 
 #include "HackStudio.h"
 #include "HackStudioWidget.h"
+#include "LanguageClients/Cpp/ServerConnection.h"
 #include "Project.h"
 #include <AK/StringBuilder.h>
 #include <LibCore/ArgsParser.h>
@@ -51,22 +52,24 @@ using namespace HackStudio;
 
 static RefPtr<GUI::Window> s_window;
 static RefPtr<HackStudioWidget> s_hack_studio_widget;
+static RefPtr<LanguageClients::Cpp::ServerConnection> s_cpp_Language_server_connection;
 
 static bool make_is_available();
 static void update_path_environment_variable();
 static String path_to_project(const String& path_argument_absolute_path);
 static void open_default_project_file(const String& project_path);
+static void initialize_connections_to_language_servers(const String& project_path);
 
 int main(int argc, char** argv)
 {
-    if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec unix fattr thread", nullptr) < 0) {
+    if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec unix fattr thread unix", nullptr) < 0) {
         perror("pledge");
         return 1;
     }
 
     auto app = GUI::Application::construct(argc, argv);
 
-    if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec fattr thread", nullptr) < 0) {
+    if (pledge("stdio tty accept rpath cpath wpath shared_buffer proc exec fattr thread unix", nullptr) < 0) {
         perror("pledge");
         return 1;
     }
@@ -89,7 +92,10 @@ int main(int argc, char** argv)
     auto argument_absolute_path = Core::File::real_path_for(path_argument);
 
     auto menubar = GUI::MenuBar::construct();
-    s_hack_studio_widget = s_window->set_main_widget<HackStudioWidget>(path_to_project(argument_absolute_path));
+    auto project_path = path_to_project(argument_absolute_path);
+    s_hack_studio_widget = s_window->set_main_widget<HackStudioWidget>(project_path);
+
+    initialize_connections_to_language_servers(project_path);
 
     s_hack_studio_widget->initialize_menubar(menubar);
     app->set_menubar(menubar);
@@ -146,6 +152,13 @@ static void open_default_project_file(const String& project_path)
         open_file(s_hack_studio_widget->project().default_file());
 }
 
+static void initialize_connections_to_language_servers(const String& project_path)
+{
+    LexicalPath project_root_dir(LexicalPath(project_path).dirname());
+    s_cpp_Language_server_connection = LanguageClients::Cpp::ServerConnection::construct(project_root_dir.string());
+    s_cpp_Language_server_connection->handshake();
+}
+
 namespace HackStudio {
 
 GUI::TextEditor& current_editor()
@@ -182,4 +195,10 @@ void set_current_editor_wrapper(RefPtr<EditorWrapper> wrapper)
     s_hack_studio_widget->set_current_editor_wrapper(wrapper);
 }
 
+LanguageClients::Cpp::ServerConnection& cpp_Language_server_connection()
+{
+    ASSERT(s_cpp_Language_server_connection);
+    return *s_cpp_Language_server_connection;
+}
+
 }

+ 5 - 2
Libraries/LibGUI/TextEditor.h

@@ -131,8 +131,8 @@ public:
     void do_delete();
     void delete_current_line();
     void select_all();
-    void undo() { document().undo(); }
-    void redo() { document().redo(); }
+    virtual void undo() { document().undo(); }
+    virtual void redo() { document().redo(); }
 
     Function<void()> on_change;
     Function<void()> on_mousedown;
@@ -263,10 +263,13 @@ private:
     inline void execute(Args&&... args)
     {
         auto command = make<T>(*m_document, forward<Args>(args)...);
+        on_edit_action(*command);
         command->execute_from(*this);
         m_document->add_to_undo_stack(move(command));
     }
 
+    virtual void on_edit_action(const Command&) { }
+
     Type m_type { MultiLine };
     Mode m_mode { Editable };