From ebaf168e160f96e35a313c1c4360401d74ff08b8 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Mon, 16 Apr 2018 12:36:02 -0700 Subject: [PATCH] Remove variant and clean up --- src/clang_complete.cc | 13 ++- src/clang_complete.h | 3 +- src/command_line.cc | 12 +-- src/import_pipeline.cc | 3 +- src/include_complete.cc | 44 ++++----- src/lsp.cc | 13 +++ src/lsp.h | 11 ++- src/messages/ccls_freshen_index.cc | 2 +- src/messages/initialize.cc | 4 +- src/messages/shutdown.cc | 4 +- src/messages/text_document_did_change.cc | 1 - src/messages/text_document_hover.cc | 89 +++++++++---------- .../workspace_did_change_configuration.cc | 2 +- src/method.cc | 40 ++++++++- src/method.h | 15 +++- src/project.h | 1 - src/serializer.cc | 18 ++-- src/serializer.h | 64 ++----------- src/working_files.cc | 22 ++--- 19 files changed, 171 insertions(+), 190 deletions(-) diff --git a/src/clang_complete.cc b/src/clang_complete.cc index b3c271d6..25eec8e7 100644 --- a/src/clang_complete.cc +++ b/src/clang_complete.cc @@ -656,18 +656,18 @@ ClangCompleteManager::ClangCompleteManager(Project* project, on_dropped_(on_dropped), preloaded_sessions_(kMaxPreloadedSessions), completion_sessions_(kMaxCompletionSessions) { - new std::thread([&]() { + std::thread([&]() { SetThreadName("comp-query"); CompletionQueryMain(this); - }); - new std::thread([&]() { + }).detach(); + std::thread([&]() { SetThreadName("comp-preload"); CompletionPreloadMain(this); - }); - new std::thread([&]() { + }).detach(); + std::thread([&]() { SetThreadName("diag-query"); DiagnosticQueryMain(this); - }); + }).detach(); } void ClangCompleteManager::CodeComplete( @@ -680,7 +680,6 @@ void ClangCompleteManager::CodeComplete( } void ClangCompleteManager::DiagnosticsUpdate( - const lsRequestId& id, const lsTextDocumentIdentifier& document) { bool has = false; diagnostic_request_.Iterate([&](const DiagnosticRequest& request) { diff --git a/src/clang_complete.h b/src/clang_complete.h index 904bc704..7eace626 100644 --- a/src/clang_complete.h +++ b/src/clang_complete.h @@ -91,8 +91,7 @@ struct ClangCompleteManager { const lsTextDocumentPositionParams& completion_location, const OnComplete& on_complete); // Request a diagnostics update. - void DiagnosticsUpdate(const lsRequestId& request_id, - const lsTextDocumentIdentifier& document); + void DiagnosticsUpdate(const lsTextDocumentIdentifier& document); // Notify the completion manager that |filename| has been viewed and we // should begin preloading completion data. diff --git a/src/command_line.cc b/src/command_line.cc index 1e569138..cea67191 100644 --- a/src/command_line.cc +++ b/src/command_line.cc @@ -172,7 +172,7 @@ void RunQueryDbThread(const std::string& bin_name, args); }, [](lsRequestId id) { - if (!std::holds_alternative(id)) { + if (id.Valid()) { Out_Error out; out.id = id; out.error.code = lsErrorCodes::InternalError; @@ -243,7 +243,7 @@ void RunQueryDbThread(const std::string& bin_name, // // |ipc| is connected to a server. void LaunchStdinLoop(std::unordered_map* request_times) { - new std::thread([request_times]() { + std::thread([request_times]() { SetThreadName("stdin"); auto* queue = QueueManager::instance(); while (true) { @@ -257,7 +257,7 @@ void LaunchStdinLoop(std::unordered_map* request_times) { // Emit an error ResponseMessage if |id| is available. if (message) { lsRequestId id = message->GetRequestId(); - if (!std::holds_alternative(id)) { + if (id.Valid()) { Out_Error out; out.id = id; out.error.code = lsErrorCodes::InvalidParams; @@ -279,12 +279,12 @@ void LaunchStdinLoop(std::unordered_map* request_times) { if (method_type == kMethodType_Exit) break; } - }); + }).detach(); } void LaunchStdoutThread(std::unordered_map* request_times, MultiQueueWaiter* waiter) { - new std::thread([=]() { + std::thread([=]() { SetThreadName("stdout"); auto* queue = QueueManager::instance(); @@ -305,7 +305,7 @@ void LaunchStdoutThread(std::unordered_map* request_times, fflush(stdout); } } - }); + }).detach(); } void LanguageServerMain(const std::string& bin_name, diff --git a/src/import_pipeline.cc b/src/import_pipeline.cc index 89bc4d38..cea5ad6e 100644 --- a/src/import_pipeline.cc +++ b/src/import_pipeline.cc @@ -393,8 +393,7 @@ void ParseFile(DiagnosticsEngine* diag_engine, file_contents, &perf); if (indexes.empty()) { - if (g_config->index.enabled && - !std::holds_alternative(request.id)) { + if (g_config->index.enabled && request.id.Valid()) { Out_Error out; out.id = request.id; out.error.code = lsErrorCodes::InternalError; diff --git a/src/include_complete.cc b/src/include_complete.cc index f44bedcc..4bedad39 100644 --- a/src/include_complete.cc +++ b/src/include_complete.cc @@ -29,21 +29,16 @@ std::string ElideLongPath(const std::string& path) { size_t TrimCommonPathPrefix(const std::string& result, const std::string& trimmer) { - size_t i = 0; - while (i < result.size() && i < trimmer.size()) { - char a = result[i]; - char b = trimmer[i]; -#if defined(_WIN32) - a = (char)tolower(a); - b = (char)tolower(b); +#ifdef _WIN32 + std::string s = result, t = trimmer; + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + std::transform(t.begin(), t.end(), t.begin(), ::tolower); + if (s.compare(0, t.size(), t) == 0) + return t.size(); +#else + if (result.compare(0, trimmer.size(), trimmer) == 0) + return trimmer.size(); #endif - if (a != b) - break; - ++i; - } - - if (i == trimmer.size()) - return i; return 0; } @@ -51,21 +46,14 @@ size_t TrimCommonPathPrefix(const std::string& result, bool TrimPath(Project* project, const std::string& project_root, std::string* insert_path) { - size_t start = 0; + size_t start = TrimCommonPathPrefix(*insert_path, project_root); bool angle = false; - size_t len = TrimCommonPathPrefix(*insert_path, project_root); - if (len > start) - start = len; - - for (auto& include_dir : project->quote_include_directories) { - len = TrimCommonPathPrefix(*insert_path, include_dir); - if (len > start) - start = len; - } + for (auto& include_dir : project->quote_include_directories) + start = std::max(start, TrimCommonPathPrefix(*insert_path, include_dir)); for (auto& include_dir : project->angle_include_directories) { - len = TrimCommonPathPrefix(*insert_path, include_dir); + auto len = TrimCommonPathPrefix(*insert_path, include_dir); if (len > start) { start = len; angle = true; @@ -115,8 +103,8 @@ void IncludeComplete::Rescan() { g_config->completion.includeBlacklist); is_scanning = true; - new std::thread([this]() { - SetThreadName("scan_includes"); + std::thread([this]() { + SetThreadName("scan_includes"); Timer timer; InsertStlIncludes(); @@ -129,7 +117,7 @@ void IncludeComplete::Rescan() { timer.ResetAndPrint("[perf] Scanning for includes"); is_scanning = false; - }); + }).detach(); } void IncludeComplete::InsertCompletionItem(const std::string& absolute_path, diff --git a/src/lsp.cc b/src/lsp.cc index 717cbb53..9b11ac76 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -275,6 +275,19 @@ bool lsTextEdit::operator==(const lsTextEdit& that) { return range == that.range && newText == that.newText; } +void Reflect(Writer& visitor, lsMarkedString& value) { + // If there is a language, emit a `{language:string, value:string}` object. If + // not, emit a string. + if (value.language) { + REFLECT_MEMBER_START(); + REFLECT_MEMBER(language); + REFLECT_MEMBER(value); + REFLECT_MEMBER_END(); + } else { + Reflect(visitor, value.value); + } +} + std::string Out_ShowLogMessage::method() { if (display_type == DisplayType::Log) return "window/logMessage"; diff --git a/src/lsp.h b/src/lsp.h index 31ebd07b..a72410b6 100644 --- a/src/lsp.h +++ b/src/lsp.h @@ -239,7 +239,7 @@ MAKE_REFLECT_STRUCT(lsTextDocumentIdentifier, uri); struct lsVersionedTextDocumentIdentifier { lsDocumentUri uri; // The version number of this document. number | null - std::variant version; + std::optional version; lsTextDocumentIdentifier AsTextDocumentIdentifier() const; }; @@ -325,12 +325,11 @@ MAKE_REFLECT_STRUCT(lsFormattingOptions, tabSize, insertSpaces); // // Note that markdown strings will be sanitized - that means html will be // escaped. -struct lsMarkedString1 { - std::string_view language; - std::string_view value; +struct lsMarkedString { + std::optional language; + std::string value; }; -using lsMarkedString = std::variant; -MAKE_REFLECT_STRUCT(lsMarkedString1, language, value); +void Reflect(Writer& visitor, lsMarkedString& value); struct lsTextDocumentContentChangeEvent { // The range of the document that changed. diff --git a/src/messages/ccls_freshen_index.cc b/src/messages/ccls_freshen_index.cc index 10220b53..91d0bc00 100644 --- a/src/messages/ccls_freshen_index.cc +++ b/src/messages/ccls_freshen_index.cc @@ -87,7 +87,7 @@ struct Handler_CclsFreshenIndex : BaseMessageHandler { Timer time; // Send index requests for every file. - project->Index(QueueManager::instance(), working_files, std::monostate()); + project->Index(QueueManager::instance(), working_files, lsRequestId()); time.ResetAndPrint("[perf] Dispatched $ccls/freshenIndex index requests"); } }; diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index d2aa5167..8ee38d91 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -518,12 +518,12 @@ struct Handler_Initialize : BaseMessageHandler { } LOG_S(INFO) << "Starting " << g_config->index.threads << " indexers"; for (int i = 0; i < g_config->index.threads; i++) { - new std::thread([=]() { + std::thread([=]() { SetThreadName("indexer" + std::to_string(i)); Indexer_Main(diag_engine, file_consumer_shared, timestamp_manager, import_manager, import_pipeline_status, project, working_files, waiter); - }); + }).detach(); } // Start scanning include directories before dispatching project diff --git a/src/messages/shutdown.cc b/src/messages/shutdown.cc index 5967e239..e16721d4 100644 --- a/src/messages/shutdown.cc +++ b/src/messages/shutdown.cc @@ -11,8 +11,8 @@ MAKE_REFLECT_STRUCT(In_Shutdown, id); REGISTER_IN_MESSAGE(In_Shutdown); struct Out_Shutdown : public lsOutMessage { - lsRequestId id; // defaults to std::monostate (null) - std::monostate result; // null + lsRequestId id; + JsonNull result; }; MAKE_REFLECT_STRUCT(Out_Shutdown, jsonrpc, id, result); diff --git a/src/messages/text_document_did_change.cc b/src/messages/text_document_did_change.cc index 5f5d2a71..bc976cbd 100644 --- a/src/messages/text_document_did_change.cc +++ b/src/messages/text_document_did_change.cc @@ -39,7 +39,6 @@ struct Handler_TextDocumentDidChange } clang_complete->NotifyEdit(path); clang_complete->DiagnosticsUpdate( - std::monostate(), request->params.textDocument.AsTextDocumentIdentifier()); } }; diff --git a/src/messages/text_document_hover.cc b/src/messages/text_document_hover.cc index a5b51812..7ea1aeee 100644 --- a/src/messages/text_document_hover.cc +++ b/src/messages/text_document_hover.cc @@ -5,41 +5,40 @@ namespace { MethodType kMethodType = "textDocument/hover"; -std::pair GetCommentsAndHover( - QueryDatabase* db, - SymbolRef sym) { - switch (sym.kind) { - case SymbolKind::Type: { - if (const auto* def = db->GetType(sym).AnyDef()) { - return {def->comments, !def->hover.empty() - ? std::string_view(def->hover) - : std::string_view(def->detailed_name)}; +// Find the comments for |sym|, if any. +std::optional GetComments(QueryDatabase* db, SymbolRef sym) { + std::optional ret; + WithEntity(db, sym, [&](const auto& entity) { + if (const auto* def = entity.AnyDef()) + if (!def->comments.empty()) { + lsMarkedString m; + m.value = def->comments; + ret = m; } - break; - } - case SymbolKind::Func: { - if (const auto* def = db->GetFunc(sym).AnyDef()) { - return {def->comments, !def->hover.empty() - ? std::string_view(def->hover) - : std::string_view(def->detailed_name)}; + }); + return ret; +} + +// Returns the hover or detailed name for `sym`, if any. +std::optional GetHoverOrName(QueryDatabase* db, + const std::string& language, + SymbolRef sym) { + + std::optional ret; + WithEntity(db, sym, [&](const auto& entity) { + if (const auto* def = entity.AnyDef()) { + lsMarkedString m; + m.language = language; + if (!def->hover.empty()) { + m.value = def->hover; + ret = m; + } else if (!def->detailed_name.empty()) { + m.value = def->detailed_name; + ret = m; } - break; } - case SymbolKind::Var: { - if (const auto* def = db->GetVar(sym).AnyDef()) { - return {def->comments, !def->hover.empty() - ? std::string_view(def->hover) - : std::string_view(def->detailed_name)}; - } - break; - } - case SymbolKind::File: - case SymbolKind::Invalid: { - assert(false && "unexpected"); - break; - } - } - return {"", ""}; + }); + return ret; } struct In_TextDocumentHover : public RequestInMessage { @@ -66,7 +65,7 @@ void Reflect(Writer& visitor, Out_TextDocumentHover& value) { if (value.result) REFLECT_MEMBER(result); else { - // Empty std::optional<> is elided by the default serializer, we need to write + // Empty optional<> is elided by the default serializer, we need to write // |null| to be compliant with the LSP. visitor.Key("result"); visitor.Null(); @@ -77,11 +76,11 @@ void Reflect(Writer& visitor, Out_TextDocumentHover& value) { struct Handler_TextDocumentHover : BaseMessageHandler { MethodType GetMethodType() const override { return kMethodType; } void Run(In_TextDocumentHover* request) override { + auto& params = request->params; QueryFile* file; if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { + params.textDocument.uri.GetPath(), &file)) return; - } WorkingFile* working_file = working_files->GetFileByFilename(file->def->path); @@ -90,25 +89,23 @@ struct Handler_TextDocumentHover : BaseMessageHandler { out.id = request->id; for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { + FindSymbolsAtLocation(working_file, file, params.position)) { // Found symbol. Return hover. std::optional ls_range = GetLsRange( working_files->GetFileByFilename(file->def->path), sym.range); if (!ls_range) continue; - std::pair comments_hover = - GetCommentsAndHover(db, sym); - if (comments_hover.first.size() || comments_hover.second.size()) { + std::optional comments = GetComments(db, sym); + std::optional hover = + GetHoverOrName(db, file->def->language, sym); + if (comments || hover) { out.result = Out_TextDocumentHover::Result(); - if (comments_hover.first.size()) { - out.result->contents.emplace_back(comments_hover.first); - } - if (comments_hover.second.size()) { - out.result->contents.emplace_back(lsMarkedString1{ - std::string_view(file->def->language), comments_hover.second}); - } out.result->range = *ls_range; + if (comments) + out.result->contents.push_back(*comments); + if (hover) + out.result->contents.push_back(*hover); break; } } diff --git a/src/messages/workspace_did_change_configuration.cc b/src/messages/workspace_did_change_configuration.cc index 8d81b892..afe578ca 100644 --- a/src/messages/workspace_did_change_configuration.cc +++ b/src/messages/workspace_did_change_configuration.cc @@ -33,7 +33,7 @@ struct Handler_WorkspaceDidChangeConfiguration std::to_string(project->entries.size()) + " files)"); time.Reset(); - project->Index(QueueManager::instance(), working_files, std::monostate()); + project->Index(QueueManager::instance(), working_files, lsRequestId()); time.ResetAndPrint( "[perf] Dispatched workspace/didChangeConfiguration index requests"); diff --git a/src/method.cc b/src/method.cc index 99af41a3..f0ba5ce1 100644 --- a/src/method.cc +++ b/src/method.cc @@ -2,6 +2,40 @@ MethodType kMethodType_Unknown = "$unknown"; MethodType kMethodType_Exit = "exit"; -MethodType kMethodType_TextDocumentPublishDiagnostics = "textDocument/publishDiagnostics"; -MethodType kMethodType_CclsPublishInactiveRegions = "$ccls/publishInactiveRegions"; -MethodType kMethodType_CclsPublishSemanticHighlighting = "$ccls/publishSemanticHighlighting"; +MethodType kMethodType_TextDocumentPublishDiagnostics = + "textDocument/publishDiagnostics"; +MethodType kMethodType_CclsPublishInactiveRegions = + "$ccls/publishInactiveRegions"; +MethodType kMethodType_CclsPublishSemanticHighlighting = + "$ccls/publishSemanticHighlighting"; + +void Reflect(Reader& visitor, lsRequestId& value) { + if (visitor.IsInt64()) { + value.type = lsRequestId::kInt; + value.value = visitor.GetInt64(); + } else if (visitor.IsInt()) { + value.type = lsRequestId::kInt; + value.value = visitor.GetInt(); + } else if (visitor.IsString()) { + value.type = lsRequestId::kString; + value.value = atoll(visitor.GetString().c_str()); + } else { + value.type = lsRequestId::kNone; + value.value = -1; + } +} + +void Reflect(Writer& visitor, lsRequestId& value) { + switch (value.type) { + case lsRequestId::kNone: + visitor.Null(); + break; + case lsRequestId::kInt: + visitor.Int(value.value); + break; + case lsRequestId::kString: + auto s = std::to_string(value.value); + visitor.String(s.c_str(), s.length()); + break; + } +} diff --git a/src/method.h b/src/method.h index a336ed5b..525c409a 100644 --- a/src/method.h +++ b/src/method.h @@ -12,7 +12,18 @@ extern MethodType kMethodType_TextDocumentPublishDiagnostics; extern MethodType kMethodType_CclsPublishInactiveRegions; extern MethodType kMethodType_CclsPublishSemanticHighlighting; -using lsRequestId = std::variant; +struct lsRequestId { + // The client can send the request id as an int or a string. We should output + // the same format we received. + enum Type { kNone, kInt, kString }; + Type type = kNone; + + int value = -1; + + bool Valid() const { return type != kNone; } +}; +void Reflect(Reader& visitor, lsRequestId& value); +void Reflect(Writer& visitor, lsRequestId& value); struct InMessage { virtual ~InMessage() = default; @@ -32,6 +43,6 @@ struct RequestInMessage : public InMessage { // NotificationInMessage does not have |id|. struct NotificationInMessage : public InMessage { lsRequestId GetRequestId() const override { - return std::monostate(); + return lsRequestId(); } }; diff --git a/src/project.h b/src/project.h index d67562ab..0767e945 100644 --- a/src/project.h +++ b/src/project.h @@ -8,7 +8,6 @@ #include #include #include -#include #include class QueueManager; diff --git a/src/serializer.cc b/src/serializer.cc index 2531c20e..2f2acd05 100644 --- a/src/serializer.cc +++ b/src/serializer.cc @@ -140,6 +140,15 @@ void Reflect(Writer& visitor, NtString& value) { visitor.String(s ? s : ""); } +void Reflect(Reader& visitor, JsonNull& value) { + assert(visitor.Format() == SerializeFormat::Json); + visitor.GetNull(); +} + +void Reflect(Writer& visitor, JsonNull& value) { + visitor.Null(); +} + // TODO: Move this to indexer.cc void Reflect(Reader& visitor, IndexInclude& value) { REFLECT_MEMBER_START(); @@ -299,15 +308,6 @@ void Reflect(TVisitor& visitor, IndexFile& value) { REFLECT_MEMBER_END(); } -void Reflect(Reader& visitor, std::monostate&) { - assert(visitor.Format() == SerializeFormat::Json); - visitor.GetNull(); -} - -void Reflect(Writer& visitor, std::monostate&) { - visitor.Null(); -} - void Reflect(Reader& visitor, SerializeFormat& value) { std::string fmt = visitor.GetString(); value = fmt[0] == 'b' ? SerializeFormat::Binary : SerializeFormat::Json; diff --git a/src/serializer.h b/src/serializer.h index edb35e1f..b31ae2b4 100644 --- a/src/serializer.h +++ b/src/serializer.h @@ -13,11 +13,12 @@ #include #include #include -#include #include enum class SerializeFormat { Binary, Json }; +struct JsonNull {}; + class Reader { public: virtual ~Reader() {} @@ -168,20 +169,18 @@ void Reflect(Writer& visitor, std::string_view& view); void Reflect(Reader& visitor, NtString& value); void Reflect(Writer& visitor, NtString& value); -// std::monostate is used to represent JSON null -void Reflect(Reader& visitor, std::monostate&); -void Reflect(Writer& visitor, std::monostate&); +void Reflect(Reader& visitor, JsonNull& value); +void Reflect(Writer& visitor, JsonNull& value); void Reflect(Reader& visitor, SerializeFormat& value); void Reflect(Writer& visitor, SerializeFormat& value); //// Type constructors -// ReflectMember std::optional is used to represent TypeScript std::optional properties +// ReflectMember std::optional is used to represent TypeScript optional properties // (in `key: value` context). // Reflect std::optional is used for a different purpose, whether an object is -// nullable (possibly in `value` context). For the nullable semantics, -// std::variant is recommended. +// nullable (possibly in `value` context). template void Reflect(Reader& visitor, std::optional& value) { if (visitor.IsNull()) { @@ -243,57 +242,6 @@ void ReflectMember(Writer& visitor, const char* name, Maybe& value) { } } -// Helper struct to reflect std::variant -template -struct ReflectVariant { - // If T appears in Ts..., we should set the value of std::variant to - // what we get from Reader. - template - void ReflectTag(Reader& visitor, std::variant& value) { - if constexpr (std::disjunction_v...>) { - T a; - Reflect(visitor, a); - value = std::move(a); - } - } - - void operator()(Reader& visitor, std::variant& value) { - // Based on tag dispatch, call different ReflectTag helper. - if (visitor.IsNull()) - ReflectTag(visitor, value); - // It is possible that IsInt64() && IsInt(). We don't call ReflectTag - // if int is not in Ts... - else if (std::disjunction_v...> && visitor.IsInt()) - ReflectTag(visitor, value); - else if (visitor.IsInt64()) - ReflectTag(visitor, value); - else if (visitor.IsString()) - ReflectTag(visitor, value); - else - assert(0); - } - - // Check which type the variant contains and call corresponding Reflect. - void operator()(Writer& visitor, std::variant& value) { - if (value.index() == N - 1) - Reflect(visitor, std::get(value)); - else - ReflectVariant()(visitor, value); - } -}; - -// Writer reflection on std::variant recurses. This is induction basis. -template -struct ReflectVariant<0, Ts...> { - void operator()(Writer& visitor, std::variant& value) {} -}; - -// std::variant -template -void Reflect(TVisitor& visitor, std::variant& value) { - ReflectVariant()(visitor, value); -} - // std::vector template void Reflect(Reader& visitor, std::vector& values) { diff --git a/src/working_files.cc b/src/working_files.cc index e759b5d5..5e293464 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -24,19 +24,15 @@ lsPosition GetPositionForOffset(const std::string& content, int offset) { if (offset >= content.size()) offset = (int)content.size() - 1; - lsPosition result; + int line = 0, col = 0; int i = 0; - while (i < offset) { - if (content[i] == '\n') { - result.line += 1; - result.character = 0; - } else { - result.character += 1; - } - ++i; + for (; i < offset; i++) { + if (content[i] == '\n') + line++, col = 0; + else + col++; } - - return result; + return {line, col}; } std::vector ToLines(const std::string& content) { @@ -511,8 +507,8 @@ void WorkingFiles::OnChange(const lsTextDocumentDidChangeParams& change) { } // version: number | null - if (std::holds_alternative(change.textDocument.version)) - file->version = std::get(change.textDocument.version); + if (change.textDocument.version) + file->version = *change.textDocument.version; for (const lsTextDocumentContentChangeEvent& diff : change.contentChanges) { // Per the spec replace everything if the rangeLength and range are not set.