Skip to content

Commit ffbfa6f

Browse files
committed
chore: Incremental document synchronization
Also, add locations to the symbols in the parser.
1 parent c84dc04 commit ffbfa6f

File tree

14 files changed

+727
-479
lines changed

14 files changed

+727
-479
lines changed

src/frontend/cxx/cxx_document.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,8 @@ auto CxxDocument::diagnostics() const -> Vector<Diagnostic> {
215215
return Vector<Diagnostic>(d->diagnosticsClient.messages);
216216
}
217217

218+
auto CxxDocument::translationUnit() const -> TranslationUnit* {
219+
return &d->unit;
220+
}
221+
218222
} // namespace cxx::lsp

src/frontend/cxx/cxx_document.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#pragma once
2222

2323
#include <cxx/lsp/fwd.h>
24+
#include <cxx/translation_unit.h>
2425

2526
#include <memory>
2627
#include <string>
@@ -39,6 +40,8 @@ class CxxDocument {
3940
[[nodiscard]] auto version() const -> long;
4041
[[nodiscard]] auto diagnostics() const -> Vector<Diagnostic>;
4142

43+
[[nodiscard]] auto translationUnit() const -> TranslationUnit*;
44+
4245
private:
4346
struct Private;
4447
std::unique_ptr<Private> d;

src/frontend/cxx/lsp_server.cc

Lines changed: 136 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <cxx/lsp/enums.h>
2424
#include <cxx/lsp/requests.h>
2525
#include <cxx/lsp/types.h>
26+
#include <utf8/unchecked.h>
2627

2728
#include <format>
2829
#include <iostream>
@@ -33,6 +34,54 @@
3334

3435
namespace cxx::lsp {
3536

37+
// TODO: move to a separate file
38+
template <typename It>
39+
inline auto skipBOM(It& it, It end) -> bool {
40+
if (it < end && *it == '\xEF') {
41+
if (it + 1 < end && it[1] == '\xBB') {
42+
if (it + 2 < end && it[2] == '\xBF') {
43+
it += 3;
44+
return true;
45+
}
46+
}
47+
}
48+
return false;
49+
}
50+
51+
auto Server::Text::offsetAt(std::size_t line, std::size_t column) const
52+
-> std::size_t {
53+
if (line >= lineStartOffsets.size()) {
54+
return std::string::npos;
55+
}
56+
57+
const auto lineStart = lineStartOffsets.at(line);
58+
const auto nextLineStart = line + 1 < lineStartOffsets.size()
59+
? lineStartOffsets.at(line + 1)
60+
: value.size();
61+
62+
const auto columnOffset = std::min(column, nextLineStart - lineStart);
63+
64+
return lineStart + columnOffset;
65+
}
66+
67+
void Server::Text::computeLineStartOffsets() {
68+
auto begin = value.begin();
69+
auto end = value.end();
70+
71+
auto it = begin;
72+
skipBOM(it, end);
73+
74+
lineStartOffsets.clear();
75+
lineStartOffsets.push_back(it - begin);
76+
77+
while (it != end) {
78+
const auto ch = utf8::unchecked::next(it);
79+
if (ch == '\n') {
80+
lineStartOffsets.push_back(it - begin);
81+
}
82+
}
83+
}
84+
3685
Server::Server(const CLI& cli)
3786
: cli(cli), input(std::cin), output(std::cout), log(std::cerr) {
3887
// create workers
@@ -46,7 +95,9 @@ Server::Server(const CLI& cli)
4695
Server::~Server() {}
4796

4897
auto Server::start() -> int {
49-
log << std::format("Starting LSP server\n");
98+
trace_ = TraceValue::kOff;
99+
100+
logTrace(std::format("Starting LSP server"));
50101

51102
startWorkersIfNeeded();
52103

@@ -137,7 +188,7 @@ void Server::sendToClient(const json& message) {
137188
#endif
138189

139190
if (cli.opt_lsp_test) {
140-
output << message.dump(2) << "\n";
191+
output << message.dump(2) << std::endl;
141192
} else {
142193
const auto text = message.dump();
143194
output << std::format("Content-Length: {}\r\n\r\n{}", text.size(), text);
@@ -171,14 +222,30 @@ void Server::sendNotification(const LSPRequest& notification) {
171222
sendToClient(response);
172223
}
173224

225+
void Server::logTrace(std::string message, std::optional<std::string> verbose) {
226+
if (trace_ == TraceValue::kOff) {
227+
return;
228+
}
229+
230+
withUnsafeJson([&](json storage) {
231+
LogTraceNotification logTrace(storage);
232+
logTrace.method("$/logTrace");
233+
logTrace.params().message(std::move(message));
234+
if (verbose.has_value()) {
235+
logTrace.params().verbose(std::move(*verbose));
236+
}
237+
sendNotification(logTrace);
238+
});
239+
}
240+
174241
auto Server::pathFromUri(const std::string& uri) -> std::string {
175242
if (uri.starts_with("file://")) {
176243
return uri.substr(7);
177244
} else if (cli.opt_lsp_test && uri.starts_with("test://")) {
178245
return uri.substr(7);
179246
}
180247

181-
lsp_runtime_error(std::format("Unsupported URI scheme: {}\n", uri));
248+
lsp_runtime_error(std::format("Unsupported URI scheme: {}", uri));
182249
}
183250

184251
void Server::startWorkersIfNeeded() {
@@ -234,7 +301,12 @@ void Server::run(std::function<void()> task) {
234301
task();
235302
}
236303

237-
void Server::parse(std::string uri, std::string text, long version) {
304+
void Server::parse(const std::string& uri) {
305+
const auto& doc = documentContents_.at(uri);
306+
307+
auto text = doc.value;
308+
auto version = doc.version;
309+
238310
run([text = std::move(text), uri = std::move(uri), version, this] {
239311
auto doc = std::make_shared<CxxDocument>(cli, version);
240312
doc->parse(std::move(text), pathFromUri(uri));
@@ -264,22 +336,23 @@ void Server::parse(std::string uri, std::string text, long version) {
264336
}
265337

266338
void Server::operator()(const InitializeRequest& request) {
267-
log << std::format("Did receive InitializeRequest\n");
339+
logTrace(std::format("Did receive InitializeRequest"));
268340

269341
withUnsafeJson([&](json storage) {
270342
InitializeResult result(storage);
271343
result.serverInfo<ServerInfo>().name("cxx-lsp").version(CXX_VERSION);
272-
result.capabilities().textDocumentSync(TextDocumentSyncKind::kFull);
344+
auto capabilities = result.capabilities();
345+
capabilities.textDocumentSync(TextDocumentSyncKind::kIncremental);
273346
sendToClient(result, request.id());
274347
});
275348
}
276349

277350
void Server::operator()(const InitializedNotification& notification) {
278-
log << std::format("Did receive InitializedNotification\n");
351+
logTrace(std::format("Did receive InitializedNotification"));
279352
}
280353

281354
void Server::operator()(const ShutdownRequest& request) {
282-
log << std::format("Did receive ShutdownRequest\n");
355+
logTrace(std::format("Did receive ShutdownRequest"));
283356

284357
withUnsafeJson([&](json storage) {
285358
LSPObject result(storage);
@@ -288,49 +361,68 @@ void Server::operator()(const ShutdownRequest& request) {
288361
}
289362

290363
void Server::operator()(const ExitNotification& notification) {
291-
log << std::format("Did receive ExitNotification\n");
364+
logTrace(std::format("Did receive ExitNotification"));
292365
done_ = true;
293366
}
294367

295368
void Server::operator()(const DidOpenTextDocumentNotification& notification) {
296-
log << std::format("Did receive DidOpenTextDocumentNotification\n");
369+
logTrace(std::format("Did receive DidOpenTextDocumentNotification"));
297370

298371
auto textDocument = notification.params().textDocument();
299-
parse(textDocument.uri(), textDocument.text(), textDocument.version());
372+
373+
auto text = textDocument.text();
374+
375+
auto& content = documentContents_[textDocument.uri()];
376+
content.value = std::move(text);
377+
content.version = textDocument.version();
378+
content.computeLineStartOffsets();
379+
380+
parse(textDocument.uri());
300381
}
301382

302383
void Server::operator()(const DidCloseTextDocumentNotification& notification) {
303-
log << std::format("Did receive DidCloseTextDocumentNotification\n");
384+
logTrace(std::format("Did receive DidCloseTextDocumentNotification"));
304385

305386
const auto uri = notification.params().textDocument().uri();
306387
documents_.erase(uri);
307388
}
308389

309390
void Server::operator()(const DidChangeTextDocumentNotification& notification) {
310-
log << std::format("Did receive DidChangeTextDocumentNotification\n");
391+
logTrace(std::format("Did receive DidChangeTextDocumentNotification"));
311392

312393
const auto textDocument = notification.params().textDocument();
313394
const auto uri = textDocument.uri();
314395
const auto version = textDocument.version();
315396

316-
// update the document
317-
auto contentChanges = notification.params().contentChanges();
318-
const auto contentChangeCount = contentChanges.size();
397+
auto& text = documentContents_[uri];
398+
text.version = version;
319399

320-
std::string text;
400+
struct {
401+
Text& text;
321402

322-
for (std::size_t i = 0; i < contentChangeCount; ++i) {
323-
auto contentChange = contentChanges.at(i);
403+
void operator()(const TextDocumentContentChangeWholeDocument& change) {
404+
text.value = change.text();
405+
}
324406

325-
if (auto change = std::get_if<TextDocumentContentChangeWholeDocument>(
326-
&contentChange)) {
327-
text = change->text();
328-
} else {
329-
lsp_runtime_error("Unsupported content change\n");
407+
void operator()(const TextDocumentContentChangePartial& change) {
408+
auto range = change.range();
409+
auto start = range.start();
410+
auto end = range.end();
411+
auto startOffset = text.offsetAt(start.line(), start.character());
412+
auto endOffset = text.offsetAt(end.line(), end.character());
413+
text.value.replace(startOffset, endOffset - startOffset, change.text());
414+
text.computeLineStartOffsets();
330415
}
416+
} visit{text};
417+
418+
auto contentChanges = notification.params().contentChanges();
419+
const auto contentChangeCount = contentChanges.size();
420+
421+
for (std::size_t i = 0; i < contentChangeCount; ++i) {
422+
std::visit(visit, contentChanges.at(i));
331423
}
332424

333-
parse(textDocument.uri(), std::move(text), textDocument.version());
425+
parse(textDocument.uri());
334426
}
335427

336428
auto Server::latestDocument(const std::string& uri)
@@ -347,17 +439,32 @@ auto Server::latestDocument(const std::string& uri)
347439
}
348440

349441
void Server::operator()(const DocumentDiagnosticRequest& request) {
350-
log << std::format("Did receive DocumentDiagnosticRequest\n");
442+
logTrace(std::format("Did receive DocumentDiagnosticRequest"));
351443
}
352444

353445
void Server::operator()(const CancelNotification& notification) {
354446
auto id = notification.params().id<long>();
355-
log << std::format("Did receive CancelNotification for request with id {}\n",
356-
id);
447+
logTrace(
448+
std::format("Did receive CancelNotification for request with id {}", id));
449+
}
450+
451+
void Server::operator()(const SetTraceNotification& notification) {
452+
logTrace(std::format("Did receive SetTraceNotification"));
453+
454+
// TODO: LSP string enumerations
455+
const auto traceValue =
456+
notification.params().get().at("value").get<std::string>();
457+
458+
if (traceValue == "messages")
459+
trace_ = TraceValue::kMessages;
460+
else if (traceValue == "verbose")
461+
trace_ = TraceValue::kVerbose;
462+
else
463+
trace_ = TraceValue::kOff;
357464
}
358465

359466
void Server::operator()(const LSPRequest& request) {
360-
log << std::format("Did receive LSPRequest {}\n", request.method());
467+
logTrace(std::format("Did receive LSPRequest {}", request.method()));
361468

362469
if (!request.id().has_value()) {
363470
// nothing to do for notifications

src/frontend/cxx/lsp_server.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class Server {
5858

5959
void operator()(const DocumentDiagnosticRequest& request);
6060

61+
void operator()(const SetTraceNotification& notification);
62+
6163
void operator()(const CancelNotification& notification);
6264
void operator()(const LSPRequest& request);
6365

@@ -67,7 +69,7 @@ class Server {
6769

6870
void run(std::function<void()> task);
6971

70-
void parse(std::string uri, std::string text, long version);
72+
void parse(const std::string& uri);
7173

7274
[[nodiscard]] auto latestDocument(const std::string& uri)
7375
-> std::shared_ptr<CxxDocument>;
@@ -82,23 +84,37 @@ class Server {
8284
const LSPObject& result,
8385
std::optional<std::variant<long, std::string>> id = std::nullopt);
8486

87+
void logTrace(std::string message, std::optional<std::string> verbose = {});
88+
8589
[[nodiscard]] auto pathFromUri(const std::string& uri) -> std::string;
8690

8791
[[nodiscard]] auto readHeaders(std::istream& input)
8892
-> std::unordered_map<std::string, std::string>;
8993

94+
struct Text {
95+
std::string value;
96+
std::vector<std::size_t> lineStartOffsets;
97+
int version = 0;
98+
99+
auto offsetAt(std::size_t line, std::size_t column) const -> std::size_t;
100+
101+
void computeLineStartOffsets();
102+
};
103+
90104
private:
91105
const CLI& cli;
92106
std::istream& input;
93107
std::ostream& output;
94108
std::ostream& log;
95109
std::unordered_map<std::string, std::shared_ptr<CxxDocument>> documents_;
110+
std::unordered_map<std::string, Text> documentContents_;
96111
#ifndef CXX_NO_THREADS
97112
SyncQueue syncQueue_;
98113
std::vector<std::thread> workers_;
99114
std::mutex documentsMutex_;
100115
std::mutex outputMutex_;
101116
#endif
117+
TraceValue trace_{};
102118
bool done_ = false;
103119
};
104120

src/lsp/tests/test_types.cc

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1+
#include <cxx/lsp/enums.h>
2+
#include <cxx/lsp/fwd.h>
13
#include <cxx/lsp/types.h>
24
#include <gtest/gtest.h>
35

4-
#include <iostream>
5-
6-
#include "cxx/lsp/enums.h"
7-
#include "cxx/lsp/fwd.h"
8-
96
using namespace cxx::lsp;
107

118
TEST(LSP, Initialization) {

0 commit comments

Comments
 (0)