diff options
Diffstat (limited to 'elpa/irony-20220110.849/server/src')
19 files changed, 2355 insertions, 0 deletions
diff --git a/elpa/irony-20220110.849/server/src/CMakeLists.txt b/elpa/irony-20220110.849/server/src/CMakeLists.txt new file mode 100644 index 0000000..c516e70 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/CMakeLists.txt @@ -0,0 +1,111 @@ +# XXX: if there are issues +# due to https://reviews.llvm.org/D30911 / SVN Revision 298424 +# then use the fallback when LLVM_VERSION VERSION_LESS 5.0.1 +find_package(Clang) + +if (Clang_FOUND) + # XXX: at least since Clang 8.0.0 + # it's looks like the 'libclang' IMPORTED target + # does not specify the following target property: + # INTERFACE_INCLUDE_DIRECTORIES ${CLANG_INCLUDE_DIRS} + # which is confirmed by https://github.com/Sarcasm/irony-mode/issues/530 + # so we work around this, + # but ideally Clang upstream should export fully usable IMPORTED targets + add_library(irony_libclang INTERFACE) + target_link_libraries(irony_libclang INTERFACE libclang) + target_include_directories(irony_libclang INTERFACE ${CLANG_INCLUDE_DIRS}) + + get_property(IRONY_CLANG_EXECUTABLE TARGET clang PROPERTY LOCATION) + execute_process( + COMMAND "${IRONY_CLANG_EXECUTABLE}" -print-resource-dir + OUTPUT_VARIABLE CLANG_RESOURCE_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +else() + # fallback to historically FindLibClang.cmake + # and -resource-dir guess mecanism + include(CheckClangResourceDir) + find_package(LibClang REQUIRED) + check_clang_resource_dir() +endif() + +# not to be taken as a module-definition file to link on Windows +set_source_files_properties(Commands.def PROPERTIES HEADER_FILE_ONLY TRUE) + +if(MSVC) + # irony-server uses some code that breaks when iterator debugging is enabled + # + # The culprit is CommandLineArgumentParser who initialize its member + # 'Position', of type 'std::string::const_iterator', to 'Input.begin() - 1'. + # With checked iterator the begin() - 1 breaks in debug build. + add_definitions(/D_ITERATOR_DEBUG_LEVEL=0) +endif() + +add_executable(irony-server + support/CommandLineParser.cpp + support/CommandLineParser.h + support/iomanip_quoted.h + support/NonCopyable.h + support/CIndex.h + support/TemporaryFile.cpp + support/TemporaryFile.h + + Command.cpp + Commands.def + Command.h + CompDBCache.h + CompDBCache.cpp + Irony.cpp + Irony.h + TUManager.cpp + TUManager.h + + main.cpp) + +irony_target_set_cxx_standard(irony-server) + +target_include_directories(irony-server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +# retrieve the package version from irony.el +function(irony_find_package_version OUTPUT_VAR) + # this is a hack that force CMake to reconfigure, it is necessary to see if + # the version in irony.el has changed, this is not possible to add the + # definitions at build time + configure_file(${PROJECT_SOURCE_DIR}/../irony.el + ${CMAKE_CURRENT_BINARY_DIR}/irony.el + COPYONLY) + + set(version_header "\;\; Version: ") + file(STRINGS ${CMAKE_CURRENT_BINARY_DIR}/irony.el version + LIMIT_COUNT 1 + REGEX "^${version_header}*") + + if (NOT version) + message (FATAL_ERROR "couldn't find irony.el's version header!") + endif() + + string(LENGTH ${version_header} version_header_length) + string(SUBSTRING ${version} ${version_header_length} -1 package_version) + set(${OUTPUT_VAR} ${package_version} PARENT_SCOPE) +endfunction() + +irony_find_package_version(IRONY_PACKAGE_VERSION) +message(STATUS "Irony package version is '${IRONY_PACKAGE_VERSION}'") + +set_source_files_properties(main.cpp + PROPERTIES + COMPILE_DEFINITIONS IRONY_PACKAGE_VERSION=\"${IRONY_PACKAGE_VERSION}\") + +if (CLANG_RESOURCE_DIR) + # look for CLANG_RESOURCE_DIR_DIR usage in the code for an explanation + set_source_files_properties(TUManager.cpp + PROPERTIES + COMPILE_DEFINITIONS CLANG_RESOURCE_DIR=\"${CLANG_RESOURCE_DIR}\") +endif() + +target_link_libraries(irony-server irony_libclang) + +set_target_properties(irony-server + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + +install(TARGETS irony-server DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/elpa/irony-20220110.849/server/src/Command.cpp b/elpa/irony-20220110.849/server/src/Command.cpp new file mode 100644 index 0000000..363b6cb --- /dev/null +++ b/elpa/irony-20220110.849/server/src/Command.cpp @@ -0,0 +1,278 @@ +/** + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief Command parser definitions. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#include "Command.h" + +#include "support/CommandLineParser.h" + +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <limits> +#include <map> + + +namespace { + +struct StringConverter { + StringConverter(std::string *dest) : dest_(dest) { + } + + bool operator()(const std::string &str) { + *dest_ = str; + return true; + } + +private: + std::string *dest_; +}; + +struct UnsignedIntConverter { + UnsignedIntConverter(unsigned *dest) : dest_(dest) { + } + + bool operator()(const std::string &str) { + char *end; + long num = std::strtol(str.c_str(), &end, 10); + + if (end != (str.c_str() + str.size())) + return false; + + if (errno == ERANGE) + return false; + + if (num < 0) + return false; + + unsigned long unum = static_cast<unsigned long>(num); + if (unum > std::numeric_limits<unsigned>::max()) + return false; + + *dest_ = unum; + return true; + } + +private: + unsigned *dest_; +}; + +/// Convert "on" and "off" to a boolean +struct OptionConverter { + OptionConverter(bool *dest) : dest_(dest) { + } + + bool operator()(const std::string &str) { + if (str == "on") { + *dest_ = true; + } else if (str == "off") { + *dest_ = false; + } else { + return false; + } + return true; + } + +private: + bool *dest_; +}; + +const std::map<std::string, PrefixMatchStyle> PREFIX_MATCH_STYLE_MAP = { + { "exact", PrefixMatchStyle::Exact }, + { "case-insensitive", PrefixMatchStyle::CaseInsensitive }, + { "smart-case", PrefixMatchStyle::SmartCase}, +}; + +/// Convert style to a PrefixMatchStyle +struct PrefixMatchStyleConverter { + PrefixMatchStyleConverter(PrefixMatchStyle *dest) : dest_(dest) { + } + + bool operator()(const std::string &str) { + auto res = PREFIX_MATCH_STYLE_MAP.find(str); + + if (res == PREFIX_MATCH_STYLE_MAP.cend()) { + return false; + } + *dest_ = res->second; + return true; + } + +private: + PrefixMatchStyle *dest_; +}; + +std::ostream &operator<<(std::ostream &os, PrefixMatchStyle style) { + for (auto it : PREFIX_MATCH_STYLE_MAP) { + if (it.second == style) { + os << it.first; + return os; + } + } + os << "UnknownStyle"; + return os; +} + + +} // unnamed namespace + +std::ostream &operator<<(std::ostream &os, const Command::Action &action) { + os << "Command::"; + + switch (action) { +#define X(sym, str, help) \ + case Command::sym: \ + os << #sym; \ + break; +#include "Commands.def" + } + return os; +} + +std::ostream &operator<<(std::ostream &os, const Command &command) { + os << "Command{action=" << command.action << ", " + << "file='" << command.file << "', " + << "unsavedFile='" << command.unsavedFile << "', " + << "dir='" << command.dir << "', " + << "line=" << command.line << ", " + << "column=" << command.column << ", " + << "prefix='" << command.prefix << "', " + << "caseStyle='" << command.style << "', " + << "flags=["; + bool first = true; + for (const std::string &flag : command.flags) { + if (!first) + os << ", "; + os << "'" << flag << "'"; + first = false; + } + os << "], " + << "opt=" << (command.opt ? "on" : "off"); + + return os << "}"; +} + +static Command::Action actionFromString(const std::string &actionStr) { +#define X(sym, str, help) \ + if (actionStr == str) \ + return Command::sym; + +#include "Commands.def" + + return Command::Unknown; +} + +CommandParser::CommandParser() : tempFile_("irony-server") { +} + +Command *CommandParser::parse(const std::vector<std::string> &argv) { + command_.clear(); + + if (argv.begin() == argv.end()) { + std::clog << "error: no command specified.\n" + "See 'irony-server help' to list available commands\n"; + return 0; + } + + const std::string &actionStr = argv[0]; + + command_.action = actionFromString(actionStr); + + bool readCompileOptions = false; + std::vector<std::function<bool(const std::string &)>> positionalArgs; + + switch (command_.action) { + case Command::SetDebug: + positionalArgs.push_back(OptionConverter(&command_.opt)); + break; + + case Command::Parse: + positionalArgs.push_back(StringConverter(&command_.file)); + readCompileOptions = true; + break; + + case Command::Complete: + positionalArgs.push_back(StringConverter(&command_.file)); + positionalArgs.push_back(UnsignedIntConverter(&command_.line)); + positionalArgs.push_back(UnsignedIntConverter(&command_.column)); + readCompileOptions = true; + break; + + case Command::GetType: + positionalArgs.push_back(UnsignedIntConverter(&command_.line)); + positionalArgs.push_back(UnsignedIntConverter(&command_.column)); + break; + + case Command::SetUnsaved: + positionalArgs.push_back(StringConverter(&command_.file)); + positionalArgs.push_back(StringConverter(&command_.unsavedFile)); + break; + + case Command::ResetUnsaved: + positionalArgs.push_back(StringConverter(&command_.file)); + break; + + case Command::Candidates: + positionalArgs.push_back(StringConverter(&command_.prefix)); + positionalArgs.push_back(PrefixMatchStyleConverter(&command_.style)); + break; + case Command::CompletionDiagnostics: + case Command::Diagnostics: + case Command::Help: + case Command::Exit: + // no-arguments commands + break; + + case Command::GetCompileOptions: + positionalArgs.push_back(StringConverter(&command_.dir)); + positionalArgs.push_back(StringConverter(&command_.file)); + break; + + case Command::Unknown: + std::clog << "error: invalid command specified: " << actionStr << "\n"; + return 0; + } + + auto argsBegin = argv.begin() + 1; + const auto argsEnd = std::find(argsBegin, argv.end(), "--"); + const int argCount = std::distance(argsBegin, argsEnd); + + // compile options are provided after '--' + if (readCompileOptions && argsEnd != argv.end()) { + command_.flags.assign(std::next(argsEnd), argv.end()); + } + + if (argCount != static_cast<int>(positionalArgs.size())) { + std::clog << "error: invalid number of arguments for '" << actionStr + << "' (requires " << positionalArgs.size() << " got " << argCount + << ")\n"; + return 0; + } + + for (auto fn : positionalArgs) { + if (!fn(*argsBegin)) { + std::clog << "error: parsing command '" << actionStr + << "': invalid argument '" << *argsBegin << "'\n"; + return 0; + } + ++argsBegin; + } + + // '-' is used as a special file to inform that the buffer hasn't been saved + // on disk and only the buffer content is available. libclang needs a file, so + // this is treated as a special value for irony-server to create a temporary + // file for this. note that libclang will gladly accept '-' as a filename but + // we don't want to let this happen since irony already reads stdin. + if (command_.file == "-") { + command_.file = tempFile_.getPath(); + } + + return &command_; +} diff --git a/elpa/irony-20220110.849/server/src/Command.h b/elpa/irony-20220110.849/server/src/Command.h new file mode 100644 index 0000000..9f36aa4 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/Command.h @@ -0,0 +1,73 @@ +/**-*-C++-*- + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief Command parser declarations. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_COMMAND_H_ +#define IRONY_MODE_SERVER_COMMAND_H_ + +#include "support/CIndex.h" +#include "support/TemporaryFile.h" +#include "Style.h" + +#include <iosfwd> +#include <string> +#include <vector> + +class TemporaryFile; + +// TODO: a tagged union? +struct Command { + Command() { + clear(); + } + + void clear() { + action = Unknown; + flags.clear(); + file.clear(); + unsavedFile.clear(); + dir.clear(); + prefix.clear(); + style = PrefixMatchStyle::Exact; + line = 0; + column = 0; + opt = false; + } + +#define X(sym, str, desc) sym, + enum Action { +#include "Commands.def" + } action; + + std::vector<std::string> flags; + std::string file; + std::string unsavedFile; + std::string dir; + std::string prefix; + PrefixMatchStyle style; + unsigned line; + unsigned column; + bool opt; +}; + +std::ostream &operator<<(std::ostream &os, const Command::Action &action); +std::ostream &operator<<(std::ostream &os, const Command &command); + +class CommandParser { +public: + CommandParser(); + + Command *parse(const std::vector<std::string> &argv); + +private: + Command command_; + TemporaryFile tempFile_; +}; + +#endif // IRONY_MODE_SERVER_COMMAND_H_ diff --git a/elpa/irony-20220110.849/server/src/Commands.def b/elpa/irony-20220110.849/server/src/Commands.def new file mode 100644 index 0000000..f99d8af --- /dev/null +++ b/elpa/irony-20220110.849/server/src/Commands.def @@ -0,0 +1,39 @@ +/**-*-C++-*- + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief Command list. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef X +#error Please define the 'X(id, command, description)' macro before inclusion! +#endif + +X(Candidates, "candidates", + "PREFIX STYLE - print completion candidates (require previous complete). " + "STYLE is \"exact\", \"case-insensitive\" or \"smart-case\"") +X(Complete, "complete", + "FILE LINE COL [-- [COMPILE_OPTIONS...]] - perform code completion at a given location") +X(CompletionDiagnostics, "completion-diagnostics", + "print the diagnostics generated during complete") +X(Diagnostics, "diagnostics", "print the diagnostics of the last parse") +X(Exit, "exit", "exit interactive mode, print nothing") +X(GetCompileOptions, "get-compile-options", "BUILD_DIR FILE - " + "get compile options for FILE from JSON database in BUILD_DIR") +X(GetType, "get-type", "LINE COL - get type of symbol at a given location") +X(Help, "help", "show this message") +X(Parse, "parse", "FILE [-- [COMPILE_OPTIONS...]] - parse the given file") +X(ResetUnsaved, "reset-unsaved", "FILE - reset FILE, its content is up to date") +X(SetDebug, "set-debug", "ON|OFF - enable or disable verbose logging") +X(SetUnsaved, + "set-unsaved", + "FILE UNSAVED-CONTENT-FILE - tell irony-server that " + "UNSAVED-CONTENT-FILE contains the effective content of FILE") + +// sentinel value, should be the last one +X(Unknown, "<unkown>", "<unspecified>") + +#undef X diff --git a/elpa/irony-20220110.849/server/src/CompDBCache.cpp b/elpa/irony-20220110.849/server/src/CompDBCache.cpp new file mode 100644 index 0000000..f79ec8c --- /dev/null +++ b/elpa/irony-20220110.849/server/src/CompDBCache.cpp @@ -0,0 +1,71 @@ +#include "CompDBCache.h" + +#include <sys/stat.h> + +#include <cassert> + +CompDBCache::CompDBCache() + : db_(nullptr), mtime_(0) { +} + +CompDBCache::~CompDBCache() { + clear(); +} + +CXCompilationDatabase CompDBCache::fromDirectory(const std::string &buildDir, + CXCompilationDatabase_Error *error) { + assert(error != nullptr); + + const std::string jsonFilename = constructJsonDbFilename(buildDir); + const time_t mtime = modificationTime(jsonFilename); + + if (jsonFilename == filename_ && mtime != 0 && mtime == mtime_) { + // Using the cached compilation database. + // Just set the provided error code to indicate success. + *error = CXCompilationDatabase_NoError; + } else { + clear(); + + db_ = clang_CompilationDatabase_fromDirectory(buildDir.c_str(), error); + + if (mtime != 0 && *error == CXCompilationDatabase_NoError) { + // Successfully loaded a JSON compilation database. + // Cache the result. + filename_ = jsonFilename; + mtime_ = mtime; + } + } + + return db_; +} + +void CompDBCache::clear() { + if (db_) { + clang_CompilationDatabase_dispose(db_); + db_ = nullptr; + filename_.clear(); + mtime_ = 0; + } +} + +std::string CompDBCache::constructJsonDbFilename(const std::string &buildDir) const { + std::string ret = buildDir; + if (!buildDir.empty() && buildDir.back() != '/') + ret += '/'; + ret += "compile_commands.json"; + return ret; +} + +time_t CompDBCache::modificationTime(const std::string &filename) const { + time_t mtime = 0; +#ifdef _WIN32 + struct _stat st; + const int statRes = _stat(filename.c_str(), &st); +#else + struct stat st; + const int statRes = stat(filename.c_str(), &st); +#endif + if (statRes == 0) + mtime = st.st_mtime; + return mtime; +} diff --git a/elpa/irony-20220110.849/server/src/CompDBCache.h b/elpa/irony-20220110.849/server/src/CompDBCache.h new file mode 100644 index 0000000..568b790 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/CompDBCache.h @@ -0,0 +1,86 @@ +/**-*-C++-*- + * \file + * \author Idar Tollefsen <idart@hotmail.com> + * + * \brief Creates a compilation database and caches it. + * + * Keeps a cache of the loaded compilation database, only creating a new + * one when needed. + * + * This file is distributed under the GNU General Public License. + * See COPYING for details. + */ + +#ifndef IRONY_MODE_COMPDBCACHE_H_ +#define IRONY_MODE_COMPDBCACHE_H_ + +#include "support/CIndex.h" +#include "support/NonCopyable.h" + +#include <ctime> +#include <string> + +class CompDBCache : public util::NonCopyable { +public: + CompDBCache(); + ~CompDBCache(); + + /** + * \brief Get a compilation database from a database found in a directory. + * + * This in essence a wrapper around + * \c clang_CompilationDatabase_fromDirectory() with added caching. + * It will either create a new compilation database or return the + * already loaded one (if any). + * + * This class owns the resulting compilation database. + * Therefore, unlike \c clang_CompilationDatabase_fromDirectory(), + * callers must NOT call \c clang_CompilationDatabase_dispose() on the + * returned compilation database. This class will handle that internally. + * + * \param buildDir Directory containing the database (such as + * "compile_commands.json") to create a compilation + * database from. + * \param error Error code from attempting to create a compilation + * database (\c CXCompilationDatabase_NoError on success). + * + * \return The compilation database or nullptr. + */ + CXCompilationDatabase fromDirectory(const std::string &buildDir, + CXCompilationDatabase_Error *error); + +private: + /** + * \brief Clear the cache. + * + * This will dispose the currently loaded compilation database (if any) by + * calling \c clang_CompilationDatabase_dispose() on it. And it will reset + * other internal housekeeping variables related to the caching of the + * compilation database. + */ + void clear(); + + /** + * \brief Construct JSON compilation database filename. + * + * \param buildDir Directory that might contain "compile_commands.json". + * + * \return Path to "compilation_commands.json" in \c buildDir. + */ + std::string constructJsonDbFilename(const std::string &buildDir) const; + + /** + * \brief Get modification time of a file. + * + * \param filename The file to get last modification time of. + * + * \return The modification time of \c filename or 0. + */ + time_t modificationTime(const std::string &filename) const; + + CXCompilationDatabase db_; + std::string filename_; + time_t mtime_; +}; + +#endif diff --git a/elpa/irony-20220110.849/server/src/Irony.cpp b/elpa/irony-20220110.849/server/src/Irony.cpp new file mode 100644 index 0000000..2157b32 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/Irony.cpp @@ -0,0 +1,638 @@ +/** + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief irony-server "API" definitions. + * + * \sa Irony.h for more information. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#include "Irony.h" + +#include "support/iomanip_quoted.h" + +#include <algorithm> +#include <cassert> +#include <iostream> +#include <fstream> +#include <cctype> + +namespace { + +std::string cxStringToStd(CXString cxString) { + std::string stdStr; + + if (const char *cstr = clang_getCString(cxString)) { + stdStr = cstr; + } + + clang_disposeString(cxString); + return stdStr; +} + +const char *diagnosticSeverity(const CXDiagnostic &diagnostic) { + switch (clang_getDiagnosticSeverity(diagnostic)) { + case CXDiagnostic_Ignored: + return "ignored"; + case CXDiagnostic_Note: + return "note"; + case CXDiagnostic_Warning: + return "warning"; + case CXDiagnostic_Error: + return "error"; + case CXDiagnostic_Fatal: + return "fatal"; + } + + return "unknown"; +} + +void dumpDiagnostic(const CXDiagnostic &diagnostic) { + std::string file; + unsigned line = 0, column = 0, offset = 0; + CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); + if (!clang_equalLocations(location, clang_getNullLocation())) { + CXFile cxFile; + +// clang_getInstantiationLocation() has been marked deprecated and +// is aimed to be replaced by clang_getExpansionLocation(). +#if CINDEX_VERSION >= 6 + clang_getExpansionLocation(location, &cxFile, &line, &column, &offset); +#else + clang_getInstantiationLocation(location, &cxFile, &line, &column, &offset); +#endif + + file = cxStringToStd(clang_getFileName(cxFile)); + } + + const char *severity = diagnosticSeverity(diagnostic); + + std::string message = cxStringToStd(clang_getDiagnosticSpelling(diagnostic)); + + std::cout << '(' << support::quoted(file) // + << ' ' << line // + << ' ' << column // + << ' ' << offset // + << ' ' << severity // + << ' ' << support::quoted(message) // + << ")\n"; +} + +bool readFileContent(const std::string &filename, + Irony::UnsavedBuffer &outBuf) { + std::ifstream ifs(filename.c_str(), + std::ios::in | std::ios::binary | std::ios::ate); + + if (!ifs.is_open()) { + return false; + } + + // FIXME: it's possible that this method of reading the file is 100% reliable, + // I can't confirm that tellg() is guaranteed to return a byte count. + // std::streamoff does not mention 'byte'. + // In practice it seems to work but this may be just luck. + // See also this discussion: + // - http://stackoverflow.com/questions/22984956/tellg-function-give-wrong-size-of-file/22986486#22986486 + auto nbytes = ifs.tellg(); + + if (nbytes == std::ifstream::pos_type(-1)) { + return false; + } + + outBuf.resize(nbytes); + ifs.seekg(0, std::ios::beg); + + ifs.read(&outBuf[0], outBuf.size()); + + if (!ifs){ + outBuf.clear(); + return false; + } + + return true; +} + +} // unnamed namespace + +Irony::Irony() + : activeTu_(nullptr), activeCompletionResults_(nullptr), debug_(false) { +} + +void Irony::parse(const std::string &file, + const std::vector<std::string> &flags) { + resetCache(); + activeTu_ = tuManager_.parse(file, flags, cxUnsavedFiles_); + file_ = file; + + if (activeTu_ == nullptr) { + std::cout << "(error . (" + << "parse-error" + << " \"failed to parse file\"" + << " " << support::quoted(file) << "))\n"; + return; + } + + std::cout << "(success . t)\n"; +} + +void Irony::diagnostics() const { + unsigned diagnosticCount; + + if (activeTu_ == nullptr) { + diagnosticCount = 0; + } else { + diagnosticCount = clang_getNumDiagnostics(activeTu_); + } + + std::cout << "(\n"; + + for (unsigned i = 0; i < diagnosticCount; ++i) { + CXDiagnostic diagnostic = clang_getDiagnostic(activeTu_, i); + + dumpDiagnostic(diagnostic); + + clang_disposeDiagnostic(diagnostic); + } + + std::cout << ")\n"; +} + +void Irony::resetCache() { + activeTu_ = nullptr; + + if (activeCompletionResults_ != nullptr) { + clang_disposeCodeCompleteResults(activeCompletionResults_); + activeCompletionResults_ = nullptr; + } +} + +void Irony::getType(unsigned line, unsigned col) const { + if (activeTu_ == nullptr) { + std::clog << "W: get-type - parse wasn't called\n"; + + std::cout << "nil\n"; + return; + } + + CXFile cxFile = clang_getFile(activeTu_, file_.c_str()); + CXSourceLocation sourceLoc = clang_getLocation(activeTu_, cxFile, line, col); + CXCursor cursor = clang_getCursor(activeTu_, sourceLoc); + + if (clang_Cursor_isNull(cursor)) { + // TODO: "error: no type at point"? + std::cout << "nil"; + return; + } + + CXType cxTypes[2]; + cxTypes[0] = clang_getCursorType(cursor); + cxTypes[1] = clang_getCanonicalType(cxTypes[0]); + + std::cout << "("; + + for (const CXType &cxType : cxTypes) { + CXString typeDescr = clang_getTypeSpelling(cxType); + std::string typeStr = clang_getCString(typeDescr); + clang_disposeString(typeDescr); + + if (typeStr.empty()) + break; + + std::cout << support::quoted(typeStr) << " "; + } + + std::cout << ")\n"; +} + +namespace { + +class CompletionChunk { +public: + explicit CompletionChunk(CXCompletionString completionString) + : completionString_(completionString) + , numChunks_(clang_getNumCompletionChunks(completionString_)) + , chunkIdx_(0) { + } + + bool hasNext() const { + return chunkIdx_ < numChunks_; + } + + void next() { + if (!hasNext()) { + assert(0 && "out of range completion chunk"); + abort(); + } + + ++chunkIdx_; + } + + CXCompletionChunkKind kind() const { + return clang_getCompletionChunkKind(completionString_, chunkIdx_); + } + + // TODO: operator>> so that one can re-use string allocated buffer + std::string text() const { + return cxStringToStd( + clang_getCompletionChunkText(completionString_, chunkIdx_)); + } + +private: + CXCompletionString completionString_; + unsigned int numChunks_; + unsigned chunkIdx_; +}; + +} // unnamed namespace + +void Irony::complete(const std::string &file, + unsigned line, + unsigned col, + const std::vector<std::string> &flags) { + resetCache(); + + if (CXTranslationUnit tu = + tuManager_.getOrCreateTU(file, flags, cxUnsavedFiles_)) { + activeCompletionResults_ = + clang_codeCompleteAt(tu, + file.c_str(), + line, + col, + const_cast<CXUnsavedFile *>(cxUnsavedFiles_.data()), + cxUnsavedFiles_.size(), + (clang_defaultCodeCompleteOptions() & + ~CXCodeComplete_IncludeCodePatterns) +#if HAS_BRIEF_COMMENTS_IN_COMPLETION + | + CXCodeComplete_IncludeBriefComments +#endif + ); + } + + if (activeCompletionResults_ == nullptr) { + std::cout << "(error . (" + << "complete-error" + << " \"failed to perform code completion\"" + << " " << support::quoted(file) << " " << line << " " << col + << "))\n"; + return; + } + + clang_sortCodeCompletionResults(activeCompletionResults_->Results, + activeCompletionResults_->NumResults); + + std::cout << "(success . t)\n"; +} + +namespace { + +bool hasUppercase(const std::string &prefix) +{ + for (char c : prefix) { + if (std::isupper(c)) { + return true; + } + } + return false; +} + +bool isEqual(const bool insensitive, const char a, const char b) +{ + if (insensitive) { + return std::tolower(a) == std::tolower(b); + } else { + return a == b; + } +} + +bool startsWith(const std::string& str, const std::string &prefix, bool caseInsensitive) +{ + if (str.length() < prefix.length()) { + return false; + } + + const auto charCmp = [&](const char a, const char b) { + return isEqual(caseInsensitive, a, b); + }; + + auto res = std::mismatch(prefix.begin(), prefix.end(), str.begin(), charCmp); + return res.first == prefix.end(); +} + +bool isStyleCaseInsensitive(const std::string &prefix, PrefixMatchStyle style) +{ + if (style == PrefixMatchStyle::SmartCase) { + // For SmartCase style, do case insensitive matching only there isn't upper + // case letter. + if (!hasUppercase(prefix)) { + style = PrefixMatchStyle::CaseInsensitive; + } + } + return style == PrefixMatchStyle::CaseInsensitive; +} + +} // unnamed namespace + +void Irony::completionDiagnostics() const { + unsigned diagnosticCount; + + if (activeCompletionResults_ == nullptr) { + diagnosticCount = 0; + } else { + diagnosticCount = + clang_codeCompleteGetNumDiagnostics(activeCompletionResults_); + } + + std::cout << "(\n"; + + for (unsigned i = 0; i < diagnosticCount; ++i) { + CXDiagnostic diagnostic = + clang_codeCompleteGetDiagnostic(activeCompletionResults_, i); + + dumpDiagnostic(diagnostic); + clang_disposeDiagnostic(diagnostic); + } + + std::cout << ")\n"; +} + +void Irony::candidates(const std::string &prefix, PrefixMatchStyle style) const { + if (activeCompletionResults_ == nullptr) { + std::cout << "nil\n"; + return; + } + + bool caseInsensitive = isStyleCaseInsensitive(prefix, style); + + CXCodeCompleteResults *completions = activeCompletionResults_; + + std::cout << "(\n"; + + // re-use the same buffers to avoid unnecessary allocations + std::string typedtext, brief, resultType, prototype, postCompCar; + + std::vector<unsigned> postCompCdr; + + for (unsigned i = 0; i < completions->NumResults; ++i) { + CXCompletionResult candidate = completions->Results[i]; + CXAvailabilityKind availability = + clang_getCompletionAvailability(candidate.CompletionString); + + unsigned priority = + clang_getCompletionPriority(candidate.CompletionString); + unsigned annotationStart = 0; + bool typedTextSet = false; + bool hasPrefix = true; + + + if (availability == CXAvailability_NotAccessible || + availability == CXAvailability_NotAvailable) { + continue; + } + + typedtext.clear(); + brief.clear(); + resultType.clear(); + prototype.clear(); + postCompCar.clear(); + postCompCdr.clear(); + + for (CompletionChunk chunk(candidate.CompletionString); chunk.hasNext(); + chunk.next()) { + char ch = 0; + + auto chunkKind = chunk.kind(); + + switch (chunkKind) { + case CXCompletionChunk_ResultType: + resultType = chunk.text(); + break; + + case CXCompletionChunk_TypedText: + case CXCompletionChunk_Text: + case CXCompletionChunk_Placeholder: + case CXCompletionChunk_Informative: + case CXCompletionChunk_CurrentParameter: + prototype += chunk.text(); + break; + + case CXCompletionChunk_LeftParen: ch = '('; break; + case CXCompletionChunk_RightParen: ch = ')'; break; + case CXCompletionChunk_LeftBracket: ch = '['; break; + case CXCompletionChunk_RightBracket: ch = ']'; break; + case CXCompletionChunk_LeftBrace: ch = '{'; break; + case CXCompletionChunk_RightBrace: ch = '}'; break; + case CXCompletionChunk_LeftAngle: ch = '<'; break; + case CXCompletionChunk_RightAngle: ch = '>'; break; + case CXCompletionChunk_Comma: ch = ','; break; + case CXCompletionChunk_Colon: ch = ':'; break; + case CXCompletionChunk_SemiColon: ch = ';'; break; + case CXCompletionChunk_Equal: ch = '='; break; + case CXCompletionChunk_HorizontalSpace: ch = ' '; break; + case CXCompletionChunk_VerticalSpace: ch = '\n'; break; + + case CXCompletionChunk_Optional: + // ignored for now + break; + } + + if (ch != 0) { + prototype += ch; + // commas look better followed by a space + if (ch == ',') { + prototype += ' '; + } + } + + if (typedTextSet) { + if (ch != 0) { + postCompCar += ch; + if (ch == ',') { + postCompCar += ' '; + } + } else if (chunkKind == CXCompletionChunk_Text || + chunkKind == CXCompletionChunk_TypedText) { + postCompCar += chunk.text(); + } else if (chunkKind == CXCompletionChunk_Placeholder || + chunkKind == CXCompletionChunk_CurrentParameter) { + postCompCdr.push_back(postCompCar.size()); + postCompCar += chunk.text(); + postCompCdr.push_back(postCompCar.size()); + } + } + + // Consider only the first typed text. The CXCompletionChunk_TypedText + // doc suggests that exactly one typed text will be given but at least + // in Objective-C it seems that more than one can appear, see: + // https://github.com/Sarcasm/irony-mode/pull/78#issuecomment-37115538 + if (chunkKind == CXCompletionChunk_TypedText && !typedTextSet) { + typedtext = chunk.text(); + if (!startsWith(typedtext, prefix, caseInsensitive)) { + hasPrefix = false; + break; + } + // annotation is what comes after the typedtext + annotationStart = prototype.size(); + typedTextSet = true; + } + } + + if (!hasPrefix) { + continue; + } + if (!typedTextSet) { + // clang may generate candidates without any typedText, and we may + // generate some output like: + // ("" 1 "bool" "" "hasUppercase(const std::string &prefix)" + // 0 ("") available) + // That will cause infinite completion in irony.el + continue; + } + +#if HAS_BRIEF_COMMENTS_IN_COMPLETION + brief = cxStringToStd( + clang_getCompletionBriefComment(candidate.CompletionString)); +#endif + + // see irony-completion.el#irony-completion-candidates + std::cout << '(' << support::quoted(typedtext) + << ' ' << priority + << ' ' << support::quoted(resultType) + << ' ' << support::quoted(brief) + << ' ' << support::quoted(prototype) + << ' ' << annotationStart + << " (" << support::quoted(postCompCar); + for (unsigned index : postCompCdr) + std::cout << ' ' << index; + std::cout << ")" + << ")\n"; + } + + std::cout << ")\n"; +} + +void Irony::computeCxUnsaved() { + cxUnsavedFiles_.clear(); + + for (const auto &p : filenameToContent_) { + CXUnsavedFile cxUnsavedFile; + + cxUnsavedFile.Filename = p.first.c_str(); + cxUnsavedFile.Contents = p.second.data(); + cxUnsavedFile.Length = p.second.size(); + cxUnsavedFiles_.push_back(cxUnsavedFile); + } +} + +void Irony::setUnsaved(const std::string &file, + const std::string &unsavedContentFile) { + resetCache(); + + UnsavedBuffer content; + if (!readFileContent(unsavedContentFile, content)) { + filenameToContent_.erase(file); + std::cout << "(error . (" + << "file-read-error" + << " \"failed to read unsaved buffer\"" + << " " << support::quoted(file) << " " + << support::quoted(unsavedContentFile) << ")\n"; + } else { + filenameToContent_[file] = content; + std::cout << "(success . t)\n"; + } + + computeCxUnsaved(); +} + +void Irony::resetUnsaved(const std::string &file) { + resetCache(); + + const auto erasedCount = filenameToContent_.erase(file); + + if (erasedCount == 0) { + std::cout << "(error . (" + << "no-such-entry" + << " \"failed reset unsaved buffer\"" + << " " << support::quoted(file) << ")\n"; + } else { + std::cout << "(success . t)\n"; + } + + computeCxUnsaved(); +} + +void Irony::getCompileOptions(const std::string &buildDir, + const std::string &file) const { +#if !(HAS_COMPILATION_DATABASE) + + (void)buildDir; + (void)file; + + CXString cxVersionString = clang_getClangVersion(); + + std::cout << "(error . (" + << "unsupported" + << " \"compilation database requires Clang >= 3.2\"" + << " " << support::quoted(clang_getCString(cxVersionString)) + << "))\n"; + + clang_disposeString(cxVersionString); + + return; + +#else + CXCompilationDatabase_Error error; + CXCompilationDatabase db = + compDBCache_.fromDirectory(buildDir.c_str(), &error); + + switch (error) { + case CXCompilationDatabase_CanNotLoadDatabase: + std::cout << "(error . (" + << "cannot-load-database" + << " \"failed to load compilation database from directory\"" + << " " << support::quoted(buildDir) << "))\n"; + return; + + case CXCompilationDatabase_NoError: + break; + } + + CXCompileCommands compileCommands = + clang_CompilationDatabase_getCompileCommands(db, file.c_str()); + + std::cout << "(success . (\n"; + + for (unsigned i = 0, numCompileCommands = + clang_CompileCommands_getSize(compileCommands); + i < numCompileCommands; ++i) { + CXCompileCommand compileCommand = + clang_CompileCommands_getCommand(compileCommands, i); + + std::cout << "(" + << "("; + for (unsigned j = 0, + numArgs = clang_CompileCommand_getNumArgs(compileCommand); + j < numArgs; ++j) { + CXString arg = clang_CompileCommand_getArg(compileCommand, j); + std::cout << support::quoted(clang_getCString(arg)) << " "; + clang_disposeString(arg); + } + + std::cout << ")" + << " . "; + + CXString directory = clang_CompileCommand_getDirectory(compileCommand); + std::cout << support::quoted(clang_getCString(directory)); + clang_disposeString(directory); + + std::cout << ")\n"; + } + + std::cout << "))\n"; + + clang_CompileCommands_dispose(compileCommands); +#endif +} diff --git a/elpa/irony-20220110.849/server/src/Irony.h b/elpa/irony-20220110.849/server/src/Irony.h new file mode 100644 index 0000000..66968f5 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/Irony.h @@ -0,0 +1,147 @@ +/**-*-C++-*- + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief irony-server "API" declarations. + * + * Contains the commands that the Emacs package relies on. These commands are + * mostly wrappers around a subset of the features provided by libclang. Command + * results are printed to \c std::cout as s-expr, in order to make it easy for + * Emacs to consume. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_IRONY_H_ +#define IRONY_MODE_SERVER_IRONY_H_ + +#include "TUManager.h" + +#include "CompDBCache.h" +#include "Style.h" + +#include <string> +#include <vector> + +class Irony { +public: + // use std::string over std::vector<char> because I have some doubts + // that libclang expect unsaved buffers to be a null terminated C strings + typedef std::string UnsavedBuffer; + +public: + Irony(); + + bool isDebugEnabled() const { + return debug_; + } + + /// \name Command + /// \{ + + /// \brief Set or unset debugging of commands. + void setDebug(bool enable) { + debug_ = enable; + } + + /// Parse or reparse the given file and compile options. + /// + /// If the compile options have changed, the translation unit is re-created to + /// take this into account. + /// + /// Output \c nil or \c t, whether or not parsing the translation unit + /// succeeded. + /// + /// \sa diagnostics(), getType() + void parse(const std::string &file, const std::vector<std::string> &flags); + + /// Parse the given file for code completion. + /// + /// Shares the same semantics and output as \c parse(). + /// + /// \sa candidates(), completionDiagnostics() + void complete(const std::string &file, + unsigned line, + unsigned col, + const std::vector<std::string> &flags); + + void setUnsaved(const std::string &file, + const std::string &unsavedContentFile); + + void resetUnsaved(const std::string &file); + + /// \} + + /// \name Queries + /// \{ + + /// \brief Retrieve the last parse diagnostics for the given file. + /// + /// \pre parse() was called. + void diagnostics() const; + + /// \brief Get types of symbol at a given location. + /// + /// Example: + /// + /// \code + /// typedef int MyType; + /// MyType a; + /// \endcode + /// + /// Type of cursor location for 'a' is: + /// + /// \code{.el} + /// ("MyType" "int") + /// \endcode + /// + /// TODO: test with CXString(), seems to be twice the same string + /// + void getType(unsigned line, unsigned col) const; + + /// Get all the completion candidates. + /// + /// \pre complete() was called. + void candidates(const std::string &prefix, PrefixMatchStyle style) const; + + /// Get the diagnostics produced by the last \c complete(). + /// + /// \pre complete() was called. + void completionDiagnostics() const; + + /// \brief Get compile options from JSON database. + /// + /// \param buildDir Directory containing compile_commands.json + /// \param file File to obtain compile commands for. + /// + /// Example output: + /// + /// \code{.el} + /// ( + /// (("-Wfoo" "-DBAR" "-Iqux") . "/path/to/working/directory") + /// (("-Wfoo-alt" "-DBAR_ALT" "-Iqux/alt") . "/alt/working/directory") + /// ) + /// \endcode + /// + void getCompileOptions(const std::string &buildDir, + const std::string &file) const; + + /// \} + +private: + void resetCache(); + void computeCxUnsaved(); + +private: + mutable CompDBCache compDBCache_; + TUManager tuManager_; + std::map<std::string, UnsavedBuffer> filenameToContent_; + CXTranslationUnit activeTu_; + std::string file_; + std::vector<CXUnsavedFile> cxUnsavedFiles_; + CXCodeCompleteResults *activeCompletionResults_; + bool debug_; +}; + +#endif // IRONY_MODE_SERVER_IRONY_H_ diff --git a/elpa/irony-20220110.849/server/src/Style.h b/elpa/irony-20220110.849/server/src/Style.h new file mode 100644 index 0000000..52adb5e --- /dev/null +++ b/elpa/irony-20220110.849/server/src/Style.h @@ -0,0 +1,17 @@ +/**-*-C++-*- + * \file + * + * \brief Style definition. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ +#ifndef IRONY_MODE_SERVER_STYLE_H_ +#define IRONY_MODE_SERVER_STYLE_H_ + +enum class PrefixMatchStyle { + Exact, CaseInsensitive, SmartCase, +}; + + +#endif //IRONY_MODE_SERVER_STYLE_H_ diff --git a/elpa/irony-20220110.849/server/src/TUManager.cpp b/elpa/irony-20220110.849/server/src/TUManager.cpp new file mode 100644 index 0000000..afbdc82 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/TUManager.cpp @@ -0,0 +1,182 @@ +/** + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief See TUManager.hh + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + * + */ + +#include "TUManager.h" + +#include <iostream> + +TUManager::TUManager() + : index_(clang_createIndex(0, 0)) + , translationUnits_() + , parseTUOptions_(clang_defaultEditingTranslationUnitOptions()) { + + // this seems necessary to trigger "correct" reparse (/codeCompleteAt) + // clang_reparseTranslationUnit documentation states: + // + // > * \param TU The translation unit whose contents will be re-parsed. The + // > * translation unit must originally have been built with + // > * \c clang_createTranslationUnitFromSourceFile(). + // + // clang_createTranslationUnitFromSourceFile() is just a call to + // clang_parseTranslationUnit() with + // CXTranslationUnit_DetailedPreprocessingRecord enabled but because we want + // some other flags to be set we can't just call + // clang_createTranslationUnitFromSourceFile() + parseTUOptions_ |= CXTranslationUnit_DetailedPreprocessingRecord; + +#if HAS_BRIEF_COMMENTS_IN_COMPLETION + parseTUOptions_ |= CXTranslationUnit_IncludeBriefCommentsInCodeCompletion; +#endif + + // XXX: A bug in old version of Clang (at least '3.1-8') caused the completion + // to fail on the standard library types when + // CXTranslationUnit_PrecompiledPreamble is used. We disable this option for + // old versions of libclang. As a result the completion will work but + // significantly slower. + // + // -- https://github.com/Sarcasm/irony-mode/issues/4 + if (CINDEX_VERSION < 6) { + parseTUOptions_ &= ~CXTranslationUnit_PrecompiledPreamble; + } + +#if CINDEX_VERSION >= 34 + // Keep going even after fatal errors, or syntax checking will stop after the + // first error. For instance unused variables will not be reported until the + // error has been fixed. + parseTUOptions_ |= CXTranslationUnit_KeepGoing; +#endif +} + +TUManager::~TUManager() { + clang_disposeIndex(index_); +} + +CXTranslationUnit &TUManager::tuRef(const std::string &filename, + const std::vector<std::string> &flags) { + CXTranslationUnit &tu = translationUnits_[filename]; + + // if the flags changed since the last time, invalidate the translation unit + auto &flagsCache = flagsPerFileCache_[filename]; + if (flagsCache.size() != flags.size() || + !std::equal(flagsCache.begin(), flagsCache.end(), flags.begin())) { + if (tu) { + clang_disposeTranslationUnit(tu); + tu = nullptr; + } + // remember the flags for the next parse + flagsCache = flags; + } + return tu; +} + +CXTranslationUnit +TUManager::parse(const std::string &filename, + const std::vector<std::string> &flags, + const std::vector<CXUnsavedFile> &unsavedFiles) { + CXTranslationUnit &tu = tuRef(filename, flags); + + if (tu == nullptr) { + std::vector<const char *> argv; + +#ifdef CLANG_RESOURCE_DIR + // Make sure libclang find its builtin headers, this is a known issue with + // libclang, see: + // - http://lists.cs.uiuc.edu/pipermail/cfe-dev/2012-July/022893.html + // + // > Make sure that Clang is using its own . It will be in a directory + // > ending in clang/3.2/include/ where 3.2 is the version of clang that you + // > are using. You may need to explicitly add it to your header search. + // > Usually clang finds this directory relative to the executable with + // > CompilerInvocation::GetResourcesPath(Argv0, MainAddr), but using just + // > the libraries, it can't automatically find it. + argv.push_back("-resource-dir"); + argv.push_back(CLANG_RESOURCE_DIR); +#endif + + for (auto &flag : flags) { + argv.push_back(flag.c_str()); + } + + tu = clang_parseTranslationUnit( + index_, + filename.c_str(), + argv.data(), + static_cast<int>(argv.size()), + const_cast<CXUnsavedFile *>(unsavedFiles.data()), + unsavedFiles.size(), + parseTUOptions_); + } + + if (tu == nullptr) { + std::clog << "error: libclang couldn't parse '" << filename << "'\n"; + return nullptr; + } + + // Reparsing is necessary to enable optimizations. + // + // From the clang mailing list (cfe-dev): + // From: Douglas Gregor + // Subject: Re: Clang indexing library performance + // ... + // You want to use the "default editing options" when parsing the translation + // unit + // clang_defaultEditingTranslationUnitOptions() + // and then reparse at least once. That will enable the various + // code-completion optimizations that should bring this time down + // significantly. + if (clang_reparseTranslationUnit( + tu, + unsavedFiles.size(), + const_cast<CXUnsavedFile *>(unsavedFiles.data()), + clang_defaultReparseOptions(tu))) { + // a 'fatal' error occured (even a diagnostic is impossible) + clang_disposeTranslationUnit(tu); + std::clog << "error: libclang couldn't reparse '" << filename << "'\n"; + tu = 0; + return nullptr; + } + + return tu; +} + +CXTranslationUnit +TUManager::getOrCreateTU(const std::string &filename, + const std::vector<std::string> &flags, + const std::vector<CXUnsavedFile> &unsavedFiles) { + if (auto tu = tuRef(filename, flags)) + return tu; + + return parse(filename, flags, unsavedFiles); +} + +void TUManager::invalidateCachedTU(const std::string &filename) { + TranslationUnitsMap::iterator it = translationUnits_.find(filename); + + if (it != translationUnits_.end()) { + if (CXTranslationUnit &tu = it->second) + clang_disposeTranslationUnit(tu); + + translationUnits_.erase(it); + } +} + +void TUManager::invalidateAllCachedTUs() { + TranslationUnitsMap::iterator it = translationUnits_.begin(); + + while (it != translationUnits_.end()) { + if (CXTranslationUnit &tu = it->second) { + clang_disposeTranslationUnit(tu); + translationUnits_.erase(it++); // post-increment keeps the iterator valid + } else { + ++it; + } + } +} diff --git a/elpa/irony-20220110.849/server/src/TUManager.h b/elpa/irony-20220110.849/server/src/TUManager.h new file mode 100644 index 0000000..bd7730d --- /dev/null +++ b/elpa/irony-20220110.849/server/src/TUManager.h @@ -0,0 +1,109 @@ +/**-*-C++-*- + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief Translation Unit manager. + * + * Keeps a cache of translation units, reparsing or recreating them as + * necessary. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_TUMANAGER_H_ +#define IRONY_MODE_SERVER_TUMANAGER_H_ + +#include "support/CIndex.h" +#include "support/NonCopyable.h" + +#include <map> +#include <string> +#include <vector> + +class TUManager : public util::NonCopyable { +public: + TUManager(); + ~TUManager(); + + /** + * \brief Parse \p filename with flag \p flags. + * + * The first time call \c clang_parseTranslationUnit() and save the TU in the + * member \c translationUnits_, The next call with the same \p filename will + * call \c clang_reparseTranslationUnit(). + * + * usage: + * \code + * std::vector<std::string> flags; + * flags.push_back("-I../utils"); + * CXTranslationUnit tu = tuManager.parse("file.cpp", flags); + * + * if (! tu) + * std::cerr << "parsing translation unit failed\n"; + * \endcode + * + * \return The translation unit, if the parsing failed the translation unit + * will be \c NULL. + */ + CXTranslationUnit parse(const std::string &filename, + const std::vector<std::string> &flags, + const std::vector<CXUnsavedFile> &unsavedFiles); + + /** + * \brief Retrieve, creating it if necessary the TU associated to filename. + * + * \return The translation unit. Will be NULL if the translation unit couldn't + * be created. + */ + CXTranslationUnit getOrCreateTU(const std::string &filename, + const std::vector<std::string> &flags, + const std::vector<CXUnsavedFile> &unsavedFiles); + + /** + * \brief Invalidate a given cached TU, the next use of a TU will require + * reparsing. + * + * This can be useful for example: when the flags used to compile a file have + * changed. + * + * \param filename The filename for which the associated + * translation unit flags need to be invalidated. + * + * \sa invalidateAllCachedTUs() + */ + void invalidateCachedTU(const std::string &filename); + + /** + * \brief Invalidate all cached TU, the next use of a TU will require + * reparsing. + * + * \sa invalidateCachedTU() + */ + void invalidateAllCachedTUs(); + +private: + /** + * \brief Get a reference to the translation unit that matches \p filename + * with the given set of flags. + * + * The TU will be null if it has never been parsed or if the flags have + * changed. + * + * \todo Find a proper name. + */ + CXTranslationUnit &tuRef(const std::string &filename, + const std::vector<std::string> &flags); + +private: + typedef std::map<const std::string, CXTranslationUnit> TranslationUnitsMap; + typedef std::map<const std::string, std::vector<std::string>> FilenameFlagsMap; + +private: + CXIndex index_; + TranslationUnitsMap translationUnits_; // cache variable + FilenameFlagsMap flagsPerFileCache_; + unsigned parseTUOptions_; +}; + +#endif /* !IRONY_MODE_SERVER_TUMANAGER_H_ */ diff --git a/elpa/irony-20220110.849/server/src/main.cpp b/elpa/irony-20220110.849/server/src/main.cpp new file mode 100644 index 0000000..7797528 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/main.cpp @@ -0,0 +1,235 @@ +/** + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#include "Irony.h" +#include "Command.h" + +#include "support/CIndex.h" +#include "support/CommandLineParser.h" + +#include <cassert> +#include <cstdlib> +#include <fstream> +#include <iomanip> +#include <iostream> +#include <iterator> +#include <memory> +#include <vector> + +static void printHelp() { + std::cout << "usage: irony-server [OPTIONS...] [COMMAND] [ARGS...]\n" + "\n" + "Options:\n" + " -v, --version\n" + " -h, --help\n" + " -i, --interactive\n" + " -d, --debug\n" + " --log-file PATH\n" + "\n" + "Commands:\n"; + +#define X(sym, str, desc) \ + if (Command::sym != Command::Unknown) \ + std::cout << std::left << std::setw(25) << " " str << desc << "\n"; +#include "Commands.def" +} + +static void printVersion() { + // do not change the format for the first line, external programs should be + // able to rely on it + std::cout << "irony-server version " IRONY_PACKAGE_VERSION "\n"; + + CXString cxVersionString = clang_getClangVersion(); + std::cout << clang_getCString(cxVersionString) << "\n"; + clang_disposeString(cxVersionString); +} + +struct CommandProviderInterface { + virtual ~CommandProviderInterface() { } + + virtual std::vector<std::string> nextCommand() = 0; +}; + +struct CommandLineCommandProvider : CommandProviderInterface { + CommandLineCommandProvider(const std::vector<std::string> &argv) + : argv_(argv), firstCall_(true) { + } + + std::vector<std::string> nextCommand() { + if (firstCall_) { + firstCall_ = false; + return argv_; + } + + return std::vector<std::string>(1, "exit"); + } + +private: + std::vector<std::string> argv_; + bool firstCall_; +}; + +struct InteractiveCommandProvider : CommandProviderInterface { + std::vector<std::string> nextCommand() { + std::string line; + + if (std::getline(std::cin, line)) { + return unescapeCommandLine(line); + } + + return std::vector<std::string>(1, "exit"); + } +}; + +struct RestoreClogOnExit { + RestoreClogOnExit() : rdbuf_(std::clog.rdbuf()) { + } + + ~RestoreClogOnExit() { + std::clog.rdbuf(rdbuf_); + } + +private: + RestoreClogOnExit(const RestoreClogOnExit &); + RestoreClogOnExit &operator=(const RestoreClogOnExit &); + +private: + std::streambuf *rdbuf_; +}; + +int main(int ac, const char *av[]) { + std::vector<std::string> argv(&av[1], &av[ac]); + + // stick to STL streams, no mix of C and C++ for IO operations + std::ios_base::sync_with_stdio(false); + + bool interactiveMode = false; + + Irony irony; + + if (ac == 1) { + printHelp(); + return 1; + } + + std::ofstream logFile; + + // When logging to a specific file, std::clog.rdbuf() is replaced by the log + // file's one. When we return from the main, this buffer is deleted (at the + // same time as logFile) but std::clog is still active, and will try to + // release the rdbuf() which has already been released in logFile's + // destructor. To avoid this we restore std::clog()'s original rdbuf on exit. + RestoreClogOnExit clogBufferRestorer; + + unsigned optCount = 0; + while (optCount < argv.size()) { + const std::string &opt = argv[optCount]; + + if (opt.c_str()[0] != '-') + break; + + if (opt == "--help" || opt == "-h") { + printHelp(); + return 0; + } + + if (opt == "--version" || opt == "-v") { + printVersion(); + return 0; + } + + if (opt == "--interactive" || opt == "-i") { + interactiveMode = true; + } else if (opt == "--debug" || opt == "-d") { + irony.setDebug(true); + } else if (opt == "--log-file" && (optCount + 1) < argv.size()) { + ++optCount; + logFile.open(argv[optCount]); + std::clog.rdbuf(logFile.rdbuf()); + } else { + std::cerr << "error: invalid option '" << opt << "'\n"; + return 1; + } + + ++optCount; + } + + argv.erase(argv.begin(), argv.begin() + optCount); + + CommandParser commandParser; + std::unique_ptr<CommandProviderInterface> commandProvider; + + if (interactiveMode) { + commandProvider.reset(new InteractiveCommandProvider()); + } else { + commandProvider.reset(new CommandLineCommandProvider(argv)); + } + + while (Command *c = commandParser.parse(commandProvider->nextCommand())) { + if (c->action != Command::Exit) { + std::clog << "execute: " << *c << std::endl; + } + + switch (c->action) { + case Command::Help: + printHelp(); + break; + + case Command::Candidates: + irony.candidates(c->prefix, c->style); + break; + + case Command::CompletionDiagnostics: + irony.completionDiagnostics(); + break; + + case Command::Complete: + irony.complete(c->file, c->line, c->column, c->flags); + break; + + case Command::Diagnostics: + irony.diagnostics(); + break; + + case Command::Exit: + return 0; + + case Command::GetCompileOptions: + irony.getCompileOptions(c->dir, c->file); + break; + + case Command::GetType: + irony.getType(c->line, c->column); + break; + + case Command::Parse: + irony.parse(c->file, c->flags); + break; + + case Command::SetDebug: + irony.setDebug(c->opt); + break; + + case Command::SetUnsaved: + irony.setUnsaved(c->file, c->unsavedFile); + break; + + case Command::ResetUnsaved: + irony.resetUnsaved(c->file); + break; + + case Command::Unknown: + assert(0 && "unreacheable code...reached!"); + break; + } + + std::cout << "\n;;EOT\n" << std::flush; + } + + return 1; +} diff --git a/elpa/irony-20220110.849/server/src/support/CIndex.h b/elpa/irony-20220110.849/server/src/support/CIndex.h new file mode 100644 index 0000000..89d3f62 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/support/CIndex.h @@ -0,0 +1,33 @@ +/** + * \file + * \brief Wrapper around Clang Indexing Public C Interface header. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_SUPPORT_CINDEXVERSION_H_ +#define IRONY_MODE_SERVER_SUPPORT_CINDEXVERSION_H_ + +#include <clang-c/Index.h> + +/// Use <tt>\#if CINDEX_VERSION_VERSION > 10047</tt> to test for +/// CINDEX_VERSION_MAJOR = 1 and CINDEX_VERSION_MINOR = 47. +#ifndef CINDEX_VERSION +#define CINDEX_VERSION 0 // pre-clang 3.2 support +#endif + +#if CINDEX_VERSION >= 6 +#define HAS_BRIEF_COMMENTS_IN_COMPLETION 1 +#else +#define HAS_BRIEF_COMMENTS_IN_COMPLETION 0 +#endif + +#if CINDEX_VERSION >= 6 +#define HAS_COMPILATION_DATABASE 1 +#include <clang-c/CXCompilationDatabase.h> +#else +#define HAS_COMPILATION_DATABASE 0 +#endif + +#endif /* !IRONY_MODE_SERVER_SUPPORT_CINDEXVERSION_H_ */ diff --git a/elpa/irony-20220110.849/server/src/support/CommandLineParser.cpp b/elpa/irony-20220110.849/server/src/support/CommandLineParser.cpp new file mode 100644 index 0000000..a838092 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/support/CommandLineParser.cpp @@ -0,0 +1,119 @@ +/** + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#include "CommandLineParser.h" + +namespace { + +/// \brief A parser for escaped strings of command line arguments. +/// +/// Assumes \-escaping for quoted arguments (see the documentation of +/// unescapeCommandLine(...)). +class CommandLineArgumentParser { +public: + CommandLineArgumentParser(const std::string &commandLine) + : input_(commandLine), position_(input_.begin() - 1) { + } + + std::vector<std::string> parse() { + bool hasMoreInput = true; + while (hasMoreInput && nextNonWhitespace()) { + std::string argument; + hasMoreInput = parseStringInto(argument); + commandLine_.push_back(argument); + } + return commandLine_; + } + +private: + // All private methods return true if there is more input available. + + bool parseStringInto(std::string &string) { + do { + if (*position_ == '"') { + if (!parseDoubleQuotedStringInto(string)) + return false; + } else if (*position_ == '\'') { + if (!parseSingleQuotedStringInto(string)) + return false; + } else { + if (!parseFreeStringInto(string)) + return false; + } + } while (*position_ != ' '); + return true; + } + + bool parseDoubleQuotedStringInto(std::string &string) { + if (!next()) + return false; + while (*position_ != '"') { + if (!skipEscapeCharacter()) + return false; + string.push_back(*position_); + if (!next()) + return false; + } + return next(); + } + + bool parseSingleQuotedStringInto(std::string &string) { + if (!next()) + return false; + while (*position_ != '\'') { + string.push_back(*position_); + if (!next()) + return false; + } + return next(); + } + + bool parseFreeStringInto(std::string &string) { + do { + if (!skipEscapeCharacter()) + return false; + string.push_back(*position_); + if (!next()) + return false; + } while (*position_ != ' ' && *position_ != '"' && *position_ != '\''); + return true; + } + + bool skipEscapeCharacter() { + if (*position_ == '\\') { + return next(); + } + return true; + } + + bool nextNonWhitespace() { + do { + if (!next()) + return false; + } while (*position_ == ' '); + return true; + } + + bool next() { + ++position_; + return position_ != input_.end(); + } + +private: + const std::string input_; + std::string::const_iterator position_; + std::vector<std::string> commandLine_; +}; + +} // unnamed namespace + +std::vector<std::string> +unescapeCommandLine(const std::string &escapedCommandLine) { + CommandLineArgumentParser parser(escapedCommandLine); + return parser.parse(); +} diff --git a/elpa/irony-20220110.849/server/src/support/CommandLineParser.h b/elpa/irony-20220110.849/server/src/support/CommandLineParser.h new file mode 100644 index 0000000..b764797 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/support/CommandLineParser.h @@ -0,0 +1,21 @@ +/** + * \file + * \brief Facility to parse a command line into a string array. + * + * \note Please note that the code borrowed from the Clang, + * lib/Tooling/JSONCompilationDatabase.cpp. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_SUPPORT_COMMAND_LINE_PARSER_H_ +#define IRONY_MODE_SERVER_SUPPORT_COMMAND_LINE_PARSER_H_ + +#include <string> +#include <vector> + +std::vector<std::string> +unescapeCommandLine(const std::string &escapedCommandLine); + +#endif // IRONY_MODE_SERVER_SUPPORT_COMMAND_LINE_PARSER_H_ diff --git a/elpa/irony-20220110.849/server/src/support/NonCopyable.h b/elpa/irony-20220110.849/server/src/support/NonCopyable.h new file mode 100644 index 0000000..d30a5b2 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/support/NonCopyable.h @@ -0,0 +1,34 @@ +/**-*-C++-*- + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief NonCopyable class like in Boost. + * + * \see http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Non-copyable_Mixin + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_SUPPORT_NONCOPYABLE_H_ +#define IRONY_MODE_SERVER_SUPPORT_NONCOPYABLE_H_ + +namespace util { + +class NonCopyable { +protected: + NonCopyable() { + } + + // Protected non-virtual destructor + ~NonCopyable() { + } + +private: + NonCopyable(const NonCopyable &); + NonCopyable &operator=(const NonCopyable &); +}; + +} // ! namespace util + +#endif /* !IRONY_MODE_SERVER_SUPPORT_NONCOPYABLE_H_ */ diff --git a/elpa/irony-20220110.849/server/src/support/TemporaryFile.cpp b/elpa/irony-20220110.849/server/src/support/TemporaryFile.cpp new file mode 100644 index 0000000..e7393e1 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/support/TemporaryFile.cpp @@ -0,0 +1,74 @@ +/** + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#include "TemporaryFile.h" + +#include <algorithm> +#include <cstdio> +#include <cstdlib> +#include <fstream> +#include <iostream> +#include <random> + +static std::string getTemporaryFileDirectory() { + const char *temporaryDirEnvVars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"}; + + for (const char *envVar : temporaryDirEnvVars) { + if (const char *dir = std::getenv(envVar)) + return dir; + } + + return "/tmp"; +} + +TemporaryFile::TemporaryFile(const std::string &prefix, + const std::string &suffix) + : pathOrPattern_(prefix + "-%%%%%%" + suffix) { +} + +TemporaryFile::~TemporaryFile() { + if (openedFile_) { + openedFile_.reset(); + std::remove(pathOrPattern_.c_str()); + } +} + +const std::string &TemporaryFile::getPath() { + if (!openedFile_) { + openedFile_.reset(new std::fstream); + + std::random_device rd; + std::default_random_engine e(rd()); + std::uniform_int_distribution<int> dist(0, 15); + std::string pattern = pathOrPattern_; + std::string tmpDir = getTemporaryFileDirectory() + "/"; + int i = 0; + + do { + // exiting is better than infinite loop + if (++i > TemporaryFile::MAX_ATTEMPS) { + std::cerr << "error: couldn't create temporary file, please check your " + "temporary file directory (" << tmpDir << ")\n"; + exit(EXIT_FAILURE); + } + + // make the filename based on the pattern + std::transform(pattern.begin(), + pattern.end(), + pathOrPattern_.begin(), + [&e, &dist](char ch) { + return ch == '%' ? "0123456789abcdef"[dist(e)] : ch; + }); + // create the file + openedFile_->open(tmpDir + pathOrPattern_, std::ios_base::out); + } while (!openedFile_->is_open()); + pathOrPattern_ = tmpDir + pathOrPattern_; + } + + return pathOrPattern_; +} diff --git a/elpa/irony-20220110.849/server/src/support/TemporaryFile.h b/elpa/irony-20220110.849/server/src/support/TemporaryFile.h new file mode 100644 index 0000000..5bf77f4 --- /dev/null +++ b/elpa/irony-20220110.849/server/src/support/TemporaryFile.h @@ -0,0 +1,36 @@ +/**-*-C++-*- + * \file + * \author Guillaume Papin <guillaume.papin@epitech.eu> + * + * \brief Not the best piece of code out there. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_SUPPORT_TEMPORARY_FILE_H_ +#define IRONY_MODE_SERVER_SUPPORT_TEMPORARY_FILE_H_ + +#include <iosfwd> +#include <memory> +#include <string> + +class TemporaryFile { + enum { + // if we can't create the temp file, exits. + MAX_ATTEMPS = 25 + }; + +public: + TemporaryFile(const std::string &prefix, const std::string &suffix = ""); + ~TemporaryFile(); + + /// Returns the path of this temporary filename + const std::string &getPath(); + +private: + std::string pathOrPattern_; + std::unique_ptr<std::fstream> openedFile_; +}; + +#endif // IRONY_MODE_SERVER_SUPPORT_TEMPORARY_FILE_H_ diff --git a/elpa/irony-20220110.849/server/src/support/iomanip_quoted.h b/elpa/irony-20220110.849/server/src/support/iomanip_quoted.h new file mode 100644 index 0000000..a8ca38b --- /dev/null +++ b/elpa/irony-20220110.849/server/src/support/iomanip_quoted.h @@ -0,0 +1,52 @@ +/**-*-C++-*- + * \file + * \brief Dumb implementation of something that might look like C++14 + * std::quoted. + * + * This file is distributed under the GNU General Public License. See + * COPYING for details. + */ + +#ifndef IRONY_MODE_SERVER_SUPPORT_IOMANIP_QUOTED_H_ +#define IRONY_MODE_SERVER_SUPPORT_IOMANIP_QUOTED_H_ + +#include <ostream> +#include <string> + +namespace support { +namespace detail { + +struct QuotedStringProxy { + QuotedStringProxy(const std::string &s) : s(s) { + } + + std::string s; +}; + +std::ostream &operator<<(std::ostream &os, const QuotedStringProxy &q) { + const std::string &s = q.s; + + os << '"'; + if (s.find_first_of("\"\\") == std::string::npos) { + os << s; + } else { + for (auto ch : s) { + if (ch == '\\' || ch == '"') + os << '\\'; + + os << ch; + } + } + os << '"'; + return os; +} + +} // namespace detail + +detail::QuotedStringProxy quoted(const std::string &s) { + return detail::QuotedStringProxy(s); +} + +} // namespace support + +#endif // IRONY_MODE_SERVER_SUPPORT_IOMANIP_QUOTED_H_ |