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>
3334
3435namespace 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+
3685Server::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)
4695Server::~Server () {}
4796
4897auto 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
@@ -121,9 +172,9 @@ auto Server::readHeaders(std::istream& input)
121172 auto value = line.substr (pos + 1 );
122173
123174 // trim whitespace
124- name.erase (name.find_last_not_of (" \t\r\n " ) + 1 );
125- value.erase (0 , value.find_first_not_of (" \t\r\n " ));
126- value.erase (value.find_last_not_of (" \t\r\n " ) + 1 );
175+ name.erase (name.find_last_not_of (" \t\r " ) + 1 );
176+ value.erase (0 , value.find_first_not_of (" \t\r " ));
177+ value.erase (value.find_last_not_of (" \t\r " ) + 1 );
127178
128179 headers.insert_or_assign (std::move (name), std::move (value));
129180 }
@@ -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+
174241auto 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
184251void 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
266338void 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
277350void Server::operator ()(const InitializedNotification& notification) {
278- log << std::format (" Did receive InitializedNotification\n " );
351+ logTrace ( std::format (" Did receive InitializedNotification" ) );
279352}
280353
281354void 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
290363void 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
295368void 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
302383void 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
309390void 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
336428auto Server::latestDocument (const std::string& uri)
@@ -347,17 +439,32 @@ auto Server::latestDocument(const std::string& uri)
347439}
348440
349441void Server::operator ()(const DocumentDiagnosticRequest& request) {
350- log << std::format (" Did receive DocumentDiagnosticRequest\n " );
442+ logTrace ( std::format (" Did receive DocumentDiagnosticRequest" ) );
351443}
352444
353445void 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
359466void 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
0 commit comments