Serenity Operating System
1/*
2 * Copyright (c) 2021, Itamar S. <itamar8910@gmail.com>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include "FileDB.h"
8
9#include <AK/Debug.h>
10#include <AK/LexicalPath.h>
11#include <AK/NonnullRefPtr.h>
12#include <LibCore/File.h>
13
14namespace LanguageServers {
15
16RefPtr<const GUI::TextDocument> FileDB::get_document(DeprecatedString const& filename) const
17{
18 auto absolute_path = to_absolute_path(filename);
19 auto document_optional = m_open_files.get(absolute_path);
20 if (!document_optional.has_value())
21 return nullptr;
22
23 return *document_optional.value();
24}
25
26RefPtr<GUI::TextDocument> FileDB::get_document(DeprecatedString const& filename)
27{
28 auto document = reinterpret_cast<FileDB const*>(this)->get_document(filename);
29 if (document.is_null())
30 return nullptr;
31 return adopt_ref(*const_cast<GUI::TextDocument*>(document.leak_ref()));
32}
33
34Optional<DeprecatedString> FileDB::get_or_read_from_filesystem(StringView filename) const
35{
36 auto absolute_path = to_absolute_path(filename);
37 auto document = get_document(absolute_path);
38 if (document)
39 return document->text();
40
41 auto document_or_error = create_from_filesystem(absolute_path);
42 if (document_or_error.is_error()) {
43 dbgln("Failed to create document '{}': {}", absolute_path, document_or_error.error());
44 return {};
45 }
46 return document_or_error.value()->text();
47}
48
49bool FileDB::is_open(DeprecatedString const& filename) const
50{
51 return m_open_files.contains(to_absolute_path(filename));
52}
53
54bool FileDB::add(DeprecatedString const& filename, int fd)
55{
56 auto document_or_error = create_from_fd(fd);
57 if (document_or_error.is_error()) {
58 dbgln("Failed to create document: {}", document_or_error.error());
59 return false;
60 }
61
62 m_open_files.set(to_absolute_path(filename), document_or_error.release_value());
63 return true;
64}
65
66DeprecatedString FileDB::to_absolute_path(DeprecatedString const& filename) const
67{
68 if (LexicalPath { filename }.is_absolute()) {
69 return filename;
70 }
71 if (m_project_root.is_null())
72 return filename;
73 return LexicalPath { DeprecatedString::formatted("{}/{}", m_project_root, filename) }.string();
74}
75
76ErrorOr<NonnullRefPtr<GUI::TextDocument>> FileDB::create_from_filesystem(DeprecatedString const& filename) const
77{
78 auto file = TRY(Core::File::open(to_absolute_path(filename), Core::File::OpenMode::Read));
79 return create_from_file(move(file));
80}
81
82ErrorOr<NonnullRefPtr<GUI::TextDocument>> FileDB::create_from_fd(int fd) const
83{
84 auto file = TRY(Core::File::adopt_fd(fd, Core::File::OpenMode::Read));
85 return create_from_file(move(file));
86}
87
88class DefaultDocumentClient final : public GUI::TextDocument::Client {
89public:
90 virtual ~DefaultDocumentClient() override = default;
91 virtual void document_did_append_line() override {};
92 virtual void document_did_insert_line(size_t) override {};
93 virtual void document_did_remove_line(size_t) override {};
94 virtual void document_did_remove_all_lines() override {};
95 virtual void document_did_change(GUI::AllowCallback) override {};
96 virtual void document_did_set_text(GUI::AllowCallback) override {};
97 virtual void document_did_set_cursor(const GUI::TextPosition&) override {};
98 virtual void document_did_update_undo_stack() override { }
99
100 virtual bool is_automatic_indentation_enabled() const override { return false; }
101 virtual int soft_tab_width() const override { return 4; }
102};
103static DefaultDocumentClient s_default_document_client;
104
105ErrorOr<NonnullRefPtr<GUI::TextDocument>> FileDB::create_from_file(NonnullOwnPtr<Core::File> file) const
106{
107 auto content = TRY(file->read_until_eof());
108 auto document = GUI::TextDocument::create(&s_default_document_client);
109 document->set_text(content);
110 return document;
111}
112
113void FileDB::on_file_edit_insert_text(DeprecatedString const& filename, DeprecatedString const& inserted_text, size_t start_line, size_t start_column)
114{
115 VERIFY(is_open(filename));
116 auto document = get_document(filename);
117 VERIFY(document);
118 GUI::TextPosition start_position { start_line, start_column };
119 document->insert_at(start_position, inserted_text, &s_default_document_client);
120
121 dbgln_if(FILE_CONTENT_DEBUG, "{}", document->text());
122}
123
124void FileDB::on_file_edit_remove_text(DeprecatedString const& filename, size_t start_line, size_t start_column, size_t end_line, size_t end_column)
125{
126 // TODO: If file is not open - need to get its contents
127 // Otherwise- somehow verify that respawned language server is synced with all file contents
128 VERIFY(is_open(filename));
129 auto document = get_document(filename);
130 VERIFY(document);
131 GUI::TextPosition start_position { start_line, start_column };
132 GUI::TextRange range {
133 GUI::TextPosition { start_line, start_column },
134 GUI::TextPosition { end_line, end_column }
135 };
136
137 document->remove(range);
138 dbgln_if(FILE_CONTENT_DEBUG, "{}", document->text());
139}
140
141RefPtr<GUI::TextDocument> FileDB::create_with_content(DeprecatedString const& content)
142{
143 StringView content_view(content);
144 auto document = GUI::TextDocument::create(&s_default_document_client);
145 document->set_text(content_view);
146 return document;
147}
148
149bool FileDB::add(DeprecatedString const& filename, DeprecatedString const& content)
150{
151 auto document = create_with_content(content);
152 if (!document) {
153 VERIFY_NOT_REACHED();
154 return false;
155 }
156
157 m_open_files.set(to_absolute_path(filename), document.release_nonnull());
158 return true;
159}
160
161}