Skip to content

Commit bed06b1

Browse files
itamar8910awesomekling
authored andcommitted
LanguageServers/Cpp: Add FileDB and pass project_root in Greet()
FileDB wraps the access to the contents of project files. When asked to fetch a file, FileDB will either return its in-memory model of the file if it has been "opened" by the language-server protocol, or otherwise fetch it from the filesystem. Previously, the cpp language server did not pledge "rpath" and got access to the contents of files whenever they were opened by the language client. However, features like inspection of header files require the language server to get the content of files that were not opened by the client. The language server now pledges rpath but makes sure to only unveil the project's directory and /usr/include.
1 parent bed3261 commit bed06b1

File tree

8 files changed

+248
-21
lines changed

8 files changed

+248
-21
lines changed

Userland/DevTools/HackStudio/LanguageClient.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ void LanguageClient::remove_text(const String& path, size_t from_line, size_t fr
6262

6363
void LanguageClient::request_autocomplete(const String& path, size_t cursor_line, size_t cursor_column)
6464
{
65+
set_active_client();
6566
m_connection.post_message(Messages::LanguageServer::AutoCompleteSuggestions(path, cursor_line, cursor_column));
6667
}
6768

@@ -78,4 +79,9 @@ void LanguageClient::set_autocomplete_mode(const String& mode)
7879
m_connection.post_message(Messages::LanguageServer::SetAutoCompleteMode(mode));
7980
}
8081

82+
void LanguageClient::set_active_client()
83+
{
84+
m_connection.attach(*this);
85+
}
86+
8187
}

Userland/DevTools/HackStudio/LanguageClient.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ class ServerConnection
4545
: public IPC::ServerConnection<LanguageClientEndpoint, LanguageServerEndpoint>
4646
, public LanguageClientEndpoint {
4747
public:
48-
ServerConnection(const StringView& socket)
48+
ServerConnection(const StringView& socket, const String& project_path)
4949
: IPC::ServerConnection<LanguageClientEndpoint, LanguageServerEndpoint>(*this, socket)
5050
{
51+
m_project_path = project_path;
5152
}
5253

5354
void attach(LanguageClient& client)
@@ -62,7 +63,7 @@ class ServerConnection
6263

6364
virtual void handshake() override
6465
{
65-
send_sync<Messages::LanguageServer::Greet>();
66+
send_sync<Messages::LanguageServer::Greet>(m_project_path);
6667
}
6768

6869
WeakPtr<LanguageClient> language_client() { return m_language_client; }
@@ -75,7 +76,7 @@ class ServerConnection
7576
if (auto instance = s_instances_for_projects.get(key); instance.has_value())
7677
return *instance.value();
7778

78-
auto connection = ConcreteType::construct();
79+
auto connection = ConcreteType::construct(project_path);
7980
connection->handshake();
8081
s_instances_for_projects.set(key, *connection);
8182
return *connection;
@@ -84,6 +85,7 @@ class ServerConnection
8485
protected:
8586
virtual void handle(const Messages::LanguageClient::AutoCompleteSuggestions&) override;
8687

88+
String m_project_path;
8789
WeakPtr<LanguageClient> m_language_client;
8890
};
8991

@@ -94,16 +96,19 @@ class LanguageClient : public Weakable<LanguageClient> {
9496
, m_server_connection(move(connection))
9597
{
9698
m_previous_client = m_connection.language_client();
99+
ASSERT(m_previous_client.ptr() != this);
97100
m_connection.attach(*this);
98101
}
99102

100103
virtual ~LanguageClient()
101104
{
102105
m_connection.detach();
106+
ASSERT(m_previous_client.ptr() != this);
103107
if (m_previous_client)
104108
m_connection.attach(*m_previous_client);
105109
}
106110

111+
void set_active_client();
107112
virtual void open_file(const String& path, int fd);
108113
virtual void set_file_content(const String& path, const String& content);
109114
virtual void insert_text(const String& path, const String& text, size_t line, size_t column);

Userland/DevTools/HackStudio/LanguageClients/ServerConnections.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@
3232
#include <DevTools/HackStudio/LanguageServers/LanguageServerEndpoint.h>
3333
#include <LibIPC/ServerConnection.h>
3434

35-
#define LANGUAGE_CLIENT(namespace_, socket_name) \
36-
namespace namespace_ { \
37-
class ServerConnection : public HackStudio::ServerConnection { \
38-
C_OBJECT(ServerConnection) \
39-
private: \
40-
ServerConnection() \
41-
: HackStudio::ServerConnection("/tmp/portal/language/" #socket_name) \
42-
{ \
43-
} \
44-
}; \
35+
#define LANGUAGE_CLIENT(namespace_, socket_name) \
36+
namespace namespace_ { \
37+
class ServerConnection : public HackStudio::ServerConnection { \
38+
C_OBJECT(ServerConnection) \
39+
private: \
40+
ServerConnection(const String& project_path) \
41+
: HackStudio::ServerConnection("/tmp/portal/language/" #socket_name, project_path) \
42+
{ \
43+
} \
44+
}; \
4545
}
4646

4747
namespace LanguageClients {

Userland/DevTools/HackStudio/LanguageServers/Cpp/ClientConnection.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,17 @@ void ClientConnection::die()
5252
exit(0);
5353
}
5454

55-
OwnPtr<Messages::LanguageServer::GreetResponse> ClientConnection::handle(const Messages::LanguageServer::Greet&)
55+
OwnPtr<Messages::LanguageServer::GreetResponse> ClientConnection::handle(const Messages::LanguageServer::Greet& message)
5656
{
57+
m_filedb.set_project_root(message.project_root());
58+
if (unveil(message.project_root().characters(), "r") < 0) {
59+
perror("unveil");
60+
exit(1);
61+
}
62+
if (unveil(nullptr, nullptr) < 0) {
63+
perror("unveil");
64+
exit(1);
65+
}
5766
return make<Messages::LanguageServer::GreetResponse>();
5867
}
5968

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright (c) 2021, Itamar S. <itamar8910@gmail.com>
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are met:
7+
*
8+
* 1. Redistributions of source code must retain the above copyright notice, this
9+
* list of conditions and the following disclaimer.
10+
*
11+
* 2. Redistributions in binary form must reproduce the above copyright notice,
12+
* this list of conditions and the following disclaimer in the documentation
13+
* and/or other materials provided with the distribution.
14+
*
15+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
27+
#include "FileDB.h"
28+
29+
#include <AK/LexicalPath.h>
30+
#include <LibCore/File.h>
31+
32+
RefPtr<const GUI::TextDocument> FileDB::get(const String& file_name) const
33+
{
34+
auto absolute_path = to_absolute_path(file_name);
35+
auto document_optional = m_open_files.get(absolute_path);
36+
if (!document_optional.has_value())
37+
return create_from_filesystem(absolute_path);
38+
39+
return document_optional.value();
40+
}
41+
42+
RefPtr<GUI::TextDocument> FileDB::get(const String& file_name)
43+
{
44+
auto absolute_path = to_absolute_path(file_name);
45+
return adopt(*const_cast<GUI::TextDocument*>(reinterpret_cast<const FileDB*>(this)->get(absolute_path).leak_ref()));
46+
}
47+
48+
bool FileDB::is_open(String file_name) const
49+
{
50+
return m_open_files.contains(to_absolute_path(file_name));
51+
}
52+
53+
bool FileDB::add(const String& file_name, int fd)
54+
{
55+
auto document = create_from_fd(fd);
56+
if (!document)
57+
return false;
58+
59+
m_open_files.set(to_absolute_path(file_name), document.release_nonnull());
60+
return true;
61+
}
62+
63+
String FileDB::to_absolute_path(const String& file_name) const
64+
{
65+
if (LexicalPath { file_name }.is_absolute()) {
66+
return file_name;
67+
}
68+
ASSERT(!m_project_root.is_null());
69+
return LexicalPath { String::formatted("{}/{}", m_project_root, file_name) }.string();
70+
}
71+
72+
RefPtr<GUI::TextDocument> FileDB::create_from_filesystem(const String& file_name) const
73+
{
74+
auto file = Core::File::open(to_absolute_path(file_name), Core::IODevice::ReadOnly);
75+
if (file.is_error()) {
76+
dbgln("failed to create document for {} from filesystem", file_name);
77+
return nullptr;
78+
}
79+
return create_from_file(*file.value());
80+
}
81+
82+
RefPtr<GUI::TextDocument> FileDB::create_from_fd(int fd) const
83+
{
84+
auto file = Core::File::construct();
85+
if (!file->open(fd, Core::IODevice::ReadOnly, Core::File::ShouldCloseFileDescriptor::Yes)) {
86+
errno = file->error();
87+
perror("open");
88+
dbgln("Failed to open project file");
89+
return nullptr;
90+
}
91+
return create_from_file(*file);
92+
}
93+
94+
class DefaultDocumentClient final : public GUI::TextDocument::Client {
95+
public:
96+
virtual ~DefaultDocumentClient() override = default;
97+
virtual void document_did_append_line() override {};
98+
virtual void document_did_insert_line(size_t) override {};
99+
virtual void document_did_remove_line(size_t) override {};
100+
virtual void document_did_remove_all_lines() override {};
101+
virtual void document_did_change() override {};
102+
virtual void document_did_set_text() override {};
103+
virtual void document_did_set_cursor(const GUI::TextPosition&) override {};
104+
105+
virtual bool is_automatic_indentation_enabled() const override { return false; }
106+
virtual int soft_tab_width() const override { return 4; }
107+
};
108+
static DefaultDocumentClient s_default_document_client;
109+
110+
RefPtr<GUI::TextDocument> FileDB::create_from_file(Core::File& file) const
111+
{
112+
auto content = file.read_all();
113+
StringView content_view(content);
114+
auto document = GUI::TextDocument::create(&s_default_document_client);
115+
document->set_text(content_view);
116+
return document;
117+
}
118+
119+
void FileDB::on_file_edit_insert_text(const String& file_name, const String& inserted_text, size_t start_line, size_t start_column)
120+
{
121+
auto document = get(file_name);
122+
if (!document) {
123+
dbgln("file {} has not been opened", file_name);
124+
return;
125+
}
126+
GUI::TextPosition start_position { start_line, start_column };
127+
document->insert_at(start_position, inserted_text, &s_default_document_client);
128+
129+
#if FILE_CONTENT_DEBUG
130+
dbgln("{}", document->text());
131+
#endif
132+
}
133+
134+
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)
135+
{
136+
auto document = get(file_name);
137+
if (!document) {
138+
dbgln("file {} has not been opened", file_name);
139+
return;
140+
}
141+
GUI::TextPosition start_position { start_line, start_column };
142+
GUI::TextRange range {
143+
GUI::TextPosition { start_line, start_column },
144+
GUI::TextPosition { end_line, end_column }
145+
};
146+
147+
document->remove(range);
148+
#if FILE_CONTENT_DEBUG
149+
dbgln("{}", document->text());
150+
#endif
151+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (c) 2021, Itamar S. <itamar8910@gmail.com>
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are met:
7+
*
8+
* 1. Redistributions of source code must retain the above copyright notice, this
9+
* list of conditions and the following disclaimer.
10+
*
11+
* 2. Redistributions in binary form must reproduce the above copyright notice,
12+
* this list of conditions and the following disclaimer in the documentation
13+
* and/or other materials provided with the distribution.
14+
*
15+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
27+
#pragma once
28+
29+
#include <AK/HashMap.h>
30+
#include <AK/NonnullRefPtr.h>
31+
#include <AK/String.h>
32+
#include <LibGUI/TextDocument.h>
33+
34+
class FileDB final {
35+
public:
36+
RefPtr<const GUI::TextDocument> get(const String& file_name) const;
37+
RefPtr<GUI::TextDocument> get(const String& file_name);
38+
bool add(const String& file_name, int fd);
39+
40+
void set_project_root(const String& root_path) { m_project_root = root_path; }
41+
42+
void on_file_edit_insert_text(const String& file_name, const String& inserted_text, size_t start_line, size_t start_column);
43+
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);
44+
String to_absolute_path(const String& file_name) const;
45+
bool is_open(String file_name) const;
46+
47+
private:
48+
RefPtr<GUI::TextDocument> create_from_filesystem(const String& file_name) const;
49+
RefPtr<GUI::TextDocument> create_from_fd(int fd) const;
50+
RefPtr<GUI::TextDocument> create_from_file(Core::File&) const;
51+
52+
private:
53+
HashMap<String, NonnullRefPtr<GUI::TextDocument>> m_open_files;
54+
String m_project_root;
55+
};

Userland/DevTools/HackStudio/LanguageServers/Cpp/main.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
2+
* Copyright (c) 2021, Itamar S. <itamar8910@gmail.com>
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -36,20 +36,21 @@
3636
int main(int, char**)
3737
{
3838
Core::EventLoop event_loop;
39-
if (pledge("stdio unix recvfd", nullptr) < 0) {
39+
if (pledge("stdio unix recvfd rpath ", nullptr) < 0) {
4040
perror("pledge");
4141
return 1;
4242
}
4343

4444
auto socket = Core::LocalSocket::take_over_accepted_socket_from_system_server();
4545
IPC::new_client_connection<LanguageServers::Cpp::ClientConnection>(socket.release_nonnull(), 1);
46-
if (pledge("stdio recvfd", nullptr) < 0) {
46+
if (pledge("stdio recvfd rpath", nullptr) < 0) {
4747
perror("pledge");
4848
return 1;
4949
}
50-
if (unveil(nullptr, nullptr) < 0) {
50+
51+
if (unveil("/usr/include", "r") < 0)
5152
perror("unveil");
52-
return 1;
53-
}
53+
54+
// unveil will be sealed later, when we know the project's root path.
5455
return event_loop.exec();
5556
}

Userland/DevTools/HackStudio/LanguageServers/LanguageServer.ipc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
endpoint LanguageServer = 8001
22
{
3-
Greet() => ()
3+
Greet(String project_root) => ()
44

55
FileOpened(String file_name, IPC::File file) =|
66
FileEditInsertText(String file_name, String text, i32 start_line, i32 start_column) =|

0 commit comments

Comments
 (0)