#pragma once #include #include "Assert.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/VirtualFileSystem.h" namespace clice { namespace path { using namespace llvm::sys::path; template std::string join(Args&&... args) { llvm::SmallString<128> path; ((path::append(path, std::forward(args))), ...); return path.str().str(); } /// Get the real path of the given file. The file must exist. If the file does not exist, inline std::string real_path(llvm::StringRef file) { llvm::SmallString<128> path; auto error = llvm::sys::fs::real_path(file, path); ASSERT(!error, "Failed to get real path of {0}, because {1}", file, error.message()); return path.str().str(); } } // namespace path namespace fs { using namespace llvm::sys::fs; inline std::string resource_dir = ""; inline std::expected init_resource_dir(llvm::StringRef execute) { llvm::SmallString<128> path; path::append(path, path::parent_path(execute), ".."); path::append(path, "lib", "clang", "21"); if(auto error = real_path(path, path)) { return std::unexpected(std::format("{}:{}", error, path.str())); } resource_dir = path.str(); return std::expected(); } using llvm::sys::fs::createTemporaryFile; inline std::expected createTemporaryFile(llvm::StringRef prefix, llvm::StringRef suffix) { llvm::SmallString<128> path; auto error = llvm::sys::fs::createTemporaryFile(prefix, suffix, path); if(error) { return std::unexpected(error); } return path.str().str(); } inline std::expected write(llvm::StringRef path, llvm::StringRef content) { std::error_code EC; llvm::raw_fd_ostream os(path, EC, llvm::sys::fs::OF_None); if(EC) { return std::unexpected(EC); } os << content; os.flush(); return std::expected(); } inline std::expected read(llvm::StringRef path) { auto buffer = llvm::MemoryBuffer::getFile(path); if(!buffer) { return std::unexpected(buffer.getError()); } return buffer.get()->getBuffer().str(); } inline std::string toURI(llvm::StringRef fspath) { if(!path::is_absolute(fspath)) std::abort(); llvm::SmallString<128> path("file://"); #if defined(_WIN32) path.append("/"); #endif for(auto c: fspath) { if(c == '\\') { path.push_back('/'); } else if(std::isalnum(c) || c == '-' || c == '_' || c == '.' || c == '/') { path.push_back(c); } else { path.push_back('%'); path.push_back(llvm::hexdigit(c >> 4)); path.push_back(llvm::hexdigit(c & 0xF)); } } return path.str().str(); } inline std::string decodePercent(llvm::StringRef content) { std::string result; result.reserve(content.size()); for(auto iter = content.begin(), send = content.end(); iter != send; ++iter) { auto c = *iter; if(c == '%' && iter + 2 < send) { auto m = *(iter + 1); auto n = *(iter + 2); if(llvm::isHexDigit(m) && llvm::isHexDigit(n)) { result += llvm::hexFromNibbles(m, n); iter += 2; continue; } } result += c; } return result; } inline std::string toPath(llvm::StringRef uri) { llvm::StringRef cloned = uri; #if defined(_WIN32) if(cloned.starts_with("file:///")) { cloned = cloned.drop_front(8); } else { std::abort(); } #elif defined(__unix__) || defined(__APPLE__) if(cloned.starts_with("file://")) { cloned = cloned.drop_front(7); } else { std::abort(); } #else #error "Unsupported platform" #endif auto decoded = decodePercent(cloned); llvm::SmallString<128> result; if(auto err = fs::real_path(decoded, result)) { std::println("Failed to get real path: {}, Input is {}\n", err.message(), decoded); std::abort(); } return result.str().str(); } } // namespace fs namespace vfs = llvm::vfs; class ThreadSafeFS : public vfs::ProxyFileSystem { public: explicit ThreadSafeFS() : ProxyFileSystem(vfs::createPhysicalFileSystem()) {} class VolatileFile : public vfs::File { public: VolatileFile(std::unique_ptr Wrapped) : wrapped(std::move(Wrapped)) { assert(this->wrapped); } llvm::ErrorOr> getBuffer(const llvm::Twine& Name, int64_t FileSize, bool RequiresNullTerminator, bool /*IsVolatile*/) override { return wrapped->getBuffer(Name, FileSize, RequiresNullTerminator, /*IsVolatile=*/true); } llvm::ErrorOr status() override { return wrapped->status(); } llvm::ErrorOr getName() override { return wrapped->getName(); } std::error_code close() override { return wrapped->close(); } private: std::unique_ptr wrapped; }; llvm::ErrorOr> openFileForRead(const llvm::Twine& InPath) override { llvm::SmallString<128> Path; InPath.toVector(Path); auto file = getUnderlyingFS().openFileForRead(Path); if(!file) return file; // Try to guess preamble files, they can be memory-mapped even on Windows as // clangd has exclusive access to those and nothing else should touch them. llvm::StringRef filename = path::filename(Path); if(filename.starts_with("preamble-") && filename.ends_with(".pch")) { return file; } return std::make_unique(std::move(*file)); } }; } // namespace clice