Initial commit.

This commit is contained in:
2016-02-18 14:53:30 +01:00
commit 8e93ca7a95
2215 changed files with 341269 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
BasedOnStyle: LLVM
AllowShortFunctionsOnASingleLine: false
AlwaysBreakTemplateDeclarations: true
BinPackParameters: false
BreakConstructorInitializersBeforeComma: true
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 2
IndentFunctionDeclarationAfterType: false

View File

@@ -0,0 +1,69 @@
cmake_minimum_required(VERSION 2.8.3) # CMAKE_CURRENT_LIST_DIR
project(IronyMode)
set(CMAKE_MODULE_PATH
${PROJECT_SOURCE_DIR}/cmake
${PROJECT_SOURCE_DIR}/cmake/modules
${CMAKE_MODULE_PATH})
include(utils)
include(CTest)
check_for_in_source_build()
release_as_default_build_type()
# Disable exception, we aren't using them and right now clang-cl needs them
# disabled to parse Windows headers.
#
# Logic stolen from LLVM
if (CMAKE_COMPILER_IS_GNUCXX)
set(IRONY_COMPILER_IS_GCC_COMPATIBLE ON)
elseif (MSVC)
set(IRONY_COMPILER_IS_GCC_COMPATIBLE OFF)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(IRONY_COMPILER_IS_GCC_COMPATIBLE ON)
endif()
if(IRONY_COMPILER_IS_GCC_COMPATIBLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
elseif(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHs-c- /D_HAS_EXCEPTIONS=0")
# 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.
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /D_ITERATOR_DEBUG_LEVEL=0")
endif()
enable_colored_diagnotics()
check_cxx11_options()
if (CXX11_COMPILE_OPTIONS)
add_compile_options_(${CXX11_COMPILE_OPTIONS})
endif()
foreach (link_option ${CXX11_LINK_OPTIONS})
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${link_option}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${link_option}")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${link_option}")
endforeach()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
add_compile_options_(-Wall -Wextra)
endif()
option(GENERATE_DOXYGEN "Whether or not to build the Doxygen documentation" OFF)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_subdirectory(src)
add_subdirectory(docs)
if (BUILD_TESTING)
add_subdirectory(test)
endif()

View File

@@ -0,0 +1,95 @@
#
# Get the directory where the libclang headers reside.
#
# If found the following variable will be set:
# - LIBCLANG_BUILTIN_HEADERS_DIR
#
set(CHECK_LIBCLANG_BUILTIN_HEADERS_DIR_CHECKER_CODE_IN
${CMAKE_CURRENT_LIST_DIR}/LibClangDiagnosticsChecker.cpp)
function(check_libclang_builtin_headers_dir)
if (LIBCLANG_BUILTIN_HEADERS_DIR)
return() # already in cache
endif()
message(STATUS "Detecting libclang builtin headers directory")
find_package (LibClang REQUIRED)
set(checker_code
${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/LibClangDiagnosticsChecker.cpp)
set(checked_file
"${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeTmp/check-libclang-stddef.cpp")
configure_file(${CHECK_LIBCLANG_BUILTIN_HEADERS_DIR_CHECKER_CODE_IN}
${checker_code} COPYONLY)
file(WRITE "${checked_file}" "#include <stddef.h>\n")
foreach (version ${LIBCLANG_KNOWN_LLVM_VERSIONS} .)
list(APPEND builtin_include_dir_suffixes "${version}/include")
endforeach()
# Paths stolen from Rip-Rip/clang_complete#getBuiltinHeaderPath()
find_path(CHECK_LIBCLANG_BUILTIN_HEADERS_STDDEF_DIR stddef.h
NO_DEFAULT_PATH
# the default path, favor this one over the other, in case a specific
# libclang has been chosen.
HINTS "${LIBCLANG_LIBRARY_DIR}/../lib/clang"
# other, distribution specific, paths
PATHS
"${LIBCLANG_LIBRARY_DIR}/../clang" # Gentoo
"${LIBCLANG_LIBRARY_DIR}/clang" # openSUSE, Windows
"${LIBCLANG_LIBRARY_DIR}/" # Google
"/usr/lib64/clang" # x86_64 (openSUSE, Fedora)
"/usr/lib/clang"
PATH_SUFFIXES ${builtin_include_dir_suffixes}
)
if (CHECK_LIBCLANG_BUILTIN_HEADERS_STDDEF_DIR)
# On Windows the paths weren't escaped correctly, similar to:
# http://public.kitware.com/pipermail/cmake/2006-February/008473.html
list(APPEND run_args -isystem \"${CHECK_LIBCLANG_BUILTIN_HEADERS_STDDEF_DIR}\")
endif()
list(APPEND run_args ${checked_file})
try_run(
CHECK_LIBCLANG_BUILTIN_HEADERS_DIR_NUM_DIAGNOSTICS
CHECK_LIBCLANG_BUILTIN_HEADERS_COMPILE_RESULT
${CMAKE_BINARY_DIR}
${checker_code}
CMAKE_FLAGS
"-DINCLUDE_DIRECTORIES:STRING=${LIBCLANG_INCLUDE_DIRS}"
"-DLINK_LIBRARIES:STRING=${LIBCLANG_LIBRARIES}"
COMPILE_OUTPUT_VARIABLE compile_output
RUN_OUTPUT_VARIABLE run_output
ARGS ${run_args}
)
if (NOT CHECK_LIBCLANG_BUILTIN_HEADERS_COMPILE_RESULT)
set(CHECK_LIBCLANG_BUILTIN_HEADERS_DIR_NUM_DIAGNOSTICS 1)
endif()
if (CHECK_LIBCLANG_BUILTIN_HEADERS_DIR_NUM_DIAGNOSTICS EQUAL 0)
message(STATUS "Detecting libclang builtin headers directory -- success")
if (CHECK_LIBCLANG_BUILTIN_HEADERS_STDDEF_DIR)
set(LIBCLANG_BUILTIN_HEADERS_DIR "${CHECK_LIBCLANG_BUILTIN_HEADERS_STDDEF_DIR}"
CACHE INTERNAL "libclang builtin headers directory.")
endif()
else()
message(STATUS "Detecting libclang builtin headers directory -- fail")
if (NOT CHECK_LIBCLANG_BUILTIN_HEADERS_COMPILE_RESULT)
message(WARNING "CheckLibClangBuiltinHeadersDir: failed to compile checker, please report.
Compile output:
${compile_output}
")
else()
message(WARNING "CheckLibClangBuiltinHeadersDir: unsupported configuration, please report.
Check with args: ${run_args}
Check output:
${run_output}
")
endif()
endif()
endfunction()

View File

@@ -0,0 +1,47 @@
/*
This program takes some forward its command line arguments to libclang and
returns the number of diagnostics that occured during the parsing.
It is used during CMake generation to adjust the default parameters to
libclang.
*/
#include <clang-c/Index.h>
#include <stdio.h>
int main(int argc, const char *argv[]) {
for (int i = 1; i < argc; ++i) {
fprintf(stdout, "argv[%d]: %s\n", i, argv[i]);
}
CXIndex Idx = clang_createIndex(0, 0);
CXTranslationUnit TU = clang_parseTranslationUnit(
Idx, NULL, &argv[1], argc - 1, 0, 0, CXTranslationUnit_None);
int NumDiagnostics;
if (TU == NULL) {
NumDiagnostics = 1;
fprintf(stderr, "failed to create translation unit!\n");
} else {
int i;
NumDiagnostics = clang_getNumDiagnostics(TU);
for (i = 0; i < NumDiagnostics; ++i) {
CXDiagnostic Diag = clang_getDiagnostic(TU, i);
CXString DiagStr =
clang_formatDiagnostic(Diag, clang_defaultDiagnosticDisplayOptions());
fprintf(stderr, "%s\n", clang_getCString(DiagStr));
clang_disposeString(DiagStr);
clang_disposeDiagnostic(Diag);
}
clang_disposeTranslationUnit(TU);
}
clang_disposeIndex(Idx);
return NumDiagnostics;
}

View File

@@ -0,0 +1,92 @@
#
# Try to find libclang
#
# Once done this will define:
# - LIBCLANG_FOUND
# System has libclang.
# - LIBCLANG_INCLUDE_DIRS
# The libclang include directories.
# - LIBCLANG_LIBRARIES
# The libraries needed to use libclang.
# - LIBCLANG_LIBRARY_DIR
# The path to the directory containing libclang.
# - LIBCLANG_KNOWN_LLVM_VERSIONS
# Known LLVM release numbers.
# most recent versions come first
# http://llvm.org/apt/
set(LIBCLANG_KNOWN_LLVM_VERSIONS 3.9.0 3.9
3.8.0 3.8
3.7.1 3.7.0 3.7
3.6.2 3.6.1 3.6.0 3.6
3.5.2 3.5.1 3.5.0 3.5
3.4.2 3.4.1 3.4
3.3
3.2
3.1)
set(libclang_llvm_header_search_paths)
set(libclang_llvm_lib_search_paths
# LLVM Fedora
/usr/lib/llvm
)
foreach (version ${LIBCLANG_KNOWN_LLVM_VERSIONS})
string(REPLACE "." "" undotted_version "${version}")
list(APPEND libclang_llvm_header_search_paths
# LLVM Debian/Ubuntu nightly packages: http://llvm.org/apt/
"/usr/lib/llvm-${version}/include/"
# LLVM MacPorts
"/opt/local/libexec/llvm-${version}/include"
# LLVM Homebrew
"/usr/local/Cellar/llvm/${version}/include"
# LLVM Homebrew/versions
"/usr/local/lib/llvm-${version}/include"
# FreeBSD ports versions
"/usr/local/llvm${undotted_version}/include"
)
list(APPEND libclang_llvm_lib_search_paths
# LLVM Debian/Ubuntu nightly packages: http://llvm.org/apt/
"/usr/lib/llvm-${version}/lib/"
# LLVM MacPorts
"/opt/local/libexec/llvm-${version}/lib"
# LLVM Homebrew
"/usr/local/Cellar/llvm/${version}/lib"
# LLVM Homebrew/versions
"/usr/local/lib/llvm-${version}/lib"
# FreeBSD ports versions
"/usr/local/llvm${undotted_version}/lib"
)
endforeach()
find_path(LIBCLANG_INCLUDE_DIR clang-c/Index.h
PATHS ${libclang_llvm_header_search_paths}
PATH_SUFFIXES LLVM/include #Windows package from http://llvm.org/releases/
DOC "The path to the directory that contains clang-c/Index.h")
# On Windows with MSVC, the import library uses the ".imp" file extension
# instead of the comon ".lib"
if (MSVC)
find_file(LIBCLANG_LIBRARY libclang.imp
PATH_SUFFIXES LLVM/lib
DOC "The file that corresponds to the libclang library.")
endif()
find_library(LIBCLANG_LIBRARY NAMES libclang.imp libclang clang
PATHS ${libclang_llvm_lib_search_paths}
PATH_SUFFIXES LLVM/lib #Windows package from http://llvm.org/releases/
DOC "The file that corresponds to the libclang library.")
get_filename_component(LIBCLANG_LIBRARY_DIR ${LIBCLANG_LIBRARY} PATH)
set(LIBCLANG_LIBRARIES ${LIBCLANG_LIBRARY})
set(LIBCLANG_INCLUDE_DIRS ${LIBCLANG_INCLUDE_DIR})
include(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LIBCLANG_FOUND to TRUE if
# all listed variables are TRUE
find_package_handle_standard_args(LibClang DEFAULT_MSG
LIBCLANG_LIBRARY LIBCLANG_INCLUDE_DIR)
mark_as_advanced(LIBCLANG_INCLUDE_DIR LIBCLANG_LIBRARY)

View File

@@ -0,0 +1,139 @@
include(CheckCXXCompilerFlag)
include(CheckCXXSourceCompiles)
#
# check_for_in_source_build()
#
function(check_for_in_source_build)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR AND NOT MSVC_IDE)
message(FATAL_ERROR "In-source builds are not allowed.
Please create a build/ directory and run cmake from there, passing
the path to this source directory as the last argument. This process
created the file `CMakeCache.txt' and the directory `CMakeFiles'.
Please delete them.
")
endif()
endfunction()
#
# release_as_default_build_type()
#
function(release_as_default_build_type)
# Set a default build type if none was specified for build systems with a
# unique configuration type (i.e: Make/Ninja builds)
if (NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
message(STATUS "Setting build type to 'Release' as none was specified")
set (CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
set_property (CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Release" "MinSizeRel" "RelWithDebInfo")
endif()
endfunction()
#
# add_compile_options_(<opts...>)
#
# Adds options to the compiler command line for sources in the current directory
# and below.
#
# note: add_compile_options() backport, which first appeared in CMake 2.8.12
function(add_compile_options_)
# possible to check with:
# if (${CMAKE_VERSION} VERSION_LESS "2.8.12")
if (COMMAND add_compile_options)
add_compile_options(${ARGN})
else()
add_definitions(${ARGN})
endif()
endfunction()
#
# enable_colored_diagnotics()
#
# Setup the flag to enable colored diagnostics if any.
#
# For now this option is enforced only for Ninja builds, where compiler output
# is redirected to pipes.
#
# Clang has '-fcolor-diagnostics' for a long time now. Since GCC 4.9, a similar
# flag has been added '-fdiagnostics-color' (somehow they managed to use another
# syntax than Clang's one...). Recent version of Clang will support both as they
# added support for GCC's -fdiagnostics-color.
#
function(enable_colored_diagnotics)
if (${CMAKE_GENERATOR} MATCHES "Ninja")
# Clang
check_cxx_compiler_flag("-fcolor-diagnostics" HAS_FCOLOR_DIAGNOSTICS_FLAG)
if (HAS_FCOLOR_DIAGNOSTICS_FLAG)
add_compile_options_(-fcolor-diagnostics)
else() # GCC (and Clang for compatibility with GCC)
check_cxx_compiler_flag("-fdiagnostics-color" HAS_DIAGNOSTICS_FCOLOR_FLAG)
if (HAS_DIAGNOSTICS_FCOLOR_FLAG)
add_compile_options_(-fdiagnostics-color)
endif()
endif()
endif()
endfunction()
#
# check_cxx11_options()
#
# Throws a FATAL_ERROR if C++11 isn't available otherwise sets:
# - CXX11_COMPILE_OPTIONS
# - CXX11_LINK_OPTIONS
#
function(check_cxx11_options)
if (CXX11_COMPILE_OPTIONS)
return() # already in cache
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
check_cxx_compiler_flag("-std=c++11" HAS_STDCXX11)
if (HAS_STDCXX11)
set(compile_options -std=c++11)
else()
check_cxx_compiler_flag("-std=c++0x" HAS_STDCXX0X)
if (HAS_STDCXX0X)
set(compile_options -std=c++0x)
endif()
endif()
# Check whether or not the system library provides proper C++11 support, if
# not we try to link specifically against libc++.
#
# This seems useful for Mac OS X builds, see:
# http://cplusplusmusings.wordpress.com/2012/07/05/clang-and-standard-libraries-on-mac-os-x/
set(CMAKE_REQUIRED_FLAGS ${compile_options})
check_cxx_source_compiles("#include <random>
int main() {
std::random_device rd;
std::default_random_engine e(rd());
std::uniform_int_distribution<int> dist(0, 15);
return dist(e);
}
"
HAS_CXX11_STDLIB)
if (NOT HAS_CXX11_STDLIB)
check_cxx_compiler_flag("-stdlib=libc++" HAS_LIBCXX)
if (HAS_LIBCXX)
list(APPEND compile_options -stdlib=libc++)
list(APPEND link_options -stdlib=libc++)
else()
message(FATAL_ERROR "Standard library doesn't support C++11!")
endif()
endif()
endif()
if (compile_options)
message(STATUS "C++11 compiler option(s): ${compile_options}")
endif()
set(CXX11_COMPILE_OPTIONS ${compile_options} CACHE INTERNAL
"C++11 compile options, if any")
set(CXX11_LINK_OPTIONS ${link_options} CACHE INTERNAL
"C++11 link options, if any")
endfunction()

View File

@@ -0,0 +1,15 @@
if (GENERATE_DOXYGEN)
find_package (Doxygen REQUIRED)
set (HAS_DOT_VALUE "NO")
if (DOXYGEN_DOT_FOUND)
set (HAS_DOT_VALUE "YES")
endif()
configure_file(irony-server.cfg.in irony-server.cfg @ONLY)
add_custom_target(doxygen
${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/irony-server.cfg
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen" VERBATIM)
endif()

View File

@@ -0,0 +1,291 @@
# Doxyfile 1.8.2
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = irony-server
PROJECT_NUMBER =
PROJECT_BRIEF = "The bridge between libclang and Emacs."
PROJECT_LOGO =
OUTPUT_DIRECTORY =
CREATE_SUBDIRS = NO
OUTPUT_LANGUAGE = English
BRIEF_MEMBER_DESC = YES
REPEAT_BRIEF = YES
ABBREVIATE_BRIEF =
ALWAYS_DETAILED_SEC = NO
INLINE_INHERITED_MEMB = NO
FULL_PATH_NAMES = NO
STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
SHORT_NAMES = NO
JAVADOC_AUTOBRIEF = YES
QT_AUTOBRIEF = NO
MULTILINE_CPP_IS_BRIEF = NO
INHERIT_DOCS = YES
SEPARATE_MEMBER_PAGES = NO
TAB_SIZE = 2
ALIASES =
TCL_SUBST =
OPTIMIZE_OUTPUT_FOR_C = NO
OPTIMIZE_OUTPUT_JAVA = NO
OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
EXTENSION_MAPPING =
MARKDOWN_SUPPORT = YES
AUTOLINK_SUPPORT = YES
BUILTIN_STL_SUPPORT = YES
CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
IDL_PROPERTY_SUPPORT = YES
DISTRIBUTE_GROUP_DOC = NO
SUBGROUPING = YES
INLINE_GROUPED_CLASSES = NO
INLINE_SIMPLE_STRUCTS = NO
TYPEDEF_HIDES_STRUCT = NO
SYMBOL_CACHE_SIZE = 0
LOOKUP_CACHE_SIZE = 0
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_PACKAGE = YES
EXTRACT_STATIC = YES
EXTRACT_LOCAL_CLASSES = YES
EXTRACT_LOCAL_METHODS = NO
EXTRACT_ANON_NSPACES = NO
HIDE_UNDOC_MEMBERS = NO
HIDE_UNDOC_CLASSES = NO
HIDE_FRIEND_COMPOUNDS = NO
HIDE_IN_BODY_DOCS = NO
INTERNAL_DOCS = NO
CASE_SENSE_NAMES = YES
HIDE_SCOPE_NAMES = NO
SHOW_INCLUDE_FILES = YES
FORCE_LOCAL_INCLUDES = NO
INLINE_INFO = YES
SORT_MEMBER_DOCS = YES
SORT_BRIEF_DOCS = NO
SORT_MEMBERS_CTORS_1ST = NO
SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
STRICT_PROTO_MATCHING = NO
GENERATE_TODOLIST = YES
GENERATE_TESTLIST = YES
GENERATE_BUGLIST = YES
GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
SHOW_FILES = YES
SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
LAYOUT_FILE =
CITE_BIB_FILES =
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
QUIET = NO
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
WARN_NO_PARAMDOC = NO
WARN_FORMAT = "$file:$line: $text"
WARN_LOGFILE =
#---------------------------------------------------------------------------
# configuration options related to the input files
#---------------------------------------------------------------------------
INPUT = @CMAKE_CURRENT_SOURCE_DIR@/../src
INPUT_ENCODING = UTF-8
FILE_PATTERNS = *.cpp *.h *.def
RECURSIVE = YES
EXCLUDE =
EXCLUDE_SYMLINKS = NO
EXCLUDE_PATTERNS =
EXCLUDE_SYMBOLS =
EXAMPLE_PATH =
EXAMPLE_PATTERNS =
EXAMPLE_RECURSIVE = NO
IMAGE_PATH =
INPUT_FILTER =
FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
FILTER_SOURCE_PATTERNS =
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
SOURCE_BROWSER = YES
INLINE_SOURCES = NO
STRIP_CODE_COMMENTS = NO
REFERENCED_BY_RELATION = YES
REFERENCES_RELATION = YES
REFERENCES_LINK_SOURCE = YES
USE_HTAGS = NO
VERBATIM_HEADERS = YES
#---------------------------------------------------------------------------
# configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
ALPHABETICAL_INDEX = YES
COLS_IN_ALPHA_INDEX = 5
IGNORE_PREFIX =
#---------------------------------------------------------------------------
# configuration options related to the HTML output
#---------------------------------------------------------------------------
GENERATE_HTML = YES
HTML_OUTPUT = html
HTML_FILE_EXTENSION = .html
HTML_HEADER =
HTML_FOOTER =
HTML_STYLESHEET =
HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES =
HTML_COLORSTYLE_HUE = 220
HTML_COLORSTYLE_SAT = 100
HTML_COLORSTYLE_GAMMA = 80
HTML_TIMESTAMP = YES
HTML_DYNAMIC_SECTIONS = NO
HTML_INDEX_NUM_ENTRIES = 100
GENERATE_DOCSET = NO
DOCSET_FEEDNAME = "Doxygen generated docs"
DOCSET_BUNDLE_ID = org.doxygen.Project
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
DOCSET_PUBLISHER_NAME = Publisher
GENERATE_HTMLHELP = NO
CHM_FILE =
HHC_LOCATION =
GENERATE_CHI = NO
CHM_INDEX_ENCODING =
BINARY_TOC = NO
TOC_EXPAND = NO
GENERATE_QHP = NO
QCH_FILE =
QHP_NAMESPACE = org.doxygen.Project
QHP_VIRTUAL_FOLDER = doc
QHP_CUST_FILTER_NAME =
QHP_CUST_FILTER_ATTRS =
QHP_SECT_FILTER_ATTRS =
QHG_LOCATION =
GENERATE_ECLIPSEHELP = NO
ECLIPSE_DOC_ID = org.doxygen.Project
DISABLE_INDEX = NO
GENERATE_TREEVIEW = NO
ENUM_VALUES_PER_LINE = 4
TREEVIEW_WIDTH = 250
EXT_LINKS_IN_WINDOW = NO
FORMULA_FONTSIZE = 10
FORMULA_TRANSPARENT = YES
USE_MATHJAX = NO
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
MATHJAX_EXTENSIONS =
SEARCHENGINE = YES
SERVER_BASED_SEARCH = NO
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
GENERATE_LATEX = NO
LATEX_OUTPUT = latex
LATEX_CMD_NAME = latex
MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
PAPER_TYPE = a4
EXTRA_PACKAGES =
LATEX_HEADER =
LATEX_FOOTER =
PDF_HYPERLINKS = YES
USE_PDFLATEX = YES
LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
LATEX_SOURCE_CODE = NO
LATEX_BIB_STYLE = plain
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
GENERATE_RTF = NO
RTF_OUTPUT = rtf
COMPACT_RTF = NO
RTF_HYPERLINKS = NO
RTF_STYLESHEET_FILE =
RTF_EXTENSIONS_FILE =
#---------------------------------------------------------------------------
# configuration options related to the man page output
#---------------------------------------------------------------------------
GENERATE_MAN = NO
MAN_OUTPUT = man
MAN_EXTENSION = .3
MAN_LINKS = NO
#---------------------------------------------------------------------------
# configuration options related to the XML output
#---------------------------------------------------------------------------
GENERATE_XML = NO
XML_OUTPUT = xml
XML_SCHEMA =
XML_DTD =
XML_PROGRAMLISTING = YES
#---------------------------------------------------------------------------
# configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# configuration options related to the Perl module output
#---------------------------------------------------------------------------
GENERATE_PERLMOD = NO
PERLMOD_LATEX = NO
PERLMOD_PRETTY = YES
PERLMOD_MAKEVAR_PREFIX =
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
ENABLE_PREPROCESSING = YES
MACRO_EXPANSION = NO
EXPAND_ONLY_PREDEF = NO
SEARCH_INCLUDES = YES
INCLUDE_PATH =
INCLUDE_FILE_PATTERNS =
PREDEFINED =
EXPAND_AS_DEFINED =
SKIP_FUNCTION_MACROS = YES
#---------------------------------------------------------------------------
# Configuration::additions related to external references
#---------------------------------------------------------------------------
TAGFILES =
GENERATE_TAGFILE =
ALLEXTERNALS = NO
EXTERNAL_GROUPS = YES
PERL_PATH = /usr/bin/perl
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
#---------------------------------------------------------------------------
CLASS_DIAGRAMS = YES
MSCGEN_PATH =
HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = @HAS_DOT_VALUE@
DOT_NUM_THREADS = 0
DOT_FONTNAME = Helvetica
DOT_FONTSIZE = 10
DOT_FONTPATH =
CLASS_GRAPH = YES
COLLABORATION_GRAPH = YES
GROUP_GRAPHS = YES
UML_LOOK = NO
UML_LIMIT_NUM_FIELDS = 10
TEMPLATE_RELATIONS = NO
INCLUDE_GRAPH = YES
INCLUDED_BY_GRAPH = YES
CALL_GRAPH = NO
CALLER_GRAPH = NO
GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
DOT_IMAGE_FORMAT = png
INTERACTIVE_SVG = YES
DOT_PATH = @DOXYGEN_DOT_PATH@
DOTFILE_DIRS =
MSCFILE_DIRS =
DOT_GRAPH_MAX_NODES = 50
MAX_DOT_GRAPH_DEPTH = 0
DOT_TRANSPARENT = NO
DOT_MULTI_TARGETS = YES
GENERATE_LEGEND = YES
DOT_CLEANUP = YES

View File

@@ -0,0 +1,70 @@
include(CheckLibClangBuiltinHeadersDir)
find_package(LibClang REQUIRED)
include_directories(${LIBCLANG_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
check_libclang_builtin_headers_dir()
if (LIBCLANG_BUILTIN_HEADERS_DIR)
# look for CLANG_BUILTIN_HEADERS_DIR usage in the code for an explanation
add_definitions(-DCLANG_BUILTIN_HEADERS_DIR=\"${LIBCLANG_BUILTIN_HEADERS_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)
add_executable(irony-server
support/arraysize.h
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
Irony.cpp
Irony.h
TUManager.cpp
TUManager.h
main.cpp)
# 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}\")
target_link_libraries(irony-server ${LIBCLANG_LIBRARIES})
install(TARGETS irony-server DESTINATION bin)

View File

@@ -0,0 +1,288 @@
/**
* \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>
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_;
};
} // 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 << "', "
<< "dir='" << command.dir << "', "
<< "line=" << command.line << ", "
<< "column=" << command.column << ", "
<< "flags=[";
bool first = true;
for (const std::string &flag : command.flags) {
if (!first)
os << ", ";
os << "'" << flag << "'";
first = false;
}
os << "], "
<< "unsavedFiles.count=" << command.unsavedFiles.size() << ", "
<< "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 handleUnsaved = false;
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));
handleUnsaved = true;
readCompileOptions = true;
break;
case Command::Complete:
positionalArgs.push_back(StringConverter(&command_.file));
positionalArgs.push_back(UnsignedIntConverter(&command_.line));
positionalArgs.push_back(UnsignedIntConverter(&command_.column));
handleUnsaved = true;
readCompileOptions = true;
break;
case Command::GetType:
positionalArgs.push_back(UnsignedIntConverter(&command_.line));
positionalArgs.push_back(UnsignedIntConverter(&command_.column));
break;
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 argIt = argv.begin() + 1;
int argCount = std::distance(argIt, argv.end());
// parse optional arguments come first
while (argIt != argv.end()) {
// '-' is allowed as a "default" file, this isn't an option but a positional
// argument
if ((*argIt)[0] != '-' || *argIt == "-")
break;
const std::string &opt = *argIt;
++argIt;
argCount--;
if (handleUnsaved) {
// TODO: handle multiple unsaved files
if (opt == "--num-unsaved=1") {
command_.unsavedFiles.resize(1);
}
} else {
std::clog << "error: invalid option for '" << actionStr << "': '" << opt
<< "' unknown\n";
return 0;
}
}
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(*argIt)) {
std::clog << "error: parsing command '" << actionStr
<< "': invalid argument '" << *argIt << "'\n";
return 0;
}
++argIt;
}
// '-' 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 taht 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();
}
// When a file is provided, the next line contains the compilation options to
// pass to libclang.
if (readCompileOptions) {
std::string compileOptions;
std::getline(std::cin, compileOptions);
command_.flags = unescapeCommandLine(compileOptions);
}
// read unsaved files
// filename
// filesize
// <file content...>
for (auto &p : command_.unsavedFiles) {
std::getline(std::cin, p.first);
unsigned length;
std::string filesizeStr;
std::getline(std::cin, filesizeStr);
UnsignedIntConverter uintConverter(&length);
if (!uintConverter(filesizeStr)) {
std::clog << "error: invalid file size '" << filesizeStr << "'\n";
return 0;
}
p.second.resize(length);
std::cin.read(p.second.data(), p.second.size());
CXUnsavedFile cxUnsavedFile;
cxUnsavedFile.Filename = p.first.c_str();
cxUnsavedFile.Contents = p.second.data();
cxUnsavedFile.Length = p.second.size();
command_.cxUnsavedFiles.push_back(cxUnsavedFile);
char nl;
std::cin.read(&nl, 1);
if (nl != '\n') {
std::clog << "error: missing newline for unsaved file content\n";
return 0;
}
}
return &command_;
}

View File

@@ -0,0 +1,70 @@
/**-*-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 <iosfwd>
#include <string>
#include <vector>
class TemporaryFile;
struct Command {
Command() {
clear();
}
void clear() {
action = Unknown;
flags.clear();
file.clear();
dir.clear();
line = 0;
column = 0;
unsavedFiles.clear();
cxUnsavedFiles.clear();
opt = false;
}
#define X(sym, str, desc) sym,
enum Action {
#include "Commands.def"
} action;
std::vector<std::string> flags;
std::string file;
std::string dir;
unsigned line;
unsigned column;
// pair of (filename, content)
std::vector<std::pair<std::string, std::vector<char>>> unsavedFiles;
std::vector<CXUnsavedFile> cxUnsavedFiles;
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_

View File

@@ -0,0 +1,28 @@
/**-*-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(Complete, "complete", "FILE LINE COL - perform code completion at a given location")
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 PROJECT_ROOT")
X(GetType, "get-type", "LINE COL - get type of symbol at a given location")
X(Help, "help", "show this message")
X(Parse, "parse", "FILE - parse the given file")
X(SetDebug, "set-debug", "[on|off] - enable or disable verbose logging")
// sentinel value, should be the last one
X(Unknown, "<unkown>", "<unspecified>")
#undef X

View File

@@ -0,0 +1,436 @@
/**
* \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>
static std::string cxStringToStd(CXString cxString) {
std::string stdStr;
if (const char *cstr = clang_getCString(cxString)) {
stdStr = cstr;
}
clang_disposeString(cxString);
return stdStr;
}
Irony::Irony() : activeTu_(nullptr), debug_(false) {
}
static const char *diagnosticSeverity(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";
}
static void dumpDiagnostics(const CXTranslationUnit &tu) {
std::cout << "(\n";
std::string file;
for (unsigned i = 0, diagnosticCount = clang_getNumDiagnostics(tu);
i < diagnosticCount;
++i) {
CXDiagnostic diagnostic = clang_getDiagnostic(tu, i);
CXSourceLocation location = clang_getDiagnosticLocation(diagnostic);
unsigned line, column, offset;
if (clang_equalLocations(location, clang_getNullLocation())) {
file.clear();
line = 0;
column = 0;
offset = 0;
} else {
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";
clang_disposeDiagnostic(diagnostic);
}
std::cout << ")\n";
}
void Irony::parse(const std::string &file,
const std::vector<std::string> &flags,
const std::vector<CXUnsavedFile> &unsavedFiles) {
activeTu_ = tuManager_.parse(file, flags, unsavedFiles);
file_ = file;
std::cout << (activeTu_ ? "t" : "nil") << "\n";
}
void Irony::diagnostics() const {
if (activeTu_ == nullptr) {
std::clog << "W: diagnostics - parse wasn't called\n";
std::cout << "nil\n";
return;
}
dumpDiagnostics(activeTu_);
}
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_);
}
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,
const std::vector<CXUnsavedFile> &unsavedFiles) {
CXTranslationUnit tu = tuManager_.getOrCreateTU(file, flags, unsavedFiles);
if (tu == nullptr) {
std::cout << "nil\n";
return;
}
if (CXCodeCompleteResults *completions =
clang_codeCompleteAt(tu,
file.c_str(),
line,
col,
const_cast<CXUnsavedFile *>(unsavedFiles.data()),
unsavedFiles.size(),
(clang_defaultCodeCompleteOptions() &
~CXCodeComplete_IncludeCodePatterns)
#if HAS_BRIEF_COMMENTS_IN_COMPLETION
|
CXCodeComplete_IncludeBriefComments
#endif
)) {
if (debug_) {
unsigned numDiags = clang_codeCompleteGetNumDiagnostics(completions);
std::clog << "debug: complete: " << numDiags << " diagnostic(s)\n";
for (unsigned i = 0; i < numDiags; ++i) {
CXDiagnostic diagnostic =
clang_codeCompleteGetDiagnostic(completions, i);
CXString s = clang_formatDiagnostic(
diagnostic, clang_defaultDiagnosticDisplayOptions());
std::clog << clang_getCString(s) << std::endl;
clang_disposeString(s);
clang_disposeDiagnostic(diagnostic);
}
}
clang_sortCodeCompletionResults(completions->Results,
completions->NumResults);
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;
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();
// annotation is what comes after the typedtext
annotationStart = prototype.size();
typedTextSet = true;
}
}
#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";
}
clang_disposeCodeCompleteResults(completions);
std::cout << ")\n";
}
}
void Irony::getCompileOptions(const std::string &buildDir,
const std::string &file) const {
#if !(HAS_COMPILATION_DATABASE)
(void)buildDir;
(void)file;
std::cout << "nil\n";
return;
#else
CXCompilationDatabase_Error error;
CXCompilationDatabase db =
clang_CompilationDatabase_fromDirectory(buildDir.c_str(), &error);
switch (error) {
case CXCompilationDatabase_CanNotLoadDatabase:
std::clog << "I: could not load compilation database in '" << buildDir
<< "'\n";
std::cout << "nil\n";
return;
case CXCompilationDatabase_NoError:
break;
}
CXCompileCommands compileCommands =
clang_CompilationDatabase_getCompileCommands(db, file.c_str());
std::cout << "(\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);
clang_CompilationDatabase_dispose(db);
#endif
}

View File

@@ -0,0 +1,126 @@
/**-*-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 <string>
#include <vector>
class Irony {
public:
Irony();
bool isDebugEnabled() const {
return debug_;
}
/// \name Modifiers
/// \{
/// \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,
const std::vector<CXUnsavedFile> &unsavedFiles);
/// \}
/// \name Observers
/// \{
/// \brief Retrieve the last parse diagnostics for the given file.
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;
/// \brief Perform code completion at a given location.
///
/// Print the list of candidate if any. The empty list is printed on error.
///
/// Example output:
///
/// \code{.el}
/// (
/// ("foo")
/// ("bar")
/// ("baz")
/// )
/// \endcode
///
void complete(const std::string &file,
unsigned line,
unsigned col,
const std::vector<std::string> &flags,
const std::vector<CXUnsavedFile> &unsavedFiles);
/// \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:
TUManager tuManager_;
CXTranslationUnit activeTu_;
std::string file_;
bool debug_;
};
#endif // IRONY_MODE_SERVER_IRONY_H_

View File

@@ -0,0 +1,180 @@
/**
* \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: Completion results caching doesn't seem to work right, changes at the
// top of the file (i.e: new declarations) aren't detected and do not appear
// in completion results.
parseTUOptions_ &= ~CXTranslationUnit_CacheCompletionResults;
// 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;
}
}
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_BUILTIN_HEADERS_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("-isystem");
argv.push_back(CLANG_BUILTIN_HEADERS_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;
}
}
}

View File

@@ -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_ */

View File

@@ -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);
}
static void dumpUnsavedFiles(Command &command) {
for (int i = 0; i < static_cast<int>(command.unsavedFiles.size()); ++i) {
std::clog << "unsaved file " << i + 1 << ": "
<< command.unsavedFiles[i].first << "\n"
<< "----\n";
std::copy(command.unsavedFiles[i].second.begin(),
command.unsavedFiles[i].second.end(),
std::ostream_iterator<char>(std::clog));
std::clog << "----" << std::endl;
}
}
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;
if (irony.isDebugEnabled()) {
dumpUnsavedFiles(*c);
}
}
switch (c->action) {
case Command::Help:
printHelp();
break;
case Command::Complete:
irony.complete(c->file, c->line, c->column, c->flags, c->cxUnsavedFiles);
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, c->cxUnsavedFiles);
break;
case Command::SetDebug:
irony.setDebug(c->opt);
break;
case Command::Unknown:
assert(0 && "unreacheable code...reached!");
break;
}
std::cout << "\n;;EOT\n" << std::flush;
}
return 1;
}

View File

@@ -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_ */

View File

@@ -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();
}

View File

@@ -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_

View File

@@ -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_ */

View File

@@ -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_;
}

View File

@@ -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_

View File

@@ -0,0 +1,39 @@
/**-*-C++-*-
* \file
* \author Guillaume Papin <guillaume.papin@epitech.eu>
*
* \brief \c arraysize() and \c ARRAYSIZE_UNSAFE() definitions.
*
* This file is distributed under the GNU General Public License. See
* COPYING for details.
*/
#ifndef IRONY_MODE_SERVER_SUPPORT_ARRAYSIZE_H_
#define IRONY_MODE_SERVER_SUPPORT_ARRAYSIZE_H_
#include <cstddef>
template <typename T, std::size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
/// \brief Convenience macro to get the size of an array.
///
/// \note Found in an article about the Chromium project, see
/// http://software.intel.com/en-us/articles/pvs-studio-vs-chromium and
/// http://codesearch.google.com/codesearch/p?hl=en#OAMlx_jo-ck/src/base/basictypes.h&q=arraysize&exact_package=chromium
///
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
/// \brief \c arraysize() 'unsafe' version to use when the \c arraysize() macro
/// can't be used.
///
/// The \c arraysize() macro can't be used on an anonymous structure array for
/// example.
///
/// \note Be careful to check that the array passed is not just a pointer.
///
#define ARRAYSIZE_UNSAFE(a) \
((sizeof(a) / sizeof(*(a))) / \
static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
#endif /* !IRONY_MODE_SERVER_SUPPORT_ARRAYSIZE_H_ */

View File

@@ -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 quoted_string_proxy {
quoted_string_proxy(const std::string &s) : s(s) {
}
std::string s;
};
std::ostream &operator<<(std::ostream &os, const quoted_string_proxy &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::quoted_string_proxy quoted(const std::string &s) {
return detail::quoted_string_proxy(s);
}
} // namespace support
#endif // IRONY_MODE_SERVER_SUPPORT_IOMANIP_QUOTED_H_

View File

@@ -0,0 +1,3 @@
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure)
add_subdirectory (elisp)

View File

@@ -0,0 +1,43 @@
# On MS-Windows, emacs_dir is a special environment variable, which
# indicates the full path of the directory in which Emacs is
# installed.
#
# There is no standard location that I know of for Emacs on Windows so
# using this special environment variable will at least help people
# who build the server from inside Emacs.
if(DEFINED ENV{emacs_dir})
list(APPEND EMACS_EXECUTABLE_HINTS $ENV{emacs_dir}/bin)
endif()
find_program(EMACS_EXECUTABLE emacs
HINTS ${EMACS_EXECUTABLE_HINTS})
if (EMACS_EXECUTABLE)
message(STATUS "Found emacs: ${EMACS_EXECUTABLE}")
else()
message(WARNING "emacs not found: elisp tests will be skipped!")
return()
endif()
#
# add_ert_test(<FileName>)
#
# Create a test which run the given Elisp script using the Emacs ERT testing
# framework.
#
# The target is deduced from the ``FileName`` argument, e.g: the file foo.el
# will be the target 'check-foo-el'.
#
# FIXME: assumes MELPA is configured...
function(add_ert_test test_file)
get_filename_component(name ${test_file} NAME_WE)
add_test(check-${name}-el
${EMACS_EXECUTABLE} -batch
-l package
--eval "(package-initialize) (unless (require 'cl-lib nil t) (package-refresh-contents) (package-install 'cl-lib))"
-l ${CMAKE_CURRENT_SOURCE_DIR}/${test_file}
-f ert-run-tests-batch-and-exit)
endfunction()
add_ert_test(irony.el)
add_ert_test(irony-cdb-json.el)

View File

@@ -0,0 +1,87 @@
;; -*-no-byte-compile: t; -*-
(load
(concat (file-name-directory (or load-file-name buffer-file-name))
"test-config"))
(require 'irony-cdb-json)
(require 'cl-lib)
(defconst irony-cdb/compile-command
'((file . "../src/file.cc")
(directory . "/home/user/project/build")
(command . "/usr/bin/clang++ -DSOMEDEF=1 -c -o file.o /home/user/project/src/file.cc")))
(ert-deftest cdb/parse/simple/path-is-absolute ()
(should
(equal "/home/user/project/src/file.cc"
(nth 0 (irony-cdb-json--transform-compile-command
irony-cdb/compile-command)))))
(ert-deftest cdb/parse/simple/compile-options ()
(should
(equal '("-DSOMEDEF=1")
(nth 1 (irony-cdb-json--transform-compile-command
irony-cdb/compile-command)))))
(ert-deftest cdb/parse/simple/invocation-directory ()
(should
(equal "/home/user/project/build"
(nth 2 (irony-cdb-json--transform-compile-command
irony-cdb/compile-command)))))
(ert-deftest cdb/choose-closest-path/chooses-closest ()
(should
(equal "/tmp/a/cdb"
(irony-cdb--choose-closest-path "/tmp/a/1"
'("/tmp/a/cdb" "/tmp/cdb")))))
(ert-deftest cdb/choose-closest-path/chooses-closest2 ()
(should
(equal "/tmp/a/cdb"
(irony-cdb--choose-closest-path "/tmp/a/1"
'("/tmp/cdb" "/tmp/a/cdb")))))
(ert-deftest cdb/choose-closest-path/prefers-deeper ()
(should
(equal "/tmp/a/build/cdb"
(irony-cdb--choose-closest-path "/tmp/a/1"
'("/tmp/a/build/cdb" "/tmp/cdb")))))
(ert-deftest cdb/choose-closest-path/prefers-deeper2 ()
(should
(equal "/tmp/a/build/cdb"
(irony-cdb--choose-closest-path "/tmp/a/1"
'("/tmp/cdb" "/tmp/a/build/cdb")))))
(ert-deftest cdb/choose-closest-path/will-survive-garbage ()
(should
(equal nil
(irony-cdb--choose-closest-path "/tmp/a/1"
'ordures))))
; http://endlessparentheses.com/understanding-letf-and-how-it-replaces-flet.html
(ert-deftest cdb/locate-db/choose-among-candidates ()
(should
(equal "/foo/build/cdb"
(cl-letf (((symbol-function 'locate-dominating-file)
(lambda (file name)
(cond
((string= name "./cdb") "/") ; found /cdb
((string= name "build/cdb") "/foo/") ; found /foo/build/cdb
))))
(irony-cdb--locate-dominating-file-with-dirs "/foo/bar/qux.cpp"
"cdb"
'("." "build" "out/x86_64"))))))
(ert-deftest cdb/locate-dominating-file-with-dirs/children-first ()
(should
(equal "/tmp/foo/bar/out/x86_64/cdb"
(cl-letf (((symbol-function 'locate-dominating-file)
(lambda (file name)
(cond
((string= name "./cdb") "/tmp/foo/") ; found /tmp/foo/cdb
((string= name "out/x86_64/cdb") "/tmp/foo/bar/") ;found /tmp/foo/bar/out/x86_64/cdb
))))
(irony-cdb--locate-dominating-file-with-dirs "/tmp/foo/bar/qux.cpp"
"cdb"
'("." "out/x86_64" ))))))

View File

@@ -0,0 +1,117 @@
;; -*-no-byte-compile: t; -*-
(load (concat (file-name-directory (or load-file-name
buffer-file-name))
"test-config"))
(ert-deftest irony/buffer-size-in-bytes ()
(with-temp-buffer
;; this smiley takes 3 bytes apparently
(insert "")
(should (equal 3 (irony--buffer-size-in-bytes)))
(erase-buffer)
(insert "\n")
(should (equal 4 (irony--buffer-size-in-bytes)))
(erase-buffer)
(insert "\t")
(should (equal 1 (irony--buffer-size-in-bytes)))))
(ert-deftest irony/split-command-line/just-spaces ()
(let ((cmd-line "clang -Wall -Wextra"))
(should (equal
'("clang" "-Wall" "-Wextra")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/start-with-space ()
(let ((cmd-line " clang -Wall -Wextra"))
(should (equal
'("clang" "-Wall" "-Wextra")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/end-with-space ()
(let ((cmd-line "clang -Wall -Wextra "))
(should (equal
'("clang" "-Wall" "-Wextra")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/space-everywhere ()
(let ((cmd-line " \t clang \t -Wall \t -Wextra\t"))
(should (equal
'("clang" "-Wall" "-Wextra")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/with-quotes ()
(let ((cmd-line "clang -Wall -Wextra \"-I/tmp/dir with spaces\""))
(should (equal
'("clang" "-Wall" "-Wextra" "-I/tmp/dir with spaces")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/with-quotes ()
"Test if files are removed from the arguments list.
https://github.com/Sarcasm/irony-mode/issues/101"
(let ((cmd-line "g++ -DFOO=\\\"\\\""))
(should (equal
'("g++" "-DFOO=\"\"")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/start-with-quotes ()
(let ((cmd-line "\"cl ang\" -Wall -Wextra \"-I/tmp/dir with spaces\""))
(should (equal
'("cl ang" "-Wall" "-Wextra" "-I/tmp/dir with spaces")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/quotes-in-word ()
(let ((cmd-line "clang -Wall -Wextra -I\"/tmp/dir with spaces\""))
(should (equal
'("clang" "-Wall" "-Wextra" "-I/tmp/dir with spaces")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/ill-end-quote ()
:expected-result :failed
(let ((cmd-line "clang -Wall -Wextra\""))
(should (equal
'("clang" "-Wall" "-Wextra" "-I/tmp/dir with spaces")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/backslash-1 ()
(let ((cmd-line "clang\\ -Wall"))
(should (equal
'("clang -Wall")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/split-command-line/backslash-2 ()
(let ((cmd-line "\\\\\\ clang\\ -Wall\\"))
(should (equal
'("\\ clang -Wall\\")
(irony--split-command-line cmd-line)))))
(ert-deftest irony/extract-working-directory-option/not-specified ()
(let ((compile-flags '("-Wall")))
(should
(not (irony--extract-working-directory-option compile-flags)))))
(ert-deftest irony/extract-working-directory-option/specified-1 ()
(let ((compile-flags '("-working-directory" "/tmp/lol")))
(should (equal "/tmp/lol"
(irony--extract-working-directory-option compile-flags)))))
(ert-deftest irony/extract-working-directory-option/specified-2 ()
(let ((compile-flags '("-Wall" "-working-directory=/tmp/lol" "-Wshadow")))
(should (equal "/tmp/lol"
(irony--extract-working-directory-option compile-flags)))))
;; TODO: restore functionality
;; (ert-deftest irony/include-directories-1 ()
;; (let ((irony-compile-flags '("-Iinclude" "-I/tmp/foo"))
;; (irony-compile-flags-work-dir "/tmp/blah/"))
;; (should (equal
;; '("/tmp/blah/include" "/tmp/foo")
;; (irony-user-search-paths)))))
;; (ert-deftest irony/include-directories-2 ()
;; (let ((irony-compile-flags '("-Wextra" "-Iinclude" "-I" "foo" "-Wall"))
;; (irony-compile-flags-work-dir "/tmp/blah/"))
;; (should (equal
;; '("/tmp/blah/include"
;; "/tmp/blah/foo")
;; (irony-user-search-paths)))))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
;; -*-no-byte-compile: t; -*-
(defvar test-dir (if load-file-name
(file-name-as-directory
(expand-file-name (concat (file-name-directory
load-file-name)))))
"Elisp test directory path.")
;; load irony
(unless (require 'irony nil t)
(let ((irony-dir (expand-file-name "../../.." test-dir)))
(add-to-list 'load-path irony-dir)
(require 'irony)))
;; load ERT, fallback to a bundled version if not found in `load-path'
(unless (require 'ert nil t)
(let ((load-path (cons (expand-file-name "support" test-dir)
load-path)))
(require 'ert)))