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,18 @@ R"(,"result":[)" + Locations + R"(]})"); } +void ClangdLSPServer::LSPProtocolCallbacks::onSwitchSourceHeader( + TextDocumentIdentifier Params, StringRef ID, JSONOutput &Out) { + + llvm::Optional Result = LangServer.Server.switchSourceHeader(Params.uri.file); + std::string test = "file://"; + Path ResultString = "\"" + test + *Result + "\""; + + Out.writeMessage( + R"({"jsonrpc":"2.0","id":)" + ID.str() + + R"(,"result":)" + ResultString + 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,12 @@ /// Get definition of symbol at a specified \p Line and \p Column in \p File. Tagged> findDefinitions(PathRef File, Position Pos); + /// If an extension hasn't been found in the lowercase array, try with uppercase. + bool checkUpperCaseExtensions(StringRef BaseArray[], int ArraySize, StringRef PathExt); + + /// 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; @@ -286,3 +288,84 @@ }); return make_tagged(std::move(Result), TaggedFS.Tag); } + + +bool ClangdServer::checkUpperCaseExtensions(StringRef BaseArray[], int ArraySize, StringRef PathExt) +{ + std::string UpperExtensions[ArraySize]; + for(int i = 0; i < ArraySize; i++) + { + UpperExtensions[i] = BaseArray[i]; + } + // Uppercase the whole array + for (std::string & s : UpperExtensions) + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return std::toupper(c); }); + + bool isUpperCase = false; + // Iterator such as std::begin won't work here so we use a standard loop + for(int i = 0; i < ArraySize; i++) + { + if (UpperExtensions[i] == PathExt.str()) + { + isUpperCase = true; + } + } + return isUpperCase; +} + +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(std::begin(SourceExtensions), std::end(SourceExtensions), PathExt); + bool IsSource = SourceIter != std::end(SourceExtensions); + + if (!IsSource) + { + IsSource = checkUpperCaseExtensions(SourceExtensions, llvm::array_lengthof(SourceExtensions), PathExt); + } + + auto HeaderIter = std::find(std::begin(HeaderExtensions), std::end(HeaderExtensions), PathExt); + bool IsHeader = HeaderIter != std::end(HeaderExtensions); + + if (!IsHeader) + { + IsHeader = checkUpperCaseExtensions(HeaderExtensions, llvm::array_lengthof(HeaderExtensions), PathExt); + } + + // 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 @@ -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)); }