Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -71,6 +71,8 @@ JSONOutput &Out) override; void onGoToDefinition(TextDocumentPositionParams Params, StringRef ID, JSONOutput &Out) override; + void onSwitchSourceHeader(TextDocumentIdentifier Params, StringRef ID, + JSONOutput &Out) override; private: ClangdLSPServer &LangServer; @@ -216,6 +218,16 @@ R"(,"result":[)" + Locations + R"(]})"); } +void ClangdLSPServer::LSPProtocolCallbacks::onSwitchSourceHeader( + TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) { + + Path Result = LangServer.Server.switchSourceHeader(Params.uri.file); + + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":)" + Result + 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. + Path 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,6 +15,7 @@ #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 @@ -286,3 +287,82 @@ }); return make_tagged(std::move(Result), TaggedFS.Tag); } + +Path ClangdServer::switchSourceHeader(PathRef path) { + + std::string DEFAULT_SOURCE_EXTENSIONS[] = { + ".cpp", ".c", ".cc", ".cxx", ".c++", ".m", ".mm", + ".CPP", ".C", ".CC", ".CXX", ".C++", ".M", ".MM"}; + std::string DEFAULT_HEADER_EXTENSIONS[] = { + ".h", ".hh", ".hpp", ".hxx", ".inc", ".H", ".HH", ".HPP", ".HXX", ".INC"}; + + const int sourceSize = + sizeof(DEFAULT_SOURCE_EXTENSIONS) / sizeof(DEFAULT_SOURCE_EXTENSIONS[0]); + const int headerSize = + sizeof(DEFAULT_HEADER_EXTENSIONS) / sizeof(DEFAULT_HEADER_EXTENSIONS[0]); + + std::string pathDataRef = std::string(path); + bool isSourceFile = false, foundExtension = false; + SmallString<128> NewPath; + std::string *p; + p = std::find(DEFAULT_SOURCE_EXTENSIONS, + DEFAULT_SOURCE_EXTENSIONS + sourceSize, + llvm::sys::path::extension(path)); + if (p != DEFAULT_SOURCE_EXTENSIONS + sourceSize) + { + std::string fileExtension = *p; + NewPath = pathDataRef; + pathDataRef = + pathDataRef.substr(0, (pathDataRef.size() - fileExtension.size())); + isSourceFile = true; + foundExtension = true; + } + + if (!foundExtension) { + p = std::find(DEFAULT_HEADER_EXTENSIONS, + DEFAULT_HEADER_EXTENSIONS + headerSize, + llvm::sys::path::extension(path)); + if (p != DEFAULT_HEADER_EXTENSIONS + headerSize) { + std::string fileExtension = *p; + NewPath = pathDataRef; + pathDataRef = + pathDataRef.substr(0, (pathDataRef.size() - fileExtension.size())); + isSourceFile = false; + foundExtension = true; + } + } + + SmallString<128> CurrentPath; + CurrentPath = std::string(pathDataRef); + bool done = false; + ArrayRef ExtensionsArray; + + if (!isSourceFile) + ExtensionsArray = DEFAULT_SOURCE_EXTENSIONS; + else + ExtensionsArray = DEFAULT_HEADER_EXTENSIONS; + + int i = 0; + while (!done && i < ExtensionsArray.size()) { + std::string CurrentPathString = CurrentPath.str(); + std::string NewPathString = NewPath.str(); + if (i == 0) + CurrentPathString = std::string(pathDataRef).append(std::string(ExtensionsArray[i])); + else + CurrentPathString = CurrentPathString.append(std::string(ExtensionsArray[i])); + + FILE *file; + auto test = FSProvider.getTaggedFileSystem(std::string(CurrentPathString)); + bool fileExists = test.Value.get()->exists(std::string(CurrentPathString)); + if (fileExists) { + done = true; + llvm::sys::path::replace_extension(NewPath, ExtensionsArray[i]); + std::string returnValue = "file://" + std::string(NewPath.str()); + returnValue = URI::unparse(URI::fromUri(returnValue)); + return Path(returnValue); + } + llvm::sys::path::replace_extension(CurrentPath, ""); + CurrentPathString = CurrentPath.str(); + i++; + } +} Index: clangd/ProtocolHandlers.h =================================================================== --- clangd/ProtocolHandlers.h +++ clangd/ProtocolHandlers.h @@ -48,6 +48,8 @@ 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, Index: clangd/ProtocolHandlers.cpp =================================================================== --- clangd/ProtocolHandlers.cpp +++ clangd/ProtocolHandlers.cpp @@ -204,6 +204,23 @@ 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, @@ -239,4 +256,7 @@ llvm::make_unique(Out, Callbacks)); Dispatcher.registerHandler("textDocument/definition", llvm::make_unique(Out, Callbacks)); + Dispatcher.registerHandler( + "textDocument/switchSourceHeader", + llvm::make_unique(Out, Callbacks)); }