[clang-tools-extra] r314377 - [clangd] LSP extension to switch between source/header file

Marc-Andre Laperle via cfe-commits cfe-commits at lists.llvm.org
Wed Sep 27 20:14:41 PDT 2017


Author: malaperle
Date: Wed Sep 27 20:14:40 2017
New Revision: 314377

URL: http://llvm.org/viewvc/llvm-project?rev=314377&view=rev
Log:
[clangd] LSP extension to switch between source/header file

Summary:
Small extension to LSP to allow clients to use clangd to switch between C header files and source files.
Final version will use the completed clangd indexer to use the index of symbols to be able to switch from header to source file when the file names don't match.

Reviewers: malaperle, krasimir, bkramer, ilya-biryukov

Reviewed By: ilya-biryukov

Subscribers: ilya-biryukov, cfe-commits, arphaman

Patch by: William Enright

Differential Revision: https://reviews.llvm.org/D36150

Modified:
    clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
    clang-tools-extra/trunk/clangd/ClangdServer.cpp
    clang-tools-extra/trunk/clangd/ClangdServer.h
    clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
    clang-tools-extra/trunk/clangd/ProtocolHandlers.h
    clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp

Modified: clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp?rev=314377&r1=314376&r2=314377&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdLSPServer.cpp Wed Sep 27 20:14:40 2017
@@ -72,6 +72,8 @@ public:
                     JSONOutput &Out) override;
   void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID,
                         JSONOutput &Out) override;
+  void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
+                        JSONOutput &Out) override;                      
 
 private:
   ClangdLSPServer &LangServer;
@@ -226,6 +228,21 @@ void ClangdLSPServer::LSPProtocolCallbac
       R"(,"result":[)" + Locations + R"(]})");
 }
 
+void ClangdLSPServer::LSPProtocolCallbacks::onSwitchSourceHeader(
+    TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) {
+  llvm::Optional<Path> Result =
+      LangServer.Server.switchSourceHeader(Params.uri.file);
+  std::string ResultUri;
+  if (Result)
+    ResultUri = URI::unparse(URI::fromFile(*Result));
+  else
+    ResultUri = "\"\"";
+
+  Out.writeMessage(
+      R"({"jsonrpc":"2.0","id":)" + ID.str() +
+      R"(,"result":)" + ResultUri + R"(})");
+}
+
 ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
                                  bool SnippetCompletions,
                                  llvm::Optional<StringRef> ResourceDir)

Modified: clang-tools-extra/trunk/clangd/ClangdServer.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.cpp?rev=314377&r1=314376&r2=314377&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.cpp (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.cpp Wed Sep 27 20:14:40 2017
@@ -296,6 +296,66 @@ Tagged<std::vector<Location>> ClangdServ
   return make_tagged(std::move(Result), TaggedFS.Tag);
 }
 
+llvm::Optional<Path> ClangdServer::switchSourceHeader(PathRef Path) {
+
+  StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
+                                  ".c++", ".m", ".mm"};
+  StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
+
+  StringRef PathExt = llvm::sys::path::extension(Path);
+
+  // Lookup in a list of known extensions.
+  auto SourceIter =
+      std::find_if(std::begin(SourceExtensions), std::end(SourceExtensions),
+                   [&PathExt](PathRef SourceExt) {
+                     return SourceExt.equals_lower(PathExt);
+                   });
+  bool IsSource = SourceIter != std::end(SourceExtensions);
+
+  auto HeaderIter =
+      std::find_if(std::begin(HeaderExtensions), std::end(HeaderExtensions),
+                   [&PathExt](PathRef HeaderExt) {
+                     return HeaderExt.equals_lower(PathExt);
+                   });
+
+  bool IsHeader = HeaderIter != std::end(HeaderExtensions);
+
+  // We can only switch between extensions known extensions.
+  if (!IsSource && !IsHeader)
+    return llvm::None;
+
+  // Array to lookup extensions for the switch. An opposite of where original
+  // extension was found.
+  ArrayRef<StringRef> NewExts;
+  if (IsSource)
+    NewExts = HeaderExtensions;
+  else
+    NewExts = SourceExtensions;
+
+  // Storage for the new path.
+  SmallString<128> NewPath = StringRef(Path);
+
+  // Instance of vfs::FileSystem, used for file existence checks.
+  auto FS = FSProvider.getTaggedFileSystem(Path).Value;
+
+  // Loop through switched extension candidates.
+  for (StringRef NewExt : NewExts) {
+    llvm::sys::path::replace_extension(NewPath, NewExt);
+    if (FS->exists(NewPath))
+      return NewPath.str().str(); // First str() to convert from SmallString to
+                                  // StringRef, second to convert from StringRef
+                                  // to std::string
+    
+    // Also check NewExt in upper-case, just in case.
+    llvm::sys::path::replace_extension(NewPath, NewExt.upper());
+    if (FS->exists(NewPath))
+      return NewPath.str().str();
+
+  }
+
+  return llvm::None;
+}
+
 std::future<void> ClangdServer::scheduleReparseAndDiags(
     PathRef File, VersionedDraft Contents, std::shared_ptr<CppFile> Resources,
     Tagged<IntrusiveRefCntPtr<vfs::FileSystem>> TaggedFS) {

Modified: clang-tools-extra/trunk/clangd/ClangdServer.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ClangdServer.h?rev=314377&r1=314376&r2=314377&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ClangdServer.h (original)
+++ clang-tools-extra/trunk/clangd/ClangdServer.h Wed Sep 27 20:14:40 2017
@@ -248,6 +248,10 @@ public:
   /// Get definition of symbol at a specified \p Line and \p Column in \p File.
   Tagged<std::vector<Location>> findDefinitions(PathRef File, Position Pos);
 
+  /// Helper function that returns a path to the corresponding source file when
+  /// given a header file and vice versa.
+  llvm::Optional<Path> switchSourceHeader(PathRef Path);
+
   /// Run formatting for \p Rng inside \p File.
   std::vector<tooling::Replacement> formatRange(PathRef File, Range Rng);
   /// Run formatting for the whole \p File.

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp?rev=314377&r1=314376&r2=314377&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.cpp Wed Sep 27 20:14:40 2017
@@ -210,6 +210,22 @@ private:
   ProtocolCallbacks &Callbacks;
 };
 
+struct SwitchSourceHeaderHandler : Handler {
+  SwitchSourceHeaderHandler(JSONOutput &Output, ProtocolCallbacks &Callbacks)
+      : Handler(Output), Callbacks(Callbacks) {}
+
+  void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID) override {
+    auto TDPP = TextDocumentIdentifier::parse(Params, Output);
+    if (!TDPP)
+      return;
+
+    Callbacks.onSwitchSourceHeader(*TDPP, ID, Output);
+  }
+
+private:
+  ProtocolCallbacks &Callbacks;
+};
+
 } // namespace
 
 void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher,
@@ -246,4 +262,7 @@ void clangd::regiterCallbackHandlers(JSO
   Dispatcher.registerHandler(
       "textDocument/definition",
       llvm::make_unique<GotoDefinitionHandler>(Out, Callbacks));
+  Dispatcher.registerHandler(
+      "textDocument/switchSourceHeader",
+      llvm::make_unique<SwitchSourceHeaderHandler>(Out, Callbacks));    
 }

Modified: clang-tools-extra/trunk/clangd/ProtocolHandlers.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clangd/ProtocolHandlers.h?rev=314377&r1=314376&r2=314377&view=diff
==============================================================================
--- clang-tools-extra/trunk/clangd/ProtocolHandlers.h (original)
+++ clang-tools-extra/trunk/clangd/ProtocolHandlers.h Wed Sep 27 20:14:40 2017
@@ -49,6 +49,8 @@ public:
                             JSONOutput &Out) = 0;
   virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID,
                                 JSONOutput &Out) = 0;
+  virtual void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID,
+                                    JSONOutput &Out) = 0;                              
 };
 
 void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,

Modified: clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp?rev=314377&r1=314376&r2=314377&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp (original)
+++ clang-tools-extra/trunk/unittests/clangd/ClangdTests.cpp Wed Sep 27 20:14:40 2017
@@ -7,6 +7,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "ClangdLSPServer.h"
 #include "ClangdServer.h"
 #include "Logger.h"
 #include "clang/Basic/VirtualFileSystem.h"
@@ -899,6 +900,84 @@ int d;
   }
 }
 
+TEST_F(ClangdVFSTest, CheckSourceHeaderSwitch) {
+  MockFSProvider FS;
+  ErrorCheckingDiagConsumer DiagConsumer;
+  MockCompilationDatabase CDB(/*AddFreestandingFlag=*/true);
+
+  ClangdServer Server(CDB, DiagConsumer, FS, getDefaultAsyncThreadsCount(),
+                      /*SnippetCompletions=*/false, EmptyLogger::getInstance());
+
+  auto SourceContents = R"cpp(
+  #include "foo.h"
+  int b = a;
+  )cpp";
+
+  auto FooCpp = getVirtualTestFilePath("foo.cpp");
+  auto FooH = getVirtualTestFilePath("foo.h");
+  auto Invalid = getVirtualTestFilePath("main.cpp");
+
+  FS.Files[FooCpp] = SourceContents;
+  FS.Files[FooH] = "int a;";
+  FS.Files[Invalid] = "int main() { \n return 0; \n }";
+
+  llvm::Optional<Path> PathResult = Server.switchSourceHeader(FooCpp);
+  EXPECT_TRUE(PathResult.hasValue());
+  ASSERT_EQ(PathResult.getValue(), FooH);
+
+  PathResult = Server.switchSourceHeader(FooH);
+  EXPECT_TRUE(PathResult.hasValue());
+  ASSERT_EQ(PathResult.getValue(), FooCpp);
+
+  SourceContents = R"c(
+  #include "foo.HH"
+  int b = a;
+  )c";
+
+  // Test with header file in capital letters and different extension, source
+  // file with different extension
+  auto FooC = getVirtualTestFilePath("bar.c");
+  auto FooHH = getVirtualTestFilePath("bar.HH");
+
+  FS.Files[FooC] = SourceContents;
+  FS.Files[FooHH] = "int a;";
+
+  PathResult = Server.switchSourceHeader(FooC);
+  EXPECT_TRUE(PathResult.hasValue());
+  ASSERT_EQ(PathResult.getValue(), FooHH);
+
+  // Test with both capital letters
+  auto Foo2C = getVirtualTestFilePath("foo2.C");
+  auto Foo2HH = getVirtualTestFilePath("foo2.HH");
+  FS.Files[Foo2C] = SourceContents;
+  FS.Files[Foo2HH] = "int a;";
+
+  PathResult = Server.switchSourceHeader(Foo2C);
+  EXPECT_TRUE(PathResult.hasValue());
+  ASSERT_EQ(PathResult.getValue(), Foo2HH);
+
+  // Test with source file as capital letter and .hxx header file
+  auto Foo3C = getVirtualTestFilePath("foo3.C");
+  auto Foo3HXX = getVirtualTestFilePath("foo3.hxx");
+
+  SourceContents = R"c(
+  #include "foo3.hxx"
+  int b = a;
+  )c";
+
+  FS.Files[Foo3C] = SourceContents;
+  FS.Files[Foo3HXX] = "int a;";
+
+  PathResult = Server.switchSourceHeader(Foo3C);
+  EXPECT_TRUE(PathResult.hasValue());
+  ASSERT_EQ(PathResult.getValue(), Foo3HXX);
+
+  // Test if asking for a corresponding file that doesn't exist returns an empty
+  // string.
+  PathResult = Server.switchSourceHeader(Invalid);
+  EXPECT_FALSE(PathResult.hasValue());
+}
+
 TEST_F(ClangdThreadingTest, NoConcurrentDiagnostics) {
   class NoConcurrentAccessDiagConsumer : public DiagnosticsConsumer {
   public:




More information about the cfe-commits mailing list