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.
This commit is contained in:
Itamar 2020-09-27 00:11:15 +03:00 committed by Andreas Kling
parent 863f14788f
commit a39c4cc340
13 changed files with 197 additions and 34 deletions

View file

@ -28,11 +28,30 @@
namespace HackStudio { 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) NonnullRefPtr<CodeDocument> CodeDocument::create(Client* client)
{ {
return adopt(*new CodeDocument(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) CodeDocument::CodeDocument(Client* client)
: TextDocument(client) : TextDocument(client)
{ {

View file

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

View file

@ -25,16 +25,22 @@
*/ */
#include "Editor.h" #include "Editor.h"
#include "CppAutoComplete.h" #include "Debugger/Debugger.h"
#include "EditorWrapper.h" #include "EditorWrapper.h"
#include "HackStudio.h"
#include "Language.h"
#include <AK/ByteBuffer.h> #include <AK/ByteBuffer.h>
#include <AK/LexicalPath.h> #include <AK/LexicalPath.h>
#include <LibCore/DirIterator.h> #include <LibCore/DirIterator.h>
#include <LibCore/File.h> #include <LibCore/File.h>
#include <LibGUI/Application.h> #include <LibGUI/Application.h>
#include <LibGUI/CppSyntaxHighlighter.h>
#include <LibGUI/INISyntaxHighlighter.h>
#include <LibGUI/JSSyntaxHighlighter.h>
#include <LibGUI/Label.h> #include <LibGUI/Label.h>
#include <LibGUI/Painter.h> #include <LibGUI/Painter.h>
#include <LibGUI/ScrollBar.h> #include <LibGUI/ScrollBar.h>
#include <LibGUI/ShellSyntaxHighlighter.h>
#include <LibGUI/SyntaxHighlighter.h> #include <LibGUI/SyntaxHighlighter.h>
#include <LibGUI/Window.h> #include <LibGUI/Window.h>
#include <LibMarkdown/Document.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 (event.button() == GUI::MouseButton::Left && event.position().x() < ruler_line_rect.width()) {
if (!breakpoint_lines().contains_slow(text_position.line())) { if (!breakpoint_lines().contains_slow(text_position.line())) {
breakpoint_lines().append(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 { } else {
breakpoint_lines().remove_first_matching([&](size_t line) { return line == text_position.line(); }); 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()); ASSERT(doc.is_code_document());
GUI::TextEditor::set_document(doc); 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() 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) void Editor::update_autocomplete(const AutoCompleteRequestData& data)
{ {
// TODO: Move this part to a language server component :) if (code_document().language() != Language::Cpp)
auto suggestions = CppAutoComplete::get_suggestions(text(), data.position); 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()) { if (suggestions.is_empty()) {
close_autocomplete(); close_autocomplete();
return; return;
@ -515,4 +547,58 @@ void Editor::close_autocomplete()
m_autocomplete_in_focus = false; 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()));
}
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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,
};
}

View file

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

View file

@ -26,6 +26,7 @@
#include "HackStudio.h" #include "HackStudio.h"
#include "HackStudioWidget.h" #include "HackStudioWidget.h"
#include "LanguageClients/Cpp/ServerConnection.h"
#include "Project.h" #include "Project.h"
#include <AK/StringBuilder.h> #include <AK/StringBuilder.h>
#include <LibCore/ArgsParser.h> #include <LibCore/ArgsParser.h>
@ -51,22 +52,24 @@ using namespace HackStudio;
static RefPtr<GUI::Window> s_window; static RefPtr<GUI::Window> s_window;
static RefPtr<HackStudioWidget> s_hack_studio_widget; static RefPtr<HackStudioWidget> s_hack_studio_widget;
static RefPtr<LanguageClients::Cpp::ServerConnection> s_cpp_Language_server_connection;
static bool make_is_available(); static bool make_is_available();
static void update_path_environment_variable(); static void update_path_environment_variable();
static String path_to_project(const String& path_argument_absolute_path); static String path_to_project(const String& path_argument_absolute_path);
static void open_default_project_file(const String& project_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) 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"); perror("pledge");
return 1; return 1;
} }
auto app = GUI::Application::construct(argc, argv); 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"); perror("pledge");
return 1; return 1;
} }
@ -89,7 +92,10 @@ int main(int argc, char** argv)
auto argument_absolute_path = Core::File::real_path_for(path_argument); auto argument_absolute_path = Core::File::real_path_for(path_argument);
auto menubar = GUI::MenuBar::construct(); 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); s_hack_studio_widget->initialize_menubar(menubar);
app->set_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()); 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 { namespace HackStudio {
GUI::TextEditor& current_editor() 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); 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;
}
} }

View file

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