Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -70,6 +70,8 @@ void onCompletion(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) override; void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, + JSONOutput &Out) override; + void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) override; private: @@ -181,9 +183,11 @@ void ClangdLSPServer::LSPProtocolCallbacks::onCompletion( TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { - auto Items = LangServer.Server.codeComplete( - Params.textDocument.uri.file, - Position{Params.position.line, Params.position.character}).Value; + auto Items = LangServer.Server + .codeComplete(Params.textDocument.uri.file, + Position{Params.position.line, + Params.position.character}) + .Value; std::string Completions; for (const auto &Item : Items) { @@ -200,9 +204,11 @@ void ClangdLSPServer::LSPProtocolCallbacks::onGoToDefinition( TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) { - auto Items = LangServer.Server.findDefinitions( - Params.textDocument.uri.file, - Position{Params.position.line, Params.position.character}).Value; + auto Items = LangServer.Server + .findDefinitions(Params.textDocument.uri.file, + Position{Params.position.line, + Params.position.character}) + .Value; std::string Locations; for (const auto &Item : Items) { @@ -216,6 +222,22 @@ R"(,"result":[)" + Locations + R"(]})"); } +void ClangdLSPServer::LSPProtocolCallbacks::onSwitchSourceHeader( + TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) { + + llvm::Optional Result = + LangServer.Server.switchSourceHeader(Params.uri.file); + URI ResultUri; + if (Result != llvm::None) + ResultUri = URI::fromFile(*Result); + else + ResultUri = URI::fromFile(""); + + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":)" + URI::unparse(ResultUri) + R"(})"); +} + ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, bool RunSynchronously) : Out(Out), DiagConsumer(*this), Server(CDB, DiagConsumer, FSProvider, RunSynchronously) {} Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -182,6 +182,9 @@ /// Get definition of symbol at a specified \p Line and \p Column in \p File. Tagged> 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 switchSourceHeader(PathRef Path); + /// Run formatting for \p Rng inside \p File. std::vector formatRange(PathRef File, Range Rng); /// Run formatting for the whole \p File. Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -15,9 +15,11 @@ #include "clang/Tooling/CompilationDatabase.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include +using namespace llvm; using namespace clang; using namespace clang::clangd; @@ -273,16 +275,76 @@ return DumpFuture.get(); } -Tagged> -ClangdServer::findDefinitions(PathRef File, Position Pos) { +Tagged> ClangdServer::findDefinitions(PathRef File, + Position Pos) { auto FileContents = DraftMgr.getDraft(File); - assert(FileContents.Draft && "findDefinitions is called for non-added document"); + assert(FileContents.Draft && + "findDefinitions is called for non-added document"); std::vector Result; auto TaggedFS = FSProvider.getTaggedFileSystem(File); - Units.runOnUnit(File, *FileContents.Draft, ResourceDir, CDB, PCHs, - TaggedFS.Value, [&](ClangdUnit &Unit) { - Result = Unit.findDefinitions(Pos); - }); + Units.runOnUnit( + File, *FileContents.Draft, ResourceDir, CDB, PCHs, TaggedFS.Value, + [&](ClangdUnit &Unit) { Result = Unit.findDefinitions(Pos); }); return make_tagged(std::move(Result), TaggedFS.Tag); } + +llvm::Optional 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 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; +} Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -47,7 +47,9 @@ virtual void onCompletion(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) = 0; virtual void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, - JSONOutput &Out) = 0; + JSONOutput &Out) = 0; + virtual void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID, + JSONOutput &Out) = 0; }; void regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -204,6 +204,22 @@ 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); + if (!TDPP) + return; + + Callbacks.onSwitchSourceHeader(*TDPP, ID, Output); + } + +private: + ProtocolCallbacks &Callbacks; +}; + } // namespace void clangd::regiterCallbackHandlers(JSONRPCDispatcher &Dispatcher, @@ -237,6 +253,10 @@ Dispatcher.registerHandler( "textDocument/completion", llvm::make_unique(Out, Callbacks)); - Dispatcher.registerHandler("textDocument/definition", + Dispatcher.registerHandler( + "textDocument/definition", llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/switchSourceHeader", + llvm::make_unique(Out, Callbacks)); } Index: clangd/tool/ClangdMain.cpp =================================================================== --- clangd/tool/ClangdMain.cpp +++ clangd/tool/ClangdMain.cpp @@ -12,7 +12,6 @@ #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Program.h" - #include #include #include