From 39a17a9fd7f064a076addc618dde0aa43cf7acc3 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i@maskray.me>
Date: Thu, 31 May 2018 20:06:09 -0700
Subject: [PATCH] Remove WithFileContent & lex_utils.{cc,h}

---
 CMakeLists.txt                                |  1 -
 src/indexer.cc                                | 13 +--
 src/indexer.h                                 | 14 ---
 src/lex_utils.cc                              | 87 -------------------
 src/lex_utils.h                               | 16 ----
 src/main.cc                                   |  6 +-
 src/message_handler.cc                        |  1 -
 src/messages/text_document_completion.cc      |  2 -
 src/messages/text_document_definition.cc      |  1 -
 .../text_document_range_formatting.cc         | 72 ---------------
 src/messages/workspace_symbol.cc              |  1 -
 src/pipeline.cc                               | 41 +++------
 src/pipeline.hh                               |  5 +-
 src/query.cc                                  |  6 +-
 src/query.h                                   | 17 +---
 src/utils.cc                                  | 18 ++++
 src/utils.h                                   |  4 +
 src/working_files.cc                          | 44 +++++++++-
 src/working_files.h                           |  5 ++
 19 files changed, 100 insertions(+), 254 deletions(-)
 delete mode 100644 src/lex_utils.cc
 delete mode 100644 src/lex_utils.h
 delete mode 100644 src/messages/text_document_range_formatting.cc

diff --git a/CMakeLists.txt b/CMakeLists.txt
index d3db90e0..fb5a8e8f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -202,7 +202,6 @@ target_sources(ccls PRIVATE
                src/indexer.cc
                src/method.cc
                src/language.cc
-               src/lex_utils.cc
                src/log.cc
                src/lsp.cc
                src/match.cc
diff --git a/src/indexer.cc b/src/indexer.cc
index 48dffa65..760d02a9 100644
--- a/src/indexer.cc
+++ b/src/indexer.cc
@@ -3,9 +3,11 @@
 #include "log.hh"
 #include "platform.h"
 #include "serializer.h"
-#include "timer.h"
 #include "type_printer.h"
 
+#include <llvm/Support/Timer.h>
+using namespace llvm;
+
 #include <assert.h>
 #include <inttypes.h>
 #include <limits.h>
@@ -2016,7 +2018,8 @@ std::vector<std::unique_ptr<IndexFile>> ClangIndexer::Index(
 
   file = NormalizePath(file);
 
-  Timer timer;
+  Timer timer("parse", "parse tu");
+  timer.startTimer();
 
   std::vector<CXUnsavedFile> unsaved_files;
   for (const FileContents& contents : file_contents) {
@@ -2034,7 +2037,7 @@ std::vector<std::unique_ptr<IndexFile>> ClangIndexer::Index(
   if (!tu)
     return {};
 
-  perf->index_parse = timer.ElapsedMicrosecondsAndReset();
+  timer.stopTimer();
 
   return ParseWithTu(vfs, perf, tu.get(), &index, file, args, unsaved_files);
 }
@@ -2048,6 +2051,7 @@ std::vector<std::unique_ptr<IndexFile>> ParseWithTu(
     const std::vector<std::string>& args,
     const std::vector<CXUnsavedFile>& file_contents) {
   Timer timer;
+  timer.startTimer();
 
   IndexerCallbacks callback = {0};
   // Available callbacks:
@@ -2085,13 +2089,12 @@ std::vector<std::unique_ptr<IndexFile>> ParseWithTu(
                  << " failed with errno=" << index_result;
     return {};
   }
-
   clang_IndexAction_dispose(index_action);
 
   ClangCursor(clang_getTranslationUnitCursor(tu->cx_tu))
       .VisitChildren(&VisitMacroDefinitionAndExpansions, &param);
 
-  perf->index_build = timer.ElapsedMicrosecondsAndReset();
+  timer.stopTimer();
 
   std::unordered_map<std::string, int> inc_to_line;
   // TODO
diff --git a/src/indexer.h b/src/indexer.h
index 6921a8dd..6c246582 100644
--- a/src/indexer.h
+++ b/src/indexer.h
@@ -27,8 +27,6 @@ struct IndexFunc;
 struct IndexVar;
 struct QueryFile;
 
-using RawId = uint32_t;
-
 struct SymbolIdx {
   Usr usr;
   SymbolKind kind;
@@ -153,19 +151,9 @@ struct TypeDef : NameMixin<TypeDef> {
   NtString hover;
   NtString comments;
 
-  // While a class/type can technically have a separate declaration/definition,
-  // it doesn't really happen in practice. The declaration never contains
-  // comments or insightful information. The user always wants to jump from
-  // the declaration to the definition - never the other way around like in
-  // functions and (less often) variables.
-  //
-  // It's also difficult to identify a `class Foo;` statement with the clang
-  // indexer API (it's doable using cursor AST traversal), so we don't bother
-  // supporting the feature.
   Maybe<Use> spell;
   Maybe<Use> extent;
 
-  // Immediate parent types.
   std::vector<Usr> bases;
 
   // Types, functions, and variables defined in this type.
@@ -221,8 +209,6 @@ struct VarDef : NameMixin<VarDef> {
   std::string detailed_name;
   NtString hover;
   NtString comments;
-  // TODO: definitions should be a list of ranges, since there can be more
-  //       than one - when??
   Maybe<Use> spell;
   Maybe<Use> extent;
 
diff --git a/src/lex_utils.cc b/src/lex_utils.cc
deleted file mode 100644
index 9380f723..00000000
--- a/src/lex_utils.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-#include "lex_utils.h"
-
-#include <doctest/doctest.h>
-
-#include <algorithm>
-
-// VSCode (UTF-16) disagrees with Emacs lsp-mode (UTF-8) on how to represent
-// text documents.
-// We use a UTF-8 iterator to approximate UTF-16 in the specification (weird).
-// This is good enough and fails only for UTF-16 surrogate pairs.
-int GetOffsetForPosition(lsPosition position, std::string_view content) {
-  size_t i = 0;
-  for (; position.line > 0 && i < content.size(); i++)
-    if (content[i] == '\n')
-      position.line--;
-  for (; position.character > 0 && i < content.size(); position.character--)
-    if (uint8_t(content[i++]) >= 128) {
-      // Skip 0b10xxxxxx
-      while (i < content.size() && uint8_t(content[i]) >= 128 &&
-             uint8_t(content[i]) < 192)
-        i++;
-    }
-  return int(i);
-}
-
-std::string_view LexIdentifierAroundPos(lsPosition position,
-                                        std::string_view content) {
-  int start = GetOffsetForPosition(position, content);
-  int end = start + 1;
-  char c;
-
-  // We search for :: before the cursor but not after to get the qualifier.
-  for (; start > 0; start--) {
-    c = content[start - 1];
-    if (isalnum(c) || c == '_')
-      ;
-    else if (c == ':' && start > 1 && content[start - 2] == ':')
-      start--;
-    else
-      break;
-  }
-
-  for (; end < (int)content.size(); end++)
-    if (c = content[end], !(isalnum(c) || c == '_'))
-      break;
-
-  return content.substr(start, end - start);
-}
-
-// Find discontinous |search| in |content|.
-// Return |found| and the count of skipped chars before found.
-int ReverseSubseqMatch(std::string_view pat,
-                       std::string_view text,
-                       int case_sensitivity) {
-  if (case_sensitivity == 1)
-    case_sensitivity = std::any_of(pat.begin(), pat.end(), isupper) ? 2 : 0;
-  int j = pat.size();
-  if (!j)
-    return text.size();
-  for (int i = text.size(); i--;)
-    if ((case_sensitivity ? text[i] == pat[j - 1]
-                          : tolower(text[i]) == tolower(pat[j - 1])) &&
-        !--j)
-      return i;
-  return -1;
-}
-
-TEST_SUITE("Offset") {
-  TEST_CASE("past end") {
-    std::string content = "foo";
-    int offset = GetOffsetForPosition(lsPosition{10, 10}, content);
-    REQUIRE(offset <= content.size());
-  }
-
-  TEST_CASE("in middle of content") {
-    std::string content = "abcdefghijk";
-    for (int i = 0; i < content.size(); ++i) {
-      int offset = GetOffsetForPosition(lsPosition{0, i}, content);
-      REQUIRE(i == offset);
-    }
-  }
-
-  TEST_CASE("at end of content") {
-    REQUIRE(GetOffsetForPosition(lsPosition{0, 0}, "") == 0);
-    REQUIRE(GetOffsetForPosition(lsPosition{0, 1}, "a") == 1);
-  }
-}
diff --git a/src/lex_utils.h b/src/lex_utils.h
deleted file mode 100644
index 4206a120..00000000
--- a/src/lex_utils.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma once
-
-#include "lsp.h"
-
-#include <string_view>
-#include <tuple>
-
-// Utility method to map |position| to an offset inside of |content|.
-int GetOffsetForPosition(lsPosition position, std::string_view content);
-
-std::string_view LexIdentifierAroundPos(lsPosition position,
-                                        std::string_view content);
-
-int ReverseSubseqMatch(std::string_view pat,
-                       std::string_view text,
-                       int case_sensitivity);
diff --git a/src/main.cc b/src/main.cc
index 03936f5c..ebb6b488 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -129,12 +129,10 @@ int main(int argc, char** argv) {
       }
     }
 
-    std::unordered_map<MethodType, Timer> request_times;
-
     // The thread that reads from stdin and dispatchs commands to the main thread.
-    pipeline::LaunchStdin(&request_times);
+    pipeline::LaunchStdin();
     // The thread that writes responses from the main thread to stdout.
-    pipeline::LaunchStdout(&request_times);
+    pipeline::LaunchStdout();
     // Main thread which also spawns indexer threads upon the "initialize" request.
     pipeline::MainLoop();
   }
diff --git a/src/message_handler.cc b/src/message_handler.cc
index ab871900..168fa605 100644
--- a/src/message_handler.cc
+++ b/src/message_handler.cc
@@ -1,6 +1,5 @@
 #include "message_handler.h"
 
-#include "lex_utils.h"
 #include "log.hh"
 #include "project.h"
 #include "query_utils.h"
diff --git a/src/messages/text_document_completion.cc b/src/messages/text_document_completion.cc
index 39998655..488f42b2 100644
--- a/src/messages/text_document_completion.cc
+++ b/src/messages/text_document_completion.cc
@@ -7,8 +7,6 @@
 #include "working_files.h"
 using namespace ccls;
 
-#include "lex_utils.h"
-
 #include <regex>
 
 namespace {
diff --git a/src/messages/text_document_definition.cc b/src/messages/text_document_definition.cc
index b65682a5..93786c3e 100644
--- a/src/messages/text_document_definition.cc
+++ b/src/messages/text_document_definition.cc
@@ -1,4 +1,3 @@
-#include "lex_utils.h"
 #include "message_handler.h"
 #include "pipeline.hh"
 #include "query_utils.h"
diff --git a/src/messages/text_document_range_formatting.cc b/src/messages/text_document_range_formatting.cc
deleted file mode 100644
index 8ffe41f8..00000000
--- a/src/messages/text_document_range_formatting.cc
+++ /dev/null
@@ -1,72 +0,0 @@
-#include "clang_format.h"
-#include "lex_utils.h"
-#include "message_handler.h"
-#include "pipeline.hh"
-using namespace ccls;
-#include "working_files.h"
-
-#include <loguru.hpp>
-
-namespace {
-MethodType kMethodType = "textDocument/rangeFormatting";
-
-struct lsTextDocumentRangeFormattingParams {
-  lsTextDocumentIdentifier textDocument;
-  lsRange range;
-  lsFormattingOptions options;
-};
-MAKE_REFLECT_STRUCT(lsTextDocumentRangeFormattingParams,
-                    textDocument,
-                    range,
-                    options);
-
-struct In_TextDocumentRangeFormatting : public RequestInMessage {
-  MethodType GetMethodType() const override { return kMethodType; }
-  lsTextDocumentRangeFormattingParams params;
-};
-MAKE_REFLECT_STRUCT(In_TextDocumentRangeFormatting, id, params);
-REGISTER_IN_MESSAGE(In_TextDocumentRangeFormatting);
-
-struct Out_TextDocumentRangeFormatting
-    : public lsOutMessage<Out_TextDocumentRangeFormatting> {
-  lsRequestId id;
-  std::vector<lsTextEdit> result;
-};
-MAKE_REFLECT_STRUCT(Out_TextDocumentRangeFormatting, jsonrpc, id, result);
-
-struct Handler_TextDocumentRangeFormatting
-    : BaseMessageHandler<In_TextDocumentRangeFormatting> {
-  MethodType GetMethodType() const override { return kMethodType; }
-
-  void Run(In_TextDocumentRangeFormatting* request) override {
-    Out_TextDocumentRangeFormatting response;
-    response.id = request->id;
-#if USE_CLANG_CXX
-    QueryFile* file;
-    if (!FindFileOrFail(db, project, request->id,
-                        request->params.textDocument.uri.GetPath(), &file)) {
-      return;
-    }
-
-    WorkingFile* working_file =
-        working_files->GetFileByFilename(file->def->path);
-
-    int start = GetOffsetForPosition(request->params.range.start,
-                                     working_file->buffer_content),
-        end = GetOffsetForPosition(request->params.range.end,
-                                   working_file->buffer_content);
-    response.result = ConvertClangReplacementsIntoTextEdits(
-        working_file->buffer_content,
-        ClangFormatDocument(working_file, start, end, request->params.options));
-#else
-    LOG_S(WARNING) << "You must compile ccls with --use-clang-cxx to use "
-                      "textDocument/rangeFormatting.";
-    // TODO: Fallback to execute the clang-format binary?
-    response.result = {};
-#endif
-
-    pipeline::WriteStdout(kMethodType, response);
-  }
-};
-REGISTER_MESSAGE_HANDLER(Handler_TextDocumentRangeFormatting);
-}  // namespace
diff --git a/src/messages/workspace_symbol.cc b/src/messages/workspace_symbol.cc
index 2515b1fc..3b608635 100644
--- a/src/messages/workspace_symbol.cc
+++ b/src/messages/workspace_symbol.cc
@@ -1,5 +1,4 @@
 #include "fuzzy_match.h"
-#include "lex_utils.h"
 #include "message_handler.h"
 #include "pipeline.hh"
 #include "query_utils.h"
diff --git a/src/pipeline.cc b/src/pipeline.cc
index d65b1db5..2a96e522 100644
--- a/src/pipeline.cc
+++ b/src/pipeline.cc
@@ -11,10 +11,10 @@
 #include "project.h"
 #include "query_utils.h"
 #include "pipeline.hh"
-#include "timer.h"
 
 #include <llvm/ADT/Twine.h>
 #include <llvm/Support/Threading.h>
+#include <llvm/Support/Timer.h>
 using namespace llvm;
 
 #include <thread>
@@ -237,14 +237,15 @@ bool Indexer_Parse(DiagnosticsEngine* diag_engine,
 
     // Write current index to disk if requested.
     LOG_S(INFO) << "store index for " << path;
-    Timer time;
     {
+      Timer timer("write", "store index");
+      timer.startTimer();
       std::string cache_path = GetCachePath(path);
       WriteToFile(cache_path, curr->file_contents);
       WriteToFile(AppendSerializationFormat(cache_path),
                   Serialize(g_config->cacheFormat, *curr));
+      timer.stopTimer();
     }
-    perf.index_save_to_disk = time.ElapsedMicrosecondsAndReset();
 
     vfs->Reset(path_to_index);
     if (entry.id >= 0) {
@@ -255,7 +256,6 @@ bool Indexer_Parse(DiagnosticsEngine* diag_engine,
 
     // Build delta update.
     IndexUpdate update = IndexUpdate::CreateDelta(prev.get(), curr.get());
-    perf.index_make_delta = time.ElapsedMicrosecondsAndReset();
     LOG_S(INFO) << "built index for " << path << " (is_delta=" << !!prev << ")";
 
     on_indexed->PushBack({std::move(update), perf}, request.is_interactive);
@@ -264,15 +264,6 @@ bool Indexer_Parse(DiagnosticsEngine* diag_engine,
   return true;
 }
 
-// This function returns true if e2e timing should be displayed for the given
-// MethodId.
-bool ShouldDisplayMethodTiming(MethodType type) {
-  return
-    type != kMethodType_TextDocumentPublishDiagnostics &&
-    type != kMethodType_CclsPublishInactiveRegions &&
-    type != kMethodType_Unknown;
-}
-
 }  // namespace
 
 void Init() {
@@ -316,20 +307,22 @@ void Main_OnIndexed(DB* db,
     return;
   }
 
-  Timer time;
+  Timer timer("apply", "apply index");
+  timer.startTimer();
   db->ApplyIndexUpdate(&response->update);
+  timer.stopTimer();
 
   // Update indexed content, inactive lines, and semantic highlighting.
   if (response->update.files_def_update) {
     auto& update = *response->update.files_def_update;
-    time.ResetAndPrint("apply index for " + update.value.path);
+    LOG_S(INFO) << "apply index for " << update.first.path;
     if (WorkingFile* working_file =
-            working_files->GetFileByFilename(update.value.path)) {
+            working_files->GetFileByFilename(update.first.path)) {
       // Update indexed content.
-      working_file->SetIndexContent(update.file_content);
+      working_file->SetIndexContent(update.second);
 
       // Inactive lines.
-      EmitInactiveLines(working_file, update.value.inactive_regions);
+      EmitInactiveLines(working_file, update.first.inactive_regions);
 
       // Semantic highlighting.
       int file_id =
@@ -340,8 +333,8 @@ void Main_OnIndexed(DB* db,
   }
 }
 
-void LaunchStdin(std::unordered_map<MethodType, Timer>* request_times) {
-  std::thread([request_times]() {
+void LaunchStdin() {
+  std::thread([]() {
     set_thread_name("stdin");
     while (true) {
       std::unique_ptr<InMessage> message;
@@ -367,7 +360,6 @@ void LaunchStdin(std::unordered_map<MethodType, Timer>* request_times) {
 
       // Cache |method_id| so we can access it after moving |message|.
       MethodType method_type = message->GetMethodType();
-      (*request_times)[method_type] = Timer();
 
       on_request->PushBack(std::move(message));
 
@@ -379,7 +371,7 @@ void LaunchStdin(std::unordered_map<MethodType, Timer>* request_times) {
   }).detach();
 }
 
-void LaunchStdout(std::unordered_map<MethodType, Timer>* request_times) {
+void LaunchStdout() {
   std::thread([=]() {
     set_thread_name("stdout");
 
@@ -391,11 +383,6 @@ void LaunchStdout(std::unordered_map<MethodType, Timer>* request_times) {
       }
 
       for (auto& message : messages) {
-        if (ShouldDisplayMethodTiming(message.method)) {
-          Timer time = (*request_times)[message.method];
-          time.ResetAndPrint("[e2e] Running " + std::string(message.method));
-        }
-
         fwrite(message.content.c_str(), message.content.size(), 1, stdout);
         fflush(stdout);
       }
diff --git a/src/pipeline.hh b/src/pipeline.hh
index 760ac190..f21a44c7 100644
--- a/src/pipeline.hh
+++ b/src/pipeline.hh
@@ -2,7 +2,6 @@
 
 #include "method.h"
 #include "query.h"
-#include "timer.h"
 
 #include <string>
 #include <unordered_map>
@@ -17,8 +16,8 @@ struct lsBaseOutMessage;
 namespace ccls::pipeline {
 
 void Init();
-void LaunchStdin(std::unordered_map<MethodType, Timer>* request_times);
-void LaunchStdout(std::unordered_map<MethodType, Timer>* request_times);
+void LaunchStdin();
+void LaunchStdout();
 void Indexer_Main(DiagnosticsEngine* diag_engine,
                   VFS* vfs,
                   Project* project,
diff --git a/src/query.cc b/src/query.cc
index c221c3f8..5ceef975 100644
--- a/src/query.cc
+++ b/src/query.cc
@@ -148,7 +148,7 @@ QueryFile::DefUpdate BuildFileDefUpdate(const IndexFile& indexed) {
               return a.range.start < b.range.start;
             });
 
-  return QueryFile::DefUpdate(def, indexed.file_contents);
+  return {std::move(def), std::move(indexed.file_contents)};
 }
 
 // Returns true if an element with the same file is found.
@@ -342,11 +342,11 @@ void DB::ApplyIndexUpdate(IndexUpdate* u) {
 
 int DB::Update(QueryFile::DefUpdate&& u) {
   int id = files.size();
-  auto it = name2file_id.try_emplace(LowerPathIfInsensitive(u.value.path), id);
+  auto it = name2file_id.try_emplace(LowerPathIfInsensitive(u.first.path), id);
   if (it.second)
     files.emplace_back().id = id;
   QueryFile& existing = files[it.first->second];
-  existing.def = u.value;
+  existing.def = u.first;
   return existing.id;
 }
 
diff --git a/src/query.h b/src/query.h
index d7621f1b..22b3982d 100644
--- a/src/query.h
+++ b/src/query.h
@@ -6,21 +6,6 @@
 #include <llvm/ADT/DenseMap.h>
 #include <llvm/ADT/SmallVector.h>
 
-struct QueryFile;
-struct QueryType;
-struct QueryFunc;
-struct QueryVar;
-struct DB;
-
-template <typename T>
-struct WithFileContent {
-  T value;
-  std::string file_content;
-
-  WithFileContent(const T& value, const std::string& file_content)
-      : value(value), file_content(file_content) {}
-};
-
 struct QueryFile {
   struct Def {
     std::string path;
@@ -38,7 +23,7 @@ struct QueryFile {
     std::vector<std::string> dependencies;
   };
 
-  using DefUpdate = WithFileContent<Def>;
+  using DefUpdate = std::pair<Def, std::string>;
 
   int id = -1;
   std::optional<Def> def;
diff --git a/src/utils.cc b/src/utils.cc
index 68de5189..9c17c129 100644
--- a/src/utils.cc
+++ b/src/utils.cc
@@ -134,6 +134,24 @@ std::optional<int64_t> LastWriteTime(const std::string& filename) {
   return Status.getLastModificationTime().time_since_epoch().count();
 }
 
+// Find discontinous |search| in |content|.
+// Return |found| and the count of skipped chars before found.
+int ReverseSubseqMatch(std::string_view pat,
+                       std::string_view text,
+                       int case_sensitivity) {
+  if (case_sensitivity == 1)
+    case_sensitivity = std::any_of(pat.begin(), pat.end(), isupper) ? 2 : 0;
+  int j = pat.size();
+  if (!j)
+    return text.size();
+  for (int i = text.size(); i--;)
+    if ((case_sensitivity ? text[i] == pat[j - 1]
+                          : tolower(text[i]) == tolower(pat[j - 1])) &&
+        !--j)
+      return i;
+  return -1;
+}
+
 std::string GetDefaultResourceDirectory() {
   return DEFAULT_RESOURCE_DIRECTORY;
 }
diff --git a/src/utils.h b/src/utils.h
index b1febec3..3b3bb511 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -58,6 +58,10 @@ std::optional<std::string> ReadContent(const std::string& filename);
 void WriteToFile(const std::string& filename, const std::string& content);
 std::optional<int64_t> LastWriteTime(const std::string& filename);
 
+int ReverseSubseqMatch(std::string_view pat,
+                       std::string_view text,
+                       int case_sensitivity);
+
 // http://stackoverflow.com/a/38140932
 //
 //  struct SomeHashKey {
diff --git a/src/working_files.cc b/src/working_files.cc
index cfefda8a..d2448daf 100644
--- a/src/working_files.cc
+++ b/src/working_files.cc
@@ -1,6 +1,5 @@
 #include "working_files.h"
 
-#include "lex_utils.h"
 #include "log.hh"
 #include "position.h"
 
@@ -545,3 +544,46 @@ WorkingFiles::Snapshot WorkingFiles::AsSnapshot(
   }
   return result;
 }
+
+// VSCode (UTF-16) disagrees with Emacs lsp-mode (UTF-8) on how to represent
+// text documents.
+// We use a UTF-8 iterator to approximate UTF-16 in the specification (weird).
+// This is good enough and fails only for UTF-16 surrogate pairs.
+int GetOffsetForPosition(lsPosition position, std::string_view content) {
+  size_t i = 0;
+  for (; position.line > 0 && i < content.size(); i++)
+    if (content[i] == '\n')
+      position.line--;
+  for (; position.character > 0 && i < content.size(); position.character--)
+    if (uint8_t(content[i++]) >= 128) {
+      // Skip 0b10xxxxxx
+      while (i < content.size() && uint8_t(content[i]) >= 128 &&
+             uint8_t(content[i]) < 192)
+        i++;
+    }
+  return int(i);
+}
+
+std::string_view LexIdentifierAroundPos(lsPosition position,
+                                        std::string_view content) {
+  int start = GetOffsetForPosition(position, content);
+  int end = start + 1;
+  char c;
+
+  // We search for :: before the cursor but not after to get the qualifier.
+  for (; start > 0; start--) {
+    c = content[start - 1];
+    if (isalnum(c) || c == '_')
+      ;
+    else if (c == ':' && start > 1 && content[start - 2] == ':')
+      start--;
+    else
+      break;
+  }
+
+  for (; end < (int)content.size(); end++)
+    if (c = content[end], !(isalnum(c) || c == '_'))
+      break;
+
+  return content.substr(start, end - start);
+}
diff --git a/src/working_files.h b/src/working_files.h
index 46e71b99..69014989 100644
--- a/src/working_files.h
+++ b/src/working_files.h
@@ -117,3 +117,8 @@ struct WorkingFiles {
   std::vector<std::unique_ptr<WorkingFile>> files;
   std::mutex files_mutex;  // Protects |files|.
 };
+
+int GetOffsetForPosition(lsPosition position, std::string_view content);
+
+std::string_view LexIdentifierAroundPos(lsPosition position,
+                                        std::string_view content);