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: _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits