diff --git a/.appveyor.yml b/.appveyor.yml index 0b89cb118..0fc36d3fe 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,49 +1,22 @@ version: "{build}" -image: Visual Studio 2017 -platform: x64 -clone_folder: C:\projects\cquery +os: Visual Studio 2017 -install: - - git submodule update --init - -environment: - CLICOLOR_FORCE: 1 - matrix: - - MSYSTEM: MINGW64 - - MSYSTEM: MSVC +platform: + - x64 -matrix: - fast_finish: true # set this flag to immediately finish build once one of the jobs fails. - allow_failures: - - platform: x64 +build: + parallel: true # enable MSBuild parallel builds + verbosity: minimal +install: + - if not exist llvm.tar.xz appveyor DownloadFile "https://ziglang.org/deps/llvm+clang-7.0.0-win64-msvc-release.tar.xz" -FileName llvm.tar.xz + - 7z e -txz llvm.tar.xz + - 7z x llvm.tar + - git submodule update --init --recursive build_script: - - ps: | - If ($Env:MSYSTEM -Eq "MSVC") { - $dir = "cquery-${env:appveyor_build_version}-win64" - cd C:\projects\cquery - python waf configure --msvc_version="msvc 15.0" - python waf build - mkdir "${dir}\build\release\bin" -ea 0 - mkdir "${dir}\build\release\lib\LLVM-6.0.0-win64\lib\clang\6.0.0\" - copy "build\release\bin\*" "${dir}\build\release\bin" - copy -recurse "build\LLVM-6.0.0-win64\lib\clang\6.0.0\include" "${dir}\build\release\lib\LLVM-6.0.0-win64\lib\clang\6.0.0\" - 7z a -tzip "C:\projects\cquery\${dir}.zip" "${dir}" - } Else { - C:\msys64\usr\bin\bash -lc @' - pacman -S --needed --noconfirm mingw-w64-x86_64-clang python - cd /c/projects/cquery - CXXFLAGS=-Wall /usr/bin/python waf configure build --llvm-config llvm-config 2>&1 - '@ - } - - set PATH=%PATH%;C:\msys64\%MSYSTEM%\bin - - build\release\bin\cquery --ci --log-all-to-stderr --test-unit - - IF "%MSYSTEM%"=="MSVC" build\release\bin\cquery --ci --log-all-to-stderr --test-index + - cmake -G"Visual Studio 15 2017 Win64" -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DSYSTEM_CLANG=ON -DCLANG_ROOT=C:\projects\ccls\llvm+clang-7.0.0-win64-msvc-release + - cmake --build build --target ccls --config Release artifacts: - - path: 'cquery-*.zip' - -cache: - - C:\projects\cquery\build\LLVM-6.0.0-win64.exe - - C:\projects\cquery\build\LLVM-6.0.0-win64\ + - path: build\Release diff --git a/.clang-format b/.clang-format index 3f19e6161..9b3aa8b72 100644 --- a/.clang-format +++ b/.clang-format @@ -1 +1 @@ -BasedOnStyle: Chromium +BasedOnStyle: LLVM diff --git a/.clang_complete b/.clang_complete deleted file mode 100644 index d0dac3366..000000000 --- a/.clang_complete +++ /dev/null @@ -1,6 +0,0 @@ --std=c++11 --Ithird_party/rapidjson/include --IC:/Program Files/LLVM/include --std=c++11 --fms-compatibility --fdelayed-template-parsing \ No newline at end of file diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index 5fa46b3e6..000000000 --- a/.gitattributes +++ /dev/null @@ -1,7 +0,0 @@ -# By default, use platform specific endings. -*.h text eol=auto -*.cpp text eol=auto -*.cc text eol=auto - -# Tests must always be crlf -index_tests/** text eol=crlf diff --git a/.gitignore b/.gitignore index 4ba98ad52..87cc1bce2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,5 @@ -.cquery_cached_index -.DS_Store -.lock-waf* -.vs -.vscode/ -.vscode/.ropeproject -.waf* -*.cquery -*.sln -*.swp -*.vcxproj -*.vcxproj.filters -*.vcxproj.user -**/*.pyc +.* build -cquery_diagnostics.log -cquery_log.txt -Debug -e2e_cache -foo -libcxx -vscode-extension.vsix -waf-* -waf2* -waf3* -x64 +debug +release +/compile_commands.json diff --git a/.gitmodules b/.gitmodules index e2c0733dd..ff65b1dbb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,15 +1,3 @@ [submodule "third_party/rapidjson"] path = third_party/rapidjson - url = https://github.com/miloyip/rapidjson -[submodule "third_party/doctest"] - path = third_party/doctest - url = https://github.com/onqtam/doctest -[submodule "third_party/sparsepp"] - path = third_party/sparsepp - url = https://github.com/greg7mdp/sparsepp -[submodule "third_party/loguru"] - path = third_party/loguru - url = https://github.com/emilk/loguru -[submodule "third_party/msgpack-c"] - path = third_party/msgpack-c - url = https://github.com/msgpack/msgpack-c + url = https://github.com/Tencent/rapidjson diff --git a/.pep8 b/.pep8 deleted file mode 100644 index 1fe66c12e..000000000 --- a/.pep8 +++ /dev/null @@ -1,2 +0,0 @@ -[pep8] -indent-size=2 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c9ac2e618..000000000 --- a/.travis.yml +++ /dev/null @@ -1,166 +0,0 @@ -dist: trusty -sudo: false -language: c++ - -env: - global: - - COMPILER=g++ - -# Default --recursive (rapidjson/thirdparty/gtest) is unnecessary -git: - submodules: false - depth: 1 - -before_install: - - git submodule update --init - -addons: - apt: - sources: &apt_sources - - ubuntu-toolchain-r-test - - llvm-toolchain-precise-3.5 - - llvm-toolchain-trusty-5.0 - -compiler: clang -os: linux - -cache: - directories: - - build/clang+llvm-6.0.0-x86_64-linux-gnu-ubuntu-14.04/ - - build/clang+llvm-6.0.0-x86_64-apple-darwin/ - -matrix: - fast_finish: true - include: - - env: COMPILER=g++-5 - compiler: gcc - addons: &gcc5 - apt: - packages: ["g++-5"] - sources: *apt_sources - - - env: COMPILER=g++-7 - compiler: gcc - addons: &gcc7 - apt: - packages: ["g++-7"] - sources: *apt_sources - - - env: COMPILER=clang++-3.5 - addons: &clang35 - apt: - packages: ["clang-3.5", "g++-7"] - sources: *apt_sources - - - env: COMPILER=clang++-5.0 - addons: &clang50 - apt: - packages: ["clang-5.0", "g++-7"] - sources: *apt_sources - - - env: COMPILER=clang++ - osx_image: xcode9.1 - os: osx - - - env: COMPILER=g++-7 - compiler: gcc - osx_image: xcode9.1 - os: osx - - - allow_failures: - - # macOS takes too long. - - #- env: COMPILER=clang++ - # osx_image: xcode9.1 - # os: osx - - #- env: COMPILER=g++-7 - # compiler: gcc - # osx_image: xcode9.1 - # os: osx - - # gcc builds that should be fixed at some point - - #- env: COMPILER=g++-5 - # compiler: gcc - - #- env: COMPILER=g++-6 - # compiler: gcc - - #- env: COMPILER=g++-7 - # compiler: gcc - - #- env: COMPILER=g++-5 - # compiler: gcc - # osx_image: xcode9.1 - # os: osx - - #- env: COMPILER=g++-6 - # compiler: gcc - # osx_image: xcode9.1 - # os: osx - - #- env: COMPILER=g++-7 - # compiler: gcc - # osx_image: xcode9.1 - # os: osx - -install: - - | - if [[ "${TRAVIS_OS_NAME}" == "osx" ]]; then - if [[ "${COMPILER}" == "g++-5" ]]; then - brew install gcc@5 - brew link --overwrite gcc@5 - fi - if [[ "${COMPILER}" == "g++-6" ]]; then - brew install gcc@6 - brew link --overwrite gcc@6 - fi - if [[ "${COMPILER}" == "g++-7" ]]; then - brew install gcc@7 - brew link --overwrite gcc@7 - fi - fi - - - | - if [[ "${COMPILER}" == g++* ]]; then - export J="-j1" - fi - - - export CXX="${COMPILER}" - -before_script: - - ${CXX} --version - -script: - - travis_retry ./waf configure - - ./waf build ${J} - - ./build/release/bin/cquery --ci --log-all-to-stderr --test-unit - - ./build/release/bin/cquery --ci --log-all-to-stderr --test-index - -notifications: - email: false - irc: - channels: - - "ircs://chat.freenode.net:6697/#cquery" - template: - - "[ %{repository_slug}#%{commit}] %{result} on %{branch} by %{author} (%{build_url} )" - -before_deploy: - #- zip -r build/cquery-$TRAVIS_TAG-$TRAVIS_OS_NAME.zip build/release/bin/ build/release/lib/clang+llvm-*/lib/libclang.* build/release/lib/clang+llvm-*/lib/clang/5.0.1/include/ - - ci/before_deploy.sh - -deploy: - provider: releases - api_key: - secure: Ahv4Wp1wveWILqp6HB8UmsXwwfZ103fuJV/u6W4oJFRpnbIXRCGFKaDR1Ql0hsHduKFd/76nNQGSVvNNuTXlWaK2n0bTu1EZ4VYmXk7Q7gn4ROP9XFrIZu0c9XKJ/bzehCLj3t6KT0R5MK5gQe+cBmx4S5uGsGG5/nM+GZpE1N4craRCh64UNXMvIx20sW4VQcgj1Ccrc/6Skb3HET7PKbY+IB/LXnaF3nM6V71LxKW2wlakBmzzaNatQ46QOcOCduY4edE8FqBs7yZ0eFktNZusmjiaZT12t0r1hVe0O8e0ER3u9/c3t+hbPUplMR2FAPBZXojgLVhSfFtBaj45T74oCIi0eUaDeS+Oxl6IzgyVho9RurOtaru3hLOVoaD9wR6lGhj6Nz/2Na3lOIorxHfAZ4OgUmluoFLCynO4ylMD03fMBGBshChnmYbrxLw0xLZP2005WUAj8JN64QOmFmLt3gV7TfVldSFHuwoZyESfkXPRM1Xn8RtgFi/89p4jtPtyBFLSaeDggCwfWEMfADCfJ/j8lXtAPdyEINoaKrxkH8qCPoMLNPXE7JhkP8L0Smdq4cFUEXg3wKWM2hXmWmh2Y25BAyh4qu9CrDPd5qqFcXMtyix4ZjmThLFs/oKYbbMUo4FQ5xT5dpt/VZOi4NpcAj0G/M3jWhu85tMdtTc= - #file: build/cquery-$TRAVIS_TAG-$TRAVIS_OS_NAME.zip - file: build/cquery-$TRAVIS_TAG-x86_64-apple-darwin.tar.xz - file: build/cquery-$TRAVIS_TAG-x86_64-unknown-linux-gnu.tar.xz - skip_cleanup: true - on: - repo: cquery-project/cquery - tags: true - diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py deleted file mode 100644 index 385f768c1..000000000 --- a/.ycm_extra_conf.py +++ /dev/null @@ -1,15 +0,0 @@ -def FlagsForFile( filename, **kwargs ): - return { - 'flags': [ - '-xc++', - '-std=c++11', - '-DLOGURU_WITH_STREAMS=1', - '-Isrc/', - '-Ithird_party/', - '-Ithird_party/doctest', - '-Ithird_party/rapidjson/include', - '-Ithird_party/sparsepp', - '-Ithird_party/loguru', - '-Ibuild/clang+llvm-4.0.0-x86_64-linux-gnu-ubuntu-14.04/include' - ] - } diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..ed4bb2bda --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,228 @@ +cmake_minimum_required(VERSION 3.1) +project(ccls LANGUAGES CXX) + +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) +include(DefaultCMakeBuildType) + +# Required Clang version +set(CLANG_DOWNLOAD_LOCATION ${CMAKE_BINARY_DIR} + CACHE STRING "Downloaded Clang location") +option(SYSTEM_CLANG "Use system installation of Clang instead of \ + downloading Clang" OFF) +option(ASAN "Compile with address sanitizers" OFF) +option(LLVM_ENABLE_RTTI "-fno-rtti if OFF. This should match LLVM libraries" OFF) +option(CLANG_USE_BUNDLED_LIBC++ "Let Clang use bundled libc++" OFF) +option(USE_SHARED_LLVM "Link against libLLVM.so instead separate LLVM{Option,Support,...}" OFF) + +# Sources for the executable are specified at end of CMakeLists.txt +add_executable(ccls "") + +### Compile options + +# CMake default compile flags: +# MSVC + Clang(Windows): +# debug: /MDd /Zi /Ob0 /Od /RTC1 +# release: /MD /O2 /Ob2 /DNDEBUG +# GCC + Clang(Linux): +# debug: -g +# release: -O3 -DNDEBUG + +# Enable C++17 (Required) +set_property(TARGET ccls PROPERTY CXX_STANDARD 17) +set_property(TARGET ccls PROPERTY CXX_STANDARD_REQUIRED ON) +# Disable gnu extensions except for Cygwin which needs them to build properly +if(NOT CYGWIN) + set_property(TARGET ccls PROPERTY CXX_EXTENSIONS OFF) +endif() + +if(NOT LLVM_ENABLE_RTTI) + # releases.llvm.org libraries are compiled with -fno-rtti + # The mismatch between lib{clang,LLVM}* and ccls can make libstdc++ std::make_shared return nullptr + # _Sp_counted_ptr_inplace::_M_get_deleter + target_compile_options(ccls PRIVATE -fno-rtti) +endif() + +# CMake sets MSVC for both MSVC and Clang(Windows) +if(MSVC) + # Common MSVC/Clang(Windows) options + target_compile_options(ccls PRIVATE + /nologo + /EHsc + /D_CRT_SECURE_NO_WARNINGS # don't try to use MSVC std replacements + /W3 # roughly -Wall + /wd4996 # ignore deprecated declaration + /wd4267 # ignores warning C4267 + # (conversion from 'size_t' to 'type'), + # roughly -Wno-sign-compare + /wd4800 + /wd4068 # Disable unknown pragma warning + /std:c++17 + $<$:/FS> + ) + # relink system libs + target_link_libraries(ccls PRIVATE Mincore.lib) +else() + # Common GCC/Clang(Linux) options + target_compile_options(ccls PRIVATE + -Wall + -Wno-sign-compare + ) + + if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU) + target_compile_options(ccls PRIVATE -Wno-return-type -Wno-unused-result) + endif() + + if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang) + target_compile_options(ccls PRIVATE + $<$:-fno-limit-debug-info>) + endif() + + if(ASAN) + target_compile_options(ccls PRIVATE -fsanitize=address,undefined) + # target_link_libraries also takes linker flags + target_link_libraries(ccls PRIVATE -fsanitize=address,undefined) + endif() +endif() + +### Download Clang if required + +if(NOT SYSTEM_CLANG) + message(STATUS "Using downloaded Clang") + + include(DownloadAndExtractClang) + download_and_extract_clang(${CLANG_DOWNLOAD_LOCATION}) + # Used by FindClang + set(CLANG_ROOT ${DOWNLOADED_CLANG_DIR}) + + if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang AND CLANG_USE_BUNDLED_LIBC++) + message(STATUS "Using bundled libc++") + target_compile_options(ccls PRIVATE -nostdinc++ -cxx-isystem ${CLANG_ROOT}/include/c++/v1) + if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) + # Don't use -stdlib=libc++ because while ccls is linked with libc++, bundled clang+llvm require libstdc++ + target_link_libraries(ccls PRIVATE -L${CLANG_ROOT}/lib c++ c++abi) + + # FreeBSD defaults to -stdlib=libc++ and uses system libcxxrt.a + endif() + endif() + +else() + message(STATUS "Using system Clang") +endif() + +### Libraries + +# See cmake/FindClang.cmake +find_package(Clang 6.0.0) +target_link_libraries(ccls PRIVATE Clang::Clang) + +# Enable threading support +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) +target_link_libraries(ccls PRIVATE Threads::Threads) + +if(${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD) + find_package(Backtrace REQUIRED) + target_link_libraries(ccls PRIVATE ${Backtrace_LIBRARIES}) + # src/platform_posix.cc uses libthr + target_link_libraries(ccls PRIVATE thr) +endif() + +### Definitions + +target_compile_definitions(ccls PRIVATE + DEFAULT_RESOURCE_DIRECTORY=R"\(${Clang_RESOURCE_DIR}\)") + +### Includes + +target_include_directories(ccls PRIVATE src) +target_include_directories(ccls SYSTEM PRIVATE + third_party + third_party/rapidjson/include) + +### Install + +install(TARGETS ccls RUNTIME DESTINATION bin) + +if(NOT SYSTEM_CLANG AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL Windows) + + if(${CMAKE_SYSTEM_NAME} MATCHES Linux|FreeBSD) + set_property(TARGET ccls APPEND PROPERTY + INSTALL_RPATH $ORIGIN/../lib) + elseif(${CMAKE_SYSTEM_NAME} STREQUAL Darwin) + set_property(TARGET ccls APPEND PROPERTY + INSTALL_RPATH @loader_path/../lib) + endif() + + file(GLOB LIBCLANG_PLUS_SYMLINKS + ${DOWNLOADED_CLANG_DIR}/lib/libclang.[so,dylib]*) + install(FILES ${LIBCLANG_PLUS_SYMLINKS} DESTINATION lib) +endif() + +# Allow running from build Windows by copying libclang.dll to build directory +if(NOT SYSTEM_CLANG AND ${CMAKE_SYSTEM_NAME} STREQUAL Windows) + add_custom_command(TARGET ccls + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${DOWNLOADED_CLANG_DIR}/bin/libclang.dll + $ + COMMENT "Copying libclang.dll to build directory ...") +endif() + +### Tools + +# We use glob here since source files are already manually added with +# target_sources further down +file(GLOB SOURCES src/*.cc src/*.h src/serializers/*.cc src/serializers/*.h + src/messages/*.h src/messages/*.cc) + +### Sources + +target_sources(ccls PRIVATE third_party/siphash.cc) + +target_sources(ccls PRIVATE + src/clang_tu.cc + src/config.cc + src/filesystem.cc + src/fuzzy_match.cc + src/main.cc + src/include_complete.cc + src/indexer.cc + src/log.cc + src/lsp.cc + src/message_handler.cc + src/pipeline.cc + src/platform_posix.cc + src/platform_win.cc + src/position.cc + src/project.cc + src/query.cc + src/sema_manager.cc + src/serializer.cc + src/test.cc + src/utils.cc + src/working_files.cc +) + +target_sources(ccls PRIVATE + src/messages/ccls_call.cc + src/messages/ccls_info.cc + src/messages/ccls_inheritance.cc + src/messages/ccls_member.cc + src/messages/ccls_navigate.cc + src/messages/ccls_reload.cc + src/messages/ccls_vars.cc + src/messages/ccls_dataFlowInto.cc + src/messages/initialize.cc + src/messages/textDocument_code.cc + src/messages/textDocument_completion.cc + src/messages/textDocument_definition.cc + src/messages/textDocument_did.cc + src/messages/textDocument_foldingRange.cc + src/messages/textDocument_formatting.cc + src/messages/textDocument_document.cc + src/messages/textDocument_hover.cc + src/messages/textDocument_references.cc + src/messages/textDocument_rename.cc + src/messages/textDocument_signatureHelp.cc + src/messages/workspace.cc +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 22648ec2e..5c3e251d5 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,47 @@ -# cquery +# ccls -[![Join the chat at https://gitter.im/cquery-project/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cquery-project/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Telegram](https://img.shields.io/badge/telegram-@cclsp-blue.svg)](https://telegram.me/cclsp) +[![Gitter](https://img.shields.io/badge/gitter-ccls--project-blue.svg?logo=gitter-white)](https://gitter.im/ccls-project/ccls) -cquery is a highly-scalable, low-latency language server for C/C++/Objective-C. It is tested -and designed for large code bases like -[Chromium](https://chromium.googlesource.com/chromium/src/). cquery provides -accurate and fast semantic analysis without interrupting workflow. - -![Demo](https://ptpb.pw/GlSQ.png?raw=true) - -cquery implements almost the entire language server protocol and provides -some extra features to boot: +ccls, which originates from [cquery](https://github.com/cquery-project/cquery), is a C/C++/Objective-C language server. * code completion (with both signature help and snippets) - * finding definition/references - * type hierarchy (parent type, derived types, expandable tree view) - * finding base/derived methods/classes, call tree - * symbol rename - * document and global symbol search - * hover tooltips showing symbol type - * diagnostics - * code actions (clang FixIts) - * darken/fade code disabled by preprocessor - * #include auto-complete, undefined type include insertion, include quick-jump - (goto definition, document links) - * auto-implement functions without a definition - * semantic highlighting, including support for [rainbow semantic highlighting](https://medium.com/@evnbr/coding-in-color-3a6db2743a1e) - -# >>> [Getting started](https://github.com/jacobdufault/cquery/wiki/Getting-started) (CLICK HERE) <<< - -# Limitations - -cquery is able to respond to queries quickly because it caches a huge amount of -information. When a request comes in, cquery just looks it up in the cache -without running many computations. As a result, there's a large memory overhead. -For example, a full index of Chrome will take about 10gb of memory. If you -exclude v8, webkit, and third_party, it goes down to about 6.5gb. - -# License - -MIT + * [definition](src/messages/textDocument_definition.cc)/[references](src/messages/textDocument_references.cc), and other cross references + * cross reference extensions: `$ccls/call` `$ccls/inheritance` `$ccls/member` `$ccls/vars` ... + * formatting + * hierarchies: [call (caller/callee) hierarchy](src/messages/ccls_call.cc), [inheritance (base/derived) hierarchy](src/messages/ccls_inheritance.cc), [member hierarchy](src/messages/ccls_member.cc) + * [symbol rename](src/messages/textDocument_rename.cc) + * [document symbols](src/messages/textDocument_documentSymbol.cc) and approximate search of [workspace symbol](src/messages/workspace_symbol.cc) + * [hover information](src/messages/textDocument_hover.cc) + * diagnostics and code actions (clang FixIts) + * semantic highlighting and preprocessor skipped regions + * semantic navigation: `$ccls/navigate` + +It has a global view of the code base and support a lot of cross reference features, see [wiki/FAQ](../../wiki/FAQ). +It starts indexing the whole project (including subprojects if exist) parallelly when you open the first file, while the main thread can serve requests before the indexing is complete. +Saving files will incrementally update the index. + +Compared with cquery, it makes use of C++17 features, has less third-party dependencies and slimmed-down code base. +It leverages Clang C++ API as [clangd](https://clang.llvm.org/extra/clangd.html) does, which provides better support for code completion and diagnostics. +Refactoring is a non-goal as it can be provided by clang-include-fixer and other Clang based tools. + +The comparison with cquery as noted on 2018-07-15: + +| | cquery | ccls | +|------------ |--------------------------------|------------------------------| +| third_party | more | fewer | +| C++ | C++14 | C++17 | +| clang API | libclang (C) | clang/llvm C++ | +| Filesystem | AbsolutePath + custom routines | llvm/Support | +| index | libclang | clangIndex, some enhancement | +| pipeline | index merge+id remapping | simpler and more robust | + +cquery has system include path detection (through running the compiler driver) while ccls uses clangDriver. + +# >>> [Getting started](../../wiki/Getting-started) (CLICK HERE) <<< + +* [Build](../../wiki/Build) +* [Client feature table](../../wiki/Client-feature-table) +* [FAQ](../../wiki/FAQ) + +ccls can index itself (~180MiB RSS when ide, noted on 2018-09-01), FreeBSD, glibc, Linux, LLVM (~1800MiB RSS), musl (~60MiB RSS), ... with decent memory footprint. See [wiki/compile_commands.json](../../wiki/compile_commands.json) for examples. diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh index 25fa14e05..e954d7f5c 100755 --- a/ci/before_deploy.sh +++ b/ci/before_deploy.sh @@ -7,15 +7,15 @@ case $(uname -s) in Darwin) libclang=(lib/clang+llvm-*/lib/libclang.dylib) strip_option="-x" - name=cquery-$version-x86_64-apple-darwin ;; + name=ccls-$version-x86_64-apple-darwin ;; FreeBSD) libclang=(lib/clang+llvm-*/lib/libclang.so.?) strip_option="-s" - name=cquery-$version-x86_64-unknown-freebsd10 ;; + name=ccls-$version-x86_64-unknown-freebsd10 ;; Linux) libclang=(lib/clang+llvm-*/lib/libclang.so.?) strip_option="-s" - name=cquery-$version-x86_64-unknown-linux-gnu ;; + name=ccls-$version-x86_64-unknown-linux-gnu ;; *) echo Unsupported >&2 exit 1 ;; @@ -26,7 +26,7 @@ mkdir "$pkg/$name" rsync -rtLR bin "./${libclang[-1]}" ./lib/clang+llvm-*/lib/clang/*/include "$pkg/$name" cd "$pkg" -strip "$strip_option" "$name/bin/cquery" "$name/${libclang[-1]}" +strip "$strip_option" "$name/bin/ccls" "$name/${libclang[-1]}" case $(uname -s) in Darwin) # https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/tar.1.html diff --git a/clang_archive_hashes/LLVM-7.0.0-win64.exe.SHA256 b/clang_archive_hashes/LLVM-7.0.0-win64.exe.SHA256 new file mode 100644 index 000000000..54bea9e6c --- /dev/null +++ b/clang_archive_hashes/LLVM-7.0.0-win64.exe.SHA256 @@ -0,0 +1 @@ +74b197a3959b0408adf0824be01db8dddfa2f9a967f4085af3fad900ed5fdbf6 \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-amd64-unknown-freebsd11.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-amd64-unknown-freebsd11.tar.xz.SHA256 new file mode 100644 index 000000000..b8a56bfa7 --- /dev/null +++ b/clang_archive_hashes/clang+llvm-7.0.0-amd64-unknown-freebsd11.tar.xz.SHA256 @@ -0,0 +1 @@ +95ceb933ccf76e3ddaa536f41ab82c442bbac07cdea6f9fbf6e3b13cc1711255 \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 new file mode 100644 index 000000000..86f733c96 --- /dev/null +++ b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz.SHA256 @@ -0,0 +1 @@ +b3ad93c3d69dfd528df9c5bb1a434367babb8f3baea47fbb99bf49f1b03c94ca \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 new file mode 100644 index 000000000..bc8306913 --- /dev/null +++ b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 @@ -0,0 +1 @@ +5c90e61b06d37270bc26edb305d7e498e2c7be22d99e0afd9f2274ef5458575a \ No newline at end of file diff --git a/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 new file mode 100644 index 000000000..1475a0a85 --- /dev/null +++ b/clang_archive_hashes/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz.SHA256 @@ -0,0 +1 @@ +69b85c833cd28ea04ce34002464f10a6ad9656dd2bba0f7133536a9927c660d2 \ No newline at end of file diff --git a/cmake/DefaultCMakeBuildType.cmake b/cmake/DefaultCMakeBuildType.cmake new file mode 100644 index 000000000..ae440bf54 --- /dev/null +++ b/cmake/DefaultCMakeBuildType.cmake @@ -0,0 +1,14 @@ +set(DEFAULT_CMAKE_BUILD_TYPE Release) + +# CMAKE_BUILD_TYPE is not available if a multi-configuration generator is used +# (eg Visual Studio generators) +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to '${DEFAULT_CMAKE_BUILD_TYPE}' as none \ +was specified.") + set(CMAKE_BUILD_TYPE ${DEFAULT_CMAKE_BUILD_TYPE} + CACHE STRING "Choose the type of build." FORCE) + + # Set the possible values of build type for cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE + PROPERTY STRINGS Debug Release MinSizeRel RelWithDebInfo) +endif() diff --git a/cmake/DownloadAndExtract7zip.cmake b/cmake/DownloadAndExtract7zip.cmake new file mode 100644 index 000000000..72bc879d5 --- /dev/null +++ b/cmake/DownloadAndExtract7zip.cmake @@ -0,0 +1,52 @@ +# Downloads and extracts the 7-Zip MSI installer from https://www.7-zip.org/. +# +# Returns the extracted 7-Zip directory in DOWNLOADED_7ZIP_DIR +function(download_and_extract_7zip 7ZIP_DOWNLOAD_LOCATION) + +set(7ZIP_VERSION 1801) +set(7ZIP_EXT .msi) +set(7ZIP_NAME 7z${7ZIP_VERSION}-x64) +set(7ZIP_FULL_NAME ${7ZIP_NAME}${7ZIP_EXT}) + +set(7ZIP_FILE ${7ZIP_DOWNLOAD_LOCATION}/${7ZIP_FULL_NAME}) +set(7ZIP_EXTRACT_DIR ${7ZIP_DOWNLOAD_LOCATION}/${7ZIP_NAME}) +set(7ZIP_URL https://www.7-zip.org/a/${7ZIP_FULL_NAME}) + +# Exit if 7-Zip is already downloaded and extracted +find_program(7ZIP_EXECUTABLE 7z NO_DEFAULT_PATH + PATHS ${7ZIP_EXTRACT_DIR}/Files/7-Zip) +if(7ZIP_EXECUTABLE) + message(STATUS "7-Zip already downloaded") + return() +endif() + +message(STATUS "Downloading 7-Zip ${7ZIP_VERSION} (${7ZIP_URL}) ...") +file(DOWNLOAD ${7ZIP_URL} ${7ZIP_FILE}) + +find_program(MSIEXEC_EXECUTABLE msiexec) +if(NOT MSIEXEC_EXECUTABLE) + message(FATAL_ERROR "Unable to find msiexec (required to extract 7-Zip msi \ +installer). Install 7-Zip yourself and make sure it is available in the path") +endif() + +message(STATUS "Extracting downloaded 7-Zip ...") + +# msiexec requires Windows path separators (\) +file(TO_NATIVE_PATH ${7ZIP_FILE} 7ZIP_FILE) +file(TO_NATIVE_PATH ${7ZIP_EXTRACT_DIR} 7ZIP_EXTRACT_DIR) + +# msiexec with /a option allows extraction of msi installers without requiring +# admin privileges. We use this to extract the 7-Zip installer without +# requiring any actions from the user +execute_process(COMMAND ${MSIEXEC_EXECUTABLE} /a ${7ZIP_FILE} /qn + TARGETDIR=${7ZIP_EXTRACT_DIR} + WORKING_DIRECTORY ${7ZIP_DOWNLOAD_LOCATION} + OUTPUT_QUIET) + +# Convert back to CMake separators (/) before returning +file(TO_CMAKE_PATH ${7ZIP_EXTRACT_DIR} 7ZIP_EXTRACT_DIR) + +# Actual 7-Zip directory is nested inside the extract directory. +set(DOWNLOADED_7ZIP_DIR ${7ZIP_EXTRACT_DIR}/Files/7-Zip PARENT_SCOPE) + +endfunction() \ No newline at end of file diff --git a/cmake/DownloadAndExtractClang.cmake b/cmake/DownloadAndExtractClang.cmake new file mode 100644 index 000000000..9365b1881 --- /dev/null +++ b/cmake/DownloadAndExtractClang.cmake @@ -0,0 +1,129 @@ +# Downloads and extracts the Clang archive for the current system from +# https://releases.llvm.org +# +# Returns the extracted Clang archive directory in DOWNLOADED_CLANG_DIR +# +# Downloads 7-Zip to extract Clang if it isn't available in the PATH +function(download_and_extract_clang CLANG_DOWNLOAD_LOCATION) + +set(CLANG_VERSION 7.0.0) +set(CLANG_ARCHIVE_EXT .tar.xz) + +if(${CMAKE_SYSTEM_NAME} STREQUAL Linux) + + # Default to Ubuntu 16.04 + set(CLANG_ARCHIVE_NAME + clang+llvm-${CLANG_VERSION}-x86_64-linux-gnu-ubuntu-16.04) + +elseif(${CMAKE_SYSTEM_NAME} STREQUAL Darwin) + + set(CLANG_ARCHIVE_NAME clang+llvm-${CLANG_VERSION}-x86_64-apple-darwin) + +elseif(${CMAKE_SYSTEM_NAME} STREQUAL Windows) + + set(CLANG_ARCHIVE_NAME LLVM-${CLANG_VERSION}-win64) + set(CLANG_ARCHIVE_EXT .exe) + +elseif(${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD) + + set(CLANG_ARCHIVE_NAME clang+llvm-${CLANG_VERSION}-amd64-unknown-freebsd11) + +endif() + +set(CLANG_ARCHIVE_FULL_NAME ${CLANG_ARCHIVE_NAME}${CLANG_ARCHIVE_EXT}) +set(CLANG_ARCHIVE_FILE ${CLANG_DOWNLOAD_LOCATION}/${CLANG_ARCHIVE_FULL_NAME}) +set(CLANG_ARCHIVE_EXTRACT_DIR ${CLANG_DOWNLOAD_LOCATION}/${CLANG_ARCHIVE_NAME}) +set(CLANG_ARCHIVE_URL + https://releases.llvm.org/${CLANG_VERSION}/${CLANG_ARCHIVE_FULL_NAME}) +set(CLANG_ARCHIVE_HASH_FILE + ${CMAKE_SOURCE_DIR}/clang_archive_hashes/${CLANG_ARCHIVE_FULL_NAME}.SHA256) + +# Exit if Clang is already downloaded and extracted +set(CLANG_ROOT ${CLANG_ARCHIVE_EXTRACT_DIR}) +find_package(Clang ${CLANG_VERSION} QUIET) +if(Clang_FOUND) + message(STATUS "Clang already downloaded") + set(DOWNLOADED_CLANG_DIR ${CLANG_ARCHIVE_EXTRACT_DIR} PARENT_SCOPE) + return() +endif() + +if(NOT CLANG_ARCHIVE_NAME) + message(FATAL_ERROR "No Clang archive url specified for current platform \ +(${CMAKE_SYSTEM_NAME}). Please file an issue to get it added.") +endif() + +if(NOT EXISTS ${CLANG_ARCHIVE_HASH_FILE}) + message(FATAL_ERROR "No SHA256 hash available for the current platform \ +(${CMAKE_SYSTEM_NAME}) + clang version (${CLANG_VERSION}) combination. Please \ +file an issue to get it added.") +endif() + +# Download Clang archive +message(STATUS "Downloading Clang ${CLANG_VERSION} (${CLANG_ARCHIVE_URL}) ...") +file(DOWNLOAD ${CLANG_ARCHIVE_URL} ${CLANG_ARCHIVE_FILE} + STATUS CLANG_ARCHIVE_DOWNLOAD_RESULT) + +# Abort if download failed +list(GET ${CLANG_ARCHIVE_DOWNLOAD_RESULT} 0 ERROR_CODE) +if(${ERROR_CODE}) + list(GET ${CLANG_ARCHIVE_DOWNLOAD_RESULT} 1 ERROR_STRING) + message(FATAL_ERROR ${ERROR_STRING}) +endif() + +# Retrieve expected hash from file and strip newline +file(READ ${CLANG_ARCHIVE_HASH_FILE} CLANG_ARCHIVE_EXPECTED_HASH) +string(STRIP ${CLANG_ARCHIVE_EXPECTED_HASH} CLANG_ARCHIVE_EXPECTED_HASH) +# Calculate actual hash +file(SHA256 ${CLANG_ARCHIVE_FILE} CLANG_ARCHIVE_HASH) +# Abort if hashes do not match +if(NOT ${CLANG_ARCHIVE_EXPECTED_HASH} STREQUAL ${CLANG_ARCHIVE_HASH}) + message(FATAL_ERROR "SHA256 hash of downloaded Clang does not match \ +expected hash. Remove the build directory and try running CMake again. If this \ +keeps happening, file an issue to report the problem.") +endif() + +if(${CLANG_ARCHIVE_EXT} STREQUAL .exe) + # Download and extract 7-zip if not found in PATH + find_program(7ZIP_EXECUTABLE 7z) + if(NOT 7ZIP_EXECUTABLE) + message(STATUS "7-Zip not found in PATH") + + include(DownloadAndExtract7zip) + download_and_extract_7zip(${CLANG_DOWNLOAD_LOCATION}) + find_program(7ZIP_EXECUTABLE + NAMES 7z + NO_DEFAULT_PATH + PATHS ${DOWNLOADED_7ZIP_DIR} + ) + else() + message(STATUS "7-Zip found in PATH") + endif() + + message(STATUS "Extracting downloaded Clang with 7-Zip ...") + + # Avoid running the Clang installer by extracting the exe with 7-Zip + execute_process( + COMMAND ${7ZIP_EXECUTABLE} x + -o${CLANG_ARCHIVE_EXTRACT_DIR} + -xr!$PLUGINSDIR ${CLANG_ARCHIVE_FILE} + WORKING_DIRECTORY ${CLANG_DOWNLOAD_LOCATION} + OUTPUT_QUIET + ) + +elseif(${CLANG_ARCHIVE_EXT} STREQUAL .tar.xz) + message(STATUS "Extracting downloaded Clang with CMake built-in tar ...") + + # CMake has builtin support for tar via the -E flag + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar -xf ${CLANG_ARCHIVE_FILE} + # Specify working directory to allow running cmake from + # everywhere + # (example: cmake -H"$HOME/cquery" -B"$home/cquery/build") + WORKING_DIRECTORY ${CLANG_DOWNLOAD_LOCATION} + OUTPUT_QUIET + ) +endif() + +set(DOWNLOADED_CLANG_DIR ${CLANG_ARCHIVE_EXTRACT_DIR} PARENT_SCOPE) + +endfunction() diff --git a/cmake/FindClang.cmake b/cmake/FindClang.cmake new file mode 100644 index 000000000..194029005 --- /dev/null +++ b/cmake/FindClang.cmake @@ -0,0 +1,154 @@ +#.rst +# FindClang +# --------- +# +# Find Clang and LLVM libraries required by ccls +# +# Results are reported in the following variables:: +# +# Clang_FOUND - True if headers and requested libraries were found +# Clang_EXECUTABLE - Clang executable +# Clang_RESOURCE_DIR - Clang resource directory +# Clang_VERSION - Clang version as reported by Clang executable +# +# The following :prop_tgt:`IMPORTED` targets are also defined:: +# +# Clang::Clang - Target for all required Clang libraries and headers +# +# This module reads hints about which libraries to look for and where to find +# them from the following variables:: +# +# CLANG_ROOT - If set, only look for Clang components in CLANG_ROOT +# +# Example to link against Clang target:: +# +# target_link_libraries( PRIVATE Clang::Clang) + +### Definitions + +# Wrapper macro's around the find_* macro's from CMake that only search in +# CLANG_ROOT if it is defined + +macro(_Clang_find_library VAR NAME) + # Windows needs lib prefix + if (CLANG_ROOT) + find_library(${VAR} NAMES ${NAME} lib${NAME} + NO_DEFAULT_PATH PATHS ${CLANG_ROOT} PATH_SUFFIXES lib) + else() + find_library(${VAR} NAMES ${NAME} lib${NAME}) + endif() +endmacro() + +macro(_Clang_find_add_library NAME) + _Clang_find_library(${NAME}_LIBRARY ${NAME}) + list(APPEND _Clang_LIBRARIES ${${NAME}_LIBRARY}) +endmacro() + +macro(_Clang_find_path VAR INCLUDE_FILE) + if (CLANG_ROOT) + find_path(${VAR} ${INCLUDE_FILE} + NO_DEFAULT_PATH PATHS ${CLANG_ROOT} PATH_SUFFIXES include) + else() + find_path(${VAR} ${INCLUDE_FILE}) + endif() +endmacro() + +macro(_Clang_find_program VAR NAME) + if (CLANG_ROOT) + find_program(${VAR} ${NAME} + NO_DEFAULT_PATH PATHS ${CLANG_ROOT} PATH_SUFFIXES bin) + else() + find_program(${VAR} ${NAME}) + endif() +endmacro() + +### Start + +set(_Clang_REQUIRED_VARS Clang_LIBRARY Clang_INCLUDE_DIR Clang_EXECUTABLE + Clang_RESOURCE_DIR Clang_VERSION + LLVM_INCLUDE_DIR LLVM_BUILD_INCLUDE_DIR) + +_Clang_find_library(Clang_LIBRARY clangIndex) +_Clang_find_add_library(clangFormat) +_Clang_find_add_library(clangTooling) + +# TODO Remove after dropping clang 6 +_Clang_find_library(clangToolingInclusions_LIBRARY clangToolingInclusions) +if (clangToolingInclusions_LIBRARY) + list(APPEND _Clang_LIBRARIES ${clangToolingInclusions_LIBRARY}) +endif() + +_Clang_find_add_library(clangToolingCore) +_Clang_find_add_library(clangRewrite) +_Clang_find_add_library(clangFrontend) +_Clang_find_add_library(clangParse) +_Clang_find_add_library(clangSerialization) +_Clang_find_add_library(clangSema) +_Clang_find_add_library(clangAnalysis) +_Clang_find_add_library(clangEdit) +_Clang_find_add_library(clangAST) +_Clang_find_add_library(clangLex) +_Clang_find_add_library(clangDriver) +_Clang_find_add_library(clangBasic) +if(USE_SHARED_LLVM) + _Clang_find_add_library(LLVM) +else() + _Clang_find_add_library(LLVMMCParser) + _Clang_find_add_library(LLVMMC) + _Clang_find_add_library(LLVMBitReader) + _Clang_find_add_library(LLVMOption) + _Clang_find_add_library(LLVMProfileData) + _Clang_find_add_library(LLVMCore) + _Clang_find_add_library(LLVMBinaryFormat) + _Clang_find_add_library(LLVMSupport) + _Clang_find_add_library(LLVMDemangle) +endif() +_Clang_find_path(Clang_INCLUDE_DIR clang-c/Index.h) +_Clang_find_path(Clang_BUILD_INCLUDE_DIR clang/Driver/Options.inc) +_Clang_find_path(LLVM_INCLUDE_DIR llvm/PassInfo.h) +_Clang_find_path(LLVM_BUILD_INCLUDE_DIR llvm/Config/llvm-config.h) + +_Clang_find_program(Clang_EXECUTABLE clang) +if(Clang_EXECUTABLE) + # Find Clang resource directory with Clang executable + execute_process(COMMAND ${Clang_EXECUTABLE} -print-resource-dir + RESULT_VARIABLE _Clang_FIND_RESOURCE_DIR_RESULT + OUTPUT_VARIABLE Clang_RESOURCE_DIR + ERROR_VARIABLE _Clang_FIND_RESOURCE_DIR_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(_Clang_FIND_RESOURCE_DIR_RESULT) + message(FATAL_ERROR "Error retrieving Clang resource directory with Clang \ +executable. Output:\n ${_Clang_FIND_RESOURCE_DIR_ERROR}") + endif() + + # Find Clang version + set(_Clang_VERSION_REGEX "([0-9]+)\\.([0-9]+)\\.([0-9]+)") + execute_process( + COMMAND ${Clang_EXECUTABLE} --version + OUTPUT_VARIABLE Clang_VERSION + ) + string(REGEX MATCH ${_Clang_VERSION_REGEX} Clang_VERSION ${Clang_VERSION}) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Clang + FOUND_VAR Clang_FOUND + REQUIRED_VARS ${_Clang_REQUIRED_VARS} + VERSION_VAR Clang_VERSION +) + +if(Clang_FOUND AND NOT TARGET Clang::Clang) + add_library(Clang::Clang UNKNOWN IMPORTED) + set_target_properties(Clang::Clang PROPERTIES + IMPORTED_LOCATION ${Clang_LIBRARY} + INTERFACE_INCLUDE_DIRECTORIES "${Clang_INCLUDE_DIR};${Clang_BUILD_INCLUDE_DIR};${LLVM_INCLUDE_DIR};${LLVM_BUILD_INCLUDE_DIR}") + if(NOT MSVC) + find_package(Curses REQUIRED) + find_package(ZLIB REQUIRED) + endif() + set_property(TARGET Clang::Clang PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES "${_Clang_LIBRARIES};${CURSES_LIBRARIES};${ZLIB_LIBRARIES}") + if(MINGW) + set_property(TARGET Clang::Clang APPEND_STRING PROPERTY IMPORTED_LINK_INTERFACE_LIBRARIES ";version") + endif() +endif() diff --git a/compile_commands.json b/compile_commands.json deleted file mode 120000 index 4595a4b07..000000000 --- a/compile_commands.json +++ /dev/null @@ -1 +0,0 @@ -build/release/compile_commands.json \ No newline at end of file diff --git a/index_tests/_empty_test.cc b/index_tests/_empty_test.cc new file mode 100644 index 000000000..228b26748 --- /dev/null +++ b/index_tests/_empty_test.cc @@ -0,0 +1,4 @@ +/* +OUTPUT: +{} +*/ diff --git a/index_tests/class_forward_declaration.cc b/index_tests/class_forward_declaration.cc new file mode 100644 index 000000000..7c5e23a23 --- /dev/null +++ b/index_tests/class_forward_declaration.cc @@ -0,0 +1,32 @@ +class Foo; +class Foo; +class Foo {}; +class Foo; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "3:7-3:10|3:1-3:13|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": ["1:7-1:10|1:1-1:10|1|-1", "2:7-2:10|2:1-2:10|1|-1", "4:7-4:10|4:1-4:10|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/constructors/constructor.cc b/index_tests/constructors/constructor.cc new file mode 100644 index 000000000..7901e0fb2 --- /dev/null +++ b/index_tests/constructors/constructor.cc @@ -0,0 +1,92 @@ +class Foo { +public: + Foo() {} +}; + +void foo() { + Foo f; + Foo* f2 = new Foo(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3385168158331140247, + "detailed_name": "Foo::Foo()", + "qual_name_offset": 0, + "short_name": "Foo", + "spell": "3:3-3:6|3:3-3:11|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["7:7-7:8|16676|-1", "8:17-8:20|16676|-1"] + }, { + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "6:6-6:9|6:1-9:2|2|-1", + "bases": [], + "vars": [10983126130596230582, 17165811951126099095], + "callees": ["7:7-7:8|3385168158331140247|3|16676", "7:7-7:8|3385168158331140247|3|16676", "8:17-8:20|3385168158331140247|3|16676", "8:17-8:20|3385168158331140247|3|16676"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-4:2|2|-1", + "bases": [], + "funcs": [3385168158331140247], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [10983126130596230582, 17165811951126099095], + "uses": ["3:3-3:6|4|-1", "7:3-7:6|4|-1", "8:3-8:6|4|-1", "8:17-8:20|4|-1"] + }], + "usr2var": [{ + "usr": 10983126130596230582, + "detailed_name": "Foo f", + "qual_name_offset": 4, + "short_name": "f", + "spell": "7:7-7:8|7:3-7:8|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 17165811951126099095, + "detailed_name": "Foo *f2", + "qual_name_offset": 5, + "short_name": "f2", + "hover": "Foo *f2 = new Foo()", + "spell": "8:8-8:10|8:3-8:22|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/constructors/destructor.cc b/index_tests/constructors/destructor.cc new file mode 100644 index 000000000..c8ad82b71 --- /dev/null +++ b/index_tests/constructors/destructor.cc @@ -0,0 +1,99 @@ +class Foo { +public: + Foo() {} + ~Foo() {}; +}; + +void foo() { + Foo f; +} + +// TODO: Support destructors (notice how the dtor has no usages listed). +// - check if variable is a pointer. if so, do *not* insert dtor +// - check if variable is normal type. if so, insert dtor +// - scan for statements that look like dtors in function def handler +// - figure out some way to support w/ unique_ptrs? +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3385168158331140247, + "detailed_name": "Foo::Foo()", + "qual_name_offset": 0, + "short_name": "Foo", + "spell": "3:3-3:6|3:3-3:11|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["8:7-8:8|16676|-1"] + }, { + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "7:6-7:9|7:1-9:2|2|-1", + "bases": [], + "vars": [1893354193220338759], + "callees": ["8:7-8:8|3385168158331140247|3|16676", "8:7-8:8|3385168158331140247|3|16676"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 7440261702884428359, + "detailed_name": "Foo::~Foo() noexcept", + "qual_name_offset": 0, + "short_name": "~Foo", + "spell": "4:3-4:7|4:3-4:12|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-5:2|2|-1", + "bases": [], + "funcs": [3385168158331140247, 7440261702884428359], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [1893354193220338759], + "uses": ["3:3-3:6|4|-1", "4:4-4:7|4|-1", "8:3-8:6|4|-1"] + }], + "usr2var": [{ + "usr": 1893354193220338759, + "detailed_name": "Foo f", + "qual_name_offset": 4, + "short_name": "f", + "spell": "8:7-8:8|8:3-8:8|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/constructors/implicit_constructor.cc b/index_tests/constructors/implicit_constructor.cc new file mode 100644 index 000000000..31c7c8ce9 --- /dev/null +++ b/index_tests/constructors/implicit_constructor.cc @@ -0,0 +1,91 @@ +struct Type { + Type() {} +}; + +void Make() { + Type foo0; + auto foo1 = Type(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3957104924306079513, + "detailed_name": "void Make()", + "qual_name_offset": 5, + "short_name": "Make", + "spell": "5:6-5:10|5:1-8:2|2|-1", + "bases": [], + "vars": [449111627548814328, 17097499197730163115], + "callees": ["6:8-6:12|10530961286677896857|3|16676", "6:8-6:12|10530961286677896857|3|16676", "7:15-7:19|10530961286677896857|3|16676", "7:15-7:19|10530961286677896857|3|16676"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 10530961286677896857, + "detailed_name": "Type::Type()", + "qual_name_offset": 0, + "short_name": "Type", + "spell": "2:3-2:7|2:3-2:12|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["6:8-6:12|16676|-1", "7:15-7:19|16676|-1"] + }], + "usr2type": [{ + "usr": 13487927231218873822, + "detailed_name": "struct Type {}", + "qual_name_offset": 7, + "short_name": "Type", + "spell": "1:8-1:12|1:1-3:2|2|-1", + "bases": [], + "funcs": [10530961286677896857], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [449111627548814328, 17097499197730163115], + "uses": ["2:3-2:7|4|-1", "6:3-6:7|4|-1", "7:15-7:19|4|-1"] + }], + "usr2var": [{ + "usr": 449111627548814328, + "detailed_name": "Type foo0", + "qual_name_offset": 5, + "short_name": "foo0", + "spell": "6:8-6:12|6:3-6:12|2|-1", + "type": 13487927231218873822, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 17097499197730163115, + "detailed_name": "Type foo1", + "qual_name_offset": 5, + "short_name": "foo1", + "hover": "Type foo1 = Type()", + "spell": "7:8-7:12|7:3-7:21|2|-1", + "type": 13487927231218873822, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/constructors/invalid_reference.cc b/index_tests/constructors/invalid_reference.cc new file mode 100644 index 000000000..3b960a163 --- /dev/null +++ b/index_tests/constructors/invalid_reference.cc @@ -0,0 +1,51 @@ +struct Foo {}; + +template +Foo::Foo() {} + +/* +EXTRA_FLAGS: +-fms-compatibility +-fdelayed-template-parsing + +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 17319723337446061757, + "detailed_name": "Foo::Foo::Foo()", + "qual_name_offset": 0, + "short_name": "Foo", + "spell": "4:6-4:9|4:1-4:11|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "1:8-1:11|1:1-1:14|2|-1", + "bases": [], + "funcs": [17319723337446061757], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["4:1-4:4|4|-1", "4:6-4:9|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/constructors/make_functions.cc b/index_tests/constructors/make_functions.cc new file mode 100644 index 000000000..5bc5004b4 --- /dev/null +++ b/index_tests/constructors/make_functions.cc @@ -0,0 +1,351 @@ +#include "make_functions.h" + +template +T* MakeUnique(Args&&... args) { + return nullptr; +} + +template +T* maKE_NoRefs(Args... args) { + return nullptr; +} + +void caller22() { + MakeUnique(); + MakeUnique(1); + MakeUnique(1, new Bar(), nullptr); + maKE_NoRefs(1, new Bar(), nullptr); +} + +// TODO: Eliminate the extra entries in the "types" array here. They come from +// the template function definitions. + +// Foobar is defined in a separate file to ensure that we can attribute +// MakeUnique calls across translation units. + +/* +OUTPUT: make_functions.h +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3765833212244435302, + "detailed_name": "Foobar::Foobar(int &&, Bar *, bool *)", + "qual_name_offset": 0, + "short_name": "Foobar", + "spell": "7:3-7:9|7:3-7:32|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 13028995015627606181, + "detailed_name": "Foobar::Foobar(int)", + "qual_name_offset": 0, + "short_name": "Foobar", + "spell": "6:3-6:9|6:3-6:17|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 13131778807733950299, + "detailed_name": "Foobar::Foobar()", + "qual_name_offset": 0, + "short_name": "Foobar", + "spell": "5:3-5:9|5:3-5:14|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 17321436359755983845, + "detailed_name": "Foobar::Foobar(int, Bar *, bool *)", + "qual_name_offset": 0, + "short_name": "Foobar", + "spell": "8:3-8:9|8:3-8:30|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 12993848456528750350, + "detailed_name": "struct Bar {}", + "qual_name_offset": 7, + "short_name": "Bar", + "spell": "1:8-1:11|1:1-1:14|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["7:17-7:20|4|-1", "8:15-8:18|4|-1"] + }, { + "usr": 14935975554338052500, + "detailed_name": "class Foobar {}", + "qual_name_offset": 6, + "short_name": "Foobar", + "spell": "3:7-3:13|3:1-9:2|2|-1", + "bases": [], + "funcs": [13131778807733950299, 13028995015627606181, 3765833212244435302, 17321436359755983845], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["5:3-5:9|4|-1", "6:3-6:9|4|-1", "7:3-7:9|4|-1", "8:3-8:9|4|-1"] + }], + "usr2var": [] +} +OUTPUT: make_functions.cc +{ + "includes": [{ + "line": 0, + "resolved_path": "&make_functions.h" + }], + "skipped_ranges": [], + "usr2func": [{ + "usr": 768523651983844320, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "vars": [2555873744476712860, 2555873744476712860, 2555873744476712860], + "callees": [], + "kind": 0, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 2532818908869373467, + "detailed_name": "T *maKE_NoRefs(Args ...args)", + "qual_name_offset": 3, + "short_name": "maKE_NoRefs", + "spell": "9:4-9:15|9:1-11:2|2|-1", + "bases": [], + "vars": [3908732770590594660], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["17:3-17:14|16420|-1"] + }, { + "usr": 2816883305867289955, + "detailed_name": "void caller22()", + "qual_name_offset": 5, + "short_name": "caller22", + "spell": "13:6-13:14|13:1-18:2|2|-1", + "bases": [], + "vars": [], + "callees": ["14:3-14:13|15793662558620604611|3|16420", "15:3-15:13|15793662558620604611|3|16420", "16:3-16:13|15793662558620604611|3|16420", "17:3-17:14|2532818908869373467|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 11138976705878544996, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "vars": [16395392342608151399], + "callees": [], + "kind": 0, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 11363675606380070883, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "vars": [180270746871803062, 180270746871803062, 180270746871803062], + "callees": [], + "kind": 0, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 15793662558620604611, + "detailed_name": "T *MakeUnique(Args &&...args)", + "qual_name_offset": 3, + "short_name": "MakeUnique", + "spell": "4:4-4:14|4:1-6:2|2|-1", + "bases": [], + "vars": [8463700030555379526], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["14:3-14:13|16420|-1", "15:3-15:13|16420|-1", "16:3-16:13|16420|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [180270746871803062], + "uses": [] + }, { + "usr": 87, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [180270746871803062], + "uses": [] + }, { + "usr": 12993848456528750350, + "detailed_name": "struct Bar {}", + "qual_name_offset": 7, + "short_name": "Bar", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["16:29-16:32|4|-1", "17:30-17:33|4|-1"] + }, { + "usr": 14935975554338052500, + "detailed_name": "class Foobar {}", + "qual_name_offset": 6, + "short_name": "Foobar", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["14:14-14:20|4|-1", "15:14-15:20|4|-1", "16:14-16:20|4|-1", "17:15-17:21|4|-1"] + }], + "usr2var": [{ + "usr": 180270746871803062, + "detailed_name": "int args", + "qual_name_offset": 4, + "short_name": "args", + "spell": "9:24-9:28|9:16-9:28|1026|-1", + "type": 87, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 2555873744476712860, + "detailed_name": "int &&args", + "qual_name_offset": 6, + "short_name": "args", + "spell": "4:25-4:29|4:15-4:29|1026|-1", + "type": 0, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 3908732770590594660, + "detailed_name": "Args ...args", + "qual_name_offset": 8, + "short_name": "args", + "spell": "9:24-9:28|9:16-9:28|1026|-1", + "type": 0, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 8463700030555379526, + "detailed_name": "Args &&...args", + "qual_name_offset": 10, + "short_name": "args", + "spell": "4:25-4:29|4:15-4:29|1026|-1", + "type": 0, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16395392342608151399, + "detailed_name": "int &&args", + "qual_name_offset": 6, + "short_name": "args", + "spell": "4:25-4:29|4:15-4:29|1026|-1", + "type": 0, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/constructors/make_functions.h b/index_tests/constructors/make_functions.h new file mode 100644 index 000000000..f119cfb8e --- /dev/null +++ b/index_tests/constructors/make_functions.h @@ -0,0 +1,10 @@ +struct Bar {}; + +class Foobar { + public: + Foobar() {} + Foobar(int) {} + Foobar(int&&, Bar*, bool*) {} + Foobar(int, Bar*, bool*) {} +}; + diff --git a/index_tests/declaration_vs_definition/class.cc b/index_tests/declaration_vs_definition/class.cc new file mode 100644 index 000000000..ee90a78bf --- /dev/null +++ b/index_tests/declaration_vs_definition/class.cc @@ -0,0 +1,34 @@ +class Foo; +class Foo; +class Foo {}; +class Foo; + +/* +// NOTE: Separate decl/definition are not supported for classes. See source +// for comments. +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "3:7-3:10|3:1-3:13|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": ["1:7-1:10|1:1-1:10|1|-1", "2:7-2:10|2:1-2:10|1|-1", "4:7-4:10|4:1-4:10|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/declaration_vs_definition/class_member.cc b/index_tests/declaration_vs_definition/class_member.cc new file mode 100644 index 000000000..0099e070e --- /dev/null +++ b/index_tests/declaration_vs_definition/class_member.cc @@ -0,0 +1,62 @@ +class Foo { + int foo; +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [9736582033442720743], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 9736582033442720743, + "R": 0 + }], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 9736582033442720743, + "detailed_name": "int Foo::foo", + "qual_name_offset": 4, + "short_name": "foo", + "spell": "2:7-2:10|2:3-2:10|1026|-1", + "type": 53, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/declaration_vs_definition/class_member_static.cc b/index_tests/declaration_vs_definition/class_member_static.cc new file mode 100644 index 000000000..6e7951e91 --- /dev/null +++ b/index_tests/declaration_vs_definition/class_member_static.cc @@ -0,0 +1,61 @@ +class Foo { + static int foo; +}; + +int Foo::foo; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [8942920329766232482, 8942920329766232482], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["5:5-5:8|4|-1"] + }], + "usr2var": [{ + "usr": 8942920329766232482, + "detailed_name": "static int Foo::foo", + "qual_name_offset": 11, + "short_name": "foo", + "spell": "5:10-5:13|5:1-5:13|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 5, + "storage": 2, + "declarations": ["2:14-2:17|2:3-2:17|1025|-1"], + "uses": [] + }] +} +*/ diff --git a/index_tests/declaration_vs_definition/func.cc b/index_tests/declaration_vs_definition/func.cc new file mode 100644 index 000000000..f1ad37498 --- /dev/null +++ b/index_tests/declaration_vs_definition/func.cc @@ -0,0 +1,31 @@ +void foo(); +void foo(); +void foo() {} +void foo(); + +/* +// Note: we always use the latest seen ("most local") definition/declaration. +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:6-3:9|3:1-3:14|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:6-1:9|1:1-1:11|1|-1", "2:6-2:9|2:1-2:11|1|-1", "4:6-4:9|4:1-4:11|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/declaration_vs_definition/func_associated_function_params.cc b/index_tests/declaration_vs_definition/func_associated_function_params.cc new file mode 100644 index 000000000..fbf17f3a8 --- /dev/null +++ b/index_tests/declaration_vs_definition/func_associated_function_params.cc @@ -0,0 +1,71 @@ +int foo(int, int); +int foo(int aa, + int bb); +int foo(int aaa, int bbb); +int foo(int a, int b) { return 0; } + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 2747674671862363334, + "detailed_name": "int foo(int, int)", + "qual_name_offset": 4, + "short_name": "foo", + "spell": "5:5-5:8|5:1-5:36|2|-1", + "bases": [], + "vars": [14555488990109936920, 10963664335057337329], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:5-1:8|1:1-1:18|1|-1", "2:5-2:8|2:1-3:16|1|-1", "4:5-4:8|4:1-4:26|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [14555488990109936920, 10963664335057337329], + "uses": [] + }], + "usr2var": [{ + "usr": 10963664335057337329, + "detailed_name": "int b", + "qual_name_offset": 4, + "short_name": "b", + "spell": "5:20-5:21|5:16-5:21|1026|-1", + "type": 53, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 14555488990109936920, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "5:13-5:14|5:9-5:14|1026|-1", + "type": 53, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/declaration_vs_definition/method.cc b/index_tests/declaration_vs_definition/method.cc new file mode 100644 index 000000000..ef280fdbd --- /dev/null +++ b/index_tests/declaration_vs_definition/method.cc @@ -0,0 +1,78 @@ +class Foo { + void declonly(); + virtual void purevirtual() = 0; + void def(); +}; + +void Foo::def() {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4012226004228259562, + "detailed_name": "void Foo::declonly()", + "qual_name_offset": 5, + "short_name": "declonly", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:8-2:16|2:3-2:18|1025|-1"], + "derived": [], + "uses": [] + }, { + "usr": 10939323144126021546, + "detailed_name": "virtual void Foo::purevirtual() = 0", + "qual_name_offset": 13, + "short_name": "purevirtual", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:16-3:27|3:3-3:33|1089|-1"], + "derived": [], + "uses": [] + }, { + "usr": 15416083548883122431, + "detailed_name": "void Foo::def()", + "qual_name_offset": 5, + "short_name": "def", + "spell": "7:11-7:14|7:1-7:19|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["4:8-4:11|4:3-4:13|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-5:2|2|-1", + "bases": [], + "funcs": [4012226004228259562, 10939323144126021546, 15416083548883122431], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["7:6-7:9|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/enums/enum_class_decl.cc b/index_tests/enums/enum_class_decl.cc new file mode 100644 index 000000000..f1b8c10d3 --- /dev/null +++ b/index_tests/enums/enum_class_decl.cc @@ -0,0 +1,81 @@ +typedef unsigned char uint8_t; +enum class Foo : uint8_t { + A, + B = 20 +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 2010430204259339553, + "detailed_name": "typedef unsigned char uint8_t", + "qual_name_offset": 22, + "short_name": "uint8_t", + "spell": "1:23-1:30|1:1-1:30|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 16985894625255407295, + "detailed_name": "enum class Foo : uint8_t {}", + "qual_name_offset": 11, + "short_name": "Foo", + "spell": "2:12-2:15|2:1-5:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 439339022761937396, + "R": -1 + }, { + "L": 15962370213938840720, + "R": -1 + }], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 439339022761937396, + "detailed_name": "Foo::A", + "qual_name_offset": 0, + "short_name": "A", + "hover": "Foo::A = 0", + "spell": "3:3-3:4|3:3-3:4|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 10, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15962370213938840720, + "detailed_name": "Foo::B = 20", + "qual_name_offset": 0, + "short_name": "B", + "spell": "4:3-4:4|4:3-4:9|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 10, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/enums/enum_decl.cc b/index_tests/enums/enum_decl.cc new file mode 100644 index 000000000..fd84c0b6a --- /dev/null +++ b/index_tests/enums/enum_decl.cc @@ -0,0 +1,57 @@ +enum Foo { + A, + B = 20 +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 16985894625255407295, + "detailed_name": "enum Foo {}", + "qual_name_offset": 5, + "short_name": "Foo", + "spell": "1:6-1:9|1:1-4:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 439339022761937396, + "detailed_name": "A", + "qual_name_offset": 0, + "short_name": "A", + "hover": "A = 0", + "spell": "2:3-2:4|2:3-2:4|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15962370213938840720, + "detailed_name": "B = 20", + "qual_name_offset": 0, + "short_name": "B", + "spell": "3:3-3:4|3:3-3:9|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/enums/enum_inherit.cc b/index_tests/enums/enum_inherit.cc new file mode 100644 index 000000000..9459dcfec --- /dev/null +++ b/index_tests/enums/enum_inherit.cc @@ -0,0 +1,129 @@ +enum Foo : int { + A, + B = 20 +}; + +typedef int int32_t; + +enum class E : int32_t { + E0, + E20 = 20 +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 2986879766914123941, + "detailed_name": "enum class E : int32_t {}", + "qual_name_offset": 11, + "short_name": "E", + "spell": "8:12-8:13|8:1-11:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 16614320383091394267, + "R": -1 + }, { + "L": 16847439761518576294, + "R": -1 + }], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 14939241684006947339, + "detailed_name": "typedef int int32_t", + "qual_name_offset": 12, + "short_name": "int32_t", + "spell": "6:13-6:20|6:1-6:20|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 16985894625255407295, + "detailed_name": "enum Foo : int {}", + "qual_name_offset": 5, + "short_name": "Foo", + "spell": "1:6-1:9|1:1-4:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 439339022761937396, + "detailed_name": "A", + "qual_name_offset": 0, + "short_name": "A", + "hover": "A = 0", + "spell": "2:3-2:4|2:3-2:4|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15962370213938840720, + "detailed_name": "B = 20", + "qual_name_offset": 0, + "short_name": "B", + "spell": "3:3-3:4|3:3-3:9|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16614320383091394267, + "detailed_name": "E::E0", + "qual_name_offset": 0, + "short_name": "E0", + "hover": "E::E0 = 0", + "spell": "9:3-9:5|9:3-9:5|1026|-1", + "type": 2986879766914123941, + "kind": 22, + "parent_kind": 10, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16847439761518576294, + "detailed_name": "E::E20 = 20", + "qual_name_offset": 0, + "short_name": "E20", + "spell": "10:3-10:6|10:3-10:11|1026|-1", + "type": 2986879766914123941, + "kind": 22, + "parent_kind": 10, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/enums/enum_usage.cc b/index_tests/enums/enum_usage.cc new file mode 100644 index 000000000..c3a9a6416 --- /dev/null +++ b/index_tests/enums/enum_usage.cc @@ -0,0 +1,78 @@ +enum class Foo { + A, + B = 20 +}; + +Foo x = Foo::A; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 16985894625255407295, + "detailed_name": "enum class Foo : int {}", + "qual_name_offset": 11, + "short_name": "Foo", + "spell": "1:12-1:15|1:1-4:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 439339022761937396, + "R": -1 + }, { + "L": 15962370213938840720, + "R": -1 + }], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [10677751717622394455], + "uses": ["6:1-6:4|4|-1", "6:9-6:12|4|-1"] + }], + "usr2var": [{ + "usr": 439339022761937396, + "detailed_name": "Foo::A", + "qual_name_offset": 0, + "short_name": "A", + "hover": "Foo::A = 0", + "spell": "2:3-2:4|2:3-2:4|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 10, + "storage": 0, + "declarations": [], + "uses": ["6:14-6:15|4|-1"] + }, { + "usr": 10677751717622394455, + "detailed_name": "Foo x", + "qual_name_offset": 4, + "short_name": "x", + "hover": "Foo x = Foo::A", + "spell": "6:5-6:6|6:1-6:15|2|-1", + "type": 16985894625255407295, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15962370213938840720, + "detailed_name": "Foo::B = 20", + "qual_name_offset": 0, + "short_name": "B", + "spell": "3:3-3:4|3:3-3:9|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 10, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/foobar.cc b/index_tests/foobar.cc new file mode 100644 index 000000000..6337f1eff --- /dev/null +++ b/index_tests/foobar.cc @@ -0,0 +1,112 @@ +enum A {}; +enum B {}; + +template +struct Foo { + struct Inner {}; +}; + +Foo::Inner a; +Foo b; +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 6697181287623958829, + "detailed_name": "enum A {}", + "qual_name_offset": 5, + "short_name": "A", + "spell": "1:6-1:7|1:1-1:10|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["9:5-9:6|4|-1"] + }, { + "usr": 10528472276654770367, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "5:8-5:11|5:1-7:2|2|-1", + "bases": [], + "funcs": [], + "types": [13938528237873543349], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [12028309045033782423], + "uses": ["9:1-9:4|4|-1", "10:1-10:4|4|-1"] + }, { + "usr": 13892793056005362145, + "detailed_name": "enum B {}", + "qual_name_offset": 5, + "short_name": "B", + "spell": "2:6-2:7|2:1-2:10|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["10:5-10:6|4|-1"] + }, { + "usr": 13938528237873543349, + "detailed_name": "struct Foo::Inner {}", + "qual_name_offset": 7, + "short_name": "Inner", + "spell": "6:10-6:15|6:3-6:18|1026|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 23, + "declarations": [], + "derived": [], + "instances": [16721564935990383768], + "uses": ["9:9-9:14|4|-1"] + }], + "usr2var": [{ + "usr": 12028309045033782423, + "detailed_name": "Foo b", + "qual_name_offset": 7, + "short_name": "b", + "spell": "10:8-10:9|10:1-10:9|2|-1", + "type": 10528472276654770367, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16721564935990383768, + "detailed_name": "Foo::Inner a", + "qual_name_offset": 14, + "short_name": "a", + "spell": "9:15-9:16|9:1-9:16|2|-1", + "type": 13938528237873543349, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/function_declaration.cc b/index_tests/function_declaration.cc new file mode 100644 index 000000000..49225d2f3 --- /dev/null +++ b/index_tests/function_declaration.cc @@ -0,0 +1,26 @@ +void foo(int a, int b); + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 2747674671862363334, + "detailed_name": "void foo(int a, int b)", + "qual_name_offset": 5, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:6-1:9|1:1-1:23|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/function_declaration_definition.cc b/index_tests/function_declaration_definition.cc new file mode 100644 index 000000000..5350dbc2b --- /dev/null +++ b/index_tests/function_declaration_definition.cc @@ -0,0 +1,29 @@ +void foo(); + +void foo() {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:6-3:9|3:1-3:14|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:6-1:9|1:1-1:11|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/function_definition.cc b/index_tests/function_definition.cc new file mode 100644 index 000000000..2176a6584 --- /dev/null +++ b/index_tests/function_definition.cc @@ -0,0 +1,27 @@ +void foo() {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-1:14|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/inheritance/class_inherit.cc b/index_tests/inheritance/class_inherit.cc new file mode 100644 index 000000000..791d98654 --- /dev/null +++ b/index_tests/inheritance/class_inherit.cc @@ -0,0 +1,47 @@ +class Parent {}; +class Derived : public Parent {}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 3866412049634585509, + "detailed_name": "class Parent {}", + "qual_name_offset": 6, + "short_name": "Parent", + "spell": "1:7-1:13|1:1-1:16|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["2:24-2:30|2052|-1"] + }, { + "usr": 10963370434658308541, + "detailed_name": "class Derived : public Parent {}", + "qual_name_offset": 6, + "short_name": "Derived", + "spell": "2:7-2:14|2:1-2:33|2|-1", + "bases": [3866412049634585509], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/inheritance/class_inherit_templated_parent.cc b/index_tests/inheritance/class_inherit_templated_parent.cc new file mode 100644 index 000000000..4e3f5bded --- /dev/null +++ b/index_tests/inheritance/class_inherit_templated_parent.cc @@ -0,0 +1,109 @@ +template +class Base1 {}; + +template +class Base2 {}; + +template +class Derived1 : Base1 {}; + +template +class Derived2 : Base2 {}; + +class Derived : Base1<3>, Base2, Derived1<4>, Derived2 {}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 5863733211528032190, + "detailed_name": "class Derived1 : Base1 {}", + "qual_name_offset": 6, + "short_name": "Derived1", + "spell": "8:7-8:15|8:1-8:29|2|-1", + "bases": [11930058224338108382], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["13:43-13:51|2052|-1"] + }, { + "usr": 10651399730831737929, + "detailed_name": "class Derived2 : Base2 {}", + "qual_name_offset": 6, + "short_name": "Derived2", + "spell": "11:7-11:15|11:1-11:29|2|-1", + "bases": [11118288764693061434], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["13:56-13:64|2052|-1"] + }, { + "usr": 10963370434658308541, + "detailed_name": "class Derived : Base1<3>, Base2, Derived1<4>, Derived2 {}", + "qual_name_offset": 6, + "short_name": "Derived", + "spell": "13:7-13:14|13:1-13:76|2|-1", + "bases": [11930058224338108382, 11118288764693061434, 5863733211528032190, 10651399730831737929], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["13:33-13:40|2052|-1", "13:65-13:72|2052|-1"] + }, { + "usr": 11118288764693061434, + "detailed_name": "class Base2 {}", + "qual_name_offset": 6, + "short_name": "Base2", + "spell": "5:7-5:12|5:1-5:15|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [10651399730831737929, 10963370434658308541], + "instances": [], + "uses": ["11:18-11:23|2052|-1", "13:27-13:32|2052|-1"] + }, { + "usr": 11930058224338108382, + "detailed_name": "class Base1 {}", + "qual_name_offset": 6, + "short_name": "Base1", + "spell": "2:7-2:12|2:1-2:15|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [5863733211528032190, 10963370434658308541], + "instances": [], + "uses": ["8:18-8:23|2052|-1", "13:17-13:22|2052|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/inheritance/class_multiple_inherit.cc b/index_tests/inheritance/class_multiple_inherit.cc new file mode 100644 index 000000000..49b09501f --- /dev/null +++ b/index_tests/inheritance/class_multiple_inherit.cc @@ -0,0 +1,83 @@ +class Root {}; +class MiddleA : public Root {}; +class MiddleB : public Root {}; +class Derived : public MiddleA, public MiddleB {}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 3897841498936210886, + "detailed_name": "class Root {}", + "qual_name_offset": 6, + "short_name": "Root", + "spell": "1:7-1:11|1:1-1:14|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [11863524815063131483, 14022569716337624303], + "instances": [], + "uses": ["2:24-2:28|2052|-1", "3:24-3:28|2052|-1"] + }, { + "usr": 10963370434658308541, + "detailed_name": "class Derived : public MiddleA, public MiddleB {}", + "qual_name_offset": 6, + "short_name": "Derived", + "spell": "4:7-4:14|4:1-4:50|2|-1", + "bases": [11863524815063131483, 14022569716337624303], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 11863524815063131483, + "detailed_name": "class MiddleA : public Root {}", + "qual_name_offset": 6, + "short_name": "MiddleA", + "spell": "2:7-2:14|2:1-2:31|2|-1", + "bases": [3897841498936210886], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["4:24-4:31|2052|-1"] + }, { + "usr": 14022569716337624303, + "detailed_name": "class MiddleB : public Root {}", + "qual_name_offset": 6, + "short_name": "MiddleB", + "spell": "3:7-3:14|3:1-3:31|2|-1", + "bases": [3897841498936210886], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["4:40-4:47|2052|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/inheritance/function_override.cc b/index_tests/inheritance/function_override.cc new file mode 100644 index 000000000..319b43946 --- /dev/null +++ b/index_tests/inheritance/function_override.cc @@ -0,0 +1,80 @@ +class Root { + virtual void foo(); +}; +class Derived : public Root { + void foo() override {} +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 6666242542855173890, + "detailed_name": "void Derived::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "5:8-5:11|5:3-5:25|5186|-1", + "bases": [9948027785633571339], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 9948027785633571339, + "detailed_name": "virtual void Root::foo()", + "qual_name_offset": 13, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:16-2:19|2:3-2:21|1089|-1"], + "derived": [6666242542855173890], + "uses": [] + }], + "usr2type": [{ + "usr": 3897841498936210886, + "detailed_name": "class Root {}", + "qual_name_offset": 6, + "short_name": "Root", + "spell": "1:7-1:11|1:1-3:2|2|-1", + "bases": [], + "funcs": [9948027785633571339], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["4:24-4:28|2052|-1"] + }, { + "usr": 10963370434658308541, + "detailed_name": "class Derived : public Root {}", + "qual_name_offset": 6, + "short_name": "Derived", + "spell": "4:7-4:14|4:1-6:2|2|-1", + "bases": [3897841498936210886], + "funcs": [6666242542855173890], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/inheritance/interface_pure_virtual.cc b/index_tests/inheritance/interface_pure_virtual.cc new file mode 100644 index 000000000..fcf9841f5 --- /dev/null +++ b/index_tests/inheritance/interface_pure_virtual.cc @@ -0,0 +1,45 @@ +class IFoo { + virtual void foo() = 0; +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3277829753446788562, + "detailed_name": "virtual void IFoo::foo() = 0", + "qual_name_offset": 13, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:16-2:19|2:3-2:25|1089|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 9949214233977131946, + "detailed_name": "class IFoo {}", + "qual_name_offset": 6, + "short_name": "IFoo", + "spell": "1:7-1:11|1:1-3:2|2|-1", + "bases": [], + "funcs": [3277829753446788562], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/inheritance/multiple_base_functions.cc b/index_tests/inheritance/multiple_base_functions.cc new file mode 100644 index 000000000..f4ef3c4a2 --- /dev/null +++ b/index_tests/inheritance/multiple_base_functions.cc @@ -0,0 +1,116 @@ +struct Base0 { + virtual ~Base0() { } +}; +struct Base1 { + virtual ~Base1() { } +}; +struct Derived : Base0, Base1 { + ~Derived() override { } +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 8401779086123965305, + "detailed_name": "virtual Base1::~Base1() noexcept", + "qual_name_offset": 8, + "short_name": "~Base1", + "spell": "5:11-5:17|5:3-5:23|1090|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 13164726294460837993, + "detailed_name": "Derived::~Derived() noexcept", + "qual_name_offset": 0, + "short_name": "~Derived", + "spell": "8:3-8:11|8:3-8:26|5186|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 16347272523198263017, + "detailed_name": "virtual Base0::~Base0() noexcept", + "qual_name_offset": 8, + "short_name": "~Base0", + "spell": "2:11-2:17|2:3-2:23|1090|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 10963370434658308541, + "detailed_name": "struct Derived : Base0, Base1 {}", + "qual_name_offset": 7, + "short_name": "Derived", + "spell": "7:8-7:15|7:1-9:2|2|-1", + "bases": [11628904180681204356, 15826803741381445676], + "funcs": [13164726294460837993], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["8:4-8:11|4|-1"] + }, { + "usr": 11628904180681204356, + "detailed_name": "struct Base0 {}", + "qual_name_offset": 7, + "short_name": "Base0", + "spell": "1:8-1:13|1:1-3:2|2|-1", + "bases": [], + "funcs": [16347272523198263017], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["2:12-2:17|4|-1", "7:18-7:23|2052|-1"] + }, { + "usr": 15826803741381445676, + "detailed_name": "struct Base1 {}", + "qual_name_offset": 7, + "short_name": "Base1", + "spell": "4:8-4:13|4:1-6:2|2|-1", + "bases": [], + "funcs": [8401779086123965305], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [10963370434658308541], + "instances": [], + "uses": ["5:12-5:17|4|-1", "7:25-7:30|2052|-1"] + }], + "usr2var": [] +} +*/ \ No newline at end of file diff --git a/index_tests/lambdas/lambda.cc b/index_tests/lambdas/lambda.cc new file mode 100644 index 000000000..059d148b8 --- /dev/null +++ b/index_tests/lambdas/lambda.cc @@ -0,0 +1,121 @@ +void foo() { + int x; + + auto dosomething = [&x](int y) { + ++x; + ++y; + }; + + dosomething(1); + dosomething(1); + dosomething(1); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-12:2|2|-1", + "bases": [], + "vars": [12666114896600231317, 2981279427664991319], + "callees": ["9:14-9:15|17926497908620168464|3|16420", "10:14-10:15|17926497908620168464|3|16420", "11:14-11:15|17926497908620168464|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 17926497908620168464, + "detailed_name": "inline void foo()::(anon class)::operator()(int y) const", + "qual_name_offset": 12, + "short_name": "operator()", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["9:14-9:15|16420|-1", "10:14-10:15|16420|-1", "11:14-11:15|16420|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [12666114896600231317], + "uses": [] + }, { + "usr": 14635009347499519042, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [2981279427664991319], + "uses": [] + }], + "usr2var": [{ + "usr": 2981279427664991319, + "detailed_name": "(lambda) dosomething", + "qual_name_offset": 9, + "short_name": "dosomething", + "hover": "(lambda) dosomething", + "spell": "4:8-4:19|4:3-7:4|2|-1", + "type": 14635009347499519042, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["9:3-9:14|4|-1", "10:3-10:14|4|-1", "11:3-11:14|4|-1"] + }, { + "usr": 12666114896600231317, + "detailed_name": "int x", + "qual_name_offset": 4, + "short_name": "x", + "spell": "2:7-2:8|2:3-2:8|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["4:24-4:25|4|-1", "5:7-5:8|28|-1"] + }, { + "usr": 12879188959314906706, + "detailed_name": "int y", + "qual_name_offset": 4, + "short_name": "y", + "spell": "4:31-4:32|4:27-4:32|2|-1", + "type": 0, + "kind": 253, + "parent_kind": 6, + "storage": 0, + "declarations": [], + "uses": ["6:7-6:8|28|-1"] + }] +} +*/ diff --git a/index_tests/macros/complex.cc b/index_tests/macros/complex.cc new file mode 100644 index 000000000..55ac9624c --- /dev/null +++ b/index_tests/macros/complex.cc @@ -0,0 +1,95 @@ +#define FOO(aaa, bbb) \ + int a();\ + int a() { return aaa + bbb; } + + +int make1() { + return 3; +} +const int make2 = 5; + + +FOO(make1(), make2); + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 9720930732776154610, + "detailed_name": "int a()", + "qual_name_offset": 4, + "short_name": "a", + "spell": "12:1-12:20|12:1-12:4|2|-1", + "bases": [], + "vars": [], + "callees": ["12:5-12:10|14400399977994209582|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["12:1-12:20|12:1-12:4|1|-1"], + "derived": [], + "uses": ["2:7-2:8|64|0", "3:7-3:8|64|0"] + }, { + "usr": 14400399977994209582, + "detailed_name": "int make1()", + "qual_name_offset": 4, + "short_name": "make1", + "spell": "6:5-6:10|6:1-8:2|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["12:5-12:10|16420|-1", "12:5-12:10|64|0"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [2878407290385495202], + "uses": [] + }], + "usr2var": [{ + "usr": 2878407290385495202, + "detailed_name": "const int make2", + "qual_name_offset": 10, + "short_name": "make2", + "hover": "const int make2 = 5", + "spell": "9:11-9:16|9:1-9:20|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": ["12:14-12:19|12|-1", "12:14-12:19|64|0"] + }, { + "usr": 14219599523415845943, + "detailed_name": "FOO", + "qual_name_offset": 0, + "short_name": "FOO", + "hover": "#define FOO(aaa, bbb) \\\n int a();\\\n int a() { return aaa + bbb; }", + "spell": "1:9-1:12|1:9-3:32|2|-1", + "type": 0, + "kind": 255, + "parent_kind": 1, + "storage": 0, + "declarations": [], + "uses": ["12:1-12:4|64|-1"] + }] +} +*/ \ No newline at end of file diff --git a/index_tests/macros/foo.cc b/index_tests/macros/foo.cc new file mode 100644 index 000000000..a75078311 --- /dev/null +++ b/index_tests/macros/foo.cc @@ -0,0 +1,106 @@ +#define A 5 +#define DISALLOW(type) type(type&&) = delete; + +struct Foo { + DISALLOW(Foo); +}; + +int x = A; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 13788753348312146871, + "detailed_name": "Foo::Foo(Foo &&) = delete", + "qual_name_offset": 0, + "short_name": "Foo", + "spell": "5:12-5:15|5:3-5:11|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["5:12-5:15|64|0"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [10677751717622394455], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "4:8-4:11|4:1-6:2|2|-1", + "bases": [], + "funcs": [13788753348312146871], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["5:12-5:15|4|-1", "5:12-5:15|64|0"] + }], + "usr2var": [{ + "usr": 1569772797058982873, + "detailed_name": "A", + "qual_name_offset": 0, + "short_name": "A", + "hover": "#define A 5", + "spell": "1:9-1:10|1:9-1:12|2|-1", + "type": 0, + "kind": 255, + "parent_kind": 1, + "storage": 0, + "declarations": [], + "uses": ["8:9-8:10|64|-1"] + }, { + "usr": 4904139678698066671, + "detailed_name": "DISALLOW", + "qual_name_offset": 0, + "short_name": "DISALLOW", + "hover": "#define DISALLOW(type) type(type&&) = delete;", + "spell": "2:9-2:17|2:9-2:46|2|-1", + "type": 0, + "kind": 255, + "parent_kind": 1, + "storage": 0, + "declarations": [], + "uses": ["5:3-5:11|64|-1"] + }, { + "usr": 10677751717622394455, + "detailed_name": "int x", + "qual_name_offset": 4, + "short_name": "x", + "hover": "int x = A", + "spell": "8:5-8:6|8:1-8:10|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/method_declaration.cc b/index_tests/method_declaration.cc new file mode 100644 index 000000000..c777fa497 --- /dev/null +++ b/index_tests/method_declaration.cc @@ -0,0 +1,49 @@ +class Foo { + void foo(); +}; + +/* +// NOTE: Lack of declaring_type in functions and funcs in Foo is okay, because +// those are processed when we find the definition for Foo::foo. Pure +// virtuals are treated specially and get added to the type immediately. + +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 17922201480358737771, + "detailed_name": "void Foo::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:8-2:11|2:3-2:13|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [17922201480358737771], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/method_definition.cc b/index_tests/method_definition.cc new file mode 100644 index 000000000..94ff8e564 --- /dev/null +++ b/index_tests/method_definition.cc @@ -0,0 +1,48 @@ +class Foo { + void foo() const; +}; + +void Foo::foo() const {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 6446764306530590711, + "detailed_name": "void Foo::foo() const", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "5:11-5:14|5:1-5:25|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["2:8-2:11|2:3-2:19|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [6446764306530590711], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["5:6-5:9|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/method_inline_declaration.cc b/index_tests/method_inline_declaration.cc new file mode 100644 index 000000000..774bac0c0 --- /dev/null +++ b/index_tests/method_inline_declaration.cc @@ -0,0 +1,46 @@ +class Foo { + void foo() {} +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 17922201480358737771, + "detailed_name": "void Foo::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "2:8-2:11|2:3-2:16|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [17922201480358737771], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/multi_file/funky_enum.cc b/index_tests/multi_file/funky_enum.cc new file mode 100644 index 000000000..b30c456d7 --- /dev/null +++ b/index_tests/multi_file/funky_enum.cc @@ -0,0 +1,87 @@ +enum Foo { +#include "funky_enum.h" +}; + +/* +// TODO: In the future try to have better support for types defined across +// multiple files. + +OUTPUT: funky_enum.h +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [], + "usr2var": [{ + "usr": 439339022761937396, + "detailed_name": "A", + "qual_name_offset": 0, + "short_name": "A", + "hover": "A = 0", + "comments": "This file cannot be built directory. It is included in an enum definition of\nanother file.", + "spell": "4:1-4:2|4:1-4:2|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 8524995777615948802, + "detailed_name": "C", + "qual_name_offset": 0, + "short_name": "C", + "hover": "C = 2", + "comments": "This file cannot be built directory. It is included in an enum definition of\nanother file.", + "spell": "6:1-6:2|6:1-6:2|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15962370213938840720, + "detailed_name": "B", + "qual_name_offset": 0, + "short_name": "B", + "hover": "B = 1", + "comments": "This file cannot be built directory. It is included in an enum definition of\nanother file.", + "spell": "5:1-5:2|5:1-5:2|1026|-1", + "type": 16985894625255407295, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +OUTPUT: funky_enum.cc +{ + "includes": [{ + "line": 1, + "resolved_path": "&funky_enum.h" + }], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 16985894625255407295, + "detailed_name": "enum Foo {}", + "qual_name_offset": 5, + "short_name": "Foo", + "spell": "1:6-1:9|1:1-3:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ \ No newline at end of file diff --git a/index_tests/multi_file/funky_enum.h b/index_tests/multi_file/funky_enum.h new file mode 100644 index 000000000..1acdf0566 --- /dev/null +++ b/index_tests/multi_file/funky_enum.h @@ -0,0 +1,6 @@ +// This file cannot be built directory. It is included in an enum definition of +// another file. + +A, +B, +C diff --git a/index_tests/multi_file/header.h b/index_tests/multi_file/header.h new file mode 100644 index 000000000..717ef8213 --- /dev/null +++ b/index_tests/multi_file/header.h @@ -0,0 +1,18 @@ +#pragma once + +struct Base {}; + +struct SameFileDerived : Base {}; + +using Foo0 = SameFileDerived; + +template +void Foo1() {} + +template +struct Foo2 {}; + +enum Foo3 { A, B, C }; + +int Foo4; +static int Foo5; \ No newline at end of file diff --git a/index_tests/multi_file/impl.cc b/index_tests/multi_file/impl.cc new file mode 100644 index 000000000..9e6e477ae --- /dev/null +++ b/index_tests/multi_file/impl.cc @@ -0,0 +1,235 @@ +#include "header.h" + +void Impl() { + Foo1(); +} + +/* +OUTPUT: header.h +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 11650481237659640387, + "detailed_name": "void Foo1()", + "qual_name_offset": 5, + "short_name": "Foo1", + "spell": "10:6-10:10|10:1-10:15|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [2638219001294786365, 8395885290297540138], + "uses": [] + }, { + "usr": 529393482671181129, + "detailed_name": "struct Foo2 {}", + "qual_name_offset": 7, + "short_name": "Foo2", + "spell": "13:8-13:12|13:1-13:15|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 619345544228965342, + "detailed_name": "using Foo0 = SameFileDerived", + "qual_name_offset": 6, + "short_name": "Foo0", + "spell": "7:7-7:11|7:1-7:29|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 16750616846959666305, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 4481210672785600703, + "detailed_name": "enum Foo3 {}", + "qual_name_offset": 5, + "short_name": "Foo3", + "spell": "15:6-15:10|15:1-15:22|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 8420119006782424779, + "detailed_name": "struct Base {}", + "qual_name_offset": 7, + "short_name": "Base", + "spell": "3:8-3:12|3:1-3:15|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [16750616846959666305], + "instances": [], + "uses": ["5:26-5:30|2052|-1"] + }, { + "usr": 16750616846959666305, + "detailed_name": "struct SameFileDerived : Base {}", + "qual_name_offset": 7, + "short_name": "SameFileDerived", + "spell": "5:8-5:23|5:1-5:33|2|-1", + "bases": [8420119006782424779], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["7:14-7:29|4|-1"] + }], + "usr2var": [{ + "usr": 2638219001294786365, + "detailed_name": "int Foo4", + "qual_name_offset": 4, + "short_name": "Foo4", + "spell": "17:5-17:9|17:1-17:9|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 6141718166919284735, + "detailed_name": "A", + "qual_name_offset": 0, + "short_name": "A", + "hover": "A = 0", + "spell": "15:13-15:14|15:13-15:14|1026|-1", + "type": 4481210672785600703, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 7285646116511901840, + "detailed_name": "C", + "qual_name_offset": 0, + "short_name": "C", + "hover": "C = 2", + "spell": "15:19-15:20|15:19-15:20|1026|-1", + "type": 4481210672785600703, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 8395885290297540138, + "detailed_name": "static int Foo5", + "qual_name_offset": 11, + "short_name": "Foo5", + "spell": "18:12-18:16|18:1-18:16|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": [] + }, { + "usr": 17716334512218775320, + "detailed_name": "B", + "qual_name_offset": 0, + "short_name": "B", + "hover": "B = 1", + "spell": "15:16-15:17|15:16-15:17|1026|-1", + "type": 4481210672785600703, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +OUTPUT: impl.cc +{ + "includes": [{ + "line": 0, + "resolved_path": "&header.h" + }], + "skipped_ranges": [], + "usr2func": [{ + "usr": 5817708529036841195, + "detailed_name": "void Impl()", + "qual_name_offset": 5, + "short_name": "Impl", + "spell": "3:6-3:10|3:1-5:2|2|-1", + "bases": [], + "vars": [], + "callees": ["4:3-4:7|11650481237659640387|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 11650481237659640387, + "detailed_name": "void Foo1()", + "qual_name_offset": 5, + "short_name": "Foo1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["4:3-4:7|16420|-1"] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/multi_file/simple_header.h b/index_tests/multi_file/simple_header.h new file mode 100644 index 000000000..3bdf008ae --- /dev/null +++ b/index_tests/multi_file/simple_header.h @@ -0,0 +1,3 @@ +#pragma once + +void header(); \ No newline at end of file diff --git a/index_tests/multi_file/simple_impl.cc b/index_tests/multi_file/simple_impl.cc new file mode 100644 index 000000000..e97af2376 --- /dev/null +++ b/index_tests/multi_file/simple_impl.cc @@ -0,0 +1,70 @@ +#include "simple_header.h" + +void impl() { + header(); +} + +/* +OUTPUT: simple_header.h +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 16236105532929924676, + "detailed_name": "void header()", + "qual_name_offset": 5, + "short_name": "header", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:6-3:12|3:1-3:14|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +OUTPUT: simple_impl.cc +{ + "includes": [{ + "line": 0, + "resolved_path": "&simple_header.h" + }], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3373269392705484958, + "detailed_name": "void impl()", + "qual_name_offset": 5, + "short_name": "impl", + "spell": "3:6-3:10|3:1-5:2|2|-1", + "bases": [], + "vars": [], + "callees": ["4:3-4:9|16236105532929924676|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 16236105532929924676, + "detailed_name": "void header()", + "qual_name_offset": 5, + "short_name": "header", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["4:3-4:9|16420|-1"] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/multi_file/static.cc b/index_tests/multi_file/static.cc new file mode 100644 index 000000000..d344cb7e5 --- /dev/null +++ b/index_tests/multi_file/static.cc @@ -0,0 +1,87 @@ +#include "static.h" + +void Buffer::CreateSharedBuffer() {} + +/* +OUTPUT: static.h +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 14576076421851654759, + "detailed_name": "static void Buffer::CreateSharedBuffer()", + "qual_name_offset": 12, + "short_name": "CreateSharedBuffer", + "bases": [], + "vars": [], + "callees": [], + "kind": 254, + "parent_kind": 0, + "storage": 0, + "declarations": ["4:15-4:33|4:3-4:35|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 9411323049603567600, + "detailed_name": "struct Buffer {}", + "qual_name_offset": 7, + "short_name": "Buffer", + "spell": "3:8-3:14|3:1-5:2|2|-1", + "bases": [], + "funcs": [14576076421851654759], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +OUTPUT: static.cc +{ + "includes": [{ + "line": 0, + "resolved_path": "&static.h" + }], + "skipped_ranges": [], + "usr2func": [{ + "usr": 14576076421851654759, + "detailed_name": "static void Buffer::CreateSharedBuffer()", + "qual_name_offset": 12, + "short_name": "CreateSharedBuffer", + "spell": "3:14-3:32|3:1-3:37|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 254, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 9411323049603567600, + "detailed_name": "struct Buffer {}", + "qual_name_offset": 7, + "short_name": "Buffer", + "bases": [], + "funcs": [14576076421851654759], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["3:6-3:12|4|-1"] + }], + "usr2var": [] +} +*/ \ No newline at end of file diff --git a/index_tests/multi_file/static.h b/index_tests/multi_file/static.h new file mode 100644 index 000000000..4204f7859 --- /dev/null +++ b/index_tests/multi_file/static.h @@ -0,0 +1,5 @@ +#pragma once + +struct Buffer { + static void CreateSharedBuffer(); +}; diff --git a/index_tests/namespaces/anonymous_function.cc b/index_tests/namespaces/anonymous_function.cc new file mode 100644 index 000000000..e8bebc888 --- /dev/null +++ b/index_tests/namespaces/anonymous_function.cc @@ -0,0 +1,28 @@ +namespace { +void foo(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 5010253035933134245, + "detailed_name": "void (anon ns)::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:6-2:9|2:1-2:11|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/namespaces/function_declaration.cc b/index_tests/namespaces/function_declaration.cc new file mode 100644 index 000000000..fd16717e5 --- /dev/null +++ b/index_tests/namespaces/function_declaration.cc @@ -0,0 +1,44 @@ +namespace hello { +void foo(int a, int b); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 18343102288837190527, + "detailed_name": "void hello::foo(int a, int b)", + "qual_name_offset": 5, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:6-2:9|2:1-2:23|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 2029211996748007610, + "detailed_name": "namespace hello {}", + "qual_name_offset": 10, + "short_name": "hello", + "bases": [], + "funcs": [18343102288837190527], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:16|1:1-3:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/namespaces/function_definition.cc b/index_tests/namespaces/function_definition.cc new file mode 100644 index 000000000..afede23d8 --- /dev/null +++ b/index_tests/namespaces/function_definition.cc @@ -0,0 +1,45 @@ +namespace hello { +void foo() {} +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 243328841292951622, + "detailed_name": "void hello::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "2:6-2:9|2:1-2:14|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 2029211996748007610, + "detailed_name": "namespace hello {}", + "qual_name_offset": 10, + "short_name": "hello", + "bases": [], + "funcs": [243328841292951622], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:16|1:1-3:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/namespaces/method_declaration.cc b/index_tests/namespaces/method_declaration.cc new file mode 100644 index 000000000..236328cd5 --- /dev/null +++ b/index_tests/namespaces/method_declaration.cc @@ -0,0 +1,63 @@ +namespace hello { +class Foo { + void foo(); +}; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 10487325150128053272, + "detailed_name": "void hello::Foo::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:8-3:11|3:3-3:13|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 2029211996748007610, + "detailed_name": "namespace hello {}", + "qual_name_offset": 10, + "short_name": "hello", + "bases": [], + "funcs": [], + "types": [4508214972876735896], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:16|1:1-5:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 4508214972876735896, + "detailed_name": "class hello::Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "2:7-2:10|2:1-4:2|1026|-1", + "bases": [], + "funcs": [10487325150128053272], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/namespaces/method_definition.cc b/index_tests/namespaces/method_definition.cc new file mode 100644 index 000000000..f2f47121c --- /dev/null +++ b/index_tests/namespaces/method_definition.cc @@ -0,0 +1,66 @@ +namespace hello { +class Foo { + void foo(); +}; + +void Foo::foo() {} +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 10487325150128053272, + "detailed_name": "void hello::Foo::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "6:11-6:14|6:1-6:19|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["3:8-3:11|3:3-3:13|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 2029211996748007610, + "detailed_name": "namespace hello {}", + "qual_name_offset": 10, + "short_name": "hello", + "bases": [], + "funcs": [], + "types": [4508214972876735896], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:16|1:1-7:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 4508214972876735896, + "detailed_name": "class hello::Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "2:7-2:10|2:1-4:2|1026|-1", + "bases": [], + "funcs": [10487325150128053272], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["6:6-6:9|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/namespaces/method_inline_declaration.cc b/index_tests/namespaces/method_inline_declaration.cc new file mode 100644 index 000000000..445bf61e9 --- /dev/null +++ b/index_tests/namespaces/method_inline_declaration.cc @@ -0,0 +1,64 @@ +namespace hello { +class Foo { + void foo() {} +}; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 10487325150128053272, + "detailed_name": "void hello::Foo::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:8-3:11|3:3-3:16|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 2029211996748007610, + "detailed_name": "namespace hello {}", + "qual_name_offset": 10, + "short_name": "hello", + "bases": [], + "funcs": [], + "types": [4508214972876735896], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:16|1:1-5:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 4508214972876735896, + "detailed_name": "class hello::Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "2:7-2:10|2:1-4:2|1026|-1", + "bases": [], + "funcs": [10487325150128053272], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/namespaces/namespace_alias.cc b/index_tests/namespaces/namespace_alias.cc new file mode 100644 index 000000000..f74a7c798 --- /dev/null +++ b/index_tests/namespaces/namespace_alias.cc @@ -0,0 +1,162 @@ +namespace foo { + namespace bar { + namespace baz { + int qux = 42; + } + } +} + +namespace fbz = foo::bar::baz; + +void func() { + int a = foo::bar::baz::qux; + int b = fbz::qux; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 10818727483146447186, + "detailed_name": "void func()", + "qual_name_offset": 5, + "short_name": "func", + "spell": "11:6-11:10|11:1-14:2|2|-1", + "bases": [], + "vars": [6030927277961448585, 7657277353101371136], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [15042442838933090518, 6030927277961448585, 7657277353101371136], + "uses": [] + }, { + "usr": 926793467007732869, + "detailed_name": "namespace foo {}", + "qual_name_offset": 10, + "short_name": "foo", + "bases": [], + "funcs": [], + "types": [17805385787823406700], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:14|1:1-7:2|1|-1"], + "derived": [17805385787823406700], + "instances": [], + "uses": ["9:17-9:20|4|-1", "12:11-12:14|4|-1"] + }, { + "usr": 11879713791858506216, + "detailed_name": "namespace fbz = foo::bar::baz", + "qual_name_offset": 10, + "short_name": "fbz", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 14450849931009540802, + "kind": 252, + "parent_kind": 0, + "declarations": ["9:11-9:14|9:1-9:30|1|-1"], + "derived": [], + "instances": [], + "uses": ["13:11-13:14|4|-1"] + }, { + "usr": 14450849931009540802, + "detailed_name": "namespace foo::bar::baz {}", + "qual_name_offset": 10, + "short_name": "baz", + "bases": [17805385787823406700], + "funcs": [], + "types": [], + "vars": [{ + "L": 15042442838933090518, + "R": -1 + }], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["3:20-3:23|3:10-5:11|1025|-1"], + "derived": [], + "instances": [], + "uses": ["9:27-9:30|4|-1", "12:21-12:24|4|-1"] + }, { + "usr": 17805385787823406700, + "detailed_name": "namespace foo::bar {}", + "qual_name_offset": 10, + "short_name": "bar", + "bases": [926793467007732869], + "funcs": [], + "types": [14450849931009540802], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["2:15-2:18|2:5-6:6|1025|-1"], + "derived": [14450849931009540802], + "instances": [], + "uses": ["9:22-9:25|4|-1", "12:16-12:19|4|-1"] + }], + "usr2var": [{ + "usr": 6030927277961448585, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "hover": "int a = foo::bar::baz::qux", + "spell": "12:7-12:8|12:3-12:29|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 7657277353101371136, + "detailed_name": "int b", + "qual_name_offset": 4, + "short_name": "b", + "hover": "int b = fbz::qux", + "spell": "13:7-13:8|13:3-13:19|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15042442838933090518, + "detailed_name": "int foo::bar::baz::qux", + "qual_name_offset": 4, + "short_name": "qux", + "hover": "int foo::bar::baz::qux = 42", + "spell": "4:18-4:21|4:14-4:26|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": ["12:26-12:29|12|-1", "13:16-13:19|12|-1"] + }] +} +*/ diff --git a/index_tests/namespaces/namespace_reference.cc b/index_tests/namespaces/namespace_reference.cc new file mode 100644 index 000000000..f90fd4b0d --- /dev/null +++ b/index_tests/namespaces/namespace_reference.cc @@ -0,0 +1,113 @@ +namespace ns { + int Foo; + void Accept(int a) {} +} + +void Runner() { + ns::Accept(ns::Foo); + using namespace ns; + Accept(Foo); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 631910859630953711, + "detailed_name": "void Runner()", + "qual_name_offset": 5, + "short_name": "Runner", + "spell": "6:6-6:12|6:1-10:2|2|-1", + "bases": [], + "vars": [], + "callees": ["7:7-7:13|17328473273923617489|3|16420", "9:3-9:9|17328473273923617489|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 17328473273923617489, + "detailed_name": "void ns::Accept(int a)", + "qual_name_offset": 5, + "short_name": "Accept", + "spell": "3:8-3:14|3:3-3:24|1026|-1", + "bases": [], + "vars": [3649375698083002347], + "callees": [], + "kind": 12, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["7:7-7:13|16420|-1", "9:3-9:9|16420|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [12898699035586282159, 3649375698083002347], + "uses": [] + }, { + "usr": 11072669167287398027, + "detailed_name": "namespace ns {}", + "qual_name_offset": 10, + "short_name": "ns", + "bases": [], + "funcs": [17328473273923617489], + "types": [], + "vars": [{ + "L": 12898699035586282159, + "R": -1 + }], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:13|1:1-4:2|1|-1"], + "derived": [], + "instances": [], + "uses": ["7:3-7:5|4|-1", "7:14-7:16|4|-1", "8:19-8:21|4|-1"] + }], + "usr2var": [{ + "usr": 3649375698083002347, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "3:19-3:20|3:15-3:20|1026|-1", + "type": 53, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 12898699035586282159, + "detailed_name": "int ns::Foo", + "qual_name_offset": 4, + "short_name": "Foo", + "spell": "2:7-2:10|2:3-2:10|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": ["7:18-7:21|12|-1", "9:10-9:13|12|-1"] + }] +} +*/ + + + diff --git a/index_tests/objective-c/class.m b/index_tests/objective-c/class.m new file mode 100644 index 000000000..4736cadef --- /dev/null +++ b/index_tests/objective-c/class.m @@ -0,0 +1,184 @@ +@interface AClass + + (void)test; + - (void)anInstanceMethod; + @property (nonatomic) int aProp; +@end + +@implementation AClass ++ (void)test {} +- (void)anInstanceMethod {} +@end + +int main(void) +{ + AClass *instance = [AClass init]; + [instance anInstanceMethod]; + instance.aProp = 12; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_by_preprocessor": [], + "types": [{ + "id": 0, + "usr": 11832280568361305387, + "detailed_name": "AClass", + "short_name": "AClass", + "kind": 7, + "spell": "7:17-7:23|-1|1|2", + "extent": "7:1-10:2|-1|1|0", + "parents": [], + "derived": [], + "types": [], + "funcs": [], + "vars": [], + "instances": [2], + "uses": ["14:3-14:9|-1|1|4", "14:23-14:29|-1|1|4"] + }, { + "id": 1, + "usr": 17, + "detailed_name": "", + "short_name": "", + "kind": 0, + "parents": [], + "derived": [], + "types": [], + "funcs": [], + "vars": [], + "instances": [0, 1], + "uses": [] + }], + "funcs": [{ + "id": 0, + "usr": 12775970426728664910, + "detailed_name": "AClass::test", + "short_name": "test", + "kind": 17, + "storage": 0, + "declarations": [{ + "spelling": "2:11-2:15", + "extent": "2:3-2:16", + "content": "+ (void)test;", + "param_spellings": [] + }], + "spell": "8:9-8:13|-1|1|2", + "extent": "8:1-8:16|-1|1|0", + "base": [], + "derived": [], + "locals": [], + "uses": [], + "callees": [] + }, { + "id": 1, + "usr": 4096877434426330804, + "detailed_name": "AClass::anInstanceMethod", + "short_name": "anInstanceMethod", + "kind": 16, + "storage": 0, + "declarations": [{ + "spelling": "3:11-3:27", + "extent": "3:3-3:28", + "content": "- (void)anInstanceMethod;", + "param_spellings": [] + }], + "spell": "9:9-9:25|-1|1|2", + "extent": "9:1-9:28|-1|1|0", + "base": [], + "derived": [], + "locals": [], + "uses": ["15:13-15:29|4|3|64"], + "callees": [] + }, { + "id": 2, + "usr": 12774569141855220778, + "detailed_name": "AClass::aProp", + "short_name": "aProp", + "kind": 16, + "storage": 0, + "declarations": [{ + "spelling": "0:0-0:0", + "extent": "4:29-4:34", + "content": "aProp", + "param_spellings": [] + }], + "extent": "4:29-4:34|-1|1|0", + "base": [], + "derived": [], + "locals": [], + "uses": [], + "callees": [] + }, { + "id": 3, + "usr": 17992064398538597892, + "detailed_name": "AClass::setAProp:", + "short_name": "setAProp:", + "kind": 16, + "storage": 0, + "declarations": [{ + "spelling": "0:0-0:0", + "extent": "4:29-4:34", + "content": "aProp", + "param_spellings": ["4:29-4:34"] + }], + "extent": "4:29-4:34|-1|1|0", + "base": [], + "derived": [], + "locals": [], + "uses": ["0:0-0:0|4|3|64"], + "callees": [] + }, { + "id": 4, + "usr": 7033269674615638282, + "detailed_name": "int main()", + "short_name": "main", + "kind": 12, + "storage": 1, + "declarations": [], + "spell": "12:5-12:9|-1|1|2", + "extent": "12:1-17:2|-1|1|0", + "base": [], + "derived": [], + "locals": [], + "uses": [], + "callees": ["15:13-15:29|1|3|64", "0:0-0:0|3|3|64"] + }], + "vars": [{ + "id": 0, + "usr": 14842397373703114213, + "detailed_name": "int AClass::aProp", + "short_name": "aProp", + "declarations": ["4:29-4:34|-1|1|1"], + "type": 1, + "uses": ["16:12-16:17|4|3|4"], + "kind": 19, + "storage": 0 + }, { + "id": 1, + "usr": 17112602610366149042, + "detailed_name": "int AClass::_aProp", + "short_name": "_aProp", + "declarations": [], + "spell": "4:29-4:34|-1|1|2", + "extent": "4:29-4:34|-1|1|0", + "type": 1, + "uses": [], + "kind": 14, + "storage": 0 + }, { + "id": 2, + "usr": 6849095699869081177, + "detailed_name": "AClass *instance", + "short_name": "instance", + "hover": "AClass *instance = [AClass init]", + "declarations": [], + "spell": "14:11-14:19|4|3|2", + "extent": "14:3-14:35|4|3|2", + "type": 0, + "uses": ["15:4-15:12|4|3|4", "16:3-16:11|4|3|4"], + "kind": 13, + "storage": 1 + }] +} +*/ diff --git a/index_tests/operators/operator.cc b/index_tests/operators/operator.cc new file mode 100644 index 000000000..0640a220e --- /dev/null +++ b/index_tests/operators/operator.cc @@ -0,0 +1,92 @@ +class Foo { + void operator()(int) { } + void operator()(bool); + int operator()(int a, int b); +}; + +Foo &operator += (const Foo&, const int&); + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3545323327609582678, + "detailed_name": "void Foo::operator()(bool)", + "qual_name_offset": 5, + "short_name": "operator()", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:8-3:16|3:3-3:24|1025|-1"], + "derived": [], + "uses": [] + }, { + "usr": 3986818119971932909, + "detailed_name": "int Foo::operator()(int a, int b)", + "qual_name_offset": 4, + "short_name": "operator()", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["4:7-4:15|4:3-4:31|1025|-1"], + "derived": [], + "uses": [] + }, { + "usr": 7874436189163837815, + "detailed_name": "void Foo::operator()(int)", + "qual_name_offset": 5, + "short_name": "operator()", + "spell": "2:8-2:18|2:3-2:27|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 8288368475529136092, + "detailed_name": "Foo &operator+=(const Foo &, const int &)", + "qual_name_offset": 5, + "short_name": "operator+=", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["7:6-7:14|7:1-7:42|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-5:2|2|-1", + "bases": [], + "funcs": [7874436189163837815, 3545323327609582678, 3986818119971932909], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["7:1-7:4|4|-1", "7:25-7:28|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/outline/static_function_in_type.cc b/index_tests/outline/static_function_in_type.cc new file mode 100644 index 000000000..7120de031 --- /dev/null +++ b/index_tests/outline/static_function_in_type.cc @@ -0,0 +1,168 @@ +#include "static_function_in_type.h" + +namespace ns { +// static +void Foo::Register(Manager* m) { +} +} + +/* +OUTPUT: static_function_in_type.h +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 17019747379608639279, + "detailed_name": "static void ns::Foo::Register(ns::Manager *)", + "qual_name_offset": 12, + "short_name": "Register", + "bases": [], + "vars": [], + "callees": [], + "kind": 254, + "parent_kind": 0, + "storage": 0, + "declarations": ["6:15-6:23|6:3-6:33|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 1972401196751872203, + "detailed_name": "class ns::Manager", + "qual_name_offset": 6, + "short_name": "Manager", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": ["3:7-3:14|3:1-3:14|1025|-1"], + "derived": [], + "instances": [], + "uses": ["6:24-6:31|4|-1"] + }, { + "usr": 11072669167287398027, + "detailed_name": "namespace ns {}", + "qual_name_offset": 10, + "short_name": "ns", + "bases": [], + "funcs": [], + "types": [1972401196751872203, 17262466801709381811], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:13|1:1-9:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 17262466801709381811, + "detailed_name": "struct ns::Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "5:8-5:11|5:1-7:2|1026|-1", + "bases": [], + "funcs": [17019747379608639279], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +OUTPUT: static_function_in_type.cc +{ + "includes": [{ + "line": 0, + "resolved_path": "&static_function_in_type.h" + }], + "skipped_ranges": [], + "usr2func": [{ + "usr": 17019747379608639279, + "detailed_name": "static void ns::Foo::Register(ns::Manager *)", + "qual_name_offset": 12, + "short_name": "Register", + "spell": "5:11-5:19|5:1-6:2|1026|-1", + "comments": "static", + "bases": [], + "vars": [13569879755236306838], + "callees": [], + "kind": 254, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 1972401196751872203, + "detailed_name": "class ns::Manager", + "qual_name_offset": 6, + "short_name": "Manager", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [13569879755236306838], + "uses": ["5:20-5:27|4|-1"] + }, { + "usr": 11072669167287398027, + "detailed_name": "namespace ns {}", + "qual_name_offset": 10, + "short_name": "ns", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["3:11-3:13|3:1-7:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 17262466801709381811, + "detailed_name": "struct ns::Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "bases": [], + "funcs": [17019747379608639279], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["5:6-5:9|4|-1"] + }], + "usr2var": [{ + "usr": 13569879755236306838, + "detailed_name": "ns::Manager *m", + "qual_name_offset": 13, + "short_name": "m", + "spell": "5:29-5:30|5:20-5:30|1026|-1", + "type": 1972401196751872203, + "kind": 253, + "parent_kind": 254, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ \ No newline at end of file diff --git a/index_tests/outline/static_function_in_type.h b/index_tests/outline/static_function_in_type.h new file mode 100644 index 000000000..013aa70e5 --- /dev/null +++ b/index_tests/outline/static_function_in_type.h @@ -0,0 +1,9 @@ +namespace ns { + +class Manager; + +struct Foo { + static void Register(Manager*); +}; + +} // namespace ns \ No newline at end of file diff --git a/index_tests/preprocessor/include_guard.cc b/index_tests/preprocessor/include_guard.cc new file mode 100644 index 000000000..efee4de53 --- /dev/null +++ b/index_tests/preprocessor/include_guard.cc @@ -0,0 +1,28 @@ +#ifndef FOO +#define FOO + +#endif + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [], + "usr2var": [{ + "usr": 14219599523415845943, + "detailed_name": "FOO", + "qual_name_offset": 0, + "short_name": "FOO", + "hover": "#define FOO", + "spell": "2:9-2:12|2:9-2:12|2|-1", + "type": 0, + "kind": 255, + "parent_kind": 1, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/preprocessor/skipped.cc b/index_tests/preprocessor/skipped.cc new file mode 100644 index 000000000..74577e1f9 --- /dev/null +++ b/index_tests/preprocessor/skipped.cc @@ -0,0 +1,25 @@ + +#ifdef FOOBAR +void hello(); +#endif + +#if false + + + +#endif + +#if defined(OS_FOO) + +#endif + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": ["2:1-5:1", "6:1-11:1", "12:1-15:1"], + "usr2func": [], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/templates/func_specialized_template_param.cc b/index_tests/templates/func_specialized_template_param.cc new file mode 100644 index 000000000..0cb68a030 --- /dev/null +++ b/index_tests/templates/func_specialized_template_param.cc @@ -0,0 +1,68 @@ +template +class Template {}; + +struct Foo { + void Bar(Template&); +}; + +void Foo::Bar(Template&) {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 8412238651648388423, + "detailed_name": "void Foo::Bar(Template &)", + "qual_name_offset": 5, + "short_name": "Bar", + "spell": "8:11-8:14|8:1-8:36|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 23, + "storage": 0, + "declarations": ["5:8-5:11|5:3-5:30|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "4:8-4:11|4:1-6:2|2|-1", + "bases": [], + "funcs": [8412238651648388423], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["8:6-8:9|4|-1"] + }, { + "usr": 17107291254533526269, + "detailed_name": "class Template {}", + "qual_name_offset": 6, + "short_name": "Template", + "spell": "2:7-2:15|2:1-2:18|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["5:12-5:20|4|-1", "8:15-8:23|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/templates/implicit_variable_instantiation.cc b/index_tests/templates/implicit_variable_instantiation.cc new file mode 100644 index 000000000..b02443587 --- /dev/null +++ b/index_tests/templates/implicit_variable_instantiation.cc @@ -0,0 +1,137 @@ +namespace ns { + enum VarType {}; + + template + struct Holder { + static constexpr VarType static_var = (VarType)0x0; + }; + + template + const typename VarType Holder<_>::static_var; + + + int Foo = Holder::static_var; + int Foo2 = Holder::static_var; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [12898699035586282159, 9008550860229740818], + "uses": [] + }, { + "usr": 1532099849728741556, + "detailed_name": "enum ns::VarType {}", + "qual_name_offset": 5, + "short_name": "VarType", + "spell": "2:8-2:15|2:3-2:18|1026|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [4731849186641714451, 4731849186641714451], + "uses": ["6:22-6:29|4|-1", "6:44-6:51|4|-1", "10:18-10:25|4|-1"] + }, { + "usr": 11072669167287398027, + "detailed_name": "namespace ns {}", + "qual_name_offset": 10, + "short_name": "ns", + "bases": [], + "funcs": [], + "types": [1532099849728741556, 12688716854043726585], + "vars": [{ + "L": 12898699035586282159, + "R": -1 + }, { + "L": 9008550860229740818, + "R": -1 + }], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:13|1:1-15:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 12688716854043726585, + "detailed_name": "struct ns::Holder {}", + "qual_name_offset": 7, + "short_name": "Holder", + "spell": "5:10-5:16|5:3-7:4|1026|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["10:26-10:32|4|-1", "13:13-13:19|4|-1", "14:14-14:20|4|-1"] + }], + "usr2var": [{ + "usr": 4731849186641714451, + "detailed_name": "static constexpr ns::VarType ns::Holder::static_var", + "qual_name_offset": 29, + "short_name": "static_var", + "hover": "static constexpr ns::VarType ns::Holder::static_var = (VarType)0x0", + "spell": "10:37-10:47|9:3-10:47|1026|-1", + "type": 1532099849728741556, + "kind": 13, + "parent_kind": 23, + "storage": 2, + "declarations": ["6:30-6:40|6:5-6:55|1025|-1"], + "uses": ["13:26-13:36|12|-1", "14:27-14:37|12|-1"] + }, { + "usr": 9008550860229740818, + "detailed_name": "int ns::Foo2", + "qual_name_offset": 4, + "short_name": "Foo2", + "hover": "int ns::Foo2 = Holder::static_var", + "spell": "14:7-14:11|14:3-14:37|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 12898699035586282159, + "detailed_name": "int ns::Foo", + "qual_name_offset": 4, + "short_name": "Foo", + "hover": "int ns::Foo = Holder::static_var", + "spell": "13:7-13:10|13:3-13:36|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/member_ref_in_template.cc b/index_tests/templates/member_ref_in_template.cc new file mode 100644 index 000000000..c9cc268e8 --- /dev/null +++ b/index_tests/templates/member_ref_in_template.cc @@ -0,0 +1,110 @@ +template +struct C { + T x; + void bar(); +}; + +template +void foo() { + C d; + d.x; // spelling range is empty, use cursor extent for range + d.bar(); // spelling range is empty, use cursor extent for range + + auto e = new C; + e->x; // `x` seems not exposed by libclang + e->bar(); // `bar` seems not exposed by libclang +} + +/* +EXTRA_FLAGS: +-fms-extensions +-fms-compatibility +-fdelayed-template-parsing + +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 6875364467121018690, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "8:6-8:9|8:1-8:11|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 8905286151237717330, + "detailed_name": "void C::bar()", + "qual_name_offset": 5, + "short_name": "bar", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["4:8-4:11|4:3-4:13|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 8402783583255987702, + "detailed_name": "struct C {}", + "qual_name_offset": 7, + "short_name": "C", + "spell": "2:8-2:9|2:1-5:2|2|-1", + "bases": [], + "funcs": [8905286151237717330], + "types": [], + "vars": [{ + "L": 5866801090710377175, + "R": -1 + }], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 14750650276757822712, + "detailed_name": "T", + "qual_name_offset": 0, + "short_name": "T", + "spell": "1:17-1:18|1:11-1:18|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 26, + "parent_kind": 5, + "declarations": [], + "derived": [], + "instances": [5866801090710377175], + "uses": [] + }], + "usr2var": [{ + "usr": 5866801090710377175, + "detailed_name": "T C::x", + "qual_name_offset": 2, + "short_name": "x", + "spell": "3:5-3:6|3:3-3:6|1026|-1", + "type": 14750650276757822712, + "kind": 8, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/namespace_template_class_template_func_usage_folded_into_one.cc b/index_tests/templates/namespace_template_class_template_func_usage_folded_into_one.cc new file mode 100644 index 000000000..de9c3143c --- /dev/null +++ b/index_tests/templates/namespace_template_class_template_func_usage_folded_into_one.cc @@ -0,0 +1,119 @@ +namespace ns { + template + struct Foo { + template + static int foo() { + return 3; + } + }; + + int a = Foo::foo(); + int b = Foo::foo(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 8221803074608342407, + "detailed_name": "static int ns::Foo::foo()", + "qual_name_offset": 11, + "short_name": "foo", + "spell": "5:16-5:19|5:5-7:6|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 254, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["10:21-10:24|36|-1", "11:22-11:25|36|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [15768138241775955040, 3182917058194750998], + "uses": [] + }, { + "usr": 11072669167287398027, + "detailed_name": "namespace ns {}", + "qual_name_offset": 10, + "short_name": "ns", + "bases": [], + "funcs": [], + "types": [14042997404480181958], + "vars": [{ + "L": 15768138241775955040, + "R": -1 + }, { + "L": 3182917058194750998, + "R": -1 + }], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:13|1:1-12:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 14042997404480181958, + "detailed_name": "struct ns::Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "3:10-3:13|3:3-8:4|1026|-1", + "bases": [], + "funcs": [8221803074608342407], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["10:11-10:14|4|-1", "11:11-11:14|4|-1"] + }], + "usr2var": [{ + "usr": 3182917058194750998, + "detailed_name": "int ns::b", + "qual_name_offset": 4, + "short_name": "b", + "hover": "int ns::b = Foo::foo()", + "spell": "11:7-11:8|11:3-11:35|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15768138241775955040, + "detailed_name": "int ns::a", + "qual_name_offset": 4, + "short_name": "a", + "hover": "int ns::a = Foo::foo()", + "spell": "10:7-10:8|10:3-10:33|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/namespace_template_type_usage_folded_into_one.cc b/index_tests/templates/namespace_template_type_usage_folded_into_one.cc new file mode 100644 index 000000000..e8c8367c3 --- /dev/null +++ b/index_tests/templates/namespace_template_type_usage_folded_into_one.cc @@ -0,0 +1,81 @@ +namespace ns { + template + class Foo {}; + + Foo a; + Foo b; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 11072669167287398027, + "detailed_name": "namespace ns {}", + "qual_name_offset": 10, + "short_name": "ns", + "bases": [], + "funcs": [], + "types": [14042997404480181958], + "vars": [{ + "L": 15768138241775955040, + "R": -1 + }, { + "L": 3182917058194750998, + "R": -1 + }], + "alias_of": 0, + "kind": 3, + "parent_kind": 0, + "declarations": ["1:11-1:13|1:1-7:2|1|-1"], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 14042997404480181958, + "detailed_name": "class ns::Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "3:9-3:12|3:3-3:15|1026|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 3, + "declarations": [], + "derived": [], + "instances": [15768138241775955040, 3182917058194750998], + "uses": ["5:3-5:6|4|-1", "6:3-6:6|4|-1"] + }], + "usr2var": [{ + "usr": 3182917058194750998, + "detailed_name": "Foo ns::b", + "qual_name_offset": 10, + "short_name": "b", + "spell": "6:13-6:14|6:3-6:14|1026|-1", + "type": 14042997404480181958, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15768138241775955040, + "detailed_name": "Foo ns::a", + "qual_name_offset": 9, + "short_name": "a", + "spell": "5:12-5:13|5:3-5:13|1026|-1", + "type": 14042997404480181958, + "kind": 13, + "parent_kind": 3, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/specialization.cc b/index_tests/templates/specialization.cc new file mode 100644 index 000000000..b943c207e --- /dev/null +++ b/index_tests/templates/specialization.cc @@ -0,0 +1,422 @@ +template +class function; + +template +class function {}; + +function f; + +template class allocator; + +template > +class vector { + void clear(); +}; + +template +class vector {}; + +struct Z1 {}; + +template class vector; + +struct Z2 {}; + +template<> +class vector { + void clear(); +}; + +vector vc; +vector vip; +vector vz1; +vector vz2; + +enum Enum { + Enum0, Enum1 +}; +template +void foo(T Value) {} + +static const int kOnst = 7; +template <> +void foo(float Value); + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3861597222587452538, + "detailed_name": "template<> void foo(float Value)", + "qual_name_offset": 16, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["43:6-43:9|42:1-43:50|1|-1"], + "derived": [], + "uses": [] + }, { + "usr": 6113470698424012876, + "detailed_name": "void vector >::clear()", + "qual_name_offset": 5, + "short_name": "clear", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["27:8-27:13|27:3-27:15|1025|-1"], + "derived": [], + "uses": [] + }, { + "usr": 17498190318698490707, + "detailed_name": "void foo(T Value)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "39:6-39:9|39:1-39:21|2|-1", + "bases": [], + "vars": [17826688417349629938], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 18107614608385228556, + "detailed_name": "void vector::clear()", + "qual_name_offset": 5, + "short_name": "clear", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["13:8-13:13|13:3-13:15|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [13914496963221806870], + "uses": [] + }, { + "usr": 218068462278884837, + "detailed_name": "template class function {}", + "qual_name_offset": 46, + "short_name": "function", + "spell": "5:7-5:15|4:1-5:30|2|-1", + "bases": [15019211479263750068], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [2933643612409209903], + "uses": ["7:1-7:9|4|-1"] + }, { + "usr": 1663022413889915338, + "detailed_name": "template<> class vector> {}", + "qual_name_offset": 17, + "short_name": "vector", + "spell": "26:7-26:13|25:1-28:2|2|-1", + "bases": [7440942986741176606], + "funcs": [6113470698424012876], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [15931696253641284761], + "uses": ["26:7-26:13|4|-1", "33:1-33:7|4|-1"] + }, { + "usr": 5760043510674081814, + "detailed_name": "struct Z1 {}", + "qual_name_offset": 7, + "short_name": "Z1", + "spell": "19:8-19:10|19:1-19:13|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["21:23-21:25|4|-1", "32:8-32:10|4|-1"] + }, { + "usr": 7440942986741176606, + "detailed_name": "class vector {}", + "qual_name_offset": 6, + "short_name": "vector", + "spell": "12:7-12:13|12:1-14:2|2|-1", + "bases": [], + "funcs": [18107614608385228556], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [16155717907537731864, 1663022413889915338], + "instances": [5792869548777559988], + "uses": ["17:7-17:13|4|-1", "21:16-21:22|4|-1", "30:1-30:7|4|-1", "32:1-32:7|4|-1"] + }, { + "usr": 9201299975592934124, + "detailed_name": "enum Enum {}", + "qual_name_offset": 5, + "short_name": "Enum", + "spell": "35:6-35:10|35:1-37:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 10124869160135436852, + "detailed_name": "struct Z2 {}", + "qual_name_offset": 7, + "short_name": "Z2", + "spell": "23:8-23:10|23:1-23:13|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["26:14-26:16|4|-1", "33:8-33:10|4|-1"] + }, { + "usr": 14111105212951082474, + "detailed_name": "T", + "qual_name_offset": 0, + "short_name": "T", + "spell": "38:20-38:21|38:11-38:21|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 26, + "parent_kind": 5, + "declarations": [], + "derived": [], + "instances": [17826688417349629938], + "uses": [] + }, { + "usr": 15019211479263750068, + "detailed_name": "class function", + "qual_name_offset": 6, + "short_name": "function", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": ["2:7-2:15|2:1-2:15|1|-1"], + "derived": [218068462278884837], + "instances": [], + "uses": ["5:7-5:15|4|-1"] + }, { + "usr": 15440970074034693939, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [3566687051827176322], + "uses": [] + }, { + "usr": 15695704394170757108, + "detailed_name": "class allocator", + "qual_name_offset": 6, + "short_name": "allocator", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": ["9:28-9:37|9:22-9:37|1|-1"], + "derived": [], + "instances": [], + "uses": ["11:39-11:48|4|-1"] + }, { + "usr": 16155717907537731864, + "detailed_name": "template class vector> {}", + "qual_name_offset": 28, + "short_name": "vector", + "spell": "17:7-17:13|16:1-17:20|2|-1", + "bases": [7440942986741176606], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [86949563628772958], + "uses": ["31:1-31:7|4|-1"] + }], + "usr2var": [{ + "usr": 86949563628772958, + "detailed_name": "vector vip", + "qual_name_offset": 14, + "short_name": "vip", + "spell": "31:14-31:17|31:1-31:17|2|-1", + "type": 16155717907537731864, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 2933643612409209903, + "detailed_name": "function f", + "qual_name_offset": 21, + "short_name": "f", + "spell": "7:21-7:22|7:1-7:22|2|-1", + "type": 218068462278884837, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 3566687051827176322, + "detailed_name": "vector vz1", + "qual_name_offset": 11, + "short_name": "vz1", + "spell": "32:12-32:15|32:1-32:15|2|-1", + "type": 15440970074034693939, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 4917621020431490070, + "detailed_name": "Enum1", + "qual_name_offset": 0, + "short_name": "Enum1", + "hover": "Enum1 = 1", + "spell": "36:10-36:15|36:10-36:15|1026|-1", + "type": 9201299975592934124, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 5792869548777559988, + "detailed_name": "vector vc", + "qual_name_offset": 13, + "short_name": "vc", + "spell": "30:14-30:16|30:1-30:16|2|-1", + "type": 7440942986741176606, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 13914496963221806870, + "detailed_name": "static const int kOnst", + "qual_name_offset": 17, + "short_name": "kOnst", + "hover": "static const int kOnst = 7", + "spell": "41:18-41:23|41:1-41:27|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": ["43:27-43:32|12|-1"] + }, { + "usr": 15477793821005285152, + "detailed_name": "Enum0", + "qual_name_offset": 0, + "short_name": "Enum0", + "hover": "Enum0 = 0", + "spell": "36:3-36:8|36:3-36:8|1026|-1", + "type": 9201299975592934124, + "kind": 22, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": ["43:20-43:25|4|-1"] + }, { + "usr": 15931696253641284761, + "detailed_name": "vector vz2", + "qual_name_offset": 11, + "short_name": "vz2", + "spell": "33:12-33:15|33:1-33:15|2|-1", + "type": 1663022413889915338, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 17826688417349629938, + "detailed_name": "T Value", + "qual_name_offset": 2, + "short_name": "Value", + "spell": "39:12-39:17|39:10-39:17|1026|-1", + "type": 14111105212951082474, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/specialized_func_definition.cc b/index_tests/templates/specialized_func_definition.cc new file mode 100644 index 000000000..c57b35ffe --- /dev/null +++ b/index_tests/templates/specialized_func_definition.cc @@ -0,0 +1,92 @@ +template +class Template { + void Foo(); +}; + +template +void Template::Foo() {} + +template<> +void Template::Foo() {} + + +/* +// TODO: usage information on Template is bad. +// TODO: Foo() should have multiple definitions. + +EXTRA_FLAGS: +-fms-compatibility +-fdelayed-template-parsing + +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 6995843774014807426, + "detailed_name": "void Template::Foo()", + "qual_name_offset": 5, + "short_name": "Foo", + "spell": "10:22-10:25|9:1-10:30|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 11994188353303124840, + "detailed_name": "void Template::Foo()", + "qual_name_offset": 5, + "short_name": "Foo", + "spell": "7:19-7:22|6:1-7:24|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["3:8-3:11|3:3-3:13|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 17107291254533526269, + "detailed_name": "class Template {}", + "qual_name_offset": 6, + "short_name": "Template", + "spell": "2:7-2:15|2:1-4:2|2|-1", + "bases": [], + "funcs": [11994188353303124840], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["7:6-7:14|4|-1", "10:6-10:14|4|-1"] + }, { + "usr": 17649312483543982122, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [6995843774014807426], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/templates/template_class_func_usage_folded_into_one.cc b/index_tests/templates/template_class_func_usage_folded_into_one.cc new file mode 100644 index 000000000..6efb299e3 --- /dev/null +++ b/index_tests/templates/template_class_func_usage_folded_into_one.cc @@ -0,0 +1,94 @@ +template +struct Foo { + static int foo() { + return 3; + } +}; + +int a = Foo::foo(); +int b = Foo::foo(); + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 8340731781048851399, + "detailed_name": "static int Foo::foo()", + "qual_name_offset": 11, + "short_name": "foo", + "spell": "3:14-3:17|3:3-5:4|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 254, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["8:19-8:22|36|-1", "9:20-9:23|36|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16721564935990383768, 12028309045033782423], + "uses": [] + }, { + "usr": 10528472276654770367, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "2:8-2:11|2:1-6:2|2|-1", + "bases": [], + "funcs": [8340731781048851399], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["8:9-8:12|4|-1", "9:9-9:12|4|-1"] + }], + "usr2var": [{ + "usr": 12028309045033782423, + "detailed_name": "int b", + "qual_name_offset": 4, + "short_name": "b", + "hover": "int b = Foo::foo()", + "spell": "9:5-9:6|9:1-9:25|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16721564935990383768, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "hover": "int a = Foo::foo()", + "spell": "8:5-8:6|8:1-8:24|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/template_class_template_func_usage_folded_into_one.cc b/index_tests/templates/template_class_template_func_usage_folded_into_one.cc new file mode 100644 index 000000000..c364718f1 --- /dev/null +++ b/index_tests/templates/template_class_template_func_usage_folded_into_one.cc @@ -0,0 +1,95 @@ +template +struct Foo { + template + static int foo() { + return 3; + } +}; + +int a = Foo::foo(); +int b = Foo::foo(); + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 9034026360701857235, + "detailed_name": "static int Foo::foo()", + "qual_name_offset": 11, + "short_name": "foo", + "spell": "4:14-4:17|4:3-6:4|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 254, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["9:19-9:22|36|-1", "10:20-10:23|36|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16721564935990383768, 12028309045033782423], + "uses": [] + }, { + "usr": 10528472276654770367, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "2:8-2:11|2:1-7:2|2|-1", + "bases": [], + "funcs": [9034026360701857235], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["9:9-9:12|4|-1", "10:9-10:12|4|-1"] + }], + "usr2var": [{ + "usr": 12028309045033782423, + "detailed_name": "int b", + "qual_name_offset": 4, + "short_name": "b", + "hover": "int b = Foo::foo()", + "spell": "10:5-10:6|10:1-10:33|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16721564935990383768, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "hover": "int a = Foo::foo()", + "spell": "9:5-9:6|9:1-9:31|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/template_class_type_usage_folded_into_one.cc b/index_tests/templates/template_class_type_usage_folded_into_one.cc new file mode 100644 index 000000000..b15eb530a --- /dev/null +++ b/index_tests/templates/template_class_type_usage_folded_into_one.cc @@ -0,0 +1,131 @@ +enum A {}; +enum B {}; + +template +struct Foo { + struct Inner {}; +}; + +Foo::Inner a; +Foo::Inner b; + +#if false +EnumDecl A +EnumDecl B +ClassTemplate Foo + TemplateTypeParameter T + StructDecl Inner +VarDecl a + TemplateRef Foo + TypeRef enum A + TypeRef struct Foo::Inner + CallExpr Inner +VarDecl b + TemplateRef Foo + TypeRef enum B + TypeRef struct Foo::Inner + CallExpr Inner +#endif + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": ["12:1-29:1"], + "usr2func": [], + "usr2type": [{ + "usr": 6697181287623958829, + "detailed_name": "enum A {}", + "qual_name_offset": 5, + "short_name": "A", + "spell": "1:6-1:7|1:1-1:10|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["9:5-9:6|4|-1"] + }, { + "usr": 10528472276654770367, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "5:8-5:11|5:1-7:2|2|-1", + "bases": [], + "funcs": [], + "types": [13938528237873543349], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["9:1-9:4|4|-1", "10:1-10:4|4|-1"] + }, { + "usr": 13892793056005362145, + "detailed_name": "enum B {}", + "qual_name_offset": 5, + "short_name": "B", + "spell": "2:6-2:7|2:1-2:10|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["10:5-10:6|4|-1"] + }, { + "usr": 13938528237873543349, + "detailed_name": "struct Foo::Inner {}", + "qual_name_offset": 7, + "short_name": "Inner", + "spell": "6:10-6:15|6:3-6:18|1026|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 23, + "declarations": [], + "derived": [], + "instances": [16721564935990383768, 12028309045033782423], + "uses": ["9:9-9:14|4|-1", "10:9-10:14|4|-1"] + }], + "usr2var": [{ + "usr": 12028309045033782423, + "detailed_name": "Foo::Inner b", + "qual_name_offset": 14, + "short_name": "b", + "spell": "10:15-10:16|10:1-10:16|2|-1", + "type": 13938528237873543349, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16721564935990383768, + "detailed_name": "Foo::Inner a", + "qual_name_offset": 14, + "short_name": "a", + "spell": "9:15-9:16|9:1-9:16|2|-1", + "type": 13938528237873543349, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/template_class_var_usage_folded_into_one.cc b/index_tests/templates/template_class_var_usage_folded_into_one.cc new file mode 100644 index 000000000..f2c4390ca --- /dev/null +++ b/index_tests/templates/template_class_var_usage_folded_into_one.cc @@ -0,0 +1,89 @@ +template +struct Foo { + static constexpr int var = 3; +}; + +int a = Foo::var; +int b = Foo::var; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [13545144895171991916, 16721564935990383768, 12028309045033782423], + "uses": [] + }, { + "usr": 10528472276654770367, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "2:8-2:11|2:1-4:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["6:9-6:12|4|-1", "7:9-7:12|4|-1"] + }], + "usr2var": [{ + "usr": 12028309045033782423, + "detailed_name": "int b", + "qual_name_offset": 4, + "short_name": "b", + "hover": "int b = Foo::var", + "spell": "7:5-7:6|7:1-7:23|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 13545144895171991916, + "detailed_name": "static constexpr int Foo::var", + "qual_name_offset": 21, + "short_name": "var", + "hover": "static constexpr int Foo::var = 3", + "type": 53, + "kind": 13, + "parent_kind": 23, + "storage": 2, + "declarations": ["3:24-3:27|3:3-3:31|1025|-1"], + "uses": ["6:19-6:22|12|-1", "7:20-7:23|12|-1"] + }, { + "usr": 16721564935990383768, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "hover": "int a = Foo::var", + "spell": "6:5-6:6|6:1-6:22|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/template_func_usage_folded_into_one.cc b/index_tests/templates/template_func_usage_folded_into_one.cc new file mode 100644 index 000000000..9c2538940 --- /dev/null +++ b/index_tests/templates/template_func_usage_folded_into_one.cc @@ -0,0 +1,78 @@ +template +static int foo() { + return 3; +} + +int a = foo(); +int b = foo(); + +// TODO: put template foo inside a namespace +// TODO: put template foo inside a template class inside a namespace + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 326583651986177228, + "detailed_name": "static int foo()", + "qual_name_offset": 11, + "short_name": "foo", + "spell": "2:12-2:15|2:1-4:2|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["6:9-6:12|36|-1", "7:9-7:12|36|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16721564935990383768, 12028309045033782423], + "uses": [] + }], + "usr2var": [{ + "usr": 12028309045033782423, + "detailed_name": "int b", + "qual_name_offset": 4, + "short_name": "b", + "hover": "int b = foo()", + "spell": "7:5-7:6|7:1-7:20|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16721564935990383768, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "hover": "int a = foo()", + "spell": "6:5-6:6|6:1-6:19|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/template_type_usage_folded_into_one.cc b/index_tests/templates/template_type_usage_folded_into_one.cc new file mode 100644 index 000000000..05dfa00e7 --- /dev/null +++ b/index_tests/templates/template_type_usage_folded_into_one.cc @@ -0,0 +1,57 @@ +template +class Foo {}; + +Foo a; +Foo b; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 10528472276654770367, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "2:7-2:10|2:1-2:13|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16721564935990383768, 12028309045033782423], + "uses": ["4:1-4:4|4|-1", "5:1-5:4|4|-1"] + }], + "usr2var": [{ + "usr": 12028309045033782423, + "detailed_name": "Foo b", + "qual_name_offset": 10, + "short_name": "b", + "spell": "5:11-5:12|5:1-5:12|2|-1", + "type": 10528472276654770367, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16721564935990383768, + "detailed_name": "Foo a", + "qual_name_offset": 9, + "short_name": "a", + "spell": "4:10-4:11|4:1-4:11|2|-1", + "type": 10528472276654770367, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/templates/template_var_usage_folded_into_one.cc b/index_tests/templates/template_var_usage_folded_into_one.cc new file mode 100644 index 000000000..7359e0dd7 --- /dev/null +++ b/index_tests/templates/template_var_usage_folded_into_one.cc @@ -0,0 +1,132 @@ +enum A {}; +enum B {}; + +template +T var = T(); + +A a = var; +B b = var; + +// NOTE: libclang before 4.0 doesn't expose template usage on |var|. + +#if false +EnumDecl A +EnumDecl B +UnexposedDecl var +VarDecl a + UnexposedExpr var + UnexposedExpr var + DeclRefExpr var + TypeRef enum A +UnexposedDecl var +VarDecl b + UnexposedExpr var + UnexposedExpr var + DeclRefExpr var + TypeRef enum B +UnexposedDecl var +#endif + +/* +EXTRA_FLAGS: +-std=c++14 + +OUTPUT: +{ + "includes": [], + "skipped_ranges": ["12:1-29:1"], + "usr2func": [], + "usr2type": [{ + "usr": 6697181287623958829, + "detailed_name": "enum A {}", + "qual_name_offset": 5, + "short_name": "A", + "spell": "1:6-1:7|1:1-1:10|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16721564935990383768], + "uses": ["7:1-7:2|4|-1", "7:11-7:12|4|-1"] + }, { + "usr": 11919899838872947844, + "detailed_name": "T", + "qual_name_offset": 0, + "short_name": "T", + "spell": "4:19-4:20|4:10-4:20|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 26, + "parent_kind": 5, + "declarations": [], + "derived": [], + "instances": [8096973118640070624], + "uses": [] + }, { + "usr": 13892793056005362145, + "detailed_name": "enum B {}", + "qual_name_offset": 5, + "short_name": "B", + "spell": "2:6-2:7|2:1-2:10|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [12028309045033782423], + "uses": ["8:1-8:2|4|-1", "8:11-8:12|4|-1"] + }], + "usr2var": [{ + "usr": 8096973118640070624, + "detailed_name": "T var", + "qual_name_offset": 2, + "short_name": "var", + "hover": "T var = T()", + "spell": "5:3-5:6|5:1-5:12|2|-1", + "type": 11919899838872947844, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": ["7:7-7:10|12|-1", "8:7-8:10|12|-1"] + }, { + "usr": 12028309045033782423, + "detailed_name": "B b", + "qual_name_offset": 2, + "short_name": "b", + "hover": "B b = var", + "spell": "8:3-8:4|8:1-8:13|2|-1", + "type": 13892793056005362145, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16721564935990383768, + "detailed_name": "A a", + "qual_name_offset": 2, + "short_name": "a", + "hover": "A a = var", + "spell": "7:3-7:4|7:1-7:13|2|-1", + "type": 6697181287623958829, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/types/anonymous_struct.cc b/index_tests/types/anonymous_struct.cc new file mode 100644 index 000000000..baa5c2dc8 --- /dev/null +++ b/index_tests/types/anonymous_struct.cc @@ -0,0 +1,134 @@ +union vector3 { + struct { float x, y, z; }; + float v[3]; +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 82, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [3348817847649945564, 4821094820988543895, 15292551660437765731], + "uses": [] + }, { + "usr": 1428566502523368801, + "detailed_name": "anon struct", + "qual_name_offset": 0, + "short_name": "anon struct", + "spell": "2:3-2:9|2:3-2:28|1026|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 3348817847649945564, + "R": 0 + }, { + "L": 4821094820988543895, + "R": 32 + }, { + "L": 15292551660437765731, + "R": 64 + }], + "alias_of": 0, + "kind": 23, + "parent_kind": 5, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 17937907487590875128, + "detailed_name": "union vector3 {}", + "qual_name_offset": 6, + "short_name": "vector3", + "spell": "1:7-1:14|1:1-4:2|2|-1", + "bases": [], + "funcs": [], + "types": [1428566502523368801], + "vars": [{ + "L": 1963212417280098348, + "R": 0 + }, { + "L": 3348817847649945564, + "R": 0 + }, { + "L": 4821094820988543895, + "R": 32 + }, { + "L": 15292551660437765731, + "R": 64 + }], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 1963212417280098348, + "detailed_name": "float vector3::v[3]", + "qual_name_offset": 6, + "short_name": "v", + "spell": "3:9-3:10|3:3-3:13|1026|-1", + "type": 0, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 3348817847649945564, + "detailed_name": "float vector3::(anon struct)::x", + "qual_name_offset": 6, + "short_name": "x", + "spell": "2:18-2:19|2:12-2:19|1026|-1", + "type": 82, + "kind": 8, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 4821094820988543895, + "detailed_name": "float vector3::(anon struct)::y", + "qual_name_offset": 6, + "short_name": "y", + "spell": "2:21-2:22|2:12-2:22|1026|-1", + "type": 82, + "kind": 8, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15292551660437765731, + "detailed_name": "float vector3::(anon struct)::z", + "qual_name_offset": 6, + "short_name": "z", + "spell": "2:24-2:25|2:12-2:25|1026|-1", + "type": 82, + "kind": 8, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/types/typedefs.cc b/index_tests/types/typedefs.cc new file mode 100644 index 000000000..4a2844b70 --- /dev/null +++ b/index_tests/types/typedefs.cc @@ -0,0 +1,44 @@ +typedef int (func)(const int *a, const int *b); +static func g; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 8105378401105136463, + "detailed_name": "static int g(const int *, const int *)", + "qual_name_offset": 11, + "short_name": "g", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:13-2:14|2:1-2:14|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 10383876566159302459, + "detailed_name": "typedef int (func)(const int *, const int *)", + "qual_name_offset": 12, + "short_name": "func", + "spell": "1:14-1:18|1:1-1:47|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["2:8-2:12|4|-1"] + }], + "usr2var": [] +} +*/ \ No newline at end of file diff --git a/index_tests/unions/union_decl.cc b/index_tests/unions/union_decl.cc new file mode 100644 index 000000000..313c201e7 --- /dev/null +++ b/index_tests/unions/union_decl.cc @@ -0,0 +1,94 @@ +union Foo { + int a; + bool b; +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 37, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [8804696910588009104], + "uses": [] + }, { + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [9529311430721959843], + "uses": [] + }, { + "usr": 8501689086387244262, + "detailed_name": "union Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-4:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 9529311430721959843, + "R": 0 + }, { + "L": 8804696910588009104, + "R": 0 + }], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 8804696910588009104, + "detailed_name": "bool Foo::b", + "qual_name_offset": 5, + "short_name": "b", + "spell": "3:8-3:9|3:3-3:9|1026|-1", + "type": 37, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 9529311430721959843, + "detailed_name": "int Foo::a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "2:7-2:8|2:3-2:8|1026|-1", + "type": 53, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/unions/union_usage.cc b/index_tests/unions/union_usage.cc new file mode 100644 index 000000000..57bb2ebf4 --- /dev/null +++ b/index_tests/unions/union_usage.cc @@ -0,0 +1,129 @@ +union Foo { + int a : 5; + bool b : 3; +}; + +Foo f; + +void act(Foo*) { + f.a = 3; +} + +/* +// TODO: instantiations on Foo should include parameter? + +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 13982179977217945200, + "detailed_name": "void act(Foo *)", + "qual_name_offset": 5, + "short_name": "act", + "spell": "8:6-8:9|8:1-10:2|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 37, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [8804696910588009104], + "uses": [] + }, { + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [9529311430721959843], + "uses": [] + }, { + "usr": 8501689086387244262, + "detailed_name": "union Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-4:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 9529311430721959843, + "R": 0 + }, { + "L": 8804696910588009104, + "R": 0 + }], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [2933643612409209903], + "uses": ["6:1-6:4|4|-1", "8:10-8:13|4|-1"] + }], + "usr2var": [{ + "usr": 2933643612409209903, + "detailed_name": "Foo f", + "qual_name_offset": 4, + "short_name": "f", + "spell": "6:5-6:6|6:1-6:6|2|-1", + "type": 8501689086387244262, + "kind": 13, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "uses": ["9:3-9:4|4|-1"] + }, { + "usr": 8804696910588009104, + "detailed_name": "bool Foo::b : 3", + "qual_name_offset": 5, + "short_name": "b", + "spell": "3:8-3:9|3:3-3:13|1026|-1", + "type": 37, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 9529311430721959843, + "detailed_name": "int Foo::a : 5", + "qual_name_offset": 4, + "short_name": "a", + "spell": "2:7-2:8|2:3-2:12|1026|-1", + "type": 53, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": ["9:5-9:6|20|-1"] + }] +} +*/ diff --git a/index_tests/usage/func_called_from_constructor.cc b/index_tests/usage/func_called_from_constructor.cc new file mode 100644 index 000000000..d002eb453 --- /dev/null +++ b/index_tests/usage/func_called_from_constructor.cc @@ -0,0 +1,67 @@ +void called() {} + +struct Foo { + Foo(); +}; + +Foo::Foo() { + called(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 468307235068920063, + "detailed_name": "void called()", + "qual_name_offset": 5, + "short_name": "called", + "spell": "1:6-1:12|1:1-1:17|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["8:3-8:9|16420|-1"] + }, { + "usr": 3385168158331140247, + "detailed_name": "Foo::Foo()", + "qual_name_offset": 0, + "short_name": "Foo", + "spell": "7:6-7:9|7:1-9:2|1026|-1", + "bases": [], + "vars": [], + "callees": ["8:3-8:9|468307235068920063|3|16420"], + "kind": 9, + "parent_kind": 23, + "storage": 0, + "declarations": ["4:3-4:6|4:3-4:8|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "3:8-3:11|3:1-5:2|2|-1", + "bases": [], + "funcs": [3385168158331140247], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["4:3-4:6|4|-1", "7:1-7:4|4|-1", "7:6-7:9|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/func_called_from_macro_argument.cc b/index_tests/usage/func_called_from_macro_argument.cc new file mode 100644 index 000000000..2c7f43050 --- /dev/null +++ b/index_tests/usage/func_called_from_macro_argument.cc @@ -0,0 +1,60 @@ +#define MACRO_CALL(e) e + +bool called(bool a, bool b); + +void caller() { + MACRO_CALL(called(true, true)); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 3787803219955606747, + "detailed_name": "bool called(bool a, bool b)", + "qual_name_offset": 5, + "short_name": "called", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:6-3:12|3:1-3:28|1|-1"], + "derived": [], + "uses": ["6:14-6:20|16420|-1", "6:14-6:20|64|0"] + }, { + "usr": 11404881820527069090, + "detailed_name": "void caller()", + "qual_name_offset": 5, + "short_name": "caller", + "spell": "5:6-5:12|5:1-7:2|2|-1", + "bases": [], + "vars": [], + "callees": ["6:14-6:20|3787803219955606747|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [{ + "usr": 16326993795872073150, + "detailed_name": "MACRO_CALL", + "qual_name_offset": 0, + "short_name": "MACRO_CALL", + "hover": "#define MACRO_CALL(e) e", + "spell": "1:9-1:19|1:9-1:24|2|-1", + "type": 0, + "kind": 255, + "parent_kind": 1, + "storage": 0, + "declarations": [], + "uses": ["6:3-6:13|64|-1"] + }] +} +*/ \ No newline at end of file diff --git a/index_tests/usage/func_called_from_template.cc b/index_tests/usage/func_called_from_template.cc new file mode 100644 index 000000000..9ff9b66ce --- /dev/null +++ b/index_tests/usage/func_called_from_template.cc @@ -0,0 +1,81 @@ +void called(); + +template +void caller() { + called(); +} + +void foo() { + caller(); +} + +/* +// NOTE: without caller() instantation caller() is never visited so +// called() is never referenced. +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 468307235068920063, + "detailed_name": "void called()", + "qual_name_offset": 5, + "short_name": "called", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:6-1:12|1:1-1:14|1|-1"], + "derived": [], + "uses": ["5:3-5:9|16420|-1"] + }, { + "usr": 2459767597003442547, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "vars": [], + "callees": ["5:3-5:9|468307235068920063|3|16420"], + "kind": 0, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "8:6-8:9|8:1-10:2|2|-1", + "bases": [], + "vars": [], + "callees": ["9:3-9:9|10177235824697315808|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 10177235824697315808, + "detailed_name": "void caller()", + "qual_name_offset": 5, + "short_name": "caller", + "spell": "4:6-4:12|4:1-6:2|2|-1", + "bases": [], + "vars": [], + "callees": ["5:3-5:9|468307235068920063|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["9:3-9:9|16420|-1"] + }], + "usr2type": [], + "usr2var": [] +} +*/ \ No newline at end of file diff --git a/index_tests/usage/func_called_implicit_ctor.cc b/index_tests/usage/func_called_implicit_ctor.cc new file mode 100644 index 000000000..771ab3ce4 --- /dev/null +++ b/index_tests/usage/func_called_implicit_ctor.cc @@ -0,0 +1,81 @@ +struct Wrapper { + Wrapper(int i); +}; + +int called() { return 1; } + +Wrapper caller() { + return called(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 468307235068920063, + "detailed_name": "int called()", + "qual_name_offset": 4, + "short_name": "called", + "spell": "5:5-5:11|5:1-5:27|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["8:10-8:16|16420|-1"] + }, { + "usr": 10544127002917214589, + "detailed_name": "Wrapper::Wrapper(int i)", + "qual_name_offset": 0, + "short_name": "Wrapper", + "bases": [], + "vars": [], + "callees": [], + "kind": 9, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:3-2:10|2:3-2:17|1025|-1"], + "derived": [], + "uses": ["8:10-8:16|16676|-1"] + }, { + "usr": 11404881820527069090, + "detailed_name": "Wrapper caller()", + "qual_name_offset": 8, + "short_name": "caller", + "spell": "7:9-7:15|7:1-9:2|2|-1", + "bases": [], + "vars": [], + "callees": ["8:10-8:16|10544127002917214589|3|16676", "8:10-8:16|468307235068920063|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 13611487872560323389, + "detailed_name": "struct Wrapper {}", + "qual_name_offset": 7, + "short_name": "Wrapper", + "spell": "1:8-1:15|1:1-3:2|2|-1", + "bases": [], + "funcs": [10544127002917214589], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["2:3-2:10|4|-1", "7:1-7:8|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/func_usage_addr_func.cc b/index_tests/usage/func_usage_addr_func.cc new file mode 100644 index 000000000..b0c5fb4f2 --- /dev/null +++ b/index_tests/usage/func_usage_addr_func.cc @@ -0,0 +1,77 @@ +void consume(void (*)()) {} + +void used() {} + +void user() { + void (*x)() = &used; + consume(&used); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 5264867802674151787, + "detailed_name": "void used()", + "qual_name_offset": 5, + "short_name": "used", + "spell": "3:6-3:10|3:1-3:15|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["6:18-6:22|132|-1", "7:12-7:16|132|-1"] + }, { + "usr": 9376923949268137283, + "detailed_name": "void user()", + "qual_name_offset": 5, + "short_name": "user", + "spell": "5:6-5:10|5:1-8:2|2|-1", + "bases": [], + "vars": [16088407831770615719], + "callees": ["6:18-6:22|5264867802674151787|3|132", "6:18-6:22|5264867802674151787|3|132", "7:3-7:10|12924914488846929470|3|16420", "7:12-7:16|5264867802674151787|3|132"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 12924914488846929470, + "detailed_name": "void consume(void (*)())", + "qual_name_offset": 5, + "short_name": "consume", + "spell": "1:6-1:13|1:1-1:28|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["7:3-7:10|16420|-1"] + }], + "usr2type": [], + "usr2var": [{ + "usr": 16088407831770615719, + "detailed_name": "void (*x)()", + "qual_name_offset": 7, + "short_name": "x", + "hover": "void (*x)() = &used", + "spell": "6:10-6:11|6:3-6:22|2|-1", + "type": 0, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/func_usage_addr_method.cc b/index_tests/usage/func_usage_addr_method.cc new file mode 100644 index 000000000..3e0582daf --- /dev/null +++ b/index_tests/usage/func_usage_addr_method.cc @@ -0,0 +1,78 @@ +struct Foo { + void Used(); +}; + +void user() { + auto x = &Foo::Used; +} + + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 9376923949268137283, + "detailed_name": "void user()", + "qual_name_offset": 5, + "short_name": "user", + "spell": "5:6-5:10|5:1-7:2|2|-1", + "bases": [], + "vars": [4636142131003982569], + "callees": ["6:18-6:22|18417145003926999463|3|132", "6:18-6:22|18417145003926999463|3|132"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 18417145003926999463, + "detailed_name": "void Foo::Used()", + "qual_name_offset": 5, + "short_name": "Used", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:8-2:12|2:3-2:14|1025|-1"], + "derived": [], + "uses": ["6:18-6:22|132|-1"] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "1:8-1:11|1:1-3:2|2|-1", + "bases": [], + "funcs": [18417145003926999463], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["6:13-6:16|4|-1"] + }], + "usr2var": [{ + "usr": 4636142131003982569, + "detailed_name": "void (Foo::*)() x", + "qual_name_offset": 16, + "short_name": "x", + "hover": "void (Foo::*)() x = &Foo::Used", + "spell": "6:8-6:9|6:3-6:22|2|-1", + "type": 0, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/func_usage_call_func.cc b/index_tests/usage/func_usage_call_func.cc new file mode 100644 index 000000000..298411347 --- /dev/null +++ b/index_tests/usage/func_usage_call_func.cc @@ -0,0 +1,45 @@ +void called() {} +void caller() { + called(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 468307235068920063, + "detailed_name": "void called()", + "qual_name_offset": 5, + "short_name": "called", + "spell": "1:6-1:12|1:1-1:17|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["3:3-3:9|16420|-1"] + }, { + "usr": 11404881820527069090, + "detailed_name": "void caller()", + "qual_name_offset": 5, + "short_name": "caller", + "spell": "2:6-2:12|2:1-4:2|2|-1", + "bases": [], + "vars": [], + "callees": ["3:3-3:9|468307235068920063|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/func_usage_call_method.cc b/index_tests/usage/func_usage_call_method.cc new file mode 100644 index 000000000..6e86be512 --- /dev/null +++ b/index_tests/usage/func_usage_call_method.cc @@ -0,0 +1,78 @@ +struct Foo { + void Used(); +}; + +void user() { + Foo* f = nullptr; + f->Used(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 9376923949268137283, + "detailed_name": "void user()", + "qual_name_offset": 5, + "short_name": "user", + "spell": "5:6-5:10|5:1-8:2|2|-1", + "bases": [], + "vars": [14045150712868309451], + "callees": ["7:6-7:10|18417145003926999463|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 18417145003926999463, + "detailed_name": "void Foo::Used()", + "qual_name_offset": 5, + "short_name": "Used", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:8-2:12|2:3-2:14|1025|-1"], + "derived": [], + "uses": ["7:6-7:10|16420|-1"] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "1:8-1:11|1:1-3:2|2|-1", + "bases": [], + "funcs": [18417145003926999463], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [14045150712868309451], + "uses": ["6:3-6:6|4|-1"] + }], + "usr2var": [{ + "usr": 14045150712868309451, + "detailed_name": "Foo *f", + "qual_name_offset": 5, + "short_name": "f", + "hover": "Foo *f = nullptr", + "spell": "6:8-6:9|6:3-6:19|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["7:3-7:4|12|-1"] + }] +} +*/ diff --git a/index_tests/usage/func_usage_class_inline_var_def.cc b/index_tests/usage/func_usage_class_inline_var_def.cc new file mode 100644 index 000000000..f62581eae --- /dev/null +++ b/index_tests/usage/func_usage_class_inline_var_def.cc @@ -0,0 +1,82 @@ +static int helper() { + return 5; +} + +class Foo { + int x = helper(); +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 9630503130605430498, + "detailed_name": "static int helper()", + "qual_name_offset": 11, + "short_name": "helper", + "spell": "1:12-1:18|1:1-3:2|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["6:11-6:17|36|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [4220150017963593039], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "5:7-5:10|5:1-7:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 4220150017963593039, + "R": 0 + }], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 4220150017963593039, + "detailed_name": "int Foo::x", + "qual_name_offset": 4, + "short_name": "x", + "hover": "int Foo::x = helper()", + "spell": "6:7-6:8|6:3-6:19|1026|-1", + "type": 53, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/func_usage_forward_decl_func.cc b/index_tests/usage/func_usage_forward_decl_func.cc new file mode 100644 index 000000000..06a1e767b --- /dev/null +++ b/index_tests/usage/func_usage_forward_decl_func.cc @@ -0,0 +1,44 @@ +void foo(); + +void usage() { + foo(); +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:6-1:9|1:1-1:11|1|-1"], + "derived": [], + "uses": ["4:3-4:6|16420|-1"] + }, { + "usr": 6767773193109753523, + "detailed_name": "void usage()", + "qual_name_offset": 5, + "short_name": "usage", + "spell": "3:6-3:11|3:1-5:2|2|-1", + "bases": [], + "vars": [], + "callees": ["4:3-4:6|4259594751088586730|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/func_usage_forward_decl_method.cc b/index_tests/usage/func_usage_forward_decl_method.cc new file mode 100644 index 000000000..7da093ea0 --- /dev/null +++ b/index_tests/usage/func_usage_forward_decl_method.cc @@ -0,0 +1,77 @@ +struct Foo { + void foo(); +}; + +void usage() { + Foo* f = nullptr; + f->foo(); +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 6767773193109753523, + "detailed_name": "void usage()", + "qual_name_offset": 5, + "short_name": "usage", + "spell": "5:6-5:11|5:1-8:2|2|-1", + "bases": [], + "vars": [16229832321010999607], + "callees": ["7:6-7:9|17922201480358737771|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 17922201480358737771, + "detailed_name": "void Foo::foo()", + "qual_name_offset": 5, + "short_name": "foo", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:8-2:11|2:3-2:13|1025|-1"], + "derived": [], + "uses": ["7:6-7:9|16420|-1"] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "1:8-1:11|1:1-3:2|2|-1", + "bases": [], + "funcs": [17922201480358737771], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16229832321010999607], + "uses": ["6:3-6:6|4|-1"] + }], + "usr2var": [{ + "usr": 16229832321010999607, + "detailed_name": "Foo *f", + "qual_name_offset": 5, + "short_name": "f", + "hover": "Foo *f = nullptr", + "spell": "6:8-6:9|6:3-6:19|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["7:3-7:4|12|-1"] + }] +} +*/ diff --git a/index_tests/usage/func_usage_template_func.cc b/index_tests/usage/func_usage_template_func.cc new file mode 100644 index 000000000..6a812a46c --- /dev/null +++ b/index_tests/usage/func_usage_template_func.cc @@ -0,0 +1,47 @@ +template +void accept(T); + +void foo() { + accept(1); + accept(true); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "4:6-4:9|4:1-7:2|2|-1", + "bases": [], + "vars": [], + "callees": ["5:3-5:9|10585861037135727329|3|16420", "6:3-6:9|10585861037135727329|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 10585861037135727329, + "detailed_name": "void accept(T)", + "qual_name_offset": 5, + "short_name": "accept", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["2:6-2:12|2:1-2:15|1|-1"], + "derived": [], + "uses": ["5:3-5:9|16420|-1", "6:3-6:9|16420|-1"] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/type_usage_as_template_parameter.cc b/index_tests/usage/type_usage_as_template_parameter.cc new file mode 100644 index 000000000..f13a593f3 --- /dev/null +++ b/index_tests/usage/type_usage_as_template_parameter.cc @@ -0,0 +1,107 @@ +template +class unique_ptr {}; + +struct S {}; + +static unique_ptr f0; +static unique_ptr f1; + +unique_ptr* return_type() { + unique_ptr* local; + return nullptr; +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 16359708726068806331, + "detailed_name": "unique_ptr *return_type()", + "qual_name_offset": 15, + "short_name": "return_type", + "spell": "9:16-9:27|9:1-12:2|2|-1", + "bases": [], + "vars": [3364438781074774169], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 3286534761799572592, + "detailed_name": "class unique_ptr {}", + "qual_name_offset": 6, + "short_name": "unique_ptr", + "spell": "2:7-2:17|2:1-2:20|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [12857919739649552168, 18075066956054788088, 3364438781074774169], + "uses": ["6:8-6:18|4|-1", "7:8-7:18|4|-1", "9:1-9:11|4|-1", "10:3-10:13|4|-1"] + }, { + "usr": 4750332761459066907, + "detailed_name": "struct S {}", + "qual_name_offset": 7, + "short_name": "S", + "spell": "4:8-4:9|4:1-4:12|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["7:19-7:20|4|-1", "9:12-9:13|4|-1", "10:14-10:15|4|-1"] + }], + "usr2var": [{ + "usr": 3364438781074774169, + "detailed_name": "unique_ptr *local", + "qual_name_offset": 15, + "short_name": "local", + "spell": "10:18-10:23|10:3-10:23|2|-1", + "type": 3286534761799572592, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 12857919739649552168, + "detailed_name": "static unique_ptr f0", + "qual_name_offset": 24, + "short_name": "f0", + "spell": "6:25-6:27|6:1-6:27|2|-1", + "type": 3286534761799572592, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": [] + }, { + "usr": 18075066956054788088, + "detailed_name": "static unique_ptr f1", + "qual_name_offset": 21, + "short_name": "f1", + "spell": "7:22-7:24|7:1-7:24|2|-1", + "type": 3286534761799572592, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_as_template_parameter_complex.cc b/index_tests/usage/type_usage_as_template_parameter_complex.cc new file mode 100644 index 000000000..4dedd1317 --- /dev/null +++ b/index_tests/usage/type_usage_as_template_parameter_complex.cc @@ -0,0 +1,238 @@ +template +class unique_ptr; + +struct S1; +struct S2; + +#if false +VarDecl f + TemplateRef unique_ptr + TemplateRef unique_ptr + TypeRef struct S1 + TypeRef struct S2 + TypeRef struct S2 +#endif +extern unique_ptr, S2> f; + +#if false +FunctionDecl as_return_type + TemplateRef unique_ptr + TemplateRef unique_ptr + TypeRef struct S1 + TypeRef struct S2 + TypeRef struct S2 + ParmDecl + TemplateRef unique_ptr + TypeRef struct S1 + TypeRef struct S2 + CompoundStmt + ReturnStmt + UnexposedExpr + CXXNullPtrLiteralExpr +#endif +unique_ptr, S2>* as_return_type(unique_ptr*) { return nullptr; } + +#if false +FunctionDecl no_return_type + ParmDecl + CompoundStmt +#endif +void no_return_type(int) {} + +#if false +FunctionDecl empty + CompoundStmt + DeclStmt + VarDecl local + TemplateRef unique_ptr + TemplateRef unique_ptr + TypeRef struct S1 + TypeRef struct S2 + TypeRef struct S2 +#endif +void empty() { + unique_ptr, S2>* local; +} + +#if false +ClassDecl Foo + CXXMethod foo + TemplateRef unique_ptr + TypeRef struct S1 + TypeRef struct S2 +#endif +class Foo { + unique_ptr* foo(); +}; + +#if false +CXXMethod foo + TemplateRef unique_ptr + TypeRef struct S1 + TypeRef struct S2 + TypeRef class Foo + CompoundStmt + ReturnStmt + UnexposedExpr + CXXNullPtrLiteralExpr +#endif +unique_ptr* Foo::foo() { return nullptr; } + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": ["7:1-15:1", "17:1-33:1", "35:1-40:1", "42:1-53:1", "57:1-64:1", "68:1-79:1"], + "usr2func": [{ + "usr": 1246637699196435450, + "detailed_name": "unique_ptr, S2> *as_return_type(unique_ptr *)", + "qual_name_offset": 36, + "short_name": "as_return_type", + "spell": "33:37-33:51|33:1-33:92|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 13067214284561914253, + "detailed_name": "void no_return_type(int)", + "qual_name_offset": 5, + "short_name": "no_return_type", + "spell": "40:6-40:20|40:1-40:28|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 17922201480358737771, + "detailed_name": "unique_ptr *Foo::foo()", + "qual_name_offset": 20, + "short_name": "foo", + "spell": "79:26-79:29|79:1-79:51|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["65:23-65:26|65:3-65:28|1025|-1"], + "derived": [], + "uses": [] + }, { + "usr": 18320186404467436976, + "detailed_name": "void empty()", + "qual_name_offset": 5, + "short_name": "empty", + "spell": "53:6-53:11|53:1-55:2|2|-1", + "bases": [], + "vars": [500112618220246], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 4310164820010458371, + "detailed_name": "struct S1", + "qual_name_offset": 7, + "short_name": "S1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["4:8-4:10|4:1-4:10|1|-1"], + "derived": [], + "instances": [], + "uses": ["15:30-15:32|4|-1", "33:23-33:25|4|-1", "33:63-33:65|4|-1", "54:25-54:27|4|-1", "65:14-65:16|4|-1", "79:12-79:14|4|-1"] + }, { + "usr": 12728490517004312484, + "detailed_name": "struct S2", + "qual_name_offset": 7, + "short_name": "S2", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["5:8-5:10|5:1-5:10|1|-1"], + "derived": [], + "instances": [], + "uses": ["15:34-15:36|4|-1", "15:39-15:41|4|-1", "33:27-33:29|4|-1", "33:32-33:34|4|-1", "33:67-33:69|4|-1", "54:29-54:31|4|-1", "54:34-54:36|4|-1", "65:18-65:20|4|-1", "79:16-79:18|4|-1"] + }, { + "usr": 14209198335088845323, + "detailed_name": "class unique_ptr", + "qual_name_offset": 6, + "short_name": "unique_ptr", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": ["2:7-2:17|2:1-2:17|1|-1"], + "derived": [], + "instances": [2933643612409209903, 500112618220246], + "uses": ["15:8-15:18|4|-1", "15:19-15:29|4|-1", "33:1-33:11|4|-1", "33:12-33:22|4|-1", "33:52-33:62|4|-1", "54:3-54:13|4|-1", "54:14-54:24|4|-1", "65:3-65:13|4|-1", "79:1-79:11|4|-1"] + }, { + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "64:7-64:10|64:1-66:2|2|-1", + "bases": [], + "funcs": [17922201480358737771], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["79:21-79:24|4|-1"] + }], + "usr2var": [{ + "usr": 500112618220246, + "detailed_name": "unique_ptr, S2> *local", + "qual_name_offset": 36, + "short_name": "local", + "spell": "54:39-54:44|54:3-54:44|2|-1", + "type": 14209198335088845323, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 2933643612409209903, + "detailed_name": "extern unique_ptr, S2> f", + "qual_name_offset": 42, + "short_name": "f", + "type": 14209198335088845323, + "kind": 13, + "parent_kind": 0, + "storage": 1, + "declarations": ["15:43-15:44|15:1-15:44|1|-1"], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_as_template_parameter_simple.cc b/index_tests/usage/type_usage_as_template_parameter_simple.cc new file mode 100644 index 000000000..75c9d63ed --- /dev/null +++ b/index_tests/usage/type_usage_as_template_parameter_simple.cc @@ -0,0 +1,62 @@ +template +class unique_ptr {}; + +struct S; + +static unique_ptr foo; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 3286534761799572592, + "detailed_name": "class unique_ptr {}", + "qual_name_offset": 6, + "short_name": "unique_ptr", + "spell": "2:7-2:17|2:1-2:20|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [3398408600781120939], + "uses": ["6:8-6:18|4|-1"] + }, { + "usr": 4750332761459066907, + "detailed_name": "struct S", + "qual_name_offset": 7, + "short_name": "S", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["4:8-4:9|4:1-4:9|1|-1"], + "derived": [], + "instances": [], + "uses": ["6:19-6:20|4|-1"] + }], + "usr2var": [{ + "usr": 3398408600781120939, + "detailed_name": "static unique_ptr foo", + "qual_name_offset": 21, + "short_name": "foo", + "spell": "6:22-6:25|6:1-6:25|2|-1", + "type": 3286534761799572592, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_declare_extern.cc b/index_tests/usage/type_usage_declare_extern.cc new file mode 100644 index 000000000..afe6962f1 --- /dev/null +++ b/index_tests/usage/type_usage_declare_extern.cc @@ -0,0 +1,41 @@ +struct T {}; + +extern T t; +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 5673439900521455039, + "detailed_name": "struct T {}", + "qual_name_offset": 7, + "short_name": "T", + "spell": "1:8-1:9|1:1-1:12|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [1346710425945444872], + "uses": ["3:8-3:9|4|-1"] + }], + "usr2var": [{ + "usr": 1346710425945444872, + "detailed_name": "extern T t", + "qual_name_offset": 9, + "short_name": "t", + "type": 5673439900521455039, + "kind": 13, + "parent_kind": 0, + "storage": 1, + "declarations": ["3:10-3:11|3:1-3:11|1|-1"], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_declare_field.cc b/index_tests/usage/type_usage_declare_field.cc new file mode 100644 index 000000000..fe6f1afb9 --- /dev/null +++ b/index_tests/usage/type_usage_declare_field.cc @@ -0,0 +1,98 @@ +struct ForwardType; +struct ImplementedType {}; + +struct Foo { + ForwardType* a; + ImplementedType b; +}; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 8508299082070213750, + "detailed_name": "struct ImplementedType {}", + "qual_name_offset": 7, + "short_name": "ImplementedType", + "spell": "2:8-2:23|2:1-2:26|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [14727441168849658842], + "uses": ["6:3-6:18|4|-1"] + }, { + "usr": 13749354388332789217, + "detailed_name": "struct ForwardType", + "qual_name_offset": 7, + "short_name": "ForwardType", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:19|1:1-1:19|1|-1"], + "derived": [], + "instances": [14314859014962085433], + "uses": ["5:3-5:14|4|-1"] + }, { + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "4:8-4:11|4:1-7:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 14314859014962085433, + "R": 0 + }, { + "L": 14727441168849658842, + "R": 64 + }], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 14314859014962085433, + "detailed_name": "ForwardType *Foo::a", + "qual_name_offset": 13, + "short_name": "a", + "spell": "5:16-5:17|5:3-5:17|1026|-1", + "type": 13749354388332789217, + "kind": 8, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 14727441168849658842, + "detailed_name": "ImplementedType Foo::b", + "qual_name_offset": 16, + "short_name": "b", + "spell": "6:19-6:20|6:3-6:20|1026|-1", + "type": 8508299082070213750, + "kind": 8, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_declare_local.cc b/index_tests/usage/type_usage_declare_local.cc new file mode 100644 index 000000000..82415f552 --- /dev/null +++ b/index_tests/usage/type_usage_declare_local.cc @@ -0,0 +1,90 @@ +struct ForwardType; +struct ImplementedType {}; + +void Foo() { + ForwardType* a; + ImplementedType b; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4654328188330986029, + "detailed_name": "void Foo()", + "qual_name_offset": 5, + "short_name": "Foo", + "spell": "4:6-4:9|4:1-7:2|2|-1", + "bases": [], + "vars": [16374832544037266261, 2580122838476012357], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 8508299082070213750, + "detailed_name": "struct ImplementedType {}", + "qual_name_offset": 7, + "short_name": "ImplementedType", + "spell": "2:8-2:23|2:1-2:26|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [2580122838476012357], + "uses": ["6:3-6:18|4|-1"] + }, { + "usr": 13749354388332789217, + "detailed_name": "struct ForwardType", + "qual_name_offset": 7, + "short_name": "ForwardType", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:19|1:1-1:19|1|-1"], + "derived": [], + "instances": [16374832544037266261], + "uses": ["5:3-5:14|4|-1"] + }], + "usr2var": [{ + "usr": 2580122838476012357, + "detailed_name": "ImplementedType b", + "qual_name_offset": 16, + "short_name": "b", + "spell": "6:19-6:20|6:3-6:20|2|-1", + "type": 8508299082070213750, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 16374832544037266261, + "detailed_name": "ForwardType *a", + "qual_name_offset": 13, + "short_name": "a", + "spell": "5:16-5:17|5:3-5:17|2|-1", + "type": 13749354388332789217, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_declare_param.cc b/index_tests/usage/type_usage_declare_param.cc new file mode 100644 index 000000000..ae7c00063 --- /dev/null +++ b/index_tests/usage/type_usage_declare_param.cc @@ -0,0 +1,87 @@ +struct ForwardType; +struct ImplementedType {}; + +void foo(ForwardType* f, ImplementedType a) {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 1699390678058422036, + "detailed_name": "void foo(ForwardType *f, ImplementedType a)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "4:6-4:9|4:1-4:47|2|-1", + "bases": [], + "vars": [13058491096576226774, 11055777568039014776], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 8508299082070213750, + "detailed_name": "struct ImplementedType {}", + "qual_name_offset": 7, + "short_name": "ImplementedType", + "spell": "2:8-2:23|2:1-2:26|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [11055777568039014776], + "uses": ["4:26-4:41|4|-1"] + }, { + "usr": 13749354388332789217, + "detailed_name": "struct ForwardType", + "qual_name_offset": 7, + "short_name": "ForwardType", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:19|1:1-1:19|1|-1"], + "derived": [], + "instances": [13058491096576226774], + "uses": ["4:10-4:21|4|-1"] + }], + "usr2var": [{ + "usr": 11055777568039014776, + "detailed_name": "ImplementedType a", + "qual_name_offset": 16, + "short_name": "a", + "spell": "4:42-4:43|4:26-4:43|1026|-1", + "type": 8508299082070213750, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 13058491096576226774, + "detailed_name": "ForwardType *f", + "qual_name_offset": 13, + "short_name": "f", + "spell": "4:23-4:24|4:10-4:24|1026|-1", + "type": 13749354388332789217, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_declare_param_prototype.cc b/index_tests/usage/type_usage_declare_param_prototype.cc new file mode 100644 index 000000000..7c33416a7 --- /dev/null +++ b/index_tests/usage/type_usage_declare_param_prototype.cc @@ -0,0 +1,63 @@ +struct Foo; + +void foo(Foo* f, Foo*); +void foo(Foo* f, Foo*) {} + +/* +// TODO: No interesting usage on prototype. But maybe that's ok! +// TODO: We should have the same variable declared for both prototype and +// declaration. So it should have a usage marker on both. Then we could +// rename parameters! + +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 8908726657907936744, + "detailed_name": "void foo(Foo *f, Foo *)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "4:6-4:9|4:1-4:26|2|-1", + "bases": [], + "vars": [13823260660189154978], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:6-3:9|3:1-3:23|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo", + "qual_name_offset": 7, + "short_name": "Foo", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:11|1:1-1:11|1|-1"], + "derived": [], + "instances": [13823260660189154978], + "uses": ["3:10-3:13|4|-1", "3:18-3:21|4|-1", "4:10-4:13|4|-1", "4:18-4:21|4|-1"] + }], + "usr2var": [{ + "usr": 13823260660189154978, + "detailed_name": "Foo *f", + "qual_name_offset": 5, + "short_name": "f", + "spell": "4:15-4:16|4:10-4:16|1026|-1", + "type": 15041163540773201510, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_declare_param_unnamed.cc b/index_tests/usage/type_usage_declare_param_unnamed.cc new file mode 100644 index 000000000..292087394 --- /dev/null +++ b/index_tests/usage/type_usage_declare_param_unnamed.cc @@ -0,0 +1,43 @@ +struct ForwardType; +void foo(ForwardType*) {} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 15327735280790448926, + "detailed_name": "void foo(ForwardType *)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "2:6-2:9|2:1-2:26|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 13749354388332789217, + "detailed_name": "struct ForwardType", + "qual_name_offset": 7, + "short_name": "ForwardType", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:19|1:1-1:19|1|-1"], + "derived": [], + "instances": [], + "uses": ["2:10-2:21|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/type_usage_declare_qualifiers.cc b/index_tests/usage/type_usage_declare_qualifiers.cc new file mode 100644 index 000000000..b6131373b --- /dev/null +++ b/index_tests/usage/type_usage_declare_qualifiers.cc @@ -0,0 +1,123 @@ +struct Type {}; + +void foo(Type& a0, const Type& a1) { + Type a2; + Type* a3; + const Type* a4; + const Type* const a5 = nullptr; +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 16858540520096802573, + "detailed_name": "void foo(Type &a0, const Type &a1)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:6-3:9|3:1-8:2|2|-1", + "bases": [], + "vars": [7997456978847868736, 17228576662112939520, 15429032129697337561, 6081981442495435784, 5004072032239834773, 14939253431683105646], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 13487927231218873822, + "detailed_name": "struct Type {}", + "qual_name_offset": 7, + "short_name": "Type", + "spell": "1:8-1:12|1:1-1:15|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [7997456978847868736, 17228576662112939520, 15429032129697337561, 6081981442495435784, 5004072032239834773, 14939253431683105646], + "uses": ["3:10-3:14|4|-1", "3:26-3:30|4|-1", "4:3-4:7|4|-1", "5:3-5:7|4|-1", "6:9-6:13|4|-1", "7:9-7:13|4|-1"] + }], + "usr2var": [{ + "usr": 5004072032239834773, + "detailed_name": "const Type *a4", + "qual_name_offset": 12, + "short_name": "a4", + "spell": "6:15-6:17|6:3-6:17|2|-1", + "type": 13487927231218873822, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 6081981442495435784, + "detailed_name": "Type *a3", + "qual_name_offset": 6, + "short_name": "a3", + "spell": "5:9-5:11|5:3-5:11|2|-1", + "type": 13487927231218873822, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 7997456978847868736, + "detailed_name": "Type &a0", + "qual_name_offset": 6, + "short_name": "a0", + "spell": "3:16-3:18|3:10-3:18|1026|-1", + "type": 13487927231218873822, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 14939253431683105646, + "detailed_name": "const Type *const a5", + "qual_name_offset": 18, + "short_name": "a5", + "hover": "const Type *const a5 = nullptr", + "spell": "7:21-7:23|7:3-7:33|2|-1", + "type": 13487927231218873822, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 15429032129697337561, + "detailed_name": "Type a2", + "qual_name_offset": 5, + "short_name": "a2", + "spell": "4:8-4:10|4:3-4:10|2|-1", + "type": 13487927231218873822, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 17228576662112939520, + "detailed_name": "const Type &a1", + "qual_name_offset": 12, + "short_name": "a1", + "spell": "3:32-3:34|3:20-3:34|1026|-1", + "type": 13487927231218873822, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_declare_static.cc b/index_tests/usage/type_usage_declare_static.cc new file mode 100644 index 000000000..3957ceb21 --- /dev/null +++ b/index_tests/usage/type_usage_declare_static.cc @@ -0,0 +1,41 @@ +struct Type {}; +static Type t; +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 13487927231218873822, + "detailed_name": "struct Type {}", + "qual_name_offset": 7, + "short_name": "Type", + "spell": "1:8-1:12|1:1-1:15|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [6601831367240627080], + "uses": ["2:8-2:12|4|-1"] + }], + "usr2var": [{ + "usr": 6601831367240627080, + "detailed_name": "static Type t", + "qual_name_offset": 12, + "short_name": "t", + "spell": "2:13-2:14|2:1-2:14|2|-1", + "type": 13487927231218873822, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/type_usage_on_return_type.cc b/index_tests/usage/type_usage_on_return_type.cc new file mode 100644 index 000000000..cb6ef08e6 --- /dev/null +++ b/index_tests/usage/type_usage_on_return_type.cc @@ -0,0 +1,136 @@ +struct Type; + +Type* foo(); +Type* foo(); +Type* foo() { return nullptr; } + +class Foo { + Type* Get(int); + void Empty(); +}; + +Type* Foo::Get(int) { return nullptr; } +void Foo::Empty() {} + +extern const Type& external(); + +static Type* bar(); +static Type* bar() { return nullptr; } + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4240751906910175539, + "detailed_name": "void Foo::Empty()", + "qual_name_offset": 5, + "short_name": "Empty", + "spell": "13:11-13:16|13:1-13:21|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["9:8-9:13|9:3-9:15|1025|-1"], + "derived": [], + "uses": [] + }, { + "usr": 4259594751088586730, + "detailed_name": "Type *foo()", + "qual_name_offset": 6, + "short_name": "foo", + "spell": "5:7-5:10|5:1-5:32|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:7-3:10|3:1-3:12|1|-1", "4:7-4:10|4:1-4:12|1|-1"], + "derived": [], + "uses": [] + }, { + "usr": 7746867874366499515, + "detailed_name": "extern const Type &external()", + "qual_name_offset": 19, + "short_name": "external", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["15:20-15:28|15:1-15:30|1|-1"], + "derived": [], + "uses": [] + }, { + "usr": 13402221340333431092, + "detailed_name": "Type *Foo::Get(int)", + "qual_name_offset": 6, + "short_name": "Get", + "spell": "12:12-12:15|12:1-12:40|1026|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["8:9-8:12|8:3-8:17|1025|-1"], + "derived": [], + "uses": [] + }, { + "usr": 18408440185620243373, + "detailed_name": "static Type *bar()", + "qual_name_offset": 13, + "short_name": "bar", + "spell": "18:14-18:17|18:1-18:39|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["17:14-17:17|17:1-17:19|1|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 13487927231218873822, + "detailed_name": "struct Type", + "qual_name_offset": 7, + "short_name": "Type", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:12|1:1-1:12|1|-1"], + "derived": [], + "instances": [], + "uses": ["3:1-3:5|4|-1", "4:1-4:5|4|-1", "5:1-5:5|4|-1", "8:3-8:7|4|-1", "12:1-12:5|4|-1", "15:14-15:18|4|-1", "17:8-17:12|4|-1", "18:8-18:12|4|-1"] + }, { + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "7:7-7:10|7:1-10:2|2|-1", + "bases": [], + "funcs": [13402221340333431092, 4240751906910175539], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["12:7-12:10|4|-1", "13:6-13:9|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/type_usage_typedef_and_using.cc b/index_tests/usage/type_usage_typedef_and_using.cc new file mode 100644 index 000000000..58c82325d --- /dev/null +++ b/index_tests/usage/type_usage_typedef_and_using.cc @@ -0,0 +1,165 @@ +struct Foo; +using Foo1 = Foo*; +typedef Foo Foo2; +using Foo3 = Foo1; +using Foo4 = int; + +void accept(Foo*) {} +void accept1(Foo1*) {} +void accept2(Foo2*) {} +void accept3(Foo3*) {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 558620830317390922, + "detailed_name": "void accept1(Foo1 *)", + "qual_name_offset": 5, + "short_name": "accept1", + "spell": "8:6-8:13|8:1-8:23|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 9119341505144503905, + "detailed_name": "void accept(Foo *)", + "qual_name_offset": 5, + "short_name": "accept", + "spell": "7:6-7:12|7:1-7:21|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 10523262907746124479, + "detailed_name": "void accept2(Foo2 *)", + "qual_name_offset": 5, + "short_name": "accept2", + "spell": "9:6-9:13|9:1-9:23|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 14986366321326974406, + "detailed_name": "void accept3(Foo3 *)", + "qual_name_offset": 5, + "short_name": "accept3", + "spell": "10:6-10:13|10:1-10:23|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 1544499294580512394, + "detailed_name": "using Foo1 = Foo *", + "qual_name_offset": 6, + "short_name": "Foo1", + "spell": "2:7-2:11|2:1-2:18|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 15041163540773201510, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["4:14-4:18|4|-1", "8:14-8:18|4|-1"] + }, { + "usr": 2638219001294786365, + "detailed_name": "using Foo4 = int", + "qual_name_offset": 6, + "short_name": "Foo4", + "spell": "5:7-5:11|5:1-5:17|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "struct Foo", + "qual_name_offset": 7, + "short_name": "Foo", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:11|1:1-1:11|1|-1"], + "derived": [], + "instances": [], + "uses": ["2:14-2:17|4|-1", "3:9-3:12|4|-1", "7:13-7:16|4|-1"] + }, { + "usr": 15466821155413653804, + "detailed_name": "typedef Foo Foo2", + "qual_name_offset": 12, + "short_name": "Foo2", + "spell": "3:13-3:17|3:1-3:17|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 15041163540773201510, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["9:14-9:18|4|-1"] + }, { + "usr": 17897026942631673064, + "detailed_name": "using Foo3 = Foo1", + "qual_name_offset": 6, + "short_name": "Foo3", + "spell": "4:7-4:11|4:1-4:18|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 1544499294580512394, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["10:14-10:18|4|-1"] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/type_usage_typedef_and_using_template.cc b/index_tests/usage/type_usage_typedef_and_using_template.cc new file mode 100644 index 000000000..008484833 --- /dev/null +++ b/index_tests/usage/type_usage_typedef_and_using_template.cc @@ -0,0 +1,66 @@ +template +struct Foo; + +using Foo1 = Foo; +typedef Foo Foo2; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 1544499294580512394, + "detailed_name": "using Foo1 = Foo", + "qual_name_offset": 6, + "short_name": "Foo1", + "spell": "4:7-4:11|4:1-4:22|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 10528472276654770367, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["5:13-5:17|4|-1"] + }, { + "usr": 10528472276654770367, + "detailed_name": "struct Foo", + "qual_name_offset": 7, + "short_name": "Foo", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": ["2:8-2:11|2:1-2:11|1|-1"], + "derived": [], + "instances": [], + "uses": ["4:14-4:17|4|-1", "5:9-5:12|4|-1"] + }, { + "usr": 15933698173231330933, + "detailed_name": "typedef Foo Foo2", + "qual_name_offset": 18, + "short_name": "Foo2", + "spell": "5:19-5:23|5:1-5:23|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 10528472276654770367, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/type_usage_various.cc b/index_tests/usage/type_usage_various.cc new file mode 100644 index 000000000..d3c8959a6 --- /dev/null +++ b/index_tests/usage/type_usage_various.cc @@ -0,0 +1,76 @@ +class Foo { + Foo* make(); +}; + +Foo* Foo::make() { + Foo f; + return nullptr; +} + +extern Foo foo; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 9488177941273031343, + "detailed_name": "Foo *Foo::make()", + "qual_name_offset": 5, + "short_name": "make", + "spell": "5:11-5:15|5:1-8:2|1026|-1", + "bases": [], + "vars": [16380484338511689669], + "callees": [], + "kind": 6, + "parent_kind": 5, + "storage": 0, + "declarations": ["2:8-2:12|2:3-2:14|1025|-1"], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [9488177941273031343], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16380484338511689669, 14455976355866885943], + "uses": ["2:3-2:6|4|-1", "5:1-5:4|4|-1", "5:6-5:9|4|-1", "6:3-6:6|4|-1", "10:8-10:11|4|-1"] + }], + "usr2var": [{ + "usr": 14455976355866885943, + "detailed_name": "extern Foo foo", + "qual_name_offset": 11, + "short_name": "foo", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 0, + "storage": 1, + "declarations": ["10:12-10:15|10:1-10:15|1|-1"], + "uses": [] + }, { + "usr": 16380484338511689669, + "detailed_name": "Foo f", + "qual_name_offset": 4, + "short_name": "f", + "spell": "6:7-6:8|6:3-6:8|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 6, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/usage/usage_inside_of_call.cc b/index_tests/usage/usage_inside_of_call.cc new file mode 100644 index 000000000..44d654160 --- /dev/null +++ b/index_tests/usage/usage_inside_of_call.cc @@ -0,0 +1,142 @@ +void called(int a); + +int gen(); + +struct Foo { + static int static_var; + int field_var; +}; + +int Foo::static_var = 0; + +void foo() { + int a = 5; + called(a + gen() + Foo().field_var + Foo::static_var); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "12:6-12:9|12:1-15:2|2|-1", + "bases": [], + "vars": [8039186520399841081], + "callees": ["14:3-14:9|18319417758892371313|3|16420", "14:14-14:17|11404602816585117695|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 11404602816585117695, + "detailed_name": "int gen()", + "qual_name_offset": 4, + "short_name": "gen", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["3:5-3:8|3:1-3:10|1|-1"], + "derived": [], + "uses": ["14:14-14:17|16420|-1"] + }, { + "usr": 18319417758892371313, + "detailed_name": "void called(int a)", + "qual_name_offset": 5, + "short_name": "called", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:6-1:12|1:1-1:19|1|-1"], + "derived": [], + "uses": ["14:3-14:9|16420|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [11489549839875479478, 9648311402855509901, 11489549839875479478, 8039186520399841081], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "5:8-5:11|5:1-8:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 9648311402855509901, + "R": 0 + }], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["10:5-10:8|4|-1", "14:22-14:25|4|-1", "14:40-14:43|4|-1"] + }], + "usr2var": [{ + "usr": 8039186520399841081, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "hover": "int a = 5", + "spell": "13:7-13:8|13:3-13:12|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["14:10-14:11|12|-1"] + }, { + "usr": 9648311402855509901, + "detailed_name": "int Foo::field_var", + "qual_name_offset": 4, + "short_name": "field_var", + "spell": "7:7-7:16|7:3-7:16|1026|-1", + "type": 53, + "kind": 8, + "parent_kind": 23, + "storage": 0, + "declarations": [], + "uses": ["14:28-14:37|12|-1"] + }, { + "usr": 11489549839875479478, + "detailed_name": "static int Foo::static_var", + "qual_name_offset": 11, + "short_name": "static_var", + "spell": "10:10-10:20|10:1-10:24|1026|-1", + "type": 53, + "kind": 13, + "parent_kind": 23, + "storage": 2, + "declarations": ["6:14-6:24|6:3-6:24|1025|-1"], + "uses": ["14:45-14:55|12|-1"] + }] +} +*/ diff --git a/index_tests/usage/usage_inside_of_call_simple.cc b/index_tests/usage/usage_inside_of_call_simple.cc new file mode 100644 index 000000000..c2320556c --- /dev/null +++ b/index_tests/usage/usage_inside_of_call_simple.cc @@ -0,0 +1,62 @@ +void called(int a); + +int gen() { return 1; } + +void foo() { + called(gen() * gen()); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "5:6-5:9|5:1-7:2|2|-1", + "bases": [], + "vars": [], + "callees": ["6:3-6:9|18319417758892371313|3|16420", "6:10-6:13|11404602816585117695|3|16420", "6:18-6:21|11404602816585117695|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 11404602816585117695, + "detailed_name": "int gen()", + "qual_name_offset": 4, + "short_name": "gen", + "spell": "3:5-3:8|3:1-3:24|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["6:10-6:13|16420|-1", "6:18-6:21|16420|-1"] + }, { + "usr": 18319417758892371313, + "detailed_name": "void called(int a)", + "qual_name_offset": 5, + "short_name": "called", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["1:6-1:12|1:1-1:19|1|-1"], + "derived": [], + "uses": ["6:3-6:9|16420|-1"] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/usage/var_usage_call_function.cc b/index_tests/usage/var_usage_call_function.cc new file mode 100644 index 000000000..0a0df74fb --- /dev/null +++ b/index_tests/usage/var_usage_call_function.cc @@ -0,0 +1,62 @@ +void called() {} + +void caller() { + auto x = &called; + x(); + + called(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 468307235068920063, + "detailed_name": "void called()", + "qual_name_offset": 5, + "short_name": "called", + "spell": "1:6-1:12|1:1-1:17|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": ["4:13-4:19|132|-1", "7:3-7:9|16420|-1"] + }, { + "usr": 11404881820527069090, + "detailed_name": "void caller()", + "qual_name_offset": 5, + "short_name": "caller", + "spell": "3:6-3:12|3:1-8:2|2|-1", + "bases": [], + "vars": [9121974011454213596], + "callees": ["4:13-4:19|468307235068920063|3|132", "4:13-4:19|468307235068920063|3|132", "7:3-7:9|468307235068920063|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [{ + "usr": 9121974011454213596, + "detailed_name": "void (*)() x", + "qual_name_offset": 11, + "short_name": "x", + "hover": "void (*)() x = &called", + "spell": "4:8-4:9|4:3-4:19|2|-1", + "type": 0, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["5:3-5:4|16428|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_class_member.cc b/index_tests/usage/var_usage_class_member.cc new file mode 100644 index 000000000..9f230cd5d --- /dev/null +++ b/index_tests/usage/var_usage_class_member.cc @@ -0,0 +1,147 @@ +class Foo { +public: + int x; + int y; +}; + +void accept(int); +void accept(int*); + +void foo() { + Foo f; + f.x = 3; + f.x += 5; + accept(f.x); + accept(f.x + 20); + accept(&f.x); + accept(f.y); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "10:6-10:9|10:1-18:2|2|-1", + "bases": [], + "vars": [14669930844300034456], + "callees": ["14:3-14:9|17175780305784503374|3|16420", "15:3-15:9|17175780305784503374|3|16420", "16:3-16:9|12086644540399881766|3|16420", "17:3-17:9|17175780305784503374|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 12086644540399881766, + "detailed_name": "void accept(int *)", + "qual_name_offset": 5, + "short_name": "accept", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["8:6-8:12|8:1-8:18|1|-1"], + "derived": [], + "uses": ["16:3-16:9|16420|-1"] + }, { + "usr": 17175780305784503374, + "detailed_name": "void accept(int)", + "qual_name_offset": 5, + "short_name": "accept", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["7:6-7:12|7:1-7:17|1|-1"], + "derived": [], + "uses": ["14:3-14:9|16420|-1", "15:3-15:9|16420|-1", "17:3-17:9|16420|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [4220150017963593039, 3873837747174060388], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-5:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 4220150017963593039, + "R": 0 + }, { + "L": 3873837747174060388, + "R": 32 + }], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [14669930844300034456], + "uses": ["11:3-11:6|4|-1"] + }], + "usr2var": [{ + "usr": 3873837747174060388, + "detailed_name": "int Foo::y", + "qual_name_offset": 4, + "short_name": "y", + "spell": "4:7-4:8|4:3-4:8|1026|-1", + "type": 53, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": ["17:12-17:13|12|-1"] + }, { + "usr": 4220150017963593039, + "detailed_name": "int Foo::x", + "qual_name_offset": 4, + "short_name": "x", + "spell": "3:7-3:8|3:3-3:8|1026|-1", + "type": 53, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": ["12:5-12:6|20|-1", "13:5-13:6|4|-1", "14:12-14:13|12|-1", "15:12-15:13|12|-1", "16:13-16:14|132|-1"] + }, { + "usr": 14669930844300034456, + "detailed_name": "Foo f", + "qual_name_offset": 4, + "short_name": "f", + "spell": "11:7-11:8|11:3-11:8|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["12:3-12:4|4|-1", "13:3-13:4|4|-1", "14:10-14:11|4|-1", "15:10-15:11|4|-1", "16:11-16:12|4|-1", "17:10-17:11|4|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_class_member_static.cc b/index_tests/usage/var_usage_class_member_static.cc new file mode 100644 index 000000000..1206c2d8d --- /dev/null +++ b/index_tests/usage/var_usage_class_member_static.cc @@ -0,0 +1,93 @@ +struct Foo { + static int x; +}; + +void accept(int); + +void foo() { + accept(Foo::x); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "7:6-7:9|7:1-9:2|2|-1", + "bases": [], + "vars": [], + "callees": ["8:3-8:9|17175780305784503374|3|16420"], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }, { + "usr": 17175780305784503374, + "detailed_name": "void accept(int)", + "qual_name_offset": 5, + "short_name": "accept", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": ["5:6-5:12|5:1-5:17|1|-1"], + "derived": [], + "uses": ["8:3-8:9|16420|-1"] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [8599782646965457351], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "struct Foo {}", + "qual_name_offset": 7, + "short_name": "Foo", + "spell": "1:8-1:11|1:1-3:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["8:10-8:13|4|-1"] + }], + "usr2var": [{ + "usr": 8599782646965457351, + "detailed_name": "static int Foo::x", + "qual_name_offset": 11, + "short_name": "x", + "type": 53, + "kind": 13, + "parent_kind": 23, + "storage": 2, + "declarations": ["2:14-2:15|2:3-2:15|1025|-1"], + "uses": ["8:15-8:16|12|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_cstyle_cast.cc b/index_tests/usage/var_usage_cstyle_cast.cc new file mode 100644 index 000000000..e041ffba4 --- /dev/null +++ b/index_tests/usage/var_usage_cstyle_cast.cc @@ -0,0 +1,135 @@ +enum VarType {}; + +struct Holder { + static constexpr VarType static_var = (VarType)0x0; +}; + +const VarType Holder::static_var; + + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 5792006888140599735, + "detailed_name": "enum VarType {}", + "qual_name_offset": 5, + "short_name": "VarType", + "spell": "1:6-1:13|1:1-1:16|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 10, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [7057400933868440116, 7057400933868440116], + "uses": ["4:20-4:27|4|-1", "4:42-4:49|4|-1", "7:7-7:14|4|-1"] + }, { + "usr": 10028537921178202800, + "detailed_name": "struct Holder {}", + "qual_name_offset": 7, + "short_name": "Holder", + "spell": "3:8-3:14|3:1-5:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["7:15-7:21|4|-1"] + }], + "usr2var": [{ + "usr": 7057400933868440116, + "detailed_name": "static constexpr VarType Holder::static_var", + "qual_name_offset": 25, + "short_name": "static_var", + "hover": "static constexpr VarType Holder::static_var = (VarType)0x0", + "spell": "7:23-7:33|7:1-7:33|1026|-1", + "type": 5792006888140599735, + "kind": 13, + "parent_kind": 23, + "storage": 2, + "declarations": ["4:28-4:38|4:3-4:53|1025|-1"], + "uses": [] + }] +} +*/ + + + + + + + + + + + + + + + + + + + + + + + + + +//#include +//#include + +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include diff --git a/index_tests/usage/var_usage_extern.cc b/index_tests/usage/var_usage_extern.cc new file mode 100644 index 000000000..14cb93e7f --- /dev/null +++ b/index_tests/usage/var_usage_extern.cc @@ -0,0 +1,57 @@ +extern int a; + +void foo() { + a = 5; +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:6-3:9|3:1-5:2|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [16721564935990383768], + "uses": [] + }], + "usr2var": [{ + "usr": 16721564935990383768, + "detailed_name": "extern int a", + "qual_name_offset": 11, + "short_name": "a", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 1, + "declarations": ["1:12-1:13|1:1-1:13|1|-1"], + "uses": ["4:3-4:4|20|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_func_parameter.cc b/index_tests/usage/var_usage_func_parameter.cc new file mode 100644 index 000000000..069c72628 --- /dev/null +++ b/index_tests/usage/var_usage_func_parameter.cc @@ -0,0 +1,56 @@ +void foo(int a) { + a += 10; +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 11998306017310352355, + "detailed_name": "void foo(int a)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-3:2|2|-1", + "bases": [], + "vars": [10063793875496522529], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [10063793875496522529], + "uses": [] + }], + "usr2var": [{ + "usr": 10063793875496522529, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "1:14-1:15|1:10-1:15|1026|-1", + "type": 53, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["2:3-2:4|4|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_local.cc b/index_tests/usage/var_usage_local.cc new file mode 100644 index 000000000..577d810c5 --- /dev/null +++ b/index_tests/usage/var_usage_local.cc @@ -0,0 +1,57 @@ +void foo() { + int x; + x = 3; +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-4:2|2|-1", + "bases": [], + "vars": [14014650769929566957], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [14014650769929566957], + "uses": [] + }], + "usr2var": [{ + "usr": 14014650769929566957, + "detailed_name": "int x", + "qual_name_offset": 4, + "short_name": "x", + "spell": "2:7-2:8|2:3-2:8|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["3:3-3:4|20|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_shadowed_local.cc b/index_tests/usage/var_usage_shadowed_local.cc new file mode 100644 index 000000000..efe2d4604 --- /dev/null +++ b/index_tests/usage/var_usage_shadowed_local.cc @@ -0,0 +1,74 @@ +void foo() { + int a; + a = 1; + { + int a; + a = 2; + } + a = 3; +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-9:2|2|-1", + "bases": [], + "vars": [13311055950748663970, 14036425367303419504], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [13311055950748663970, 14036425367303419504], + "uses": [] + }], + "usr2var": [{ + "usr": 13311055950748663970, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "2:7-2:8|2:3-2:8|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["3:3-3:4|20|-1", "8:3-8:4|20|-1"] + }, { + "usr": 14036425367303419504, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "5:9-5:10|5:5-5:10|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["6:5-6:6|20|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_shadowed_parameter.cc b/index_tests/usage/var_usage_shadowed_parameter.cc new file mode 100644 index 000000000..dfa4a6ca0 --- /dev/null +++ b/index_tests/usage/var_usage_shadowed_parameter.cc @@ -0,0 +1,74 @@ +void foo(int a) { + a = 1; + { + int a; + a = 2; + } + a = 3; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 11998306017310352355, + "detailed_name": "void foo(int a)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-8:2|2|-1", + "bases": [], + "vars": [11608231465452906059, 6997229590862003559], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [11608231465452906059, 6997229590862003559], + "uses": [] + }], + "usr2var": [{ + "usr": 6997229590862003559, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "4:9-4:10|4:5-4:10|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["5:5-5:6|20|-1"] + }, { + "usr": 11608231465452906059, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "1:14-1:15|1:10-1:15|1026|-1", + "type": 53, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["2:3-2:4|20|-1", "7:3-7:4|20|-1"] + }] +} +*/ diff --git a/index_tests/usage/var_usage_static.cc b/index_tests/usage/var_usage_static.cc new file mode 100644 index 000000000..705fddff9 --- /dev/null +++ b/index_tests/usage/var_usage_static.cc @@ -0,0 +1,59 @@ +static int a; + +void foo() { + a = 3; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:6-3:9|3:1-5:2|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [11823161916242867318], + "uses": [] + }], + "usr2var": [{ + "usr": 11823161916242867318, + "detailed_name": "static int a", + "qual_name_offset": 11, + "short_name": "a", + "spell": "1:12-1:13|1:1-1:13|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": ["4:3-4:4|20|-1"] + }] +} +*/ diff --git a/index_tests/vars/class_member.cc b/index_tests/vars/class_member.cc new file mode 100644 index 000000000..9b62aec7e --- /dev/null +++ b/index_tests/vars/class_member.cc @@ -0,0 +1,45 @@ +class Foo { + Foo* member; +}; +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [{ + "L": 13799811842374292251, + "R": 0 + }], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [13799811842374292251], + "uses": ["2:3-2:6|4|-1"] + }], + "usr2var": [{ + "usr": 13799811842374292251, + "detailed_name": "Foo *Foo::member", + "qual_name_offset": 5, + "short_name": "member", + "spell": "2:8-2:14|2:3-2:14|1026|-1", + "type": 15041163540773201510, + "kind": 8, + "parent_kind": 5, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/class_static_member.cc b/index_tests/vars/class_static_member.cc new file mode 100644 index 000000000..3c67ac6ec --- /dev/null +++ b/index_tests/vars/class_static_member.cc @@ -0,0 +1,44 @@ +class Foo { + static Foo* member; +}; +Foo* Foo::member = nullptr; + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [5844987037615239736, 5844987037615239736], + "uses": ["2:10-2:13|4|-1", "4:1-4:4|4|-1", "4:6-4:9|4|-1"] + }], + "usr2var": [{ + "usr": 5844987037615239736, + "detailed_name": "static Foo *Foo::member", + "qual_name_offset": 12, + "short_name": "member", + "spell": "4:11-4:17|4:1-4:27|1026|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 5, + "storage": 2, + "declarations": ["2:15-2:21|2:3-2:21|1025|-1"], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/class_static_member_decl_only.cc b/index_tests/vars/class_static_member_decl_only.cc new file mode 100644 index 000000000..0cf61b2c6 --- /dev/null +++ b/index_tests/vars/class_static_member_decl_only.cc @@ -0,0 +1,57 @@ +class Foo { + static int member; +}; +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [5844987037615239736], + "uses": [] + }, { + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-3:2|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": [] + }], + "usr2var": [{ + "usr": 5844987037615239736, + "detailed_name": "static int Foo::member", + "qual_name_offset": 11, + "short_name": "member", + "type": 53, + "kind": 13, + "parent_kind": 5, + "storage": 2, + "declarations": ["2:14-2:20|2:3-2:20|1025|-1"], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/deduce_auto_type.cc b/index_tests/vars/deduce_auto_type.cc new file mode 100644 index 000000000..7e9c0cbc2 --- /dev/null +++ b/index_tests/vars/deduce_auto_type.cc @@ -0,0 +1,74 @@ +class Foo {}; +void f() { + auto x = new Foo(); + auto* y = new Foo(); +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 880549676430489861, + "detailed_name": "void f()", + "qual_name_offset": 5, + "short_name": "f", + "spell": "2:6-2:7|2:1-5:2|2|-1", + "bases": [], + "vars": [10601729374837386290, 18422884837902130475], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "class Foo {}", + "qual_name_offset": 6, + "short_name": "Foo", + "spell": "1:7-1:10|1:1-1:13|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 5, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [10601729374837386290, 18422884837902130475], + "uses": ["3:16-3:19|4|-1", "4:17-4:20|4|-1"] + }], + "usr2var": [{ + "usr": 10601729374837386290, + "detailed_name": "Foo *x", + "qual_name_offset": 5, + "short_name": "x", + "hover": "Foo *x = new Foo()", + "spell": "3:8-3:9|3:3-3:21|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 18422884837902130475, + "detailed_name": "Foo *y", + "qual_name_offset": 5, + "short_name": "y", + "hover": "Foo *y = new Foo()", + "spell": "4:9-4:10|4:3-4:22|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/function_local.cc b/index_tests/vars/function_local.cc new file mode 100644 index 000000000..70a985bab --- /dev/null +++ b/index_tests/vars/function_local.cc @@ -0,0 +1,59 @@ +struct Foo; + +void foo() { + Foo* a; +} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:6-3:9|3:1-5:2|2|-1", + "bases": [], + "vars": [13198746475679542317], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo", + "qual_name_offset": 7, + "short_name": "Foo", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:11|1:1-1:11|1|-1"], + "derived": [], + "instances": [13198746475679542317], + "uses": ["4:3-4:6|4|-1"] + }], + "usr2var": [{ + "usr": 13198746475679542317, + "detailed_name": "Foo *a", + "qual_name_offset": 5, + "short_name": "a", + "spell": "4:8-4:9|4:3-4:9|2|-1", + "type": 15041163540773201510, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/function_param.cc b/index_tests/vars/function_param.cc new file mode 100644 index 000000000..751fbd651 --- /dev/null +++ b/index_tests/vars/function_param.cc @@ -0,0 +1,69 @@ +struct Foo; + +void foo(Foo* p0, Foo* p1) {} + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 8908726657907936744, + "detailed_name": "void foo(Foo *p0, Foo *p1)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "3:6-3:9|3:1-3:30|2|-1", + "bases": [], + "vars": [8730439006497971620, 2525014371090380500], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 15041163540773201510, + "detailed_name": "struct Foo", + "qual_name_offset": 7, + "short_name": "Foo", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": ["1:8-1:11|1:1-1:11|1|-1"], + "derived": [], + "instances": [8730439006497971620, 2525014371090380500], + "uses": ["3:10-3:13|4|-1", "3:19-3:22|4|-1"] + }], + "usr2var": [{ + "usr": 2525014371090380500, + "detailed_name": "Foo *p1", + "qual_name_offset": 5, + "short_name": "p1", + "spell": "3:24-3:26|3:19-3:26|1026|-1", + "type": 15041163540773201510, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 8730439006497971620, + "detailed_name": "Foo *p0", + "qual_name_offset": 5, + "short_name": "p0", + "spell": "3:15-3:17|3:10-3:17|1026|-1", + "type": 15041163540773201510, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/function_param_unnamed.cc b/index_tests/vars/function_param_unnamed.cc new file mode 100644 index 000000000..bd8c9d84b --- /dev/null +++ b/index_tests/vars/function_param_unnamed.cc @@ -0,0 +1,26 @@ +void foo(int, int) {} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 2747674671862363334, + "detailed_name": "void foo(int, int)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-1:22|2|-1", + "bases": [], + "vars": [], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [], + "usr2var": [] +} +*/ diff --git a/index_tests/vars/function_shadow_local.cc b/index_tests/vars/function_shadow_local.cc new file mode 100644 index 000000000..0ccc91e30 --- /dev/null +++ b/index_tests/vars/function_shadow_local.cc @@ -0,0 +1,74 @@ +void foo() { + int a; + a = 1; + { + int a; + a = 2; + } + a = 3; +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4259594751088586730, + "detailed_name": "void foo()", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-9:2|2|-1", + "bases": [], + "vars": [1894874819807168345, 4508045017817092115], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [1894874819807168345, 4508045017817092115], + "uses": [] + }], + "usr2var": [{ + "usr": 1894874819807168345, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "2:7-2:8|2:3-2:8|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["3:3-3:4|20|-1", "8:3-8:4|20|-1"] + }, { + "usr": 4508045017817092115, + "detailed_name": "int a", + "qual_name_offset": 4, + "short_name": "a", + "spell": "5:9-5:10|5:5-5:10|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": ["6:5-6:6|20|-1"] + }] +} +*/ diff --git a/index_tests/vars/function_shadow_param.cc b/index_tests/vars/function_shadow_param.cc new file mode 100644 index 000000000..80f8d4f1c --- /dev/null +++ b/index_tests/vars/function_shadow_param.cc @@ -0,0 +1,69 @@ +void foo(int p) { + { int p = 0; } +} +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 11998306017310352355, + "detailed_name": "void foo(int p)", + "qual_name_offset": 5, + "short_name": "foo", + "spell": "1:6-1:9|1:1-3:2|2|-1", + "bases": [], + "vars": [5875271969926422921, 11404600766177939811], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [5875271969926422921, 11404600766177939811], + "uses": [] + }], + "usr2var": [{ + "usr": 5875271969926422921, + "detailed_name": "int p", + "qual_name_offset": 4, + "short_name": "p", + "spell": "1:14-1:15|1:10-1:15|1026|-1", + "type": 53, + "kind": 253, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }, { + "usr": 11404600766177939811, + "detailed_name": "int p", + "qual_name_offset": 4, + "short_name": "p", + "hover": "int p = 0", + "spell": "2:9-2:10|2:5-2:14|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/global_variable.cc b/index_tests/vars/global_variable.cc new file mode 100644 index 000000000..3ce2d1192 --- /dev/null +++ b/index_tests/vars/global_variable.cc @@ -0,0 +1,40 @@ +static int global = 0; +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [6834525061342585382], + "uses": [] + }], + "usr2var": [{ + "usr": 6834525061342585382, + "detailed_name": "static int global", + "qual_name_offset": 11, + "short_name": "global", + "hover": "static int global = 0", + "spell": "1:12-1:18|1:1-1:22|2|-1", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 2, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/global_variable_decl_only.cc b/index_tests/vars/global_variable_decl_only.cc new file mode 100644 index 000000000..1ba9f2e4c --- /dev/null +++ b/index_tests/vars/global_variable_decl_only.cc @@ -0,0 +1,38 @@ +extern int global; +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [], + "usr2type": [{ + "usr": 53, + "detailed_name": "", + "qual_name_offset": 0, + "short_name": "", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 0, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [9937941849651546906], + "uses": [] + }], + "usr2var": [{ + "usr": 9937941849651546906, + "detailed_name": "extern int global", + "qual_name_offset": 11, + "short_name": "global", + "type": 53, + "kind": 13, + "parent_kind": 0, + "storage": 1, + "declarations": ["1:12-1:18|1:1-1:18|1|-1"], + "uses": [] + }] +} +*/ diff --git a/index_tests/vars/type_instance_on_using_type.cc b/index_tests/vars/type_instance_on_using_type.cc new file mode 100644 index 000000000..ce3607eb2 --- /dev/null +++ b/index_tests/vars/type_instance_on_using_type.cc @@ -0,0 +1,79 @@ +struct S {}; +using F = S; +void Foo() { + F a; +} + +// TODO: Should we also add a usage to |S|? + +/* +OUTPUT: +{ + "includes": [], + "skipped_ranges": [], + "usr2func": [{ + "usr": 4654328188330986029, + "detailed_name": "void Foo()", + "qual_name_offset": 5, + "short_name": "Foo", + "spell": "3:6-3:9|3:1-5:2|2|-1", + "bases": [], + "vars": [6975456769752895964], + "callees": [], + "kind": 12, + "parent_kind": 0, + "storage": 0, + "declarations": [], + "derived": [], + "uses": [] + }], + "usr2type": [{ + "usr": 4750332761459066907, + "detailed_name": "struct S {}", + "qual_name_offset": 7, + "short_name": "S", + "spell": "1:8-1:9|1:1-1:12|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 0, + "kind": 23, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [], + "uses": ["2:11-2:12|4|-1"] + }, { + "usr": 7434820806199665424, + "detailed_name": "using F = S", + "qual_name_offset": 6, + "short_name": "F", + "spell": "2:7-2:8|2:1-2:12|2|-1", + "bases": [], + "funcs": [], + "types": [], + "vars": [], + "alias_of": 4750332761459066907, + "kind": 252, + "parent_kind": 0, + "declarations": [], + "derived": [], + "instances": [6975456769752895964], + "uses": ["4:3-4:4|4|-1"] + }], + "usr2var": [{ + "usr": 6975456769752895964, + "detailed_name": "F a", + "qual_name_offset": 2, + "short_name": "a", + "spell": "4:5-4:6|4:3-4:6|2|-1", + "type": 7434820806199665424, + "kind": 13, + "parent_kind": 12, + "storage": 0, + "declarations": [], + "uses": [] + }] +} +*/ diff --git a/message_handler.hh b/message_handler.hh new file mode 100644 index 000000000..9932e6149 --- /dev/null +++ b/message_handler.hh @@ -0,0 +1,266 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "lsp.h" +#include "query.h" + +#include +#include +#include +#include +#include + +struct CompletionManager; +struct Config; +struct GroupMatch; +struct VFS; +struct IncludeComplete; +struct MultiQueueWaiter; +struct Project; +struct DB; +struct WorkingFile; +struct WorkingFiles; + +namespace ccls { +namespace pipeline { +void Reply(lsRequestId id, const std::function &fn); +void ReplyError(lsRequestId id, const std::function &fn); +} + +struct EmptyParam { + bool placeholder; +}; +struct TextDocumentParam { + lsTextDocumentIdentifier textDocument; +}; +struct DidOpenTextDocumentParam { + lsTextDocumentItem textDocument; +}; + +struct TextDocumentPositionParam { + lsTextDocumentIdentifier textDocument; + lsPosition position; +}; + +struct RenameParam { + lsTextDocumentIdentifier textDocument; + lsPosition position; + std::string newName; +}; + +// code* +struct CodeActionParam { + lsTextDocumentIdentifier textDocument; + lsRange range; + struct Context { + std::vector diagnostics; + } context; +}; + +// completion +enum class lsCompletionTriggerKind { + Invoked = 1, + TriggerCharacter = 2, + TriggerForIncompleteCompletions = 3, +}; +struct lsCompletionContext { + lsCompletionTriggerKind triggerKind = lsCompletionTriggerKind::Invoked; + std::optional triggerCharacter; +}; +struct lsCompletionParams : TextDocumentPositionParam { + lsCompletionContext context; +}; +enum class lsCompletionItemKind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, +}; +enum class lsInsertTextFormat { + PlainText = 1, + Snippet = 2 +}; +struct lsCompletionItem { + std::string label; + lsCompletionItemKind kind = lsCompletionItemKind::Text; + std::string detail; + std::optional documentation; + std::string sortText; + std::optional filterText; + std::string insertText; + lsInsertTextFormat insertTextFormat = lsInsertTextFormat::PlainText; + lsTextEdit textEdit; + std::vector additionalTextEdits; + + std::vector parameters_; + int score_; + unsigned priority_; + bool use_angle_brackets_ = false; +}; + +// formatting +struct FormattingOptions { + int tabSize; + bool insertSpaces; +}; +struct DocumentFormattingParam { + lsTextDocumentIdentifier textDocument; + FormattingOptions options; +}; +struct DocumentOnTypeFormattingParam { + lsTextDocumentIdentifier textDocument; + lsPosition position; + std::string ch; + FormattingOptions options; +}; +struct DocumentRangeFormattingParam { + lsTextDocumentIdentifier textDocument; + lsRange range; + FormattingOptions options; +}; + +// workspace +enum class FileChangeType { + Created = 1, + Changed = 2, + Deleted = 3, +}; +struct DidChangeWatchedFilesParam { + struct Event { + lsDocumentUri uri; + FileChangeType type; + }; + std::vector changes; +}; +struct DidChangeWorkspaceFoldersParam { + struct Event { + std::vector added, removed; + } event; +}; +struct WorkspaceSymbolParam { + std::string query; +}; + +// TODO llvm 8 llvm::unique_function +template +using Callback = std::function; + +struct ReplyOnce { + lsRequestId id; + template void operator()(Res &result) const { + if (id.Valid()) + pipeline::Reply(id, [&](Writer &w) { Reflect(w, result); }); + } + template void Error(Err &err) const { + if (id.Valid()) + pipeline::ReplyError(id, [&](Writer &w) { Reflect(w, err); }); + } +}; + +struct MessageHandler { + DB *db = nullptr; + Project *project = nullptr; + VFS *vfs = nullptr; + WorkingFiles *working_files = nullptr; + CompletionManager *clang_complete = nullptr; + IncludeComplete *include_complete = nullptr; + + llvm::StringMap> method2notification; + llvm::StringMap> + method2request; + + MessageHandler(); + void Run(InMessage &msg); + QueryFile *FindFile(ReplyOnce &reply, const std::string &path, + int *out_file_id = nullptr); + +private: + void Bind(const char *method, void (MessageHandler::*handler)(JsonReader &)); + template + void Bind(const char *method, void (MessageHandler::*handler)(Param &)); + void Bind(const char *method, + void (MessageHandler::*handler)(JsonReader &, ReplyOnce &)); + template + void Bind(const char *method, + void (MessageHandler::*handler)(Param &, ReplyOnce &)); + + void ccls_call(JsonReader &, ReplyOnce &); + void ccls_fileInfo(TextDocumentParam &, ReplyOnce &); + void ccls_info(EmptyParam &, ReplyOnce &); + void ccls_inheritance(JsonReader &, ReplyOnce &); + void ccls_member(JsonReader &, ReplyOnce &); + void ccls_navigate(JsonReader &, ReplyOnce &); + void ccls_reload(JsonReader &); + void ccls_vars(JsonReader &, ReplyOnce &); + void exit(EmptyParam &); + void initialize(JsonReader &, ReplyOnce &); + void shutdown(EmptyParam &, ReplyOnce &); + void textDocument_codeAction(CodeActionParam &, ReplyOnce &); + void textDocument_codeLens(TextDocumentParam &, ReplyOnce &); + void textDocument_completion(lsCompletionParams &, ReplyOnce &); + void textDocument_definition(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_didChange(TextDocumentDidChangeParam &); + void textDocument_didClose(TextDocumentParam &); + void textDocument_didOpen(DidOpenTextDocumentParam &); + void textDocument_didSave(TextDocumentParam &); + void textDocument_documentHighlight(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_documentLink(TextDocumentParam &, ReplyOnce &); + void textDocument_documentSymbol(JsonReader &, ReplyOnce &); + void textDocument_foldingRange(TextDocumentParam &, ReplyOnce &); + void textDocument_formatting(DocumentFormattingParam &, ReplyOnce &); + void textDocument_hover(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_implementation(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &, + ReplyOnce &); + void textDocument_rangeFormatting(DocumentRangeFormattingParam &, + ReplyOnce &); + void textDocument_references(JsonReader &, ReplyOnce &); + void textDocument_rename(RenameParam &, ReplyOnce &); + void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &); + void workspace_didChangeConfiguration(EmptyParam &); + void workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam &); + void workspace_didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParam &); + void workspace_executeCommand(JsonReader &, ReplyOnce &); + void workspace_symbol(WorkspaceSymbolParam &, ReplyOnce &); +}; + +void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file); + +void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file); +} // namespace ccls diff --git a/src/cache_manager.cc b/src/cache_manager.cc deleted file mode 100644 index 520e9dcf8..000000000 --- a/src/cache_manager.cc +++ /dev/null @@ -1,156 +0,0 @@ -#include "cache_manager.h" - -#include "config.h" -#include "indexer.h" -#include "lsp.h" -#include "platform.h" - -#include - -#include -#include - -namespace { - -// Manages loading caches from file paths for the indexer process. -struct RealCacheManager : ICacheManager { - explicit RealCacheManager(Config* config) : config_(config) {} - ~RealCacheManager() override = default; - - void WriteToCache(IndexFile& file) override { - std::string cache_path = GetCachePath(file.path); - WriteToFile(cache_path, file.file_contents); - - std::string indexed_content = Serialize(config_->cacheFormat, file); - WriteToFile(AppendSerializationFormat(cache_path), indexed_content); - } - - optional LoadCachedFileContents( - const std::string& path) override { - return ReadContent(GetCachePath(path)); - } - - std::unique_ptr RawCacheLoad(const std::string& path) override { - std::string cache_path = GetCachePath(path); - optional file_content = ReadContent(cache_path); - optional serialized_indexed_content = - ReadContent(AppendSerializationFormat(cache_path)); - if (!file_content || !serialized_indexed_content) - return nullptr; - - return Deserialize(config_->cacheFormat, path, *serialized_indexed_content, - *file_content, IndexFile::kMajorVersion); - } - - std::string GetCachePath(const std::string& source_file) { - assert(!config_->cacheDirectory.empty()); - std::string cache_file; - size_t len = config_->projectRoot.size(); - if (StartsWith(source_file, config_->projectRoot)) { - cache_file = EscapeFileName(config_->projectRoot) + '/' + - EscapeFileName(source_file.substr(len)); - } else { - cache_file = '@' + EscapeFileName(config_->projectRoot) + '/' + - EscapeFileName(source_file); - } - - return config_->cacheDirectory + cache_file; - } - - std::string AppendSerializationFormat(const std::string& base) { - switch (config_->cacheFormat) { - case SerializeFormat::Json: - return base + ".json"; - case SerializeFormat::MessagePack: - return base + ".mpack"; - } - assert(false); - return ".json"; - } - - Config* config_; -}; - -struct FakeCacheManager : ICacheManager { - explicit FakeCacheManager(const std::vector& entries) - : entries_(entries) {} - - void WriteToCache(IndexFile& file) override { assert(false); } - - optional LoadCachedFileContents( - const std::string& path) override { - for (const FakeCacheEntry& entry : entries_) { - if (entry.path == path) { - return entry.content; - } - } - - return nullopt; - } - - std::unique_ptr RawCacheLoad(const std::string& path) override { - for (const FakeCacheEntry& entry : entries_) { - if (entry.path == path) { - return Deserialize(SerializeFormat::Json, path, entry.json, "", - nullopt); - } - } - - return nullptr; - } - - std::vector entries_; -}; - -} // namespace - -// static -std::shared_ptr ICacheManager::Make(Config* config) { - return std::make_shared(config); -} - -// static -std::shared_ptr ICacheManager::MakeFake( - const std::vector& entries) { - return std::make_shared(entries); -} - -ICacheManager::~ICacheManager() = default; - -IndexFile* ICacheManager::TryLoad(const std::string& path) { - auto it = caches_.find(path); - if (it != caches_.end()) - return it->second.get(); - - std::unique_ptr cache = RawCacheLoad(path); - if (!cache) - return nullptr; - - caches_[path] = std::move(cache); - return caches_[path].get(); -} - -std::unique_ptr ICacheManager::TryTakeOrLoad( - const std::string& path) { - auto it = caches_.find(path); - if (it != caches_.end()) { - auto result = std::move(it->second); - caches_.erase(it); - return result; - } - - return RawCacheLoad(path); -} - -std::unique_ptr ICacheManager::TakeOrLoad(const std::string& path) { - auto result = TryTakeOrLoad(path); - assert(result); - return result; -} - -void ICacheManager::IterateLoadedCaches(std::function fn) { - for (const auto& cache : caches_) { - assert(cache.second); - fn(cache.second.get()); - } -} diff --git a/src/cache_manager.h b/src/cache_manager.h deleted file mode 100644 index e5fd96720..000000000 --- a/src/cache_manager.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -struct Config; -struct IndexFile; - -struct ICacheManager { - struct FakeCacheEntry { - std::string path; - std::string content; - std::string json; - }; - - static std::shared_ptr Make(Config* config); - static std::shared_ptr MakeFake( - const std::vector& entries); - - virtual ~ICacheManager(); - - // Tries to load a cache for |path|, returning null if there is none. The - // cache loader still owns the cache. - IndexFile* TryLoad(const std::string& path); - - // Takes the existing cache or loads the cache at |path|. May return null if - // the cache does not exist. - std::unique_ptr TryTakeOrLoad(const std::string& path); - - // Takes the existing cache or loads the cache at |path|. Asserts the cache - // exists. - std::unique_ptr TakeOrLoad(const std::string& path); - - virtual void WriteToCache(IndexFile& file) = 0; - - virtual optional LoadCachedFileContents( - const std::string& path) = 0; - - // Iterate over all loaded caches. - void IterateLoadedCaches(std::function fn); - - protected: - virtual std::unique_ptr RawCacheLoad(const std::string& path) = 0; - std::unordered_map> caches_; -}; diff --git a/src/clang_complete.cc b/src/clang_complete.cc deleted file mode 100644 index 0eec9a710..000000000 --- a/src/clang_complete.cc +++ /dev/null @@ -1,804 +0,0 @@ -#include "clang_complete.h" - -#include "clang_utils.h" -#include "platform.h" -#include "timer.h" - -#include - -#include -#include - -namespace { - -unsigned Flags() { - // TODO: use clang_defaultEditingTranslationUnitOptions()? - return CXTranslationUnit_Incomplete | CXTranslationUnit_KeepGoing | - CXTranslationUnit_CacheCompletionResults | - CXTranslationUnit_PrecompiledPreamble | - CXTranslationUnit_IncludeBriefCommentsInCodeCompletion | - CXTranslationUnit_DetailedPreprocessingRecord -#if !defined(_WIN32) - // For whatever reason, CreatePreambleOnFirstParse causes clang to - // become very crashy on windows. - // TODO: do more investigation, submit fixes to clang. - | CXTranslationUnit_CreatePreambleOnFirstParse -#endif - ; -} - -unsigned GetCompletionPriority(const CXCompletionString& str, - CXCursorKind result_kind, - const optional& typedText) { - unsigned priority = clang_getCompletionPriority(str); - - // XXX: What happens if priority overflows? - if (result_kind == CXCursor_Destructor) { - priority *= 100; - } - if (result_kind == CXCursor_ConversionFunction || - (result_kind == CXCursor_CXXMethod && typedText && - StartsWith(*typedText, "operator"))) { - priority *= 100; - } - if (clang_getCompletionAvailability(str) != CXAvailability_Available) { - priority *= 100; - } - return priority; -} - -/* -bool IsCallKind(CXCursorKind kind) { - switch (kind) { - case CXCursor_ObjCInstanceMethodDecl: - case CXCursor_CXXMethod: - case CXCursor_FunctionTemplate: - case CXCursor_FunctionDecl: - case CXCursor_Constructor: - case CXCursor_Destructor: - case CXCursor_ConversionFunction: - return true; - default: - return false; - } -} -*/ - -lsCompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { - switch (cursor_kind) { - case CXCursor_UnexposedDecl: - return lsCompletionItemKind::Text; - - case CXCursor_StructDecl: - case CXCursor_UnionDecl: - return lsCompletionItemKind::Struct; - case CXCursor_ClassDecl: - return lsCompletionItemKind::Class; - case CXCursor_EnumDecl: - return lsCompletionItemKind::Enum; - case CXCursor_FieldDecl: - return lsCompletionItemKind::Field; - case CXCursor_EnumConstantDecl: - return lsCompletionItemKind::EnumMember; - case CXCursor_FunctionDecl: - return lsCompletionItemKind::Function; - case CXCursor_VarDecl: - case CXCursor_ParmDecl: - return lsCompletionItemKind::Variable; - case CXCursor_ObjCInterfaceDecl: - return lsCompletionItemKind::Interface; - - case CXCursor_ObjCInstanceMethodDecl: - case CXCursor_CXXMethod: - case CXCursor_ObjCClassMethodDecl: - return lsCompletionItemKind::Method; - - case CXCursor_FunctionTemplate: - return lsCompletionItemKind::Function; - - case CXCursor_Constructor: - case CXCursor_Destructor: - case CXCursor_ConversionFunction: - return lsCompletionItemKind::Constructor; - - case CXCursor_ObjCIvarDecl: - return lsCompletionItemKind::Variable; - - case CXCursor_ClassTemplate: - case CXCursor_ClassTemplatePartialSpecialization: - case CXCursor_UsingDeclaration: - case CXCursor_TypedefDecl: - case CXCursor_TypeAliasDecl: - case CXCursor_TypeAliasTemplateDecl: - case CXCursor_ObjCCategoryDecl: - case CXCursor_ObjCProtocolDecl: - case CXCursor_ObjCImplementationDecl: - case CXCursor_ObjCCategoryImplDecl: - return lsCompletionItemKind::Class; - - case CXCursor_ObjCPropertyDecl: - return lsCompletionItemKind::Property; - - case CXCursor_MacroInstantiation: - case CXCursor_MacroDefinition: - return lsCompletionItemKind::Interface; - - case CXCursor_Namespace: - case CXCursor_NamespaceAlias: - case CXCursor_NamespaceRef: - return lsCompletionItemKind::Module; - - case CXCursor_MemberRef: - case CXCursor_TypeRef: - case CXCursor_ObjCSuperClassRef: - case CXCursor_ObjCProtocolRef: - case CXCursor_ObjCClassRef: - return lsCompletionItemKind::Reference; - - // return lsCompletionItemKind::Unit; - // return lsCompletionItemKind::Value; - // return lsCompletionItemKind::Keyword; - // return lsCompletionItemKind::Snippet; - // return lsCompletionItemKind::Color; - // return lsCompletionItemKind::File; - - case CXCursor_NotImplemented: - case CXCursor_OverloadCandidate: - return lsCompletionItemKind::Text; - - case CXCursor_TemplateTypeParameter: - case CXCursor_TemplateTemplateParameter: - return lsCompletionItemKind::TypeParameter; - - default: - LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind; - return lsCompletionItemKind::Text; - } -} - -void BuildCompletionItemTexts(std::vector& out, - CXCompletionString completion_string, - bool include_snippets) { - assert(!out.empty()); - auto out_first = out.size() - 1; - - std::string result_type; - - int num_chunks = clang_getNumCompletionChunks(completion_string); - for (int i = 0; i < num_chunks; ++i) { - CXCompletionChunkKind kind = - clang_getCompletionChunkKind(completion_string, i); - - std::string text; - switch (kind) { - // clang-format off - case CXCompletionChunk_LeftParen: text = '('; break; - case CXCompletionChunk_RightParen: text = ')'; break; - case CXCompletionChunk_LeftBracket: text = '['; break; - case CXCompletionChunk_RightBracket: text = ']'; break; - case CXCompletionChunk_LeftBrace: text = '{'; break; - case CXCompletionChunk_RightBrace: text = '}'; break; - case CXCompletionChunk_LeftAngle: text = '<'; break; - case CXCompletionChunk_RightAngle: text = '>'; break; - case CXCompletionChunk_Comma: text = ", "; break; - case CXCompletionChunk_Colon: text = ':'; break; - case CXCompletionChunk_SemiColon: text = ';'; break; - case CXCompletionChunk_Equal: text = '='; break; - case CXCompletionChunk_HorizontalSpace: text = ' '; break; - case CXCompletionChunk_VerticalSpace: text = ' '; break; - // clang-format on - - case CXCompletionChunk_ResultType: - result_type = - ToString(clang_getCompletionChunkText(completion_string, i)); - continue; - - case CXCompletionChunk_TypedText: - case CXCompletionChunk_Placeholder: - case CXCompletionChunk_Text: - case CXCompletionChunk_Informative: - text = ToString(clang_getCompletionChunkText(completion_string, i)); - - for (auto i = out_first; i < out.size(); ++i) { - // first typed text is used for filtering - if (kind == CXCompletionChunk_TypedText && !out[i].filterText) - out[i].filterText = text; - - if (kind == CXCompletionChunk_Placeholder) - out[i].parameters_.push_back(text); - } - break; - - case CXCompletionChunk_CurrentParameter: - // We have our own parsing logic for active parameter. This doesn't seem - // to be very reliable. - continue; - - case CXCompletionChunk_Optional: { - CXCompletionString nested = - clang_getCompletionChunkCompletionString(completion_string, i); - // duplicate last element, the recursive call will complete it - out.push_back(out.back()); - BuildCompletionItemTexts(out, nested, include_snippets); - continue; - } - } - - for (auto i = out_first; i < out.size(); ++i) - out[i].label += text; - - if (kind == CXCompletionChunk_Informative) - continue; - - for (auto i = out_first; i < out.size(); ++i) { - if (!include_snippets && !out[i].parameters_.empty()) - continue; - - if (kind == CXCompletionChunk_Placeholder) { - out[i].insertText += - "${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}"; - out[i].insertTextFormat = lsInsertTextFormat::Snippet; - } else { - out[i].insertText += text; - } - } - } - - if (result_type.empty()) - return; - - for (auto i = out_first; i < out.size(); ++i) { - // ' : ' for variables, - // ' -> ' (trailing return type-like) for functions - out[i].label += (out[i].label == out[i].filterText ? " : " : " -> "); - out[i].label += result_type; - } -} - -// |do_insert|: if |!do_insert|, do not append strings to |insert| after -// a placeholder. -void BuildDetailString(CXCompletionString completion_string, - std::string& label, - std::string& detail, - std::string& insert, - bool& do_insert, - lsInsertTextFormat& format, - std::vector* parameters, - bool include_snippets) { - int num_chunks = clang_getNumCompletionChunks(completion_string); - auto append = [&](const char* text) { - detail += text; - if (do_insert) - insert += text; - }; - for (int i = 0; i < num_chunks; ++i) { - CXCompletionChunkKind kind = - clang_getCompletionChunkKind(completion_string, i); - - switch (kind) { - case CXCompletionChunk_Optional: { - CXCompletionString nested = - clang_getCompletionChunkCompletionString(completion_string, i); - BuildDetailString(nested, label, detail, insert, do_insert, format, - parameters, include_snippets); - break; - } - - case CXCompletionChunk_Placeholder: { - std::string text = - ToString(clang_getCompletionChunkText(completion_string, i)); - parameters->push_back(text); - detail += text; - // Add parameter declarations as snippets if enabled - if (include_snippets) { - insert += - "${" + std::to_string(parameters->size()) + ":" + text + "}"; - format = lsInsertTextFormat::Snippet; - } else - do_insert = false; - break; - } - - case CXCompletionChunk_CurrentParameter: - // We have our own parsing logic for active parameter. This doesn't seem - // to be very reliable. - break; - - case CXCompletionChunk_TypedText: { - std::string text = - ToString(clang_getCompletionChunkText(completion_string, i)); - label = text; - detail += text; - if (do_insert) - insert += text; - break; - } - - case CXCompletionChunk_Text: { - std::string text = - ToString(clang_getCompletionChunkText(completion_string, i)); - detail += text; - if (do_insert) - insert += text; - break; - } - - case CXCompletionChunk_Informative: { - detail += ToString(clang_getCompletionChunkText(completion_string, i)); - break; - } - - case CXCompletionChunk_ResultType: { - CXString text = clang_getCompletionChunkText(completion_string, i); - std::string new_detail = ToString(text) + detail + " "; - detail = new_detail; - break; - } - - // clang-format off - case CXCompletionChunk_LeftParen: append("("); break; - case CXCompletionChunk_RightParen: append(")"); break; - case CXCompletionChunk_LeftBracket: append("["); break; - case CXCompletionChunk_RightBracket: append("]"); break; - case CXCompletionChunk_LeftBrace: append("{"); break; - case CXCompletionChunk_RightBrace: append("}"); break; - case CXCompletionChunk_LeftAngle: append("<"); break; - case CXCompletionChunk_RightAngle: append(">"); break; - case CXCompletionChunk_Comma: append(", "); break; - case CXCompletionChunk_Colon: append(":"); break; - case CXCompletionChunk_SemiColon: append(";"); break; - case CXCompletionChunk_Equal: append("="); break; - // clang-format on - case CXCompletionChunk_HorizontalSpace: - case CXCompletionChunk_VerticalSpace: - append(" "); - break; - } - } -} - -void TryEnsureDocumentParsed(ClangCompleteManager* manager, - std::shared_ptr session, - std::unique_ptr* tu, - ClangIndex* index) { - // Nothing to do. We already have a translation unit. - if (*tu) - return; - - std::vector args = session->file.args; - - // -fspell-checking enables FixIts for, ie, misspelled types. - if (!AnyStartsWith(args, "-fno-spell-checking") && - !AnyStartsWith(args, "-fspell-checking")) { - args.push_back("-fspell-checking"); - } - - WorkingFiles::Snapshot snapshot = session->working_files->AsSnapshot( - {StripFileType(session->file.filename)}); - std::vector unsaved = snapshot.AsUnsavedFiles(); - - LOG_S(INFO) << "Creating completion session with arguments " - << StringJoin(args, " "); - *tu = ClangTranslationUnit::Create(index, session->file.filename, args, - unsaved, Flags()); - - // Build diagnostics. - if (manager->config_->diagnostics.onParse && *tu) { - // If we're emitting diagnostics, do an immediate reparse, otherwise we will - // emit stale/bad diagnostics. - *tu = ClangTranslationUnit::Reparse(std::move(*tu), unsaved); - if (!*tu) { - LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for " - << session->file.filename; - return; - } - - std::vector ls_diagnostics; - unsigned num_diagnostics = clang_getNumDiagnostics((*tu)->cx_tu); - for (unsigned i = 0; i < num_diagnostics; ++i) { - optional diagnostic = BuildAndDisposeDiagnostic( - clang_getDiagnostic((*tu)->cx_tu, i), session->file.filename); - // Filter messages like "too many errors emitted, stopping now - // [-ferror-limit=]" which has line = 0 and got subtracted by 1 after - // conversion to lsDiagnostic - if (diagnostic && diagnostic->range.start.line >= 0) - ls_diagnostics.push_back(*diagnostic); - } - manager->on_diagnostic_(session->file.filename, ls_diagnostics); - } -} - -void CompletionParseMain(ClangCompleteManager* completion_manager) { - while (true) { - // Fetching the completion request blocks until we have a request. - ClangCompleteManager::ParseRequest request = - completion_manager->parse_requests_.Dequeue(); - - // If we don't get a session then that means we don't care about the file - // anymore - abandon the request. - std::shared_ptr session = - completion_manager->TryGetSession(request.path, - false /*mark_as_completion*/, - false /*create_if_needed*/); - if (!session) - continue; - - // If we've parsed it more recently than the request time, don't bother - // reparsing. - if (session->tu_last_parsed_at && - *session->tu_last_parsed_at > request.request_time) { - continue; - } - - std::unique_ptr parsing; - TryEnsureDocumentParsed(completion_manager, session, &parsing, - &session->index); - - // Activate new translation unit. - // tu_last_parsed_at is only read by this thread, so it doesn't need to be - // under the mutex. - session->tu_last_parsed_at = std::chrono::high_resolution_clock::now(); - std::lock_guard lock(session->tu_lock); - session->tu = std::move(parsing); - } -} - -void CompletionQueryMain(ClangCompleteManager* completion_manager) { - while (true) { - // Fetching the completion request blocks until we have a request. - std::unique_ptr request = - completion_manager->completion_request_.Dequeue(); - - // Drop older requests if we're not buffering. - while (completion_manager->config_->completion.dropOldRequests && - !completion_manager->completion_request_.IsEmpty()) { - completion_manager->on_dropped_(request->id); - request = completion_manager->completion_request_.Dequeue(); - } - - std::string path = request->document.uri.GetPath(); - - std::shared_ptr session = - completion_manager->TryGetSession(path, true /*mark_as_completion*/, - true /*create_if_needed*/); - - std::lock_guard lock(session->tu_lock); - Timer timer; - TryEnsureDocumentParsed(completion_manager, session, &session->tu, - &session->index); - timer.ResetAndPrint("[complete] TryEnsureDocumentParsed"); - - // It is possible we failed to create the document despite - // |TryEnsureDocumentParsed|. - if (!session->tu) - continue; - - timer.Reset(); - WorkingFiles::Snapshot snapshot = - completion_manager->working_files_->AsSnapshot({StripFileType(path)}); - std::vector unsaved = snapshot.AsUnsavedFiles(); - timer.ResetAndPrint("[complete] Creating WorkingFile snapshot"); - - // Emit code completion data. - if (request->position) { - // Language server is 0-based, clang is 1-based. - unsigned line = request->position->line + 1; - unsigned column = request->position->character + 1; - - timer.Reset(); - unsigned const kCompleteOptions = - CXCodeComplete_IncludeMacros | CXCodeComplete_IncludeBriefComments; - CXCodeCompleteResults* cx_results = clang_codeCompleteAt( - session->tu->cx_tu, session->file.filename.c_str(), line, column, - unsaved.data(), (unsigned)unsaved.size(), kCompleteOptions); - timer.ResetAndPrint("[complete] clangCodeCompleteAt"); - if (!cx_results) { - if (request->on_complete) - request->on_complete({}, false /*is_cached_result*/); - continue; - } - - { - if (request->on_complete) { - std::vector ls_result; - // this is a guess but can be larger in case of optional parameters, - // as they may be expanded into multiple items - ls_result.reserve(cx_results->NumResults); - - timer.Reset(); - for (unsigned i = 0; i < cx_results->NumResults; ++i) { - CXCompletionResult& result = cx_results->Results[i]; - - // TODO: Try to figure out how we can hide base method calls without - // also hiding method implementation assistance, ie, - // - // void Foo::* { - // } - // - - if (clang_getCompletionAvailability(result.CompletionString) == - CXAvailability_NotAvailable) - continue; - - // TODO: fill in more data - lsCompletionItem ls_completion_item; - - ls_completion_item.kind = GetCompletionKind(result.CursorKind); - ls_completion_item.documentation = ToString( - clang_getCompletionBriefComment(result.CompletionString)); - - // label/detail/filterText/insertText/priority - if (completion_manager->config_->completion.detailedLabel) { - ls_completion_item.detail = ToString( - clang_getCompletionParent(result.CompletionString, nullptr)); - - auto first_idx = ls_result.size(); - ls_result.push_back(ls_completion_item); - - // label/filterText/insertText - BuildCompletionItemTexts( - ls_result, result.CompletionString, - completion_manager->config_->client.snippetSupport); - - for (auto i = first_idx; i < ls_result.size(); ++i) { - if (completion_manager->config_->client.snippetSupport && - ls_result[i].insertTextFormat == - lsInsertTextFormat::Snippet) { - ls_result[i].insertText += "$0"; - } - - ls_result[i].priority_ = GetCompletionPriority( - result.CompletionString, result.CursorKind, - ls_result[i].filterText); - } - } else { - bool do_insert = true; - BuildDetailString( - result.CompletionString, ls_completion_item.label, - ls_completion_item.detail, ls_completion_item.insertText, - do_insert, ls_completion_item.insertTextFormat, - &ls_completion_item.parameters_, - completion_manager->config_->client.snippetSupport); - if (completion_manager->config_->client.snippetSupport && - ls_completion_item.insertTextFormat == - lsInsertTextFormat::Snippet) { - ls_completion_item.insertText += "$0"; - } - ls_completion_item.priority_ = GetCompletionPriority( - result.CompletionString, result.CursorKind, - ls_completion_item.label); - ls_result.push_back(ls_completion_item); - } - } - - timer.ResetAndPrint("[complete] Building " + - std::to_string(ls_result.size()) + - " completion results"); - - request->on_complete(ls_result, false /*is_cached_result*/); - } - } - - // Make sure |ls_results| is destroyed before clearing |cx_results|. - clang_disposeCodeCompleteResults(cx_results); - } - - // Emit diagnostics. - if (request->emit_diagnostics) { - // TODO: before emitting diagnostics check if we have another completion - // request and think about servicing that first, because it may be much - // faster than reparsing the document. - // TODO: have a separate thread for diagnostics? - - timer.Reset(); - session->tu = - ClangTranslationUnit::Reparse(std::move(session->tu), unsaved); - timer.ResetAndPrint("[complete] clang_reparseTranslationUnit"); - if (!session->tu) { - LOG_S(ERROR) << "Reparsing translation unit for diagnostics failed for " - << path; - continue; - } - - size_t num_diagnostics = clang_getNumDiagnostics(session->tu->cx_tu); - std::vector ls_diagnostics; - ls_diagnostics.reserve(num_diagnostics); - for (unsigned i = 0; i < num_diagnostics; ++i) { - CXDiagnostic cx_diag = clang_getDiagnostic(session->tu->cx_tu, i); - optional diagnostic = - BuildAndDisposeDiagnostic(cx_diag, path); - // Filter messages like "too many errors emitted, stopping now - // [-ferror-limit=]" which has line = 0 and got subtracted by 1 after - // conversion to lsDiagnostic - if (diagnostic && diagnostic->range.start.line >= 0) - ls_diagnostics.push_back(*diagnostic); - } - completion_manager->on_diagnostic_(session->file.filename, - ls_diagnostics); - - /* - timer.Reset(); - completion_manager->on_index_(session->tu.get(), unsaved, - session->file.filename, session->file.args); - timer.ResetAndPrint("[complete] Reindex file"); - */ - } - - continue; - } -} - -} // namespace - -CompletionSession::CompletionSession(const Project::Entry& file, - WorkingFiles* working_files) - : file(file), - working_files(working_files), - index(0 /*excludeDeclarationsFromPCH*/, 0 /*displayDiagnostics*/) {} - -CompletionSession::~CompletionSession() {} - -ClangCompleteManager::ParseRequest::ParseRequest(const std::string& path) - : request_time(std::chrono::high_resolution_clock::now()), path(path) {} - -ClangCompleteManager::CompletionRequest::CompletionRequest( - const lsRequestId& id, - const lsTextDocumentIdentifier& document, - bool emit_diagnostics) - : id(id), document(document), emit_diagnostics(emit_diagnostics) {} -ClangCompleteManager::CompletionRequest::CompletionRequest( - const lsRequestId& id, - const lsTextDocumentIdentifier& document, - const lsPosition& position, - const OnComplete& on_complete, - bool emit_diagnostics) - : id(id), - document(document), - position(position), - on_complete(on_complete), - emit_diagnostics(emit_diagnostics) {} - -ClangCompleteManager::ClangCompleteManager(Config* config, - Project* project, - WorkingFiles* working_files, - OnDiagnostic on_diagnostic, - OnIndex on_index, - OnDropped on_dropped) - : config_(config), - project_(project), - working_files_(working_files), - on_diagnostic_(on_diagnostic), - on_index_(on_index), - on_dropped_(on_dropped), - preloaded_sessions_(kMaxPreloadedSessions), - completion_sessions_(kMaxCompletionSessions) { - new std::thread([&]() { - SetCurrentThreadName("completequery"); - CompletionQueryMain(this); - }); - - new std::thread([&]() { - SetCurrentThreadName("completeparse"); - CompletionParseMain(this); - }); -} - -ClangCompleteManager::~ClangCompleteManager() {} - -void ClangCompleteManager::CodeComplete( - const lsRequestId& id, - const lsTextDocumentPositionParams& completion_location, - const OnComplete& on_complete) { - completion_request_.PushBack(std::make_unique( - id, completion_location.textDocument, completion_location.position, - on_complete, false)); -} - -void ClangCompleteManager::DiagnosticsUpdate( - const lsRequestId& id, - const lsTextDocumentIdentifier& document) { - completion_request_.PushBack( - std::make_unique(id, document, true)); -} - -void ClangCompleteManager::NotifyView(const std::string& filename) { - // - // On view, we reparse only if the file has not been parsed. The existence of - // a CompletionSession instance implies the file is already parsed or will be - // parsed soon. - // - - // Only reparse the file if we create a new CompletionSession. - if (EnsureCompletionOrCreatePreloadSession(filename)) - parse_requests_.PushBack(ParseRequest(filename), true); -} - -void ClangCompleteManager::NotifyEdit(const std::string& filename) { - // - // We treat an edit like a view, because the completion logic will handle - // moving the CompletionSession instance from preloaded to completion - // storage. - // - - NotifyView(filename); -} - -void ClangCompleteManager::NotifySave(const std::string& filename) { - // - // On save, always reparse. - // - - EnsureCompletionOrCreatePreloadSession(filename); - parse_requests_.PushBack(ParseRequest(filename), true); -} - -void ClangCompleteManager::NotifyClose(const std::string& filename) { - // - // On close, we clear any existing CompletionSession instance. - // - - std::lock_guard lock(sessions_lock_); - - // Take and drop. It's okay if we don't actually drop the file, it'll - // eventually get pushed out of the caches as the user opens other files. - auto preloaded_ptr = preloaded_sessions_.TryTake(filename); - LOG_IF_S(INFO, !!preloaded_ptr) - << "Dropped preloaded-based code completion session for " << filename; - auto completion_ptr = completion_sessions_.TryTake(filename); - LOG_IF_S(INFO, !!completion_ptr) - << "Dropped completion-based code completion session for " << filename; - - // We should never have both a preloaded and completion session. - assert((preloaded_ptr && completion_ptr) == false); -} - -bool ClangCompleteManager::EnsureCompletionOrCreatePreloadSession( - const std::string& filename) { - std::lock_guard lock(sessions_lock_); - - // Check for an existing CompletionSession. - if (preloaded_sessions_.TryGet(filename) || - completion_sessions_.TryGet(filename)) { - return false; - } - - // No CompletionSession, create new one. - auto session = std::make_shared( - project_->FindCompilationEntryForFile(filename), working_files_); - preloaded_sessions_.Insert(session->file.filename, session); - return true; -} - -std::shared_ptr ClangCompleteManager::TryGetSession( - const std::string& filename, - bool mark_as_completion, - bool create_if_needed) { - std::lock_guard lock(sessions_lock_); - - // Try to find a preloaded session. - std::shared_ptr preloaded_session = - preloaded_sessions_.TryGet(filename); - - if (preloaded_session) { - // If this request is for a completion, we should move it to - // |completion_sessions|. - if (mark_as_completion) { - assert(!completion_sessions_.TryGet(filename)); - preloaded_sessions_.TryTake(filename); - completion_sessions_.Insert(filename, preloaded_session); - } - - return preloaded_session; - } - - // Try to find a completion session. If none create one. - std::shared_ptr completion_session = - completion_sessions_.TryGet(filename); - if (!completion_session && create_if_needed) { - completion_session = std::make_shared( - project_->FindCompilationEntryForFile(filename), working_files_); - completion_sessions_.Insert(filename, completion_session); - } - - return completion_session; -} diff --git a/src/clang_complete.h b/src/clang_complete.h deleted file mode 100644 index cf7524178..000000000 --- a/src/clang_complete.h +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once - -#include "clang_index.h" -#include "clang_translation_unit.h" -#include "lsp_completion.h" -#include "lsp_diagnostic.h" -#include "lru_cache.h" -#include "project.h" -#include "threaded_queue.h" -#include "working_files.h" - -#include - -#include -#include -#include -#include - -struct CompletionSession - : public std::enable_shared_from_this { - Project::Entry file; - WorkingFiles* working_files; - ClangIndex index; - - // When |tu| was last parsed. - optional> - tu_last_parsed_at; - - // Acquired when |tu| is being used. - std::mutex tu_lock; - - // The active translation unit. - std::unique_ptr tu; - - CompletionSession(const Project::Entry& file, WorkingFiles* working_files); - ~CompletionSession(); -}; - -struct ClangCompleteManager { - using OnDiagnostic = - std::function diagnostics)>; - using OnIndex = std::function& unsaved, - const std::string& path, - const std::vector& args)>; - using OnComplete = - std::function& results, - bool is_cached_result)>; - using OnDropped = std::function; - - struct ParseRequest { - ParseRequest(const std::string& path); - - std::chrono::time_point request_time; - std::string path; - }; - struct CompletionRequest { - CompletionRequest(const lsRequestId& id, - const lsTextDocumentIdentifier& document, - bool emit_diagnostics); - CompletionRequest(const lsRequestId& id, - const lsTextDocumentIdentifier& document, - const lsPosition& position, - const OnComplete& on_complete, - bool emit_diagnostics); - - lsRequestId id; - lsTextDocumentIdentifier document; - optional position; - OnComplete on_complete; // May be null/empty. - bool emit_diagnostics = false; - }; - - ClangCompleteManager(Config* config, - Project* project, - WorkingFiles* working_files, - OnDiagnostic on_diagnostic, - OnIndex on_index, - OnDropped on_dropped); - ~ClangCompleteManager(); - - // Start a code completion at the given location. |on_complete| will run when - // completion results are available. |on_complete| may run on any thread. - void CodeComplete(const lsRequestId& request_id, - const lsTextDocumentPositionParams& completion_location, - const OnComplete& on_complete); - // Request a diagnostics update. - void DiagnosticsUpdate(const lsRequestId& request_id, - const lsTextDocumentIdentifier& document); - - // Notify the completion manager that |filename| has been viewed and we - // should begin preloading completion data. - void NotifyView(const std::string& filename); - // Notify the completion manager that |filename| has been edited. - void NotifyEdit(const std::string& filename); - // Notify the completion manager that |filename| has been saved. This - // triggers a reparse. - void NotifySave(const std::string& filename); - // Notify the completion manager that |filename| has been closed. Any existing - // completion session will be dropped. - void NotifyClose(const std::string& filename); - - // Ensures there is a completion or preloaded session. Returns true if a new - // session was created. - bool EnsureCompletionOrCreatePreloadSession(const std::string& filename); - // Tries to find an edit session for |filename|. This will move the session - // from view to edit. - std::shared_ptr TryGetSession(const std::string& filename, - bool mark_as_completion, - bool create_if_needed); - - // TODO: make these configurable. - const int kMaxPreloadedSessions = 10; - const int kMaxCompletionSessions = 5; - - // Global state. - Config* config_; - Project* project_; - WorkingFiles* working_files_; - OnDiagnostic on_diagnostic_; - OnIndex on_index_; - OnDropped on_dropped_; - - using LruSessionCache = LruCache; - - // CompletionSession instances which are preloaded, ie, files which the user - // has viewed but not requested code completion for. - LruSessionCache preloaded_sessions_; - // CompletionSession instances which the user has actually performed - // completion on. This is more rare so these instances tend to stay alive - // much longer than the ones in |preloaded_sessions_|. - LruSessionCache completion_sessions_; - // Mutex which protects |view_sessions_| and |edit_sessions_|. - std::mutex sessions_lock_; - - // Request a code completion at the given location. - ThreadedQueue> completion_request_; - // Parse requests. The path may already be parsed, in which case it should be - // reparsed. - ThreadedQueue parse_requests_; -}; diff --git a/src/clang_cursor.cc b/src/clang_cursor.cc deleted file mode 100644 index 8ce4c2626..000000000 --- a/src/clang_cursor.cc +++ /dev/null @@ -1,286 +0,0 @@ -#include "clang_cursor.h" - -#include "clang_utils.h" - -#include - -#include -#include - -Range ResolveCXSourceRange(const CXSourceRange& range, CXFile* cx_file) { - CXSourceLocation start = clang_getRangeStart(range); - CXSourceLocation end = clang_getRangeEnd(range); - - unsigned int start_line, start_column; - clang_getSpellingLocation(start, cx_file, &start_line, &start_column, - nullptr); - unsigned int end_line, end_column; - clang_getSpellingLocation(end, nullptr, &end_line, &end_column, nullptr); - - return Range(Position((int16_t)start_line - 1, (int16_t)start_column - 1), - Position((int16_t)end_line - 1, (int16_t)end_column - 1)); -} - -ClangType::ClangType() : cx_type() {} - -ClangType::ClangType(const CXType& other) : cx_type(other) {} - -bool ClangType::operator==(const ClangType& rhs) const { - return clang_equalTypes(cx_type, rhs.cx_type); -} - -ClangCursor ClangType::get_declaration() const { - return clang_getTypeDeclaration(cx_type); -} - -std::string ClangType::get_usr() const { - return ClangCursor(clang_getTypeDeclaration(cx_type)).get_usr(); -} - -Usr ClangType::get_usr_hash() const { - if (is_builtin()) - return static_cast(cx_type.kind); - return ClangCursor(clang_getTypeDeclaration(cx_type)).get_usr_hash(); -} - -ClangType ClangType::get_canonical() const { - return clang_getCanonicalType(cx_type); -} - -ClangType ClangType::strip_qualifiers() const { - CXType cx = cx_type; - while (1) { - switch (cx.kind) { - default: - break; - case CXType_ConstantArray: - case CXType_DependentSizedArray: - case CXType_IncompleteArray: - case CXType_VariableArray: - cx = clang_getElementType(cx); - continue; - case CXType_BlockPointer: - case CXType_LValueReference: - case CXType_MemberPointer: - case CXType_ObjCObjectPointer: - case CXType_Pointer: - case CXType_RValueReference: - cx = clang_getPointeeType(cx); - continue; - } - break; - } - - return cx; -} - -std::string ClangType::get_spell_name() const { - return ToString(clang_getTypeSpelling(cx_type)); -} - -ClangType ClangType::get_return_type() const { - return ClangType(clang_getResultType(cx_type)); -} - -std::vector ClangType::get_arguments() const { - int size = clang_getNumArgTypes(cx_type); - if (size < 0) - return {}; - std::vector types(size); - for (int i = 0; i < size; ++i) - types.emplace_back(clang_getArgType(cx_type, i)); - return types; -} - -std::vector ClangType::get_template_arguments() const { - int size = clang_Type_getNumTemplateArguments(cx_type); - assert(size >= 0); - if (size < 0) - return std::vector(); - - std::vector types(size); - for (int i = 0; i < size; ++i) - types.emplace_back(clang_Type_getTemplateArgumentAsType(cx_type, i)); - return types; -} - -static_assert(sizeof(ClangCursor) == sizeof(CXCursor), - "Cursor must be the same size as CXCursor"); - -ClangCursor::ClangCursor() : cx_cursor(clang_getNullCursor()) {} - -ClangCursor::ClangCursor(const CXCursor& other) : cx_cursor(other) {} - -ClangCursor::operator bool() const { - return !clang_Cursor_isNull(cx_cursor); -} - -bool ClangCursor::operator==(const ClangCursor& rhs) const { - return clang_equalCursors(cx_cursor, rhs.cx_cursor); -} - -bool ClangCursor::operator!=(const ClangCursor& rhs) const { - return !(*this == rhs); -} - -CXCursorKind ClangCursor::get_kind() const { - return cx_cursor.kind; -} - -ClangType ClangCursor::get_type() const { - return ClangType(clang_getCursorType(cx_cursor)); -} - -std::string ClangCursor::get_spell_name() const { - return ::ToString(clang_getCursorSpelling(cx_cursor)); -} - -Range ClangCursor::get_spell(CXFile* cx_file) const { - // TODO for Objective-C methods and Objective-C message expressions, there are - // multiple pieces for each selector identifier. - CXSourceRange range = clang_Cursor_getSpellingNameRange(cx_cursor, 0, 0); - return ResolveCXSourceRange(range, cx_file); -} - -Range ClangCursor::get_extent() const { - CXSourceRange range = clang_getCursorExtent(cx_cursor); - return ResolveCXSourceRange(range, nullptr); -} - -std::string ClangCursor::get_display_name() const { - return ::ToString(clang_getCursorDisplayName(cx_cursor)); -} - -std::string ClangCursor::get_usr() const { - return ::ToString(clang_getCursorUSR(cx_cursor)); -} - -Usr ClangCursor::get_usr_hash() const { - CXString usr = clang_getCursorUSR(cx_cursor); - Usr ret = HashUsr(clang_getCString(usr)); - clang_disposeString(usr); - return ret; -} - -bool ClangCursor::is_definition() const { - return clang_isCursorDefinition(cx_cursor); -} - -ClangCursor ClangCursor::template_specialization_to_template_definition() - const { - CXCursor definition = clang_getSpecializedCursorTemplate(cx_cursor); - if (definition.kind == CXCursor_FirstInvalid) - return cx_cursor; - return definition; -} - -ClangCursor ClangCursor::get_referenced() const { - return ClangCursor(clang_getCursorReferenced(cx_cursor)); -} - -ClangCursor ClangCursor::get_canonical() const { - return ClangCursor(clang_getCanonicalCursor(cx_cursor)); -} - -ClangCursor ClangCursor::get_definition() const { - return ClangCursor(clang_getCursorDefinition(cx_cursor)); -} - -ClangCursor ClangCursor::get_lexical_parent() const { - return ClangCursor(clang_getCursorLexicalParent(cx_cursor)); -} - -ClangCursor ClangCursor::get_semantic_parent() const { - return ClangCursor(clang_getCursorSemanticParent(cx_cursor)); -} - -std::vector ClangCursor::get_arguments() const { - int size = clang_Cursor_getNumArguments(cx_cursor); - if (size < 0) - return std::vector(); - - std::vector cursors(size); - for (int i = 0; i < size; ++i) - cursors.emplace_back(clang_Cursor_getArgument(cx_cursor, i)); - return cursors; -} - -bool ClangCursor::is_valid_kind() const { - CXCursor referenced = clang_getCursorReferenced(cx_cursor); - if (clang_Cursor_isNull(referenced)) - return false; - - CXCursorKind kind = get_kind(); - return kind > CXCursor_UnexposedDecl && - (kind < CXCursor_FirstInvalid || kind > CXCursor_LastInvalid); -} - -std::string ClangCursor::get_type_description() const { - auto type = clang_getCursorType(cx_cursor); - return ::ToString(clang_getTypeSpelling(type)); -} - -NtString ClangCursor::get_comments() const { - CXSourceRange range = clang_Cursor_getCommentRange(cx_cursor); - if (clang_Range_isNull(range)) - return {}; - - unsigned start_column; - clang_getSpellingLocation(clang_getRangeStart(range), nullptr, nullptr, - &start_column, nullptr); - - // Get associated comment text. - CXString cx_raw = clang_Cursor_getRawCommentText(cx_cursor); - int pad = -1; - std::string ret; - for (const char* p = clang_getCString(cx_raw); *p;) { - // The first line starts with a comment marker, but the rest needs - // un-indenting. - unsigned skip = start_column - 1; - for (; skip > 0 && (*p == ' ' || *p == '\t'); p++) - skip--; - const char* q = p; - while (*q != '\n' && *q) - q++; - if (*q) - q++; - // A minimalist approach to skip Doxygen comment markers. - // See https://www.stack.nl/~dimitri/doxygen/manual/docblocks.html - if (pad < 0) { - // First line, detect the length of comment marker and put into |pad| - const char* begin = p; - while (*p == '/' || *p == '*') - p++; - if (*p == '<' || *p == '!') - p++; - if (*p == ' ') - p++; - pad = int(p - begin); - } else { - // Other lines, skip |pad| bytes - int prefix = pad; - while (prefix > 0 && - (*p == ' ' || *p == '/' || *p == '*' || *p == '<' || *p == '!')) - prefix--, p++; - } - ret.insert(ret.end(), p, q); - p = q; - } - clang_disposeString(cx_raw); - while (ret.size() && isspace(ret.back())) - ret.pop_back(); - if (EndsWith(ret, "*/")) { - ret.resize(ret.size() - 2); - } else if (EndsWith(ret, "\n/")) { - ret.resize(ret.size() - 2); - } - while (ret.size() && isspace(ret.back())) - ret.pop_back(); - if (ret.empty()) - return {}; - return static_cast(ret); -} - -std::string ClangCursor::ToString() const { - return ::ToString(get_kind()) + " " + get_spell_name(); -} diff --git a/src/clang_cursor.h b/src/clang_cursor.h deleted file mode 100644 index f7129b9de..000000000 --- a/src/clang_cursor.h +++ /dev/null @@ -1,116 +0,0 @@ -#pragma once - -#include "nt_string.h" -#include "position.h" - -#include -#include - -#include -#include -#include - -using Usr = uint64_t; - -Range ResolveCXSourceRange(const CXSourceRange& range, - CXFile* cx_file = nullptr); - -class ClangCursor; - -class ClangType { - public: - ClangType(); - ClangType(const CXType& other); - - bool operator==(const ClangType& rhs) const; - - // Returns true if this is a fundamental type like int. - bool is_builtin() const { - // NOTE: This will return false for pointed types. Should we call - // strip_qualifiers for the user? - return cx_type.kind >= CXType_FirstBuiltin && - cx_type.kind <= CXType_LastBuiltin; - } - - ClangCursor get_declaration() const; - std::string get_usr() const; - Usr get_usr_hash() const; - std::string get_spell_name() const; - ClangType get_canonical() const; - - // Try to resolve this type and remove qualifies, ie, Foo* will become Foo - ClangType strip_qualifiers() const; - - ClangType get_return_type() const; - std::vector get_arguments() const; - std::vector get_template_arguments() const; - - CXType cx_type; -}; - -class ClangCursor { - public: - ClangCursor(); - ClangCursor(const CXCursor& other); - - explicit operator bool() const; - bool operator==(const ClangCursor& rhs) const; - bool operator!=(const ClangCursor& rhs) const; - - CXCursorKind get_kind() const; - ClangType get_type() const; - std::string get_spell_name() const; - Range get_spell(CXFile* cx_file = nullptr) const; - Range get_extent() const; - std::string get_display_name() const; - std::string get_usr() const; - Usr get_usr_hash() const; - - bool is_definition() const; - - // If the given cursor points to a template specialization, this - // will return the cursor pointing to the template definition. - // If the given cursor is not a template specialization, this will - // just return the same cursor. - // - // This means it is always safe to call this method. - ClangCursor template_specialization_to_template_definition() const; - - ClangCursor get_referenced() const; - ClangCursor get_canonical() const; - ClangCursor get_definition() const; - ClangCursor get_lexical_parent() const; - ClangCursor get_semantic_parent() const; - std::vector get_arguments() const; - bool is_valid_kind() const; - - std::string get_type_description() const; - NtString get_comments() const; - - std::string ToString() const; - - enum class VisitResult { Break, Continue, Recurse }; - - template - using Visitor = VisitResult (*)(ClangCursor cursor, - ClangCursor parent, - TClientData* client_data); - - template - void VisitChildren(Visitor visitor, - TClientData* client_data) const { - clang_visitChildren(cx_cursor, reinterpret_cast(visitor), - client_data); - } - - CXCursor cx_cursor; -}; - -namespace std { -template <> -struct hash { - size_t operator()(const ClangCursor& x) const { - return clang_hashCursor(x.cx_cursor); - } -}; -} // namespace std diff --git a/src/clang_format.cc b/src/clang_format.cc deleted file mode 100644 index 5bb238a83..000000000 --- a/src/clang_format.cc +++ /dev/null @@ -1,117 +0,0 @@ -#if USE_CLANG_CXX - -#include "clang_format.h" -#include "working_files.h" - -#include -#include - -using namespace clang; -using clang::format::FormatStyle; - -namespace { - -// TODO Objective-C 'header/interface' files may use .h, we should get this from -// project information. -FormatStyle::LanguageKind getLanguageKindFromFilename( - llvm::StringRef filename) { - if (filename.endswith(".m") || filename.endswith(".mm")) { - return FormatStyle::LK_ObjC; - } - return FormatStyle::LK_Cpp; -} - -} // namespace - -std::vector ClangFormatDocument( - WorkingFile* working_file, - int start, - int end, - lsFormattingOptions options) { - const auto language_kind = - getLanguageKindFromFilename(working_file->filename); - FormatStyle predefined_style; - getPredefinedStyle("chromium", language_kind, &predefined_style); - llvm::Expected style = - format::getStyle("file", working_file->filename, "chromium"); - if (!style) { - // If, for some reason, we cannot get a format style, use Chromium's with - // tab configuration provided by the client editor. - LOG_S(ERROR) << llvm::toString(style.takeError()); - predefined_style.UseTab = options.insertSpaces - ? FormatStyle::UseTabStyle::UT_Never - : FormatStyle::UseTabStyle::UT_Always; - predefined_style.IndentWidth = options.tabSize; - } - - auto format_result = reformat( - style ? *style : predefined_style, working_file->buffer_content, - llvm::ArrayRef(tooling::Range(start, end - start)), - working_file->filename); - return std::vector(format_result.begin(), - format_result.end()); -} - -TEST_SUITE("ClangFormat") { - TEST_CASE("entireDocument") { - const std::string sample_document = "int main() { int *i = 0; return 0; }"; - WorkingFile* file = new WorkingFile("foo.cc", sample_document); - lsFormattingOptions formatting_options; - formatting_options.insertSpaces = true; - const auto replacements = ClangFormatDocument( - file, 0, sample_document.size(), formatting_options); - - // echo "int main() { int *i = 0; return 0; }" | clang-format - // -style=Chromium -output-replacements-xml - // - // - // - // - // - // - // - // - // - - REQUIRE(replacements.size() == 5); - REQUIRE(replacements[0].getOffset() == 12); - REQUIRE(replacements[0].getLength() == 1); - REQUIRE(replacements[0].getReplacementText() == "\n "); - - REQUIRE(replacements[1].getOffset() == 16); - REQUIRE(replacements[1].getLength() == 1); - REQUIRE(replacements[1].getReplacementText() == ""); - - REQUIRE(replacements[2].getOffset() == 18); - REQUIRE(replacements[2].getLength() == 0); - REQUIRE(replacements[2].getReplacementText() == " "); - - REQUIRE(replacements[3].getOffset() == 24); - REQUIRE(replacements[3].getLength() == 1); - REQUIRE(replacements[3].getReplacementText() == "\n "); - - REQUIRE(replacements[4].getOffset() == 34); - REQUIRE(replacements[4].getLength() == 1); - REQUIRE(replacements[4].getReplacementText() == "\n"); - } - - TEST_CASE("range") { - const std::string sampleDocument = "int main() { int *i = 0; return 0; }"; - WorkingFile* file = new WorkingFile("foo.cc", sampleDocument); - lsFormattingOptions formattingOptions; - formattingOptions.insertSpaces = true; - const auto replacements = - ClangFormatDocument(file, 30, sampleDocument.size(), formattingOptions); - - REQUIRE(replacements.size() == 2); - REQUIRE(replacements[0].getOffset() == 24); - REQUIRE(replacements[0].getLength() == 1); - REQUIRE(replacements[0].getReplacementText() == "\n "); - - REQUIRE(replacements[1].getOffset() == 34); - REQUIRE(replacements[1].getLength() == 1); - REQUIRE(replacements[1].getReplacementText() == "\n"); - } -} - -#endif diff --git a/src/clang_format.h b/src/clang_format.h deleted file mode 100644 index 862f3e067..000000000 --- a/src/clang_format.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#if USE_CLANG_CXX - -#include "lsp.h" -#include "working_files.h" - -#include - -#include - -std::vector ClangFormatDocument( - WorkingFile* working_file, - int start, - int end, - lsFormattingOptions options); - -#endif diff --git a/src/clang_index.cc b/src/clang_index.cc deleted file mode 100644 index 9c74fdfac..000000000 --- a/src/clang_index.cc +++ /dev/null @@ -1,22 +0,0 @@ -#include "clang_index.h" - -#include - -ClangIndex::ClangIndex() : ClangIndex(1, 0) {} - -ClangIndex::ClangIndex(int exclude_declarations_from_pch, - int display_diagnostics) { - // llvm::InitializeAllTargets (and possibly others) called by - // clang_createIndex transtively modifies/reads lib/Support/TargetRegistry.cpp - // FirstTarget. There will be a race condition if two threads call - // clang_createIndex concurrently. - static std::mutex mutex_; - std::lock_guard lock(mutex_); - - cx_index = - clang_createIndex(exclude_declarations_from_pch, display_diagnostics); -} - -ClangIndex::~ClangIndex() { - clang_disposeIndex(cx_index); -} diff --git a/src/clang_index.h b/src/clang_index.h deleted file mode 100644 index 1350d9000..000000000 --- a/src/clang_index.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -// Simple RAII wrapper about CXIndex. -// Note: building a ClangIndex instance acquires a global lock, since libclang -// API does not appear to be thread-safe here. -class ClangIndex { - public: - ClangIndex(); - ClangIndex(int exclude_declarations_from_pch, int display_diagnostics); - ~ClangIndex(); - CXIndex cx_index; -}; diff --git a/src/clang_indexer.cc b/src/clang_indexer.cc deleted file mode 100644 index df82f2426..000000000 --- a/src/clang_indexer.cc +++ /dev/null @@ -1,2413 +0,0 @@ -#include "indexer.h" - -#include "clang_cursor.h" -#include "clang_utils.h" -#include "platform.h" -#include "serializer.h" -#include "timer.h" -#include "type_printer.h" - -#include - -#include -#include -#include -#include -#include - -// TODO: See if we can use clang_indexLoc_getFileLocation to get a type ref on -// |Foobar| in DISALLOW_COPY(Foobar) - -#if CINDEX_VERSION >= 47 -#define CINDEX_HAVE_PRETTY 1 -#endif -#if CINDEX_VERSION >= 48 -#define CINDEX_HAVE_ROLE 1 -#endif - -namespace { - -// For typedef/using spanning less than or equal to (this number) of lines, -// display their declarations on hover. -constexpr int kMaxLinesDisplayTypeAliasDeclarations = 3; - -// TODO How to check if a reference to type is a declaration? -// This currently also includes constructors/destructors. -// It seems declarations in functions are not indexed. -bool IsDeclContext(CXIdxEntityKind kind) { - switch (kind) { - case CXIdxEntity_CXXClass: - case CXIdxEntity_CXXNamespace: - case CXIdxEntity_ObjCCategory: - case CXIdxEntity_ObjCClass: - case CXIdxEntity_ObjCProtocol: - case CXIdxEntity_Struct: - return true; - default: - return false; - } -} - -Role GetRole(const CXIdxEntityRefInfo* ref_info, Role role) { -#if CINDEX_HAVE_ROLE - return static_cast(static_cast(ref_info->role)); -#else - return role; -#endif -} - -SymbolKind GetSymbolKind(CXCursorKind kind) { - switch (kind) { - case CXCursor_TranslationUnit: - return SymbolKind::File; - - case CXCursor_FunctionDecl: - case CXCursor_CXXMethod: - case CXCursor_Constructor: - case CXCursor_Destructor: - case CXCursor_ConversionFunction: - case CXCursor_FunctionTemplate: - case CXCursor_OverloadedDeclRef: - case CXCursor_LambdaExpr: - case CXCursor_ObjCInstanceMethodDecl: - case CXCursor_ObjCClassMethodDecl: - return SymbolKind::Func; - - case CXCursor_StructDecl: - case CXCursor_UnionDecl: - case CXCursor_ClassDecl: - case CXCursor_EnumDecl: - case CXCursor_ObjCInterfaceDecl: - case CXCursor_ObjCCategoryDecl: - case CXCursor_ObjCImplementationDecl: - case CXCursor_Namespace: - return SymbolKind::Type; - - default: - return SymbolKind::Invalid; - } -} - -// Inverse of libclang/CXIndexDataConsumer.cpp getEntityKindFromSymbolKind -lsSymbolKind GetSymbolKind(CXIdxEntityKind kind) { - switch (kind) { - case CXIdxEntity_Unexposed: - return lsSymbolKind::Unknown; - case CXIdxEntity_Typedef: - return lsSymbolKind::TypeAlias; - case CXIdxEntity_Function: - return lsSymbolKind::Function; - case CXIdxEntity_Variable: - // Can also be Parameter - return lsSymbolKind::Variable; - case CXIdxEntity_Field: - return lsSymbolKind::Field; - case CXIdxEntity_EnumConstant: - return lsSymbolKind::EnumMember; - - case CXIdxEntity_ObjCClass: - return lsSymbolKind::Class; - case CXIdxEntity_ObjCProtocol: - return lsSymbolKind::Interface; - case CXIdxEntity_ObjCCategory: - return lsSymbolKind::Interface; - - case CXIdxEntity_ObjCInstanceMethod: - return lsSymbolKind::Method; - case CXIdxEntity_ObjCClassMethod: - return lsSymbolKind::StaticMethod; - case CXIdxEntity_ObjCProperty: - return lsSymbolKind::Property; - case CXIdxEntity_ObjCIvar: - return lsSymbolKind::Field; - - case CXIdxEntity_Enum: - return lsSymbolKind::Enum; - case CXIdxEntity_Struct: - case CXIdxEntity_Union: - return lsSymbolKind::Struct; - - case CXIdxEntity_CXXClass: - return lsSymbolKind::Class; - case CXIdxEntity_CXXNamespace: - return lsSymbolKind::Namespace; - case CXIdxEntity_CXXNamespaceAlias: - return lsSymbolKind::Namespace; - case CXIdxEntity_CXXStaticVariable: - return lsSymbolKind::Field; - case CXIdxEntity_CXXStaticMethod: - return lsSymbolKind::StaticMethod; - case CXIdxEntity_CXXInstanceMethod: - return lsSymbolKind::Method; - case CXIdxEntity_CXXConstructor: - return lsSymbolKind::Constructor; - case CXIdxEntity_CXXDestructor: - return lsSymbolKind::Method; - case CXIdxEntity_CXXConversionFunction: - return lsSymbolKind::Constructor; - case CXIdxEntity_CXXTypeAlias: - return lsSymbolKind::TypeAlias; - case CXIdxEntity_CXXInterface: - return lsSymbolKind::Struct; - } -} - -StorageClass GetStorageClass(CX_StorageClass storage) { - switch (storage) { - case CX_SC_Invalid: - case CX_SC_OpenCLWorkGroupLocal: - return StorageClass::Invalid; - case CX_SC_None: - return StorageClass::None; - case CX_SC_Extern: - return StorageClass::Extern; - case CX_SC_Static: - return StorageClass::Static; - case CX_SC_PrivateExtern: - return StorageClass::PrivateExtern; - case CX_SC_Auto: - return StorageClass::Auto; - case CX_SC_Register: - return StorageClass::Register; - } -} - -// Caches all instances of constructors, regardless if they are indexed or not. -// The constructor may have a make_unique call associated with it that we need -// to export. If we do not capture the parameter type description for the -// constructor we will not be able to attribute the constructor call correctly. -struct ConstructorCache { - struct Constructor { - Usr usr; - std::vector param_type_desc; - }; - std::unordered_map> constructors_; - - // This should be called whenever there is a constructor declaration. - void NotifyConstructor(ClangCursor ctor_cursor) { - auto build_type_desc = [](ClangCursor cursor) { - std::vector type_desc; - for (ClangCursor arg : cursor.get_arguments()) { - if (arg.get_kind() == CXCursor_ParmDecl) - type_desc.push_back(arg.get_type_description()); - } - return type_desc; - }; - - Constructor ctor{ctor_cursor.get_usr_hash(), build_type_desc(ctor_cursor)}; - - // Insert into |constructors_|. - auto type_usr_hash = ctor_cursor.get_semantic_parent().get_usr_hash(); - auto existing_ctors = constructors_.find(type_usr_hash); - if (existing_ctors != constructors_.end()) { - existing_ctors->second.push_back(ctor); - } else { - constructors_[type_usr_hash] = {ctor}; - } - } - - // Tries to lookup a constructor in |type_usr| that takes arguments most - // closely aligned to |param_type_desc|. - optional TryFindConstructorUsr( - Usr type_usr, - const std::vector& param_type_desc) { - auto count_matching_prefix_length = [](const char* a, const char* b) { - int matched = 0; - while (*a && *b) { - if (*a != *b) - break; - ++a; - ++b; - ++matched; - } - // Additional score if the strings were the same length, which makes - // "a"/"a" match higher than "a"/"a&" - if (*a == *b) - matched += 1; - return matched; - }; - - // Try to find constructors for the type. If there are no constructors - // available, return an empty result. - auto ctors_it = constructors_.find(type_usr); - if (ctors_it == constructors_.end()) - return nullopt; - const std::vector& ctors = ctors_it->second; - if (ctors.empty()) - return nullopt; - - Usr best_usr = ctors[0].usr; - int best_score = INT_MIN; - - // Scan constructors for the best possible match. - for (const Constructor& ctor : ctors) { - // If |param_type_desc| is empty and the constructor is as well, we don't - // need to bother searching, as this is the match. - if (param_type_desc.empty() && ctor.param_type_desc.empty()) { - best_usr = ctor.usr; - break; - } - - // Weight matching parameter length heavily, as it is more accurate than - // the fuzzy type matching approach. - int score = 0; - if (param_type_desc.size() == ctor.param_type_desc.size()) - score += param_type_desc.size() * 1000; - - // Do prefix-based match on parameter type description. This works well in - // practice because clang appends qualifiers to the end of the type, ie, - // |foo *&&| - for (size_t i = 0; - i < std::min(param_type_desc.size(), ctor.param_type_desc.size()); - ++i) { - score += count_matching_prefix_length(param_type_desc[i].c_str(), - ctor.param_type_desc[i].c_str()); - } - - if (score > best_score) { - best_usr = ctor.usr; - best_score = score; - } - } - - return best_usr; - } -}; - -struct IndexParam { - Config* config = nullptr; - - std::unordered_set seen_cx_files; - std::vector seen_files; - FileContentsMap file_contents; - std::unordered_map file_modification_times; - - // Only use this when strictly needed (ie, primary translation unit is - // needed). Most logic should get the IndexFile instance via - // |file_consumer|. - // - // This can be null if we're not generating an index for the primary - // translation unit. - IndexFile* primary_file = nullptr; - - ClangTranslationUnit* tu = nullptr; - - FileConsumer* file_consumer = nullptr; - NamespaceHelper ns; - ConstructorCache ctors; - - IndexParam(Config* config, - ClangTranslationUnit* tu, - FileConsumer* file_consumer) - : config(config), tu(tu), file_consumer(file_consumer) {} - -#if CINDEX_HAVE_PRETTY - CXPrintingPolicy print_policy = nullptr; - CXPrintingPolicy print_policy_more = nullptr; - ~IndexParam() { - clang_PrintingPolicy_dispose(print_policy); - clang_PrintingPolicy_dispose(print_policy_more); - } - - std::string PrettyPrintCursor(CXCursor cursor, bool initializer = true) { - if (!print_policy) { - print_policy = clang_getCursorPrintingPolicy(cursor); - clang_PrintingPolicy_setProperty(print_policy, - CXPrintingPolicy_TerseOutput, 1); - clang_PrintingPolicy_setProperty(print_policy, - CXPrintingPolicy_FullyQualifiedName, 1); - clang_PrintingPolicy_setProperty( - print_policy, CXPrintingPolicy_SuppressInitializers, 1); - print_policy_more = clang_getCursorPrintingPolicy(cursor); - clang_PrintingPolicy_setProperty(print_policy_more, - CXPrintingPolicy_FullyQualifiedName, 1); - clang_PrintingPolicy_setProperty(print_policy_more, - CXPrintingPolicy_TerseOutput, 1); - } - return ToString(clang_getCursorPrettyPrinted( - cursor, initializer ? print_policy_more : print_policy)); - } -#endif -}; - -IndexFile* ConsumeFile(IndexParam* param, CXFile file) { - bool is_first_ownership = false; - IndexFile* db = param->file_consumer->TryConsumeFile( - file, &is_first_ownership, ¶m->file_contents); - - // If this is the first time we have seen the file (ignoring if we are - // generating an index for it): - if (param->seen_cx_files.insert(file).second) { - std::string file_name = FileName(file); - // file_name may be empty when it contains .. and is outside of WorkingDir. - // https://reviews.llvm.org/D42893 - // https://github.com/cquery-project/cquery/issues/413 - if (!file_name.empty()) { - // Add to all files we have seen so we can generate proper dependency - // graph. - param->seen_files.push_back(file_name); - - // Set modification time. - optional modification_time = GetLastModificationTime(file_name); - LOG_IF_S(ERROR, !modification_time) - << "Failed fetching modification time for " << file_name; - if (modification_time) - param->file_modification_times[file_name] = *modification_time; - } - } - - if (is_first_ownership) { - // Report skipped source range list. - CXSourceRangeList* skipped = clang_getSkippedRanges(param->tu->cx_tu, file); - for (unsigned i = 0; i < skipped->count; ++i) { - Range range = ResolveCXSourceRange(skipped->ranges[i]); - // clang_getSkippedRanges reports start one token after the '#', move it - // back so it starts at the '#' - range.start.column -= 1; - db->skipped_by_preprocessor.push_back(range); - } - clang_disposeSourceRangeList(skipped); - } - - return db; -} - -// Returns true if the given entity kind can be called implicitly, ie, without -// actually being written in the source code. -bool CanBeCalledImplicitly(CXIdxEntityKind kind) { - switch (kind) { - case CXIdxEntity_CXXConstructor: - case CXIdxEntity_CXXConversionFunction: - case CXIdxEntity_CXXDestructor: - return true; - default: - return false; - } -} - -// Returns true if the cursor spelling contains the given string. This is -// useful to check for implicit function calls. -bool CursorSpellingContainsString(CXCursor cursor, - CXTranslationUnit cx_tu, - std::string_view needle) { - CXSourceRange range = clang_Cursor_getSpellingNameRange(cursor, 0, 0); - CXToken* tokens; - unsigned num_tokens; - clang_tokenize(cx_tu, range, &tokens, &num_tokens); - - bool result = false; - - for (unsigned i = 0; i < num_tokens; ++i) { - CXString name = clang_getTokenSpelling(cx_tu, tokens[i]); - if (needle == clang_getCString(name)) { - result = true; - break; - } - clang_disposeString(name); - } - - clang_disposeTokens(cx_tu, tokens, num_tokens); - return result; -} - -// Returns the document content for the given range. May not work perfectly -// when there are tabs instead of spaces. -std::string GetDocumentContentInRange(CXTranslationUnit cx_tu, - CXSourceRange range) { - std::string result; - - CXToken* tokens; - unsigned num_tokens; - clang_tokenize(cx_tu, range, &tokens, &num_tokens); - - optional previous_token_range; - - for (unsigned i = 0; i < num_tokens; ++i) { - // Add whitespace between the previous token and this one. - Range token_range = - ResolveCXSourceRange(clang_getTokenExtent(cx_tu, tokens[i])); - if (previous_token_range) { - // Insert newlines. - int16_t line_delta = - token_range.start.line - previous_token_range->end.line; - assert(line_delta >= 0); - if (line_delta > 0) { - result.append((size_t)line_delta, '\n'); - // Reset column so we insert starting padding. - previous_token_range->end.column = 0; - } - // Insert spaces. - int16_t column_delta = - token_range.start.column - previous_token_range->end.column; - assert(column_delta >= 0); - result.append((size_t)column_delta, ' '); - } - previous_token_range = token_range; - - // Add token content. - CXString spelling = clang_getTokenSpelling(cx_tu, tokens[i]); - result += clang_getCString(spelling); - clang_disposeString(spelling); - } - - clang_disposeTokens(cx_tu, tokens, num_tokens); - - return result; -} - -void SetUsePreflight(IndexFile* db, ClangCursor parent) { - switch (GetSymbolKind(parent.get_kind())) { - case SymbolKind::Func: - (void)db->ToFuncId(parent.cx_cursor); - break; - case SymbolKind::Type: - (void)db->ToTypeId(parent.cx_cursor); - break; - case SymbolKind::Var: - (void)db->ToVarId(parent.cx_cursor); - break; - default: - break; - } -} - -// |parent| should be resolved before using |SetUsePreflight| so that |def| will -// not be invalidated by |To{Func,Type,Var}Id|. -Use SetUse(IndexFile* db, Range range, ClangCursor parent, Role role) { - switch (GetSymbolKind(parent.get_kind())) { - case SymbolKind::Func: - return Use(range, db->ToFuncId(parent.cx_cursor), SymbolKind::Func, role, - {}); - case SymbolKind::Type: - return Use(range, db->ToTypeId(parent.cx_cursor), SymbolKind::Type, role, - {}); - case SymbolKind::Var: - return Use(range, db->ToVarId(parent.cx_cursor), SymbolKind::Var, role, - {}); - default: - return Use(range, Id(), SymbolKind::File, role, {}); - } -} - -const char* GetAnonName(CXCursorKind kind) { - switch (kind) { - case CXCursor_ClassDecl: - return "(anon class)"; - case CXCursor_EnumDecl: - return "(anon enum)"; - case CXCursor_StructDecl: - return "(anon struct)"; - case CXCursor_UnionDecl: - return "(anon union)"; - default: - return "(anon)"; - } -} - -void SetTypeName(IndexType* type, - const ClangCursor& cursor, - const CXIdxContainerInfo* container, - const char* name, - IndexParam* param) { - CXIdxContainerInfo parent; - // |name| can be null in an anonymous struct (see - // tests/types/anonymous_struct.cc). - if (!name) - name = GetAnonName(cursor.get_kind()); - if (!container) - parent.cursor = cursor.get_semantic_parent().cx_cursor; - // Investigate why clang_getCursorPrettyPrinted gives `struct A {}` `namespace - // ns {}` which are not qualified. - // type->def.detailed_name = param->PrettyPrintCursor(cursor.cx_cursor); - type->def.detailed_name = - param->ns.QualifiedName(container ? container : &parent, name); - auto idx = type->def.detailed_name.rfind(name); - assert(idx != std::string::npos); - type->def.short_name_offset = idx; - type->def.short_name_size = strlen(name); -} - -// Finds the cursor associated with the declaration type of |cursor|. This -// strips -// qualifies from |cursor| (ie, Foo* => Foo) and removes template arguments -// (ie, Foo => Foo<*,*>). -optional ResolveToDeclarationType(IndexFile* db, - ClangCursor cursor, - IndexParam* param) { - ClangType type = cursor.get_type(); - - // auto x = new Foo() will not be deduced to |Foo| if we do not use the - // canonical type. However, a canonical type will look past typedefs so we - // will not accurately report variables on typedefs if we always do this. - if (type.cx_type.kind == CXType_Auto) - type = type.get_canonical(); - - type = type.strip_qualifiers(); - - if (type.is_builtin()) { - // For builtin types, use type kinds as USR hash. - return db->ToTypeId(type.cx_type.kind); - } - - ClangCursor declaration = - type.get_declaration().template_specialization_to_template_definition(); - CXString cx_usr = clang_getCursorUSR(declaration.cx_cursor); - const char* str_usr = clang_getCString(cx_usr); - if (!str_usr || str_usr[0] == '\0') { - clang_disposeString(cx_usr); - return nullopt; - } - Usr usr = HashUsr(str_usr); - clang_disposeString(cx_usr); - IndexTypeId type_id = db->ToTypeId(usr); - IndexType* typ = db->Resolve(type_id); - if (typ->def.detailed_name.empty()) { - std::string name = declaration.get_spell_name(); - SetTypeName(typ, declaration, nullptr, name.c_str(), param); - } - return type_id; -} - -void SetVarDetail(IndexVar* var, - std::string_view short_name, - const ClangCursor& cursor, - const CXIdxContainerInfo* semanticContainer, - bool is_first_seen, - IndexFile* db, - IndexParam* param) { - IndexVar::Def& def = var->def; - const CXType cx_type = clang_getCursorType(cursor.cx_cursor); - std::string type_name = ToString(clang_getTypeSpelling(cx_type)); - // clang may report "(lambda at foo.cc)" which end up being a very long - // string. Shorten it to just "lambda". - if (type_name.find("(lambda at") != std::string::npos) - type_name = "lambda"; - if (param->config->index.comments) - def.comments = cursor.get_comments(); - def.storage = GetStorageClass(clang_Cursor_getStorageClass(cursor.cx_cursor)); - - // TODO how to make PrettyPrint'ed variable name qualified? - std::string qualified_name = -#if 0 && CINDEX_HAVE_PRETTY - cursor.get_kind() != CXCursor_EnumConstantDecl - ? param->PrettyPrintCursor(cursor.cx_cursor) - : -#endif - param->ns.QualifiedName(semanticContainer, short_name); - - if (cursor.get_kind() == CXCursor_EnumConstantDecl && semanticContainer) { - CXType enum_type = clang_getCanonicalType( - clang_getEnumDeclIntegerType(semanticContainer->cursor)); - std::string hover = qualified_name + " = "; - if (enum_type.kind == CXType_UInt || enum_type.kind == CXType_ULong || - enum_type.kind == CXType_ULongLong) - hover += std::to_string( - clang_getEnumConstantDeclUnsignedValue(cursor.cx_cursor)); - else - hover += std::to_string(clang_getEnumConstantDeclValue(cursor.cx_cursor)); - def.detailed_name = std::move(qualified_name); - def.hover = hover; - } else { -#if 0 && CINDEX_HAVE_PRETTY - //def.detailed_name = param->PrettyPrintCursor(cursor.cx_cursor, false); -#else - ConcatTypeAndName(type_name, qualified_name); - def.detailed_name = type_name; - // Append the textual initializer, bit field, constructor to |hover|. - // Omit |hover| for these types: - // int (*a)(); int (&a)(); int (&&a)(); int a[1]; auto x = ... - // We can take these into consideration after we have better support for - // inside-out syntax. - CXType deref = cx_type; - while (deref.kind == CXType_Pointer || deref.kind == CXType_MemberPointer || - deref.kind == CXType_LValueReference || - deref.kind == CXType_RValueReference) - deref = clang_getPointeeType(deref); - if (deref.kind != CXType_Unexposed && deref.kind != CXType_Auto && - clang_getResultType(deref).kind == CXType_Invalid && - clang_getElementType(deref).kind == CXType_Invalid) { - const FileContents& fc = param->file_contents[db->path]; - optional spell_end = fc.ToOffset(cursor.get_spell().end); - optional extent_end = fc.ToOffset(cursor.get_extent().end); - if (extent_end && *spell_end < *extent_end) - def.hover = std::string(def.detailed_name.c_str()) + - fc.content.substr(*spell_end, *extent_end - *spell_end); - } -#endif - } - // FIXME QualifiedName should return index - auto idx = def.detailed_name.rfind(short_name.begin(), std::string::npos, - short_name.size()); - assert(idx != std::string::npos); - def.short_name_offset = idx; - def.short_name_size = short_name.size(); - - if (is_first_seen) { - optional var_type = - ResolveToDeclarationType(db, cursor, param); - if (var_type) { - // Don't treat enum definition variables as instantiations. - bool is_enum_member = semanticContainer && - semanticContainer->cursor.kind == CXCursor_EnumDecl; - if (!is_enum_member) - db->Resolve(var_type.value())->instances.push_back(var->id); - - def.type = *var_type; - } - } -} - -void OnIndexReference_Function(IndexFile* db, - Range loc, - ClangCursor parent_cursor, - IndexFuncId called_id, - Role role) { - switch (GetSymbolKind(parent_cursor.get_kind())) { - case SymbolKind::Func: { - IndexFunc* parent = db->Resolve(db->ToFuncId(parent_cursor.cx_cursor)); - IndexFunc* called = db->Resolve(called_id); - parent->def.callees.push_back( - SymbolRef(loc, called->id, SymbolKind::Func, role)); - called->uses.push_back(Use(loc, parent->id, SymbolKind::Func, role, {})); - break; - } - case SymbolKind::Type: { - IndexType* parent = db->Resolve(db->ToTypeId(parent_cursor.cx_cursor)); - IndexFunc* called = db->Resolve(called_id); - called = db->Resolve(called_id); - called->uses.push_back(Use(loc, parent->id, SymbolKind::Type, role, {})); - break; - } - default: { - IndexFunc* called = db->Resolve(called_id); - called->uses.push_back(Use(loc, Id(), SymbolKind::File, role, {})); - break; - } - } -} - -} // namespace - -// static -const int IndexFile::kMajorVersion = 15; -const int IndexFile::kMinorVersion = 0; - -IndexFile::IndexFile(const std::string& path, const std::string& contents) - : id_cache(path), path(path), file_contents(contents) {} - -IndexTypeId IndexFile::ToTypeId(Usr usr) { - auto it = id_cache.usr_to_type_id.find(usr); - if (it != id_cache.usr_to_type_id.end()) - return it->second; - - IndexTypeId id(types.size()); - types.push_back(IndexType(id, usr)); - id_cache.usr_to_type_id[usr] = id; - id_cache.type_id_to_usr[id] = usr; - return id; -} -IndexFuncId IndexFile::ToFuncId(Usr usr) { - auto it = id_cache.usr_to_func_id.find(usr); - if (it != id_cache.usr_to_func_id.end()) - return it->second; - - IndexFuncId id(funcs.size()); - funcs.push_back(IndexFunc(id, usr)); - id_cache.usr_to_func_id[usr] = id; - id_cache.func_id_to_usr[id] = usr; - return id; -} -IndexVarId IndexFile::ToVarId(Usr usr) { - auto it = id_cache.usr_to_var_id.find(usr); - if (it != id_cache.usr_to_var_id.end()) - return it->second; - - IndexVarId id(vars.size()); - vars.push_back(IndexVar(id, usr)); - id_cache.usr_to_var_id[usr] = id; - id_cache.var_id_to_usr[id] = usr; - return id; -} - -IndexTypeId IndexFile::ToTypeId(const CXCursor& cursor) { - return ToTypeId(ClangCursor(cursor).get_usr_hash()); -} - -IndexFuncId IndexFile::ToFuncId(const CXCursor& cursor) { - return ToFuncId(ClangCursor(cursor).get_usr_hash()); -} - -IndexVarId IndexFile::ToVarId(const CXCursor& cursor) { - return ToVarId(ClangCursor(cursor).get_usr_hash()); -} - -IndexType* IndexFile::Resolve(IndexTypeId id) { - return &types[id.id]; -} -IndexFunc* IndexFile::Resolve(IndexFuncId id) { - return &funcs[id.id]; -} -IndexVar* IndexFile::Resolve(IndexVarId id) { - return &vars[id.id]; -} - -std::string IndexFile::ToString() { - return Serialize(SerializeFormat::Json, *this); -} - -IndexType::IndexType(IndexTypeId id, Usr usr) : usr(usr), id(id) {} - -template -void Uniquify(std::vector>& ids) { - std::unordered_set> seen; - size_t n = 0; - for (size_t i = 0; i < ids.size(); i++) - if (seen.insert(ids[i]).second) - ids[n++] = ids[i]; - ids.resize(n); -} - -void Uniquify(std::vector& uses) { - std::unordered_set seen; - size_t n = 0; - for (size_t i = 0; i < uses.size(); i++) - if (seen.insert(uses[i].range).second) - uses[n++] = uses[i]; - uses.resize(n); -} - -// FIXME Reference: set id in call sites and remove this -//void AddUse(std::vector& values, Range value) { -// values.push_back( -// Use(value, Id(), SymbolKind::File, Role::Reference, {})); -//} - -void AddUse(IndexFile* db, - std::vector& uses, - Range range, - ClangCursor parent, - Role role = Role::Reference) { - switch (GetSymbolKind(parent.get_kind())) { - case SymbolKind::Func: - uses.push_back(Use(range, db->ToFuncId(parent.cx_cursor), - SymbolKind::Func, role, {})); - break; - case SymbolKind::Type: - uses.push_back(Use(range, db->ToTypeId(parent.cx_cursor), - SymbolKind::Type, role, {})); - break; - default: - uses.push_back(Use(range, Id(), SymbolKind::File, role, {})); - break; - } -} - -CXCursor fromContainer(const CXIdxContainerInfo* parent) { - return parent ? parent->cursor : clang_getNullCursor(); -} - -void AddUseSpell(IndexFile* db, std::vector& uses, ClangCursor cursor) { - AddUse(db, uses, cursor.get_spell(), cursor.get_lexical_parent().cx_cursor); -} - -IdCache::IdCache(const std::string& primary_file) - : primary_file(primary_file) {} - -void OnIndexDiagnostic(CXClientData client_data, - CXDiagnosticSet diagnostics, - void* reserved) { - IndexParam* param = static_cast(client_data); - - for (unsigned i = 0; i < clang_getNumDiagnosticsInSet(diagnostics); ++i) { - CXDiagnostic diagnostic = clang_getDiagnosticInSet(diagnostics, i); - - CXSourceLocation diag_loc = clang_getDiagnosticLocation(diagnostic); - // Skip diagnostics in system headers. - // if (clang_Location_isInSystemHeader(diag_loc)) - // continue; - - // Get db so we can attribute diagnostic to the right indexed file. - CXFile file; - unsigned int line, column; - clang_getSpellingLocation(diag_loc, &file, &line, &column, nullptr); - // Skip empty diagnostic. - if (!line && !column) - continue; - IndexFile* db = ConsumeFile(param, file); - if (!db) - continue; - - // Build diagnostic. - optional ls_diagnostic = - BuildAndDisposeDiagnostic(diagnostic, db->path); - if (ls_diagnostic) - db->diagnostics_.push_back(*ls_diagnostic); - } -} - -CXIdxClientFile OnIndexIncludedFile(CXClientData client_data, - const CXIdxIncludedFileInfo* file) { - IndexParam* param = static_cast(client_data); - - // file->hashLoc only has the position of the hash. We don't have the full - // range for the include. - CXSourceLocation hash_loc = clang_indexLoc_getCXSourceLocation(file->hashLoc); - CXFile cx_file; - unsigned int line; - clang_getSpellingLocation(hash_loc, &cx_file, &line, nullptr, nullptr); - line--; - - IndexFile* db = ConsumeFile(param, cx_file); - if (!db) - return nullptr; - - IndexInclude include; - include.line = line; - include.resolved_path = FileName(file->file); - if (!include.resolved_path.empty()) - db->includes.push_back(include); - - return nullptr; -} - -ClangCursor::VisitResult DumpVisitor(ClangCursor cursor, - ClangCursor parent, - int* level) { - for (int i = 0; i < *level; ++i) - std::cerr << " "; - std::cerr << ToString(cursor.get_kind()) << " " << cursor.get_spell_name() - << std::endl; - - *level += 1; - cursor.VisitChildren(&DumpVisitor, level); - *level -= 1; - - return ClangCursor::VisitResult::Continue; -} - -void Dump(ClangCursor cursor) { - int level = 0; - cursor.VisitChildren(&DumpVisitor, &level); -} - -struct FindChildOfKindParam { - CXCursorKind target_kind; - optional result; - - FindChildOfKindParam(CXCursorKind target_kind) : target_kind(target_kind) {} -}; - -ClangCursor::VisitResult FindTypeVisitor(ClangCursor cursor, - ClangCursor parent, - optional* result) { - switch (cursor.get_kind()) { - case CXCursor_TypeRef: - case CXCursor_TemplateRef: - *result = cursor; - return ClangCursor::VisitResult::Break; - default: - break; - } - - return ClangCursor::VisitResult::Recurse; -} - -optional FindType(ClangCursor cursor) { - optional result; - cursor.VisitChildren(&FindTypeVisitor, &result); - return result; -} - -bool IsTypeDefinition(const CXIdxContainerInfo* container) { - if (!container) - return false; - return GetSymbolKind(container->cursor.kind) == SymbolKind::Type; -} - -struct VisitDeclForTypeUsageParam { - IndexFile* db; - optional toplevel_type; - int has_processed_any = false; - optional previous_cursor; - optional initial_type; - - VisitDeclForTypeUsageParam(IndexFile* db, optional toplevel_type) - : db(db), toplevel_type(toplevel_type) {} -}; - -void VisitDeclForTypeUsageVisitorHandler(ClangCursor cursor, - VisitDeclForTypeUsageParam* param) { - param->has_processed_any = true; - IndexFile* db = param->db; - - // For |A a| where there is a specialization for |A|, - // the |referenced_usr| below resolves to the primary template and - // attributes the use to the primary template instead of the specialization. - // |toplevel_type| is retrieved |clang_getCursorType| which can be a - // specialization. If its name is the same as the primary template's, we - // assume the use should be attributed to the specialization. This heuristic - // fails when a member class bears the same name with its container. - // - // template - // struct C { struct C {}; }; - // C::C a; - // - // We will attribute |::C| to the parent class. - if (param->toplevel_type) { - IndexType* ref_type = db->Resolve(*param->toplevel_type); - std::string name = cursor.get_referenced().get_spell_name(); - if (name == ref_type->def.ShortName()) { - AddUseSpell(db, ref_type->uses, cursor); - param->toplevel_type = nullopt; - return; - } - } - - std::string referenced_usr = - cursor.get_referenced() - .template_specialization_to_template_definition() - .get_usr(); - // TODO: things in STL cause this to be empty. Figure out why and document it. - if (referenced_usr == "") - return; - - IndexTypeId ref_type_id = db->ToTypeId(HashUsr(referenced_usr)); - - if (!param->initial_type) - param->initial_type = ref_type_id; - - IndexType* ref_type_def = db->Resolve(ref_type_id); - // TODO: Should we even be visiting this if the file is not from the main - // def? Try adding assert on |loc| later. - AddUseSpell(db, ref_type_def->uses, cursor); -} - -ClangCursor::VisitResult VisitDeclForTypeUsageVisitor( - ClangCursor cursor, - ClangCursor parent, - VisitDeclForTypeUsageParam* param) { - switch (cursor.get_kind()) { - case CXCursor_TemplateRef: - case CXCursor_TypeRef: - if (param->previous_cursor) { - VisitDeclForTypeUsageVisitorHandler(param->previous_cursor.value(), - param); - } - - param->previous_cursor = cursor; - return ClangCursor::VisitResult::Continue; - - // We do not want to recurse for everything, since if we do that we will end - // up visiting method definition bodies/etc. Instead, we only recurse for - // things that can logically appear as part of an inline variable - // initializer, - // ie, - // - // class Foo { - // int x = (Foo)3; - // } - case CXCursor_CallExpr: - case CXCursor_CStyleCastExpr: - case CXCursor_CXXStaticCastExpr: - case CXCursor_CXXReinterpretCastExpr: - return ClangCursor::VisitResult::Recurse; - - default: - return ClangCursor::VisitResult::Continue; - } - - return ClangCursor::VisitResult::Continue; -} - -// Add usages to any seen TypeRef or TemplateRef under the given |decl_cursor|. -// This returns the first seen TypeRef or TemplateRef value, which can be -// useful if trying to figure out ie, what a using statement refers to. If -// trying to generally resolve a cursor to a type, use -// ResolveToDeclarationType, which works in more scenarios. -// If |decl_cursor| is a variable of a template type, clang_getCursorType -// may return a specialized template which is preciser than the primary -// template. -// We use |toplevel_type| to attribute the use to the specialized template -// instead of the primary template. -optional AddDeclTypeUsages( - IndexFile* db, - ClangCursor decl_cursor, - optional toplevel_type, - const CXIdxContainerInfo* semantic_container, - const CXIdxContainerInfo* lexical_container) { - // - // The general AST format for definitions follows this pattern: - // - // template - // struct Container; - // - // struct S1; - // struct S2; - // - // Container, S2> foo; - // - // => - // - // VarDecl - // TemplateRef Container - // TemplateRef Container - // TypeRef struct S1 - // TypeRef struct S2 - // TypeRef struct S2 - // - // - // Here is another example: - // - // enum A {}; - // enum B {}; - // - // template - // struct Foo { - // struct Inner {}; - // }; - // - // Foo::Inner a; - // Foo b; - // - // => - // - // EnumDecl A - // EnumDecl B - // ClassTemplate Foo - // TemplateTypeParameter T - // StructDecl Inner - // VarDecl a - // TemplateRef Foo - // TypeRef enum A - // TypeRef struct Foo::Inner - // CallExpr Inner - // VarDecl b - // TemplateRef Foo - // TypeRef enum B - // CallExpr Foo - // - // - // Determining the actual type of the variable/declaration from just the - // children is tricky. Doing so would require looking up the template - // definition associated with a TemplateRef, figuring out how many children - // it has, and then skipping that many TypeRef values. This also has to work - // with the example below (skipping the last TypeRef). As a result, we - // determine variable types using |ResolveToDeclarationType|. - // - // - // We skip the last type reference for methods/variables which are defined - // out-of-line w.r.t. the parent type. - // - // S1* Foo::foo() {} - // - // The above example looks like this in the AST: - // - // CXXMethod foo - // TypeRef struct S1 - // TypeRef class Foo - // CompoundStmt - // ... - // - // The second TypeRef is an uninteresting usage. - bool process_last_type_ref = true; - if (IsTypeDefinition(semantic_container) && - !IsTypeDefinition(lexical_container)) { - // - // In some code, such as the following example, we receive a cursor which is - // not - // a definition and is not associated with a definition due to an error - // condition. - // In this case, it is the Foo::Foo constructor. - // - // struct Foo {}; - // - // template - // Foo::Foo() {} - // - if (!decl_cursor.is_definition()) { - ClangCursor def = decl_cursor.get_definition(); - if (def.get_kind() != CXCursor_FirstInvalid) - decl_cursor = def; - } - process_last_type_ref = false; - } - - VisitDeclForTypeUsageParam param(db, toplevel_type); - decl_cursor.VisitChildren(&VisitDeclForTypeUsageVisitor, ¶m); - - // VisitDeclForTypeUsageVisitor guarantees that if there are multiple TypeRef - // children, the first one will always be visited. - if (param.previous_cursor && process_last_type_ref) { - VisitDeclForTypeUsageVisitorHandler(param.previous_cursor.value(), ¶m); - } else { - // If we are not processing the last type ref, it *must* be a TypeRef or - // TemplateRef. - // - // We will not visit every child if the is_interseting is false, so - // previous_cursor - // may not point to the last TemplateRef. - assert(param.previous_cursor.has_value() == false || - (param.previous_cursor.value().get_kind() == CXCursor_TypeRef || - param.previous_cursor.value().get_kind() == CXCursor_TemplateRef)); - } - - if (param.initial_type) - return param.initial_type; - CXType cx_under = clang_getTypedefDeclUnderlyingType(decl_cursor.cx_cursor); - if (cx_under.kind == CXType_Invalid) - return nullopt; - return db->ToTypeId(ClangType(cx_under).strip_qualifiers().get_usr_hash()); -} - -// Various versions of LLVM (ie, 4.0) will not visit inline variable references -// for template arguments. -ClangCursor::VisitResult AddDeclInitializerUsagesVisitor(ClangCursor cursor, - ClangCursor parent, - IndexFile* db) { - /* - We need to index the |DeclRefExpr| below (ie, |var| inside of - Foo::var). - - template - struct Foo { - static constexpr int var = 3; - }; - - int a = Foo::var; - - => - - VarDecl a - UnexposedExpr var - DeclRefExpr var - TemplateRef Foo - - */ - - switch (cursor.get_kind()) { - case CXCursor_DeclRefExpr: { - if (cursor.get_referenced().get_kind() != CXCursor_VarDecl) - break; - - // TODO: when we resolve the template type to the definition, we get a - // different Usr. - - // ClangCursor ref = - // cursor.get_referenced().template_specialization_to_template_definition().get_type().strip_qualifiers().get_usr_hash(); - // std::string ref_usr = - // cursor.get_referenced().template_specialization_to_template_definition().get_type().strip_qualifiers().get_usr_hash(); - auto ref_usr = cursor.get_referenced() - .template_specialization_to_template_definition() - .get_usr(); - // std::string ref_usr = ref.get_usr_hash(); - if (ref_usr == "") - break; - - IndexVar* ref_var = db->Resolve(db->ToVarId(HashUsr(ref_usr))); - AddUseSpell(db, ref_var->uses, cursor); - break; - } - - default: - break; - } - - return ClangCursor::VisitResult::Recurse; -} - -ClangCursor::VisitResult VisitMacroDefinitionAndExpansions(ClangCursor cursor, - ClangCursor parent, - IndexParam* param) { - switch (cursor.get_kind()) { - case CXCursor_MacroDefinition: - case CXCursor_MacroExpansion: { - // Resolve location, find IndexFile instance. - CXSourceRange cx_source_range = - clang_Cursor_getSpellingNameRange(cursor.cx_cursor, 0, 0); - CXFile file; - Range decl_loc_spelling = ResolveCXSourceRange(cx_source_range, &file); - IndexFile* db = ConsumeFile(param, file); - if (!db) - break; - - // TODO: Considering checking clang_Cursor_isMacroFunctionLike, but the - // only real difference will be that we show 'callers' instead of 'refs' - // (especially since macros cannot have overrides) - - Usr decl_usr; - if (cursor.get_kind() == CXCursor_MacroDefinition) - decl_usr = cursor.get_usr_hash(); - else - decl_usr = cursor.get_referenced().get_usr_hash(); - - SetUsePreflight(db, parent); - IndexVar* var_def = db->Resolve(db->ToVarId(decl_usr)); - if (cursor.get_kind() == CXCursor_MacroDefinition) { - CXSourceRange cx_extent = clang_getCursorExtent(cursor.cx_cursor); - var_def->def.detailed_name = cursor.get_display_name(); - var_def->def.short_name_offset = 0; - var_def->def.short_name_size = - int16_t(strlen(var_def->def.detailed_name.c_str())); - var_def->def.hover = - "#define " + GetDocumentContentInRange(param->tu->cx_tu, cx_extent); - var_def->def.kind = lsSymbolKind::Macro; - if (param->config->index.comments) - var_def->def.comments = cursor.get_comments(); - var_def->def.spell = - SetUse(db, decl_loc_spelling, parent, Role::Definition); - var_def->def.extent = SetUse( - db, ResolveCXSourceRange(cx_extent, nullptr), parent, Role::None); - } else - AddUse(db, var_def->uses, decl_loc_spelling, parent); - - break; - } - default: - break; - } - - return ClangCursor::VisitResult::Continue; -} - -namespace { - -// TODO Move to another file and use clang C++ API -struct TemplateVisitorData { - IndexFile* db; - IndexParam* param; - ClangCursor container; -}; - -ClangCursor::VisitResult TemplateVisitor(ClangCursor cursor, - ClangCursor parent, - TemplateVisitorData* data) { - IndexFile* db = data->db; - IndexParam* param = data->param; - switch (cursor.get_kind()) { - default: - break; - case CXCursor_DeclRefExpr: { - ClangCursor ref_cursor = clang_getCursorReferenced(cursor.cx_cursor); - if (ref_cursor.get_kind() == CXCursor_NonTypeTemplateParameter) { - IndexVarId ref_var_id = db->ToVarId(ref_cursor.get_usr_hash()); - IndexVar* ref_var = db->Resolve(ref_var_id); - if (ref_var->def.detailed_name.empty()) { - ClangCursor sem_parent = ref_cursor.get_semantic_parent(); - ClangCursor lex_parent = ref_cursor.get_lexical_parent(); - SetUsePreflight(db, sem_parent); - SetUsePreflight(db, lex_parent); - ref_var = db->Resolve(ref_var_id); - ref_var->def.spell = - SetUse(db, ref_cursor.get_spell(), sem_parent, Role::Definition); - ref_var->def.extent = - SetUse(db, ref_cursor.get_extent(), lex_parent, Role::None); - ref_var = db->Resolve(ref_var_id); - ref_var->def.kind = lsSymbolKind::TypeParameter; - SetVarDetail(ref_var, ref_cursor.get_spell_name(), ref_cursor, - nullptr, true, db, param); - - ClangType ref_type = clang_getCursorType(ref_cursor.cx_cursor); - // TODO optimize - if (ref_type.get_usr().size()) { - IndexType* ref_type_index = - db->Resolve(db->ToTypeId(ref_type.get_usr_hash())); - // The cursor extent includes `type name`, not just `name`. There - // seems no way to extract the spelling range of `type` and we do - // not want to do subtraction here. - // See https://github.com/jacobdufault/cquery/issues/252 - AddUse(db, ref_type_index->uses, ref_cursor.get_extent(), - ref_cursor.get_lexical_parent()); - } - } - AddUseSpell(db, ref_var->uses, cursor); - } - break; - } - case CXCursor_OverloadedDeclRef: { - unsigned num_overloaded = clang_getNumOverloadedDecls(cursor.cx_cursor); - for (unsigned i = 0; i != num_overloaded; i++) { - ClangCursor overloaded = clang_getOverloadedDecl(cursor.cx_cursor, i); - switch (overloaded.get_kind()) { - default: - break; - case CXCursor_FunctionDecl: - case CXCursor_FunctionTemplate: { - IndexFuncId called_id = db->ToFuncId(overloaded.get_usr_hash()); - OnIndexReference_Function(db, cursor.get_spell(), data->container, - called_id, Role::Call); - break; - } - } - } - break; - } - case CXCursor_TemplateRef: { - ClangCursor ref_cursor = clang_getCursorReferenced(cursor.cx_cursor); - if (ref_cursor.get_kind() == CXCursor_TemplateTemplateParameter) { - IndexTypeId ref_type_id = db->ToTypeId(ref_cursor.get_usr_hash()); - IndexType* ref_type = db->Resolve(ref_type_id); - // TODO It seems difficult to get references to template template - // parameters. - // CXCursor_TemplateTemplateParameter can be visited by visiting - // CXCursor_TranslationUnit, but not (confirm this) by visiting - // {Class,Function}Template. Thus we need to initialize it here. - if (ref_type->def.detailed_name.empty()) { - ClangCursor sem_parent = ref_cursor.get_semantic_parent(); - ClangCursor lex_parent = ref_cursor.get_lexical_parent(); - SetUsePreflight(db, sem_parent); - SetUsePreflight(db, lex_parent); - ref_type = db->Resolve(ref_type_id); - ref_type->def.spell = - SetUse(db, ref_cursor.get_spell(), sem_parent, Role::Definition); - ref_type->def.extent = - SetUse(db, ref_cursor.get_extent(), lex_parent, Role::None); -#if 0 && CINDEX_HAVE_PRETTY - ref_type->def.detailed_name = param->PrettyPrintCursor(ref_cursor.cx_cursor); -#else - ref_type->def.detailed_name = ref_cursor.get_spell_name(); -#endif - ref_type->def.short_name_offset = 0; - ref_type->def.short_name_size = - int16_t(strlen(ref_type->def.detailed_name.c_str())); - ref_type->def.kind = lsSymbolKind::TypeParameter; - } - AddUseSpell(db, ref_type->uses, cursor); - } - break; - } - case CXCursor_TypeRef: { - ClangCursor ref_cursor = clang_getCursorReferenced(cursor.cx_cursor); - if (ref_cursor.get_kind() == CXCursor_TemplateTypeParameter) { - IndexTypeId ref_type_id = db->ToTypeId(ref_cursor.get_usr_hash()); - IndexType* ref_type = db->Resolve(ref_type_id); - // TODO It seems difficult to get a FunctionTemplate's template - // parameters. - // CXCursor_TemplateTypeParameter can be visited by visiting - // CXCursor_TranslationUnit, but not (confirm this) by visiting - // {Class,Function}Template. Thus we need to initialize it here. - if (ref_type->def.detailed_name.empty()) { - ClangCursor sem_parent = ref_cursor.get_semantic_parent(); - ClangCursor lex_parent = ref_cursor.get_lexical_parent(); - SetUsePreflight(db, sem_parent); - SetUsePreflight(db, lex_parent); - ref_type = db->Resolve(ref_type_id); - ref_type->def.spell = - SetUse(db, ref_cursor.get_spell(), sem_parent, Role::Definition); - ref_type->def.extent = - SetUse(db, ref_cursor.get_extent(), lex_parent, Role::None); -#if 0 && CINDEX_HAVE_PRETTY - // template void f(T t){} // weird, the name is empty - ref_type->def.detailed_name = param->PrettyPrintCursor(ref_cursor.cx_cursor); -#else - ref_type->def.detailed_name = ref_cursor.get_spell_name(); -#endif - ref_type->def.short_name_offset = 0; - ref_type->def.short_name_size = - int16_t(strlen(ref_type->def.detailed_name.c_str())); - ref_type->def.kind = lsSymbolKind::TypeParameter; - } - AddUseSpell(db, ref_type->uses, cursor); - } - break; - } - } - return ClangCursor::VisitResult::Recurse; -} - -} // namespace - -std::string NamespaceHelper::QualifiedName(const CXIdxContainerInfo* container, - std::string_view unqualified_name) { - if (!container) - return std::string(unqualified_name); - // Anonymous namespaces are not processed by indexDeclaration. We trace - // nested namespaces bottom-up through clang_getCursorSemanticParent until - // one that we know its qualified name. Then do another trace top-down and - // put their names into a map of USR -> qualified_name. - ClangCursor cursor(container->cursor); - std::vector namespaces; - std::string qualifier; - while (cursor.get_kind() != CXCursor_TranslationUnit && - GetSymbolKind(cursor.get_kind()) == SymbolKind::Type) { - auto it = container_cursor_to_qualified_name.find(cursor); - if (it != container_cursor_to_qualified_name.end()) { - qualifier = it->second; - break; - } - namespaces.push_back(cursor); - cursor = clang_getCursorSemanticParent(cursor.cx_cursor); - } - for (size_t i = namespaces.size(); i > 0;) { - i--; - std::string name = namespaces[i].get_spell_name(); - // Empty name indicates unnamed namespace, anonymous struct, anonymous - // union, ... - if (name.size()) - qualifier += name; - else - qualifier += GetAnonName(namespaces[i].get_kind()); - qualifier += "::"; - container_cursor_to_qualified_name[namespaces[i]] = qualifier; - } - // C++17 string::append - return qualifier + std::string(unqualified_name); -} - -void OnIndexDeclaration(CXClientData client_data, const CXIdxDeclInfo* decl) { - IndexParam* param = static_cast(client_data); - - // Track all constructor declarations, as we may need to use it to manually - // associate std::make_unique and the like as constructor invocations. - if (decl->entityInfo->kind == CXIdxEntity_CXXConstructor) { - param->ctors.NotifyConstructor(decl->cursor); - } - - CXFile file; - clang_getSpellingLocation(clang_indexLoc_getCXSourceLocation(decl->loc), - &file, nullptr, nullptr, nullptr); - IndexFile* db = ConsumeFile(param, file); - if (!db) - return; - - // The language of this declaration - LanguageId decl_lang = [&decl]() { - switch (clang_getCursorLanguage(decl->cursor)) { - case CXLanguage_C: - return LanguageId::C; - case CXLanguage_CPlusPlus: - return LanguageId::Cpp; - case CXLanguage_ObjC: - return LanguageId::ObjC; - default: - return LanguageId::Unknown; - }; - }(); - - // Only update the file language if the new language is "greater" than the old - if (decl_lang > db->language) { - db->language = decl_lang; - } - - ClangCursor sem_parent(fromContainer(decl->semanticContainer)); - ClangCursor lex_parent(fromContainer(decl->lexicalContainer)); - SetUsePreflight(db, sem_parent); - SetUsePreflight(db, lex_parent); - ClangCursor cursor = decl->cursor; - - switch (decl->entityInfo->kind) { - case CXIdxEntity_Unexposed: - LOG_S(INFO) << "CXIdxEntity_Unexposed " << cursor.get_spell_name(); - break; - - case CXIdxEntity_CXXNamespace: { - Range spell = cursor.get_spell(); - IndexTypeId ns_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); - IndexType* ns = db->Resolve(ns_id); - ns->def.kind = GetSymbolKind(decl->entityInfo->kind); - if (ns->def.detailed_name.empty()) { - SetTypeName(ns, cursor, decl->semanticContainer, decl->entityInfo->name, - param); - ns->def.spell = SetUse(db, spell, sem_parent, Role::Definition); - ns->def.extent = - SetUse(db, cursor.get_extent(), lex_parent, Role::None); - if (decl->semanticContainer) { - IndexTypeId parent_id = db->ToTypeId( - ClangCursor(decl->semanticContainer->cursor).get_usr_hash()); - db->Resolve(parent_id)->derived.push_back(ns_id); - // |ns| may be invalidated. - ns = db->Resolve(ns_id); - ns->def.bases.push_back(parent_id); - } - } - AddUse(db, ns->uses, spell, lex_parent); - break; - } - - case CXIdxEntity_CXXNamespaceAlias: - assert(false && "CXXNamespaceAlias"); - break; - - case CXIdxEntity_ObjCProperty: - case CXIdxEntity_ObjCIvar: - case CXIdxEntity_EnumConstant: - case CXIdxEntity_Field: - case CXIdxEntity_Variable: - case CXIdxEntity_CXXStaticVariable: { - Range spell = cursor.get_spell(); - - // Do not index implicit template instantiations. - if (cursor != cursor.template_specialization_to_template_definition()) - break; - - IndexVarId var_id = db->ToVarId(HashUsr(decl->entityInfo->USR)); - IndexVar* var = db->Resolve(var_id); - - // TODO: Eventually run with this if. Right now I want to iron out bugs - // this may shadow. - // TODO: Verify this gets called multiple times - // if (!decl->isRedeclaration) { - SetVarDetail(var, std::string(decl->entityInfo->name), decl->cursor, - decl->semanticContainer, !decl->isRedeclaration, db, param); - - // FIXME https://github.com/jacobdufault/cquery/issues/239 - var->def.kind = GetSymbolKind(decl->entityInfo->kind); - if (var->def.kind == lsSymbolKind::Variable && - decl->cursor.kind == CXCursor_ParmDecl) - var->def.kind = lsSymbolKind::Parameter; - //} - - if (decl->isDefinition) { - var->def.spell = SetUse(db, spell, sem_parent, Role::Definition); - var->def.extent = - SetUse(db, cursor.get_extent(), lex_parent, Role::None); - } else { - var->declarations.push_back( - SetUse(db, spell, lex_parent, Role::Declaration)); - } - - cursor.VisitChildren(&AddDeclInitializerUsagesVisitor, db); - var = db->Resolve(var_id); - - // Declaring variable type information. Note that we do not insert an - // interesting reference for parameter declarations - that is handled when - // the function declaration is encountered since we won't receive ParmDecl - // declarations for unnamed parameters. - // TODO: See if we can remove this function call. - AddDeclTypeUsages(db, cursor, var->def.type, decl->semanticContainer, - decl->lexicalContainer); - - // We don't need to assign declaring type multiple times if this variable - // has already been seen. - - if (decl->isDefinition && decl->semanticContainer) { - switch (GetSymbolKind(decl->semanticContainer->cursor.kind)) { - case SymbolKind::Func: { - db->Resolve(db->ToFuncId(decl->semanticContainer->cursor)) - ->def.vars.push_back(var_id); - break; - } - case SymbolKind::Type: - if (decl->semanticContainer->cursor.kind != CXCursor_EnumDecl) { - db->Resolve(db->ToTypeId(decl->semanticContainer->cursor)) - ->def.vars.push_back(var_id); - } - break; - default: - break; - } - } - - break; - } - - case CXIdxEntity_ObjCInstanceMethod: - case CXIdxEntity_ObjCClassMethod: - case CXIdxEntity_Function: - case CXIdxEntity_CXXConstructor: - case CXIdxEntity_CXXDestructor: - case CXIdxEntity_CXXInstanceMethod: - case CXIdxEntity_CXXStaticMethod: - case CXIdxEntity_CXXConversionFunction: { - Range spell = cursor.get_spell(); - Range extent = cursor.get_extent(); - - ClangCursor decl_cursor_resolved = - cursor.template_specialization_to_template_definition(); - bool is_template_specialization = cursor != decl_cursor_resolved; - - IndexFuncId func_id = db->ToFuncId(decl_cursor_resolved.cx_cursor); - IndexFunc* func = db->Resolve(func_id); - if (param->config->index.comments) - func->def.comments = cursor.get_comments(); - func->def.kind = GetSymbolKind(decl->entityInfo->kind); - func->def.storage = - GetStorageClass(clang_Cursor_getStorageClass(decl->cursor)); - - // We don't actually need to know the return type, but we need to mark it - // as an interesting usage. - AddDeclTypeUsages(db, cursor, nullopt, decl->semanticContainer, - decl->lexicalContainer); - - // Add definition or declaration. This is a bit tricky because we treat - // template specializations as declarations, even though they are - // technically definitions. - // TODO: Support multiple function definitions, which is common for - // template specializations. - if (decl->isDefinition && !is_template_specialization) { - // assert(!func->def.spell); - // assert(!func->def.extent); - func->def.spell = SetUse(db, spell, sem_parent, Role::Definition); - func->def.extent = SetUse(db, extent, lex_parent, Role::None); - } else { - IndexFunc::Declaration declaration; - declaration.spell = SetUse(db, spell, lex_parent, Role::Declaration); - - // Add parameters. - for (ClangCursor arg : cursor.get_arguments()) { - switch (arg.get_kind()) { - case CXCursor_ParmDecl: { - Range param_spelling = arg.get_spell(); - - // If the name is empty (which is common for parameters), clang - // will report a range with length 1, which is not correct. - if (param_spelling.start.column == - (param_spelling.end.column - 1) && - arg.get_display_name().empty()) { - param_spelling.end.column -= 1; - } - - declaration.param_spellings.push_back(param_spelling); - break; - } - default: - break; - } - } - - func->declarations.push_back(declaration); - } - - // Emit definition data for the function. We do this even if it isn't a - // definition because there can be, for example, interfaces, or a class - // declaration that doesn't have a definition yet. If we never end up - // indexing the definition, then there will not be any (ie) outline - // information. - if (!is_template_specialization) { - // Build detailed name. The type desc looks like void (void *). We - // insert the qualified name before the first '('. - // FIXME GetFunctionSignature should set index -#if CINDEX_HAVE_PRETTY - func->def.detailed_name = param->PrettyPrintCursor(decl->cursor); -#else - func->def.detailed_name = GetFunctionSignature(db, ¶m->ns, decl); -#endif - auto idx = func->def.detailed_name.find(decl->entityInfo->name); - assert(idx != std::string::npos); - func->def.short_name_offset = idx; - func->def.short_name_size = strlen(decl->entityInfo->name); - - // CXCursor_OverloadedDeclRef in templates are not processed by - // OnIndexReference, thus we use TemplateVisitor to collect function - // references. - if (decl->entityInfo->templateKind == CXIdxEntity_Template) { - TemplateVisitorData data; - data.db = db; - data.param = param; - data.container = cursor; - cursor.VisitChildren(&TemplateVisitor, &data); - // TemplateVisitor calls ToFuncId which invalidates func - func = db->Resolve(func_id); - } - - // Add function usage information. We only want to do it once per - // definition/declaration. Do it on definition since there should only - // ever be one of those in the entire program. - if (IsTypeDefinition(decl->semanticContainer)) { - IndexTypeId declaring_type_id = - db->ToTypeId(decl->semanticContainer->cursor); - IndexType* declaring_type_def = db->Resolve(declaring_type_id); - func->def.declaring_type = declaring_type_id; - - // Mark a type reference at the ctor/dtor location. - if (decl->entityInfo->kind == CXIdxEntity_CXXConstructor) - AddUse(db, declaring_type_def->uses, spell, - fromContainer(decl->lexicalContainer)); - - // Add function to declaring type. - declaring_type_def->def.funcs.push_back(func_id); - } - - // Process inheritance. - if (clang_CXXMethod_isVirtual(decl->cursor)) { - CXCursor* overridden; - unsigned int num_overridden; - clang_getOverriddenCursors(decl->cursor, &overridden, - &num_overridden); - - for (unsigned i = 0; i < num_overridden; ++i) { - ClangCursor parent = - ClangCursor(overridden[i]) - .template_specialization_to_template_definition(); - IndexFuncId parent_id = db->ToFuncId(parent.get_usr_hash()); - IndexFunc* parent_def = db->Resolve(parent_id); - func = db->Resolve(func_id); // ToFuncId invalidated func_def - - func->def.bases.push_back(parent_id); - parent_def->derived.push_back(func_id); - } - - clang_disposeOverriddenCursors(overridden); - } - } - break; - } - - case CXIdxEntity_Typedef: - case CXIdxEntity_CXXTypeAlias: { - // Note we want to fetch the first TypeRef. Running - // ResolveCursorType(decl->cursor) would return - // the type of the typedef/using, not the type of the referenced type. - optional alias_of = AddDeclTypeUsages( - db, cursor, nullopt, decl->semanticContainer, decl->lexicalContainer); - - IndexTypeId type_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); - IndexType* type = db->Resolve(type_id); - - if (alias_of) - type->def.alias_of = alias_of.value(); - - ClangCursor decl_cursor = decl->cursor; - Range spell = decl_cursor.get_spell(); - Range extent = decl_cursor.get_extent(); - type->def.spell = SetUse(db, spell, sem_parent, Role::Definition); - type->def.extent = SetUse(db, extent, lex_parent, Role::None); - - SetTypeName(type, decl_cursor, decl->semanticContainer, - decl->entityInfo->name, param); - type->def.kind = GetSymbolKind(decl->entityInfo->kind); - if (param->config->index.comments) - type->def.comments = decl_cursor.get_comments(); - - // For Typedef/CXXTypeAlias spanning a few lines, display the declaration - // line, with spelling name replaced with qualified name. - // TODO Think how to display multi-line declaration like `typedef struct { - // ... } foo;` https://github.com/jacobdufault/cquery/issues/29 - if (extent.end.line - extent.start.line < - kMaxLinesDisplayTypeAliasDeclarations) { - FileContents& fc = param->file_contents[db->path]; - optional extent_start = fc.ToOffset(extent.start), - spell_start = fc.ToOffset(spell.start), - spell_end = fc.ToOffset(spell.end), - extent_end = fc.ToOffset(extent.end); - if (extent_start && spell_start && spell_end && extent_end) { - type->def.hover = - fc.content.substr(*extent_start, *spell_start - *extent_start) + - type->def.detailed_name.c_str() + - fc.content.substr(*spell_end, *extent_end - *spell_end); - } - } - - AddUse(db, type->uses, spell, fromContainer(decl->lexicalContainer)); - break; - } - - case CXIdxEntity_ObjCProtocol: - case CXIdxEntity_ObjCCategory: - case CXIdxEntity_ObjCClass: - case CXIdxEntity_Enum: - case CXIdxEntity_Union: - case CXIdxEntity_Struct: - case CXIdxEntity_CXXInterface: - case CXIdxEntity_CXXClass: { - Range spell = cursor.get_spell(); - - IndexTypeId type_id = db->ToTypeId(HashUsr(decl->entityInfo->USR)); - IndexType* type = db->Resolve(type_id); - - // TODO: Eventually run with this if. Right now I want to iron out bugs - // this may shadow. - // TODO: For type section, verify if this ever runs for non definitions? - // if (!decl->isRedeclaration) { - - SetTypeName(type, cursor, decl->semanticContainer, - decl->entityInfo->name, param); - type->def.kind = GetSymbolKind(decl->entityInfo->kind); - if (param->config->index.comments) - type->def.comments = cursor.get_comments(); - // } - - if (decl->isDefinition) { - type->def.spell = SetUse(db, spell, sem_parent, Role::Definition); - type->def.extent = - SetUse(db, cursor.get_extent(), lex_parent, Role::None); - - if (cursor.get_kind() == CXCursor_EnumDecl) { - ClangType enum_type = clang_getEnumDeclIntegerType(decl->cursor); - if (!enum_type.is_builtin()) { - IndexType* int_type = - db->Resolve(db->ToTypeId(enum_type.get_usr_hash())); - AddUse(db, int_type->uses, spell, - fromContainer(decl->lexicalContainer)); - // type is invalidated. - type = db->Resolve(type_id); - } - } - } else - AddUse(db, type->declarations, spell, - fromContainer(decl->lexicalContainer), Role::Declaration); - - switch (decl->entityInfo->templateKind) { - default: - break; - case CXIdxEntity_TemplateSpecialization: - case CXIdxEntity_TemplatePartialSpecialization: { - // TODO Use a different dimension - ClangCursor origin_cursor = - cursor.template_specialization_to_template_definition(); - IndexTypeId origin_id = db->ToTypeId(origin_cursor.get_usr_hash()); - IndexType* origin = db->Resolve(origin_id); - // |type| may be invalidated. - type = db->Resolve(type_id); - // template class function; // not visited by - // OnIndexDeclaration template<> class function {}; // current - // cursor - if (origin->def.detailed_name.empty()) { - SetTypeName(origin, origin_cursor, nullptr, - &type->def.ShortName()[0], param); - origin->def.kind = type->def.kind; - } - // TODO The name may be assigned in |ResolveToDeclarationType| but - // |spell| is nullopt. - CXFile origin_file; - Range origin_spell = origin_cursor.get_spell(&origin_file); - if (!origin->def.spell && file == origin_file) { - ClangCursor origin_sem = origin_cursor.get_semantic_parent(); - ClangCursor origin_lex = origin_cursor.get_lexical_parent(); - SetUsePreflight(db, origin_sem); - SetUsePreflight(db, origin_lex); - origin = db->Resolve(origin_id); - type = db->Resolve(type_id); - origin->def.spell = - SetUse(db, origin_spell, origin_sem, Role::Definition); - origin->def.extent = - SetUse(db, origin_cursor.get_extent(), origin_lex, Role::None); - } - origin->derived.push_back(type_id); - type->def.bases.push_back(origin_id); - } - // fallthrough - case CXIdxEntity_Template: { - TemplateVisitorData data; - data.db = db; - data.container = cursor; - data.param = param; - cursor.VisitChildren(&TemplateVisitor, &data); - break; - } - } - - // type_def->alias_of - // type_def->funcs - // type_def->types - // type_def->uses - // type_def->vars - - // Add type-level inheritance information. - CXIdxCXXClassDeclInfo const* class_info = - clang_index_getCXXClassDeclInfo(decl); - if (class_info) { - for (unsigned int i = 0; i < class_info->numBases; ++i) { - const CXIdxBaseClassInfo* base_class = class_info->bases[i]; - - AddDeclTypeUsages(db, base_class->cursor, nullopt, - decl->semanticContainer, decl->lexicalContainer); - optional parent_type_id = - ResolveToDeclarationType(db, base_class->cursor, param); - // type_def ptr could be invalidated by ResolveToDeclarationType and - // TemplateVisitor. - type = db->Resolve(type_id); - if (parent_type_id) { - IndexType* parent_type_def = db->Resolve(parent_type_id.value()); - parent_type_def->derived.push_back(type_id); - type->def.bases.push_back(*parent_type_id); - } - } - } - break; - } - } -} - -// https://github.com/jacobdufault/cquery/issues/174 -// Type-dependent member access expressions do not have accurate spelling -// ranges. -// -// Not type dependent -// C f; f.x // .x produces a MemberRefExpr which has a spelling range -// of `x`. -// -// Type dependent -// C e; e.x // .x produces a MemberRefExpr which has a spelling range -// of `e` (weird) and an empty spelling name. -// -// To attribute the use of `x` in `e.x`, we use cursor extent `e.x` -// minus cursor spelling `e` minus the period. -void CheckTypeDependentMemberRefExpr(Range* spell, - const ClangCursor& cursor, - IndexParam* param, - const IndexFile* db) { - if (cursor.get_kind() == CXCursor_MemberRefExpr && - cursor.get_spell_name().empty()) { - *spell = cursor.get_extent().RemovePrefix(spell->end); - const FileContents& fc = param->file_contents[db->path]; - optional maybe_period = fc.ToOffset(spell->start); - if (maybe_period) { - int i = *maybe_period; - if (fc.content[i] == '.') - spell->start.column++; - // -> is likely unexposed. - } - } -} - -void OnIndexReference(CXClientData client_data, const CXIdxEntityRefInfo* ref) { - // TODO: Use clang_getFileUniqueID - CXFile file; - clang_getSpellingLocation(clang_indexLoc_getCXSourceLocation(ref->loc), &file, - nullptr, nullptr, nullptr); - IndexParam* param = static_cast(client_data); - IndexFile* db = ConsumeFile(param, file); - if (!db) - return; - - ClangCursor cursor(ref->cursor); - ClangCursor lex_parent(fromContainer(ref->container)); - ClangCursor referenced; - if (ref->referencedEntity) - referenced = ref->referencedEntity->cursor; - SetUsePreflight(db, lex_parent); - - switch (ref->referencedEntity->kind) { - case CXIdxEntity_Unexposed: - LOG_S(INFO) << "CXIdxEntity_Unexposed " << cursor.get_spell_name(); - break; - - case CXIdxEntity_CXXNamespace: { - IndexType* ns = db->Resolve(db->ToTypeId(referenced.get_usr_hash())); - AddUse(db, ns->uses, cursor.get_spell(), fromContainer(ref->container)); - break; - } - - case CXIdxEntity_CXXNamespaceAlias: { - IndexType* ns = db->Resolve(db->ToTypeId(referenced.get_usr_hash())); - AddUse(db, ns->uses, cursor.get_spell(), fromContainer(ref->container)); - if (!ns->def.spell) { - ClangCursor sem_parent = referenced.get_semantic_parent(); - ClangCursor lex_parent = referenced.get_lexical_parent(); - SetUsePreflight(db, sem_parent); - SetUsePreflight(db, lex_parent); - ns->def.spell = - SetUse(db, referenced.get_spell(), sem_parent, Role::Definition); - ns->def.extent = - SetUse(db, referenced.get_extent(), lex_parent, Role::None); - std::string name = referenced.get_spell_name(); - SetTypeName(ns, referenced, nullptr, name.c_str(), param); - } - break; - } - - case CXIdxEntity_ObjCProperty: - case CXIdxEntity_ObjCIvar: - case CXIdxEntity_EnumConstant: - case CXIdxEntity_CXXStaticVariable: - case CXIdxEntity_Variable: - case CXIdxEntity_Field: { - Range loc = cursor.get_spell(); - CheckTypeDependentMemberRefExpr(&loc, cursor, param, db); - - referenced = referenced.template_specialization_to_template_definition(); - - IndexVarId var_id = db->ToVarId(referenced.get_usr_hash()); - IndexVar* var = db->Resolve(var_id); - // Lambda paramaters are not processed by OnIndexDeclaration and - // may not have a short_name yet. Note that we only process the lambda - // parameter as a definition if it is in the same file as the reference, - // as lambdas cannot be split across files. - if (var->def.detailed_name.empty()) { - CXFile referenced_file; - Range spell = referenced.get_spell(&referenced_file); - if (file == referenced_file) { - var->def.spell = SetUse(db, spell, lex_parent, Role::Definition); - var->def.extent = - SetUse(db, referenced.get_extent(), lex_parent, Role::None); - - // TODO Some of the logic here duplicates CXIdxEntity_Variable branch - // of OnIndexDeclaration. But there `decl` is of type CXIdxDeclInfo - // and has more information, thus not easy to reuse the code. - SetVarDetail(var, referenced.get_spell_name(), referenced, nullptr, - true, db, param); - var->def.kind = lsSymbolKind::Parameter; - } - } - AddUse(db, var->uses, loc, fromContainer(ref->container), - GetRole(ref, Role::Reference)); - break; - } - - case CXIdxEntity_CXXConversionFunction: - case CXIdxEntity_CXXStaticMethod: - case CXIdxEntity_CXXInstanceMethod: - case CXIdxEntity_ObjCInstanceMethod: - case CXIdxEntity_ObjCClassMethod: - case CXIdxEntity_Function: - case CXIdxEntity_CXXConstructor: - case CXIdxEntity_CXXDestructor: { - // TODO: Redirect container to constructor for the following example, ie, - // we should be inserting an outgoing function call from the Foo - // ctor. - // - // int Gen() { return 5; } - // class Foo { - // int x = Gen(); - // } - - // TODO: search full history? - Range loc = cursor.get_spell(); - - IndexFuncId called_id = db->ToFuncId(HashUsr(ref->referencedEntity->USR)); - IndexFunc* called = db->Resolve(called_id); - - std::string_view short_name = called->def.ShortName(); - // libclang doesn't provide a nice api to check if the given function - // call is implicit. ref->kind should probably work (it's either direct - // or implicit), but libclang only supports implicit for objective-c. - bool is_implicit = - CanBeCalledImplicitly(ref->referencedEntity->kind) && - // Treats empty short_name as an implicit call like implicit move - // constructor in `vector a = f();` - (short_name.empty() || - // For explicit destructor call, ref->cursor may be "~" while - // called->def.short_name is "~A" - // "~A" is not a substring of ref->cursor, but we should take this - // case as not `is_implicit`. - (short_name[0] != '~' && - !CursorSpellingContainsString(ref->cursor, param->tu->cx_tu, - short_name))); - - // Extents have larger ranges and thus less specific, and will be - // overriden by other functions if exist. - // - // Type-dependent member access expressions do not have useful spelling - // ranges. See the comment above for the CXIdxEntity_Field case. - if (is_implicit) - loc = cursor.get_extent(); - else - CheckTypeDependentMemberRefExpr(&loc, cursor, param, db); - - OnIndexReference_Function( - db, loc, ref->container->cursor, called_id, - GetRole(ref, Role::Call) | - (is_implicit ? Role::Implicit : Role::None)); - - // Checks if |str| starts with |start|. Ignores case. - auto str_begin = [](const char* start, const char* str) { - while (*start && *str) { - char a = tolower(*start); - char b = tolower(*str); - if (a != b) - return false; - ++start; - ++str; - } - return !*start; - }; - - bool is_template = ref->referencedEntity->templateKind != - CXIdxEntityCXXTemplateKind::CXIdxEntity_NonTemplate; - if (param->config->index.attributeMakeCallsToCtor && is_template && - str_begin("make", ref->referencedEntity->name)) { - // Try to find the return type of called function. That type will have - // the constructor function we add a usage to. - optional opt_found_type = FindType(ref->cursor); - if (opt_found_type) { - Usr ctor_type_usr = opt_found_type->get_referenced().get_usr_hash(); - ClangCursor call_cursor = ref->cursor; - - // Build a type description from the parameters of the call, so we - // can try to find a constructor with the same type description. - std::vector call_type_desc; - for (ClangType type : call_cursor.get_type().get_arguments()) { - std::string type_desc = type.get_spell_name(); - if (!type_desc.empty()) - call_type_desc.push_back(type_desc); - } - - // Try to find the constructor and add a reference. - optional ctor_usr = - param->ctors.TryFindConstructorUsr(ctor_type_usr, call_type_desc); - if (ctor_usr) { - IndexFunc* ctor = db->Resolve(db->ToFuncId(*ctor_usr)); - ctor->uses.push_back(Use(loc, Id(), SymbolKind::File, - Role::Call | Role::Implicit, {})); - } - } - } - - break; - } - - case CXIdxEntity_ObjCCategory: - case CXIdxEntity_ObjCProtocol: - case CXIdxEntity_ObjCClass: - case CXIdxEntity_Typedef: - case CXIdxEntity_CXXInterface: // MSVC __interface - case CXIdxEntity_CXXTypeAlias: - case CXIdxEntity_Enum: - case CXIdxEntity_Union: - case CXIdxEntity_Struct: - case CXIdxEntity_CXXClass: { - referenced = referenced.template_specialization_to_template_definition(); - IndexType* ref_type = - db->Resolve(db->ToTypeId(referenced.get_usr_hash())); - if (!ref->parentEntity || IsDeclContext(ref->parentEntity->kind)) - AddUseSpell(db, ref_type->declarations, ref->cursor); - else - AddUseSpell(db, ref_type->uses, ref->cursor); - break; - } - } -} - -optional>> Parse( - Config* config, - FileConsumerSharedState* file_consumer_shared, - std::string file, - const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf, - ClangIndex* index, - bool dump_ast) { - if (!config->index.enabled) - return nullopt; - - file = NormalizePath(file); - - Timer timer; - - std::vector unsaved_files; - for (const FileContents& contents : file_contents) { - CXUnsavedFile unsaved; - unsaved.Filename = contents.path.c_str(); - unsaved.Contents = contents.content.c_str(); - unsaved.Length = (unsigned long)contents.content.size(); - unsaved_files.push_back(unsaved); - } - - std::unique_ptr tu = ClangTranslationUnit::Create( - index, file, args, unsaved_files, - CXTranslationUnit_KeepGoing | - CXTranslationUnit_DetailedPreprocessingRecord); - if (!tu) - return nullopt; - - perf->index_parse = timer.ElapsedMicrosecondsAndReset(); - - if (dump_ast) - Dump(clang_getTranslationUnitCursor(tu->cx_tu)); - - return ParseWithTu(config, file_consumer_shared, perf, tu.get(), index, file, - args, unsaved_files); -} - -optional>> ParseWithTu( - Config* config, - FileConsumerSharedState* file_consumer_shared, - PerformanceImportFile* perf, - ClangTranslationUnit* tu, - ClangIndex* index, - const std::string& file, - const std::vector& args, - const std::vector& file_contents) { - Timer timer; - - IndexerCallbacks callback = {0}; - // Available callbacks: - // - abortQuery - // - enteredMainFile - // - ppIncludedFile - // - importedASTFile - // - startedTranslationUnit - callback.diagnostic = &OnIndexDiagnostic; - callback.ppIncludedFile = &OnIndexIncludedFile; - callback.indexDeclaration = &OnIndexDeclaration; - callback.indexEntityReference = &OnIndexReference; - - FileConsumer file_consumer(file_consumer_shared, file); - IndexParam param(config, tu, &file_consumer); - for (const CXUnsavedFile& contents : file_contents) { - param.file_contents[contents.Filename] = FileContents( - contents.Filename, std::string(contents.Contents, contents.Length)); - } - - CXFile cx_file = clang_getFile(tu->cx_tu, file.c_str()); - param.primary_file = ConsumeFile(¶m, cx_file); - - CXIndexAction index_action = clang_IndexAction_create(index->cx_index); - - // |index_result| is a CXErrorCode instance. - int index_result = clang_indexTranslationUnit( - index_action, ¶m, &callback, sizeof(IndexerCallbacks), - CXIndexOpt_IndexFunctionLocalSymbols | - CXIndexOpt_SkipParsedBodiesInSession | - CXIndexOpt_IndexImplicitTemplateInstantiations, - tu->cx_tu); - if (index_result != CXError_Success) { - LOG_S(ERROR) << "Indexing " << file - << " failed with errno=" << index_result; - return nullopt; - } - - clang_IndexAction_dispose(index_action); - - ClangCursor(clang_getTranslationUnitCursor(tu->cx_tu)) - .VisitChildren(&VisitMacroDefinitionAndExpansions, ¶m); - - perf->index_build = timer.ElapsedMicrosecondsAndReset(); - - std::unordered_map inc_to_line; - // TODO - if (param.primary_file) - for (auto& inc : param.primary_file->includes) - inc_to_line[inc.resolved_path] = inc.line; - - auto result = param.file_consumer->TakeLocalState(); - for (std::unique_ptr& entry : result) { - entry->import_file = file; - entry->args = args; - for (IndexFunc& func : entry->funcs) { - // e.g. declaration + out-of-line definition - Uniquify(func.derived); - Uniquify(func.uses); - } - for (IndexType& type : entry->types) { - Uniquify(type.derived); - Uniquify(type.uses); - // e.g. declaration + out-of-line definition - Uniquify(type.def.funcs); - } - for (IndexVar& var : entry->vars) - Uniquify(var.uses); - - if (param.primary_file) { - // If there are errors, show at least one at the include position. - auto it = inc_to_line.find(entry->path); - if (it != inc_to_line.end()) { - int line = it->second; - for (auto ls_diagnostic : entry->diagnostics_) { - if (ls_diagnostic.severity != lsDiagnosticSeverity::Error) - continue; - ls_diagnostic.range = - lsRange(lsPosition(line, 10), lsPosition(line, 10)); - param.primary_file->diagnostics_.push_back(ls_diagnostic); - break; - } - } - } - - // Update file contents and modification time. - entry->last_modification_time = param.file_modification_times[entry->path]; - - // Update dependencies for the file. Do not include the file in its own - // dependency set. - entry->dependencies = param.seen_files; - entry->dependencies.erase( - std::remove(entry->dependencies.begin(), entry->dependencies.end(), - entry->path), - entry->dependencies.end()); - } - - return std::move(result); -} - -void ConcatTypeAndName(std::string& type, const std::string& name) { - if (type.size() && - (type.back() != ' ' && type.back() != '*' && type.back() != '&')) - type.push_back(' '); - type.append(name); -} - -void IndexInit() { - clang_enableStackTraces(); - if (!getenv("LIBCLANG_DISABLE_CRASH_RECOVERY")) - clang_toggleCrashRecovery(1); -} - -void ClangSanityCheck() { - std::vector args = {"clang", "index_tests/vars/class_member.cc"}; - unsigned opts = 0; - CXIndex index = clang_createIndex(0, 1); - CXTranslationUnit tu; - clang_parseTranslationUnit2FullArgv(index, nullptr, args.data(), args.size(), - nullptr, 0, opts, &tu); - assert(tu); - - IndexerCallbacks callback = {0}; - callback.abortQuery = [](CXClientData client_data, void* reserved) { - return 0; - }; - callback.diagnostic = [](CXClientData client_data, - CXDiagnosticSet diagnostics, void* reserved) {}; - callback.enteredMainFile = [](CXClientData client_data, CXFile mainFile, - void* reserved) -> CXIdxClientFile { - return nullptr; - }; - callback.ppIncludedFile = - [](CXClientData client_data, - const CXIdxIncludedFileInfo* file) -> CXIdxClientFile { - return nullptr; - }; - callback.importedASTFile = - [](CXClientData client_data, - const CXIdxImportedASTFileInfo*) -> CXIdxClientASTFile { - return nullptr; - }; - callback.startedTranslationUnit = [](CXClientData client_data, - void* reserved) -> CXIdxClientContainer { - return nullptr; - }; - callback.indexDeclaration = [](CXClientData client_data, - const CXIdxDeclInfo* decl) {}; - callback.indexEntityReference = [](CXClientData client_data, - const CXIdxEntityRefInfo* ref) {}; - - const unsigned kIndexOpts = 0; - CXIndexAction index_action = clang_IndexAction_create(index); - int index_param = 0; - clang_toggleCrashRecovery(0); - clang_indexTranslationUnit(index_action, &index_param, &callback, - sizeof(IndexerCallbacks), kIndexOpts, tu); - clang_IndexAction_dispose(index_action); - - clang_disposeTranslationUnit(tu); - clang_disposeIndex(index); -} - -std::string GetClangVersion() { - return ToString(clang_getClangVersion()); -} - -// |SymbolRef| is serialized this way. -// |Use| also uses this though it has an extra field |file|, -// which is not used by Index* so it does not need to be serialized. -void Reflect(Reader& visitor, Reference& value) { - if (visitor.Format() == SerializeFormat::Json) { - std::string t = visitor.GetString(); - char* s = const_cast(t.c_str()); - value.range = Range(s); - s = strchr(s, '|'); - value.id.id = RawId(strtol(s + 1, &s, 10)); - value.kind = static_cast(strtol(s + 1, &s, 10)); - value.role = static_cast(strtol(s + 1, &s, 10)); - } else { - Reflect(visitor, value.range); - Reflect(visitor, value.id); - Reflect(visitor, value.kind); - Reflect(visitor, value.role); - } -} -void Reflect(Writer& visitor, Reference& value) { - if (visitor.Format() == SerializeFormat::Json) { - std::string s = value.range.ToString(); - // RawId(-1) -> "-1" - s += '|' + std::to_string( - static_cast::type>(value.id.id)); - s += '|' + std::to_string(int(value.kind)); - s += '|' + std::to_string(int(value.role)); - Reflect(visitor, s); - } else { - Reflect(visitor, value.range); - Reflect(visitor, value.id); - Reflect(visitor, value.kind); - Reflect(visitor, value.role); - } -} diff --git a/src/clang_translation_unit.cc b/src/clang_translation_unit.cc deleted file mode 100644 index 88365f23a..000000000 --- a/src/clang_translation_unit.cc +++ /dev/null @@ -1,156 +0,0 @@ -#include "clang_translation_unit.h" - -#include "clang_utils.h" -#include "platform.h" -#include "utils.h" - -#include - -namespace { - -void EmitDiagnostics(std::string path, - std::vector args, - CXTranslationUnit tu) { - std::string output = "Fatal errors while trying to parse " + path + "\n"; - output += - "Args: " + - StringJoinMap(args, [](const char* arg) { return std::string(arg); }) + - "\n"; - - size_t num_diagnostics = clang_getNumDiagnostics(tu); - for (unsigned i = 0; i < num_diagnostics; ++i) { - output += " - "; - - CXDiagnostic diagnostic = clang_getDiagnostic(tu, i); - - // Location. - CXFile file; - unsigned int line, column; - clang_getSpellingLocation(clang_getDiagnosticLocation(diagnostic), &file, - &line, &column, nullptr); - std::string path = FileName(file); - output += path + ":" + std::to_string(line - 1) + ":" + - std::to_string(column) + " "; - - // Severity - switch (clang_getDiagnosticSeverity(diagnostic)) { - case CXDiagnostic_Ignored: - case CXDiagnostic_Note: - output += "[info]"; - break; - case CXDiagnostic_Warning: - output += "[warning]"; - break; - case CXDiagnostic_Error: - output += "[error]"; - break; - case CXDiagnostic_Fatal: - output += "[fatal]"; - break; - } - - // Content. - output += " " + ToString(clang_getDiagnosticSpelling(diagnostic)); - - clang_disposeDiagnostic(diagnostic); - - output += "\n"; - } - - LOG_S(WARNING) << output; -} -} // namespace - -// static -std::unique_ptr ClangTranslationUnit::Create( - ClangIndex* index, - const std::string& filepath, - const std::vector& arguments, - std::vector& unsaved_files, - unsigned flags) { - std::vector args; - for (auto& arg : arguments) - args.push_back(arg.c_str()); - - CXTranslationUnit cx_tu; - CXErrorCode error_code; - { - error_code = clang_parseTranslationUnit2FullArgv( - index->cx_index, nullptr, args.data(), (int)args.size(), - unsaved_files.data(), (unsigned)unsaved_files.size(), flags, &cx_tu); - } - - if (error_code != CXError_Success && cx_tu) - EmitDiagnostics(filepath, args, cx_tu); - - // We sometimes dump the command to logs and ask the user to run it. Include - // -fsyntax-only so they don't do a full compile. - auto make_msg = [&]() { - return "Please try running the following, identify which flag causes the " - "issue, and report a bug. cquery will then filter the flag for you " - " automatically:\n$ " + - StringJoin(args, " ") + " -fsyntax-only"; - }; - - switch (error_code) { - case CXError_Success: - return std::make_unique(cx_tu); - case CXError_Failure: - LOG_S(ERROR) << "libclang generic failure for " << filepath << ". " - << make_msg(); - return nullptr; - case CXError_Crashed: - LOG_S(ERROR) << "libclang crashed for " << filepath << ". " << make_msg(); - return nullptr; - case CXError_InvalidArguments: - LOG_S(ERROR) << "libclang had invalid arguments for " << filepath << ". " - << make_msg(); - return nullptr; - case CXError_ASTReadError: - LOG_S(ERROR) << "libclang had ast read error for " << filepath << ". " - << make_msg(); - return nullptr; - } - - return nullptr; -} - -// static -std::unique_ptr ClangTranslationUnit::Reparse( - std::unique_ptr tu, - std::vector& unsaved) { - int error_code; - { - error_code = clang_reparseTranslationUnit( - tu->cx_tu, (unsigned)unsaved.size(), unsaved.data(), - clang_defaultReparseOptions(tu->cx_tu)); - } - - if (error_code != CXError_Success && tu->cx_tu) - EmitDiagnostics("", {}, tu->cx_tu); - - switch (error_code) { - case CXError_Success: - return tu; - case CXError_Failure: - LOG_S(ERROR) << "libclang reparse generic failure"; - return nullptr; - case CXError_Crashed: - LOG_S(ERROR) << "libclang reparse crashed"; - return nullptr; - case CXError_InvalidArguments: - LOG_S(ERROR) << "libclang reparse had invalid arguments"; - return nullptr; - case CXError_ASTReadError: - LOG_S(ERROR) << "libclang reparse had ast read error"; - return nullptr; - } - - return nullptr; -} - -ClangTranslationUnit::ClangTranslationUnit(CXTranslationUnit tu) : cx_tu(tu) {} - -ClangTranslationUnit::~ClangTranslationUnit() { - clang_disposeTranslationUnit(cx_tu); -} diff --git a/src/clang_translation_unit.h b/src/clang_translation_unit.h deleted file mode 100644 index efeedc6c6..000000000 --- a/src/clang_translation_unit.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "clang_cursor.h" -#include "clang_index.h" - -#include - -#include -#include -#include - -// RAII wrapper around CXTranslationUnit which also makes it much more -// challenging to use a CXTranslationUnit instance that is not correctly -// initialized. -struct ClangTranslationUnit { - static std::unique_ptr Create( - ClangIndex* index, - const std::string& filepath, - const std::vector& arguments, - std::vector& unsaved_files, - unsigned flags); - - static std::unique_ptr Reparse( - std::unique_ptr tu, - std::vector& unsaved); - - explicit ClangTranslationUnit(CXTranslationUnit tu); - ~ClangTranslationUnit(); - - CXTranslationUnit cx_tu; -}; \ No newline at end of file diff --git a/src/clang_tu.cc b/src/clang_tu.cc new file mode 100644 index 000000000..4092372bf --- /dev/null +++ b/src/clang_tu.cc @@ -0,0 +1,259 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "clang_tu.hh" + +#include "config.hh" +#include "platform.hh" + +#include +#include +#include + +using namespace clang; + +namespace ccls { +std::string PathFromFileEntry(const FileEntry &file) { + StringRef Name = file.tryGetRealPathName(); + if (Name.empty()) + Name = file.getName(); + std::string ret = NormalizePath(Name); + // Resolve /usr/include/c++/7.3.0 symlink. + if (!llvm::any_of(g_config->workspaceFolders, [&](const std::string &root) { + return StringRef(ret).startswith(root); + })) { + SmallString<256> dest; + llvm::sys::fs::real_path(ret, dest); + ret = llvm::sys::path::convert_to_slash(dest.str()); + } + return ret; +} + +static Pos Decomposed2LineAndCol(const SourceManager &SM, + std::pair I) { + int l = SM.getLineNumber(I.first, I.second) - 1, + c = SM.getColumnNumber(I.first, I.second) - 1; + return {(int16_t)std::min(l, INT16_MAX), + (int16_t)std::min(c, INT16_MAX)}; +} + +Range FromCharSourceRange(const SourceManager &SM, const LangOptions &LangOpts, + CharSourceRange R, + llvm::sys::fs::UniqueID *UniqueID) { + SourceLocation BLoc = R.getBegin(), ELoc = R.getEnd(); + std::pair BInfo = SM.getDecomposedLoc(BLoc), + EInfo = SM.getDecomposedLoc(ELoc); + if (R.isTokenRange()) + EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, LangOpts); + if (UniqueID) { + if (const FileEntry *F = SM.getFileEntryForID(BInfo.first)) + *UniqueID = F->getUniqueID(); + else + *UniqueID = llvm::sys::fs::UniqueID(0, 0); + } + return {Decomposed2LineAndCol(SM, BInfo), Decomposed2LineAndCol(SM, EInfo)}; +} + +Range FromCharRange(const SourceManager &SM, const LangOptions &Lang, + SourceRange R, llvm::sys::fs::UniqueID *UniqueID) { + return FromCharSourceRange(SM, Lang, CharSourceRange::getCharRange(R), + UniqueID); +} + +Range FromTokenRange(const SourceManager &SM, const LangOptions &Lang, + SourceRange R, llvm::sys::fs::UniqueID *UniqueID) { + return FromCharSourceRange(SM, Lang, CharSourceRange::getTokenRange(R), + UniqueID); +} + +Range FromTokenRangeDefaulted(const SourceManager &SM, const LangOptions &Lang, + SourceRange R, const FileEntry *FE, Range range) { + auto I = SM.getDecomposedLoc(SM.getExpansionLoc(R.getBegin())); + if (SM.getFileEntryForID(I.first) == FE) + range.start = Decomposed2LineAndCol(SM, I); + SourceLocation L = SM.getExpansionLoc(R.getEnd()); + I = SM.getDecomposedLoc(L); + if (SM.getFileEntryForID(I.first) == FE) { + I.second += Lexer::MeasureTokenLength(L, SM, Lang); + range.end = Decomposed2LineAndCol(SM, I); + } + return range; +} + +std::unique_ptr +BuildCompilerInvocation(std::vector args, + IntrusiveRefCntPtr VFS) { + std::string save = "-resource-dir=" + g_config->clang.resourceDir; + args.push_back(save.c_str()); + IntrusiveRefCntPtr Diags( + CompilerInstance::createDiagnostics(new DiagnosticOptions, + new IgnoringDiagConsumer, true)); + std::unique_ptr CI = + createInvocationFromCommandLine(args, Diags, VFS); + if (CI) { + CI->getDiagnosticOpts().IgnoreWarnings = true; + CI->getFrontendOpts().DisableFree = false; + CI->getLangOpts()->SpellChecking = false; + } + return CI; +} + +// clang::BuiltinType::getName without PrintingPolicy +const char *ClangBuiltinTypeName(int kind) { + switch (BuiltinType::Kind(kind)) { + case BuiltinType::Void: + return "void"; + case BuiltinType::Bool: + return "bool"; + case BuiltinType::Char_S: + return "char"; + case BuiltinType::Char_U: + return "char"; + case BuiltinType::SChar: + return "signed char"; + case BuiltinType::Short: + return "short"; + case BuiltinType::Int: + return "int"; + case BuiltinType::Long: + return "long"; + case BuiltinType::LongLong: + return "long long"; + case BuiltinType::Int128: + return "__int128"; + case BuiltinType::UChar: + return "unsigned char"; + case BuiltinType::UShort: + return "unsigned short"; + case BuiltinType::UInt: + return "unsigned int"; + case BuiltinType::ULong: + return "unsigned long"; + case BuiltinType::ULongLong: + return "unsigned long long"; + case BuiltinType::UInt128: + return "unsigned __int128"; + case BuiltinType::Half: + return "__fp16"; + case BuiltinType::Float: + return "float"; + case BuiltinType::Double: + return "double"; + case BuiltinType::LongDouble: + return "long double"; +#if LLVM_VERSION_MAJOR >= 7 + case BuiltinType::ShortAccum: + return "short _Accum"; + case BuiltinType::Accum: + return "_Accum"; + case BuiltinType::LongAccum: + return "long _Accum"; + case BuiltinType::UShortAccum: + return "unsigned short _Accum"; + case BuiltinType::UAccum: + return "unsigned _Accum"; + case BuiltinType::ULongAccum: + return "unsigned long _Accum"; + case BuiltinType::BuiltinType::ShortFract: + return "short _Fract"; + case BuiltinType::BuiltinType::Fract: + return "_Fract"; + case BuiltinType::BuiltinType::LongFract: + return "long _Fract"; + case BuiltinType::BuiltinType::UShortFract: + return "unsigned short _Fract"; + case BuiltinType::BuiltinType::UFract: + return "unsigned _Fract"; + case BuiltinType::BuiltinType::ULongFract: + return "unsigned long _Fract"; + case BuiltinType::BuiltinType::SatShortAccum: + return "_Sat short _Accum"; + case BuiltinType::BuiltinType::SatAccum: + return "_Sat _Accum"; + case BuiltinType::BuiltinType::SatLongAccum: + return "_Sat long _Accum"; + case BuiltinType::BuiltinType::SatUShortAccum: + return "_Sat unsigned short _Accum"; + case BuiltinType::BuiltinType::SatUAccum: + return "_Sat unsigned _Accum"; + case BuiltinType::BuiltinType::SatULongAccum: + return "_Sat unsigned long _Accum"; + case BuiltinType::BuiltinType::SatShortFract: + return "_Sat short _Fract"; + case BuiltinType::BuiltinType::SatFract: + return "_Sat _Fract"; + case BuiltinType::BuiltinType::SatLongFract: + return "_Sat long _Fract"; + case BuiltinType::BuiltinType::SatUShortFract: + return "_Sat unsigned short _Fract"; + case BuiltinType::BuiltinType::SatUFract: + return "_Sat unsigned _Fract"; + case BuiltinType::BuiltinType::SatULongFract: + return "_Sat unsigned long _Fract"; +#endif + case BuiltinType::Float16: + return "_Float16"; + case BuiltinType::Float128: + return "__float128"; + case BuiltinType::WChar_S: + case BuiltinType::WChar_U: + return "wchar_t"; +#if LLVM_VERSION_MAJOR >= 7 + case BuiltinType::Char8: + return "char8_t"; +#endif + case BuiltinType::Char16: + return "char16_t"; + case BuiltinType::Char32: + return "char32_t"; + case BuiltinType::NullPtr: + return "nullptr_t"; + case BuiltinType::Overload: + return ""; + case BuiltinType::BoundMember: + return ""; + case BuiltinType::PseudoObject: + return ""; + case BuiltinType::Dependent: + return ""; + case BuiltinType::UnknownAny: + return ""; + case BuiltinType::ARCUnbridgedCast: + return ""; + case BuiltinType::BuiltinFn: + return ""; + case BuiltinType::ObjCId: + return "id"; + case BuiltinType::ObjCClass: + return "Class"; + case BuiltinType::ObjCSel: + return "SEL"; + case BuiltinType::OCLSampler: + return "sampler_t"; + case BuiltinType::OCLEvent: + return "event_t"; + case BuiltinType::OCLClkEvent: + return "clk_event_t"; + case BuiltinType::OCLQueue: + return "queue_t"; + case BuiltinType::OCLReserveID: + return "reserve_id_t"; + case BuiltinType::OMPArraySection: + return ""; + default: + return ""; + } +} +} // namespace ccls diff --git a/src/clang_tu.hh b/src/clang_tu.hh new file mode 100644 index 000000000..2bfa23659 --- /dev/null +++ b/src/clang_tu.hh @@ -0,0 +1,58 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "position.hh" + +#include +#include +#include +#include + +#if LLVM_VERSION_MAJOR < 8 +// D52783 Lift VFS from clang to llvm +namespace llvm { +namespace vfs = clang::vfs; +} +#endif + +namespace ccls { +std::string PathFromFileEntry(const clang::FileEntry &file); + +Range FromCharSourceRange(const clang::SourceManager &SM, + const clang::LangOptions &LangOpts, + clang::CharSourceRange R, + llvm::sys::fs::UniqueID *UniqueID = nullptr); + +Range FromCharRange(const clang::SourceManager &SM, + const clang::LangOptions &LangOpts, clang::SourceRange R, + llvm::sys::fs::UniqueID *UniqueID = nullptr); + +Range FromTokenRange(const clang::SourceManager &SM, + const clang::LangOptions &LangOpts, clang::SourceRange R, + llvm::sys::fs::UniqueID *UniqueID = nullptr); + +Range FromTokenRangeDefaulted(const clang::SourceManager &SM, + const clang::LangOptions &Lang, + clang::SourceRange R, const clang::FileEntry *FE, + Range range); + +std::unique_ptr +BuildCompilerInvocation(std::vector args, + llvm::IntrusiveRefCntPtr VFS); + +const char *ClangBuiltinTypeName(int); +} // namespace ccls diff --git a/src/clang_utils.cc b/src/clang_utils.cc deleted file mode 100644 index 8be4a15ca..000000000 --- a/src/clang_utils.cc +++ /dev/null @@ -1,231 +0,0 @@ -#include "clang_utils.h" - -#include "platform.h" - -#include - -namespace { - -lsRange GetLsRangeForFixIt(const CXSourceRange& range) { - CXSourceLocation start = clang_getRangeStart(range); - CXSourceLocation end = clang_getRangeEnd(range); - - unsigned int start_line, start_column; - clang_getSpellingLocation(start, nullptr, &start_line, &start_column, - nullptr); - unsigned int end_line, end_column; - clang_getSpellingLocation(end, nullptr, &end_line, &end_column, nullptr); - - return lsRange(lsPosition(start_line - 1, start_column - 1) /*start*/, - lsPosition(end_line - 1, end_column) /*end*/); -} - -} // namespace - -// See clang_formatDiagnostic -optional BuildAndDisposeDiagnostic(CXDiagnostic diagnostic, - const std::string& path) { - // Get diagnostic location. - CXFile file; - unsigned start_line, start_column; - clang_getSpellingLocation(clang_getDiagnosticLocation(diagnostic), &file, - &start_line, &start_column, nullptr); - - if (file && path != FileName(file)) { - clang_disposeDiagnostic(diagnostic); - return nullopt; - } - - unsigned end_line = start_line, end_column = start_column, - num_ranges = clang_getDiagnosticNumRanges(diagnostic); - for (unsigned i = 0; i < num_ranges; i++) { - CXFile file0, file1; - unsigned line0, column0, line1, column1; - CXSourceRange range = clang_getDiagnosticRange(diagnostic, i); - clang_getSpellingLocation(clang_getRangeStart(range), &file0, &line0, - &column0, nullptr); - clang_getSpellingLocation(clang_getRangeEnd(range), &file1, &line1, - &column1, nullptr); - if (file0 != file1 || file0 != file) - continue; - if (line0 < start_line || (line0 == start_line && column0 < start_column)) { - start_line = line0; - start_column = column0; - } - if (line1 > end_line || (line1 == end_line && column1 > end_column)) { - end_line = line1; - end_column = column1; - } - } - - // Build diagnostic. - lsDiagnostic ls_diagnostic; - ls_diagnostic.range = lsRange(lsPosition(start_line - 1, start_column - 1), - lsPosition(end_line - 1, end_column - 1)); - - ls_diagnostic.message = ToString(clang_getDiagnosticSpelling(diagnostic)); - - // Append the flag that enables this diagnostic, ie, [-Wswitch] - std::string enabling_flag = - ToString(clang_getDiagnosticOption(diagnostic, nullptr)); - if (!enabling_flag.empty()) - ls_diagnostic.message += " [" + enabling_flag + "]"; - - ls_diagnostic.code = clang_getDiagnosticCategory(diagnostic); - - switch (clang_getDiagnosticSeverity(diagnostic)) { - case CXDiagnostic_Ignored: - // llvm_unreachable - break; - case CXDiagnostic_Note: - ls_diagnostic.severity = lsDiagnosticSeverity::Information; - break; - case CXDiagnostic_Warning: - ls_diagnostic.severity = lsDiagnosticSeverity::Warning; - break; - case CXDiagnostic_Error: - case CXDiagnostic_Fatal: - ls_diagnostic.severity = lsDiagnosticSeverity::Error; - break; - } - - // Report fixits - unsigned num_fixits = clang_getDiagnosticNumFixIts(diagnostic); - for (unsigned i = 0; i < num_fixits; ++i) { - CXSourceRange replacement_range; - CXString text = clang_getDiagnosticFixIt(diagnostic, i, &replacement_range); - - lsTextEdit edit; - edit.newText = ToString(text); - edit.range = GetLsRangeForFixIt(replacement_range); - ls_diagnostic.fixits_.push_back(edit); - } - - clang_disposeDiagnostic(diagnostic); - - return ls_diagnostic; -} - -#if USE_CLANG_CXX -static lsPosition OffsetToRange(llvm::StringRef document, size_t offset) { - // TODO: Support Windows line endings, etc. - llvm::StringRef text_before = document.substr(0, offset); - int num_line = text_before.count('\n'); - int num_column = text_before.size() - text_before.rfind('\n') - 1; - return {num_line, num_column}; -} - -std::vector ConvertClangReplacementsIntoTextEdits( - llvm::StringRef document, - const std::vector& clang_replacements) { - std::vector text_edits_result; - for (const auto& replacement : clang_replacements) { - const auto startPosition = OffsetToRange(document, replacement.getOffset()); - const auto endPosition = OffsetToRange( - document, replacement.getOffset() + replacement.getLength()); - text_edits_result.push_back( - {{startPosition, endPosition}, replacement.getReplacementText()}); - } - return text_edits_result; -} - -#endif - -std::string FileName(CXFile file) { - CXString cx_name = clang_getFileName(file); - std::string name = ToString(cx_name); - return NormalizePath(name); -} - -std::string ToString(CXString cx_string) { - std::string string; - if (cx_string.data != nullptr) { - string = clang_getCString(cx_string); - clang_disposeString(cx_string); - } - return string; -} - -std::string ToString(CXCursorKind kind) { - return ToString(clang_getCursorKindSpelling(kind)); -} - -const char* ClangBuiltinTypeName(CXTypeKind kind) { - switch (kind) { - // clang-format off - case CXType_Bool: return "bool"; - case CXType_Char_U: return "char"; - case CXType_UChar: return "unsigned char"; - case CXType_UShort: return "unsigned short"; - case CXType_UInt: return "unsigned int"; - case CXType_ULong: return "unsigned long"; - case CXType_ULongLong: return "unsigned long long"; - case CXType_UInt128: return "unsigned __int128"; - case CXType_Char_S: return "char"; - case CXType_SChar: return "signed char"; - case CXType_WChar: return "wchar_t"; - case CXType_Int: return "int"; - case CXType_Long: return "long"; - case CXType_LongLong: return "long long"; - case CXType_Int128: return "__int128"; - case CXType_Float: return "float"; - case CXType_Double: return "double"; - case CXType_LongDouble: return "long double"; - case CXType_Float128: return "__float128"; -#if CINDEX_VERSION_MINOR >= 43 - case CXType_Half: return "_Float16"; -#endif - case CXType_NullPtr: return "nullptr"; - default: return ""; - // clang-format on - } -} - -#if USE_CLANG_CXX -TEST_SUITE("ClangUtils") { - TEST_CASE("replacements") { - const std::string sample_document = - "int \n" - "main() { int *i = 0; return 0; \n" - "}"; - const std::vector clang_replacements = { - {"foo.cc", 3, 2, " "}, {"foo.cc", 13, 1, "\n "}, - {"foo.cc", 17, 1, ""}, {"foo.cc", 19, 0, " "}, - {"foo.cc", 25, 1, "\n "}, {"foo.cc", 35, 2, "\n"}}; - - // Expected format: - // - // int main() { - // int *i = 0; - // return 0; - // } - - const auto text_edits = ConvertClangReplacementsIntoTextEdits( - sample_document, clang_replacements); - REQUIRE(text_edits.size() == 6); - REQUIRE(text_edits[0].range.start.line == 0); - REQUIRE(text_edits[0].range.start.character == 3); - REQUIRE(text_edits[0].newText == " "); - - REQUIRE(text_edits[1].range.start.line == 1); - REQUIRE(text_edits[1].range.start.character == 8); - REQUIRE(text_edits[1].newText == "\n "); - - REQUIRE(text_edits[2].range.start.line == 1); - REQUIRE(text_edits[2].range.start.character == 12); - REQUIRE(text_edits[2].newText == ""); - - REQUIRE(text_edits[3].range.start.line == 1); - REQUIRE(text_edits[3].range.start.character == 14); - REQUIRE(text_edits[3].newText == " "); - - REQUIRE(text_edits[4].range.start.line == 1); - REQUIRE(text_edits[4].range.start.character == 20); - REQUIRE(text_edits[4].newText == "\n "); - - REQUIRE(text_edits[5].range.start.line == 1); - REQUIRE(text_edits[5].range.start.character == 30); - REQUIRE(text_edits[5].newText == "\n"); - } -} -#endif diff --git a/src/clang_utils.h b/src/clang_utils.h deleted file mode 100644 index 24175efd8..000000000 --- a/src/clang_utils.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "lsp_diagnostic.h" - -#include -#if USE_CLANG_CXX -#include -#endif -#include - -#include - -optional BuildAndDisposeDiagnostic(CXDiagnostic diagnostic, - const std::string& path); - -// Returns the absolute path to |file|. -std::string FileName(CXFile file); - -std::string ToString(CXString cx_string); - -std::string ToString(CXCursorKind cursor_kind); - -const char* ClangBuiltinTypeName(CXTypeKind); - -// Converts Clang formatting replacement operations into LSP text edits. -#if USE_CLANG_CXX -std::vector ConvertClangReplacementsIntoTextEdits( - llvm::StringRef document, - const std::vector& clang_replacements); -#endif diff --git a/src/code_complete_cache.cc b/src/code_complete_cache.cc deleted file mode 100644 index df9244619..000000000 --- a/src/code_complete_cache.cc +++ /dev/null @@ -1,12 +0,0 @@ -#include "code_complete_cache.h" - -void CodeCompleteCache::WithLock(std::function action) { - std::lock_guard lock(mutex_); - action(); -} - -bool CodeCompleteCache::IsCacheValid(lsTextDocumentPositionParams position) { - std::lock_guard lock(mutex_); - return cached_path_ == position.textDocument.uri.GetPath() && - cached_completion_position_ == position.position; -} \ No newline at end of file diff --git a/src/code_complete_cache.h b/src/code_complete_cache.h deleted file mode 100644 index 76ed61049..000000000 --- a/src/code_complete_cache.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "lsp_completion.h" - -#include - -#include - -// Cached completion information, so we can give fast completion results when -// the user erases a character. vscode will resend the completion request if -// that happens. -struct CodeCompleteCache { - // NOTE: Make sure to access these variables under |WithLock|. - optional cached_path_; - optional cached_completion_position_; - std::vector cached_results_; - - std::mutex mutex_; - - void WithLock(std::function action); - bool IsCacheValid(lsTextDocumentPositionParams position); -}; diff --git a/src/command_line.cc b/src/command_line.cc deleted file mode 100644 index 717ba3af8..000000000 --- a/src/command_line.cc +++ /dev/null @@ -1,502 +0,0 @@ -// TODO: cleanup includes -#include "cache_manager.h" -#include "clang_complete.h" -#include "code_complete_cache.h" -#include "diagnostics_engine.h" -#include "file_consumer.h" -#include "import_manager.h" -#include "import_pipeline.h" -#include "include_complete.h" -#include "indexer.h" -#include "lsp_diagnostic.h" -#include "lex_utils.h" -#include "lru_cache.h" -#include "match.h" -#include "message_handler.h" -#include "options.h" -#include "platform.h" -#include "project.h" -#include "query.h" -#include "query_utils.h" -#include "queue_manager.h" -#include "recorder.h" -#include "semantic_highlight_symbol_cache.h" -#include "serializer.h" -#include "serializers/json.h" -#include "test.h" -#include "timer.h" -#include "timestamp_manager.h" -#include "work_thread.h" -#include "working_files.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// TODO: provide a feature like 'https://github.com/goldsborough/clang-expand', -// ie, a fully linear view of a function with inline function calls expanded. -// We can probably use vscode decorators to achieve it. - -// TODO: implement ThreadPool type which monitors CPU usage / number of work -// items per second completed and scales up/down number of running threads. - -std::string g_init_options; - -namespace { - -std::vector kEmptyArgs; - -// This function returns true if e2e timing should be displayed for the given -// IpcId. -bool ShouldDisplayIpcTiming(IpcId id) { - switch (id) { - case IpcId::TextDocumentPublishDiagnostics: - case IpcId::CqueryPublishInactiveRegions: - case IpcId::Unknown: - return false; - default: - return true; - } -} - -REGISTER_IPC_MESSAGE(Ipc_CancelRequest); - -void PrintHelp() { - std::cout - << R"help(cquery is a low-latency C/C++/Objective-C language server. - -Mode: - --clang-sanity-check - Run a simple index test. Verifies basic clang functionality. - Needs to be executed from the cquery root checkout directory. - --test-unit Run unit tests. - --test-index - Run index tests. opt_filter_path can be used to specify which - test to run (ie, "foo" will run all tests which contain "foo" - in the path). If not provided all tests are run. - (default if no other mode is specified) - Run as a language server over stdin and stdout - -Other command line options: - --init - Override client provided initialization options - https://github.com/cquery-project/cquery/wiki/Initialization-options - --record - Writes stdin to .in and stdout to .out - --log-file Logging file for diagnostics - --log-file-append Like --log-file, but appending - --log-all-to-stderr Write all log messages to STDERR. - --wait-for-input Wait for an '[Enter]' before exiting - --help Print this help information. - --ci Prevents tests from prompting the user for input. Used for - continuous integration so it can fail faster instead of timing - out. - -See more on https://github.com/cquery-project/cquery/wiki -)help"; -} - -} // namespace - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// QUERYDB MAIN //////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -bool QueryDbMainLoop(Config* config, - QueryDatabase* db, - MultiQueueWaiter* waiter, - Project* project, - FileConsumerSharedState* file_consumer_shared, - ImportManager* import_manager, - ImportPipelineStatus* status, - TimestampManager* timestamp_manager, - SemanticHighlightSymbolCache* semantic_cache, - WorkingFiles* working_files, - ClangCompleteManager* clang_complete, - IncludeComplete* include_complete, - CodeCompleteCache* global_code_complete_cache, - CodeCompleteCache* non_global_code_complete_cache, - CodeCompleteCache* signature_cache) { - auto* queue = QueueManager::instance(); - std::vector> messages = - queue->for_querydb.DequeueAll(); - bool did_work = messages.size(); - for (auto& message : messages) { - for (MessageHandler* handler : *MessageHandler::message_handlers) { - if (handler->GetId() == message->method_id) { - handler->Run(std::move(message)); - break; - } - } - - if (message) { - LOG_S(FATAL) << "Exiting; unhandled IPC message " - << IpcIdToString(message->method_id); - exit(1); - } - } - - // TODO: consider rate-limiting and checking for IPC messages so we don't - // block requests / we can serve partial requests. - - if (QueryDb_ImportMain(config, db, import_manager, status, semantic_cache, - working_files)) { - did_work = true; - } - - return did_work; -} - -void RunQueryDbThread(const std::string& bin_name, - Config* config, - MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter) { - Project project; - SemanticHighlightSymbolCache semantic_cache; - WorkingFiles working_files; - FileConsumerSharedState file_consumer_shared; - DiagnosticsEngine diag_engine; - - ClangCompleteManager clang_complete( - config, &project, &working_files, - [&](std::string path, std::vector diagnostics) { - diag_engine.Publish(&working_files, path, diagnostics); - }, - [&](ClangTranslationUnit* tu, const std::vector& unsaved, - const std::string& path, const std::vector& args) { - IndexWithTuFromCodeCompletion(config, &file_consumer_shared, tu, - unsaved, path, args); - }, - [](lsRequestId id) { - if (!std::holds_alternative(id)) { - Out_Error out; - out.id = id; - out.error.code = lsErrorCodes::InternalError; - out.error.message = "Dropping completion request; a newer request " - "has come in that will be serviced instead."; - QueueManager::WriteStdout(IpcId::Unknown, out); - } - }); - - IncludeComplete include_complete(config, &project); - auto global_code_complete_cache = std::make_unique(); - auto non_global_code_complete_cache = std::make_unique(); - auto signature_cache = std::make_unique(); - ImportManager import_manager; - ImportPipelineStatus import_pipeline_status; - TimestampManager timestamp_manager; - QueryDatabase db; - - // Setup shared references. - for (MessageHandler* handler : *MessageHandler::message_handlers) { - handler->config = config; - handler->db = &db; - handler->waiter = indexer_waiter; - handler->project = &project; - handler->diag_engine = &diag_engine; - handler->file_consumer_shared = &file_consumer_shared; - handler->import_manager = &import_manager; - handler->import_pipeline_status = &import_pipeline_status; - handler->timestamp_manager = ×tamp_manager; - handler->semantic_cache = &semantic_cache; - handler->working_files = &working_files; - handler->clang_complete = &clang_complete; - handler->include_complete = &include_complete; - handler->global_code_complete_cache = global_code_complete_cache.get(); - handler->non_global_code_complete_cache = - non_global_code_complete_cache.get(); - handler->signature_cache = signature_cache.get(); - } - - // Run query db main loop. - SetCurrentThreadName("querydb"); - while (true) { - bool did_work = QueryDbMainLoop( - config, &db, querydb_waiter, &project, &file_consumer_shared, - &import_manager, &import_pipeline_status, ×tamp_manager, - &semantic_cache, &working_files, &clang_complete, &include_complete, - global_code_complete_cache.get(), non_global_code_complete_cache.get(), - signature_cache.get()); - - // Cleanup and free any unused memory. - FreeUnusedMemory(); - - if (!did_work) { - auto* queue = QueueManager::instance(); - querydb_waiter->Wait(&queue->on_indexed, &queue->for_querydb, - &queue->do_id_map); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// STDIN MAIN ////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// Separate thread whose only job is to read from stdin and -// dispatch read commands to the actual indexer program. This -// cannot be done on the main thread because reading from std::cin -// blocks. -// -// |ipc| is connected to a server. -void LaunchStdinLoop(Config* config, - std::unordered_map* request_times) { - // If flushing cin requires flushing cout there could be deadlocks in some - // clients. - std::cin.tie(nullptr); - - WorkThread::StartThread("stdin", [request_times]() { - auto* queue = QueueManager::instance(); - while (true) { - std::unique_ptr message; - optional err = - MessageRegistry::instance()->ReadMessageFromStdin(&message); - - // Message parsing can fail if we don't recognize the method. - if (err) { - // The message may be partially deserialized. - // Emit an error ResponseMessage if |id| is available. - if (message) { - lsRequestId id = message->GetRequestId(); - if (!std::holds_alternative(id)) { - Out_Error out; - out.id = id; - out.error.code = lsErrorCodes::InvalidParams; - out.error.message = std::move(*err); - queue->WriteStdout(IpcId::Unknown, out); - } - } - continue; - } - - // Cache |method_id| so we can access it after moving |message|. - IpcId method_id = message->method_id; - (*request_times)[method_id] = Timer(); - - switch (method_id) { - case IpcId::Initialized: { - // TODO: don't send output until we get this notification - break; - } - - case IpcId::CancelRequest: { - // TODO: support cancellation - break; - } - - case IpcId::Exit: -#define CASE(name, method) case IpcId::name: -#include "methods.inc" -#undef CASE - queue->for_querydb.PushBack(std::move(message)); - break; - - case IpcId::Unknown: - break; - } - - // If the message was to exit then querydb will take care of the actual - // exit. Stop reading from stdin since it might be detached. - if (method_id == IpcId::Exit) - break; - } - }); -} - -void LaunchStdoutThread(std::unordered_map* request_times, - MultiQueueWaiter* waiter) { - WorkThread::StartThread("stdout", [=]() { - auto* queue = QueueManager::instance(); - - while (true) { - std::vector messages = queue->for_stdout.DequeueAll(); - if (messages.empty()) { - waiter->Wait(&queue->for_stdout); - continue; - } - - for (auto& message : messages) { - if (ShouldDisplayIpcTiming(message.id)) { - Timer time = (*request_times)[message.id]; - time.ResetAndPrint("[e2e] Running " + - std::string(IpcIdToString(message.id))); - } - - RecordOutput(message.content); - - fwrite(message.content.c_str(), message.content.size(), 1, stdout); - fflush(stdout); - } - } - }); -} - -void LanguageServerMain(const std::string& bin_name, - Config* config, - MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter) { - std::unordered_map request_times; - - LaunchStdinLoop(config, &request_times); - - // We run a dedicated thread for writing to stdout because there can be an - // unknown number of delays when output information. - LaunchStdoutThread(&request_times, stdout_waiter); - - // Start querydb which takes over this thread. The querydb will launch - // indexer threads as needed. - RunQueryDbThread(bin_name, config, querydb_waiter, indexer_waiter); -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -// MAIN //////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -int main(int argc, char** argv) { - TraceMe(); - - std::unordered_map options = - ParseOptions(argc, argv); - - if (HasOption(options, "-h") || HasOption(options, "--help")) { - PrintHelp(); - // Also emit doctest help if --test-unit is passed. - if (!HasOption(options, "--test-unit")) - return 0; - } - - if (!HasOption(options, "--log-all-to-stderr")) - loguru::g_stderr_verbosity = loguru::Verbosity_WARNING; - - loguru::g_flush_interval_ms = 0; - loguru::init(argc, argv); - - MultiQueueWaiter querydb_waiter, indexer_waiter, stdout_waiter; - QueueManager::Init(&querydb_waiter, &indexer_waiter, &stdout_waiter); - - PlatformInit(); - IndexInit(); - - bool language_server = true; - - if (HasOption(options, "--log-file")) { - loguru::add_file(options["--log-file"].c_str(), loguru::Truncate, - loguru::Verbosity_MAX); - } - if (HasOption(options, "--log-file-append")) { - loguru::add_file(options["--log-file-append"].c_str(), loguru::Append, - loguru::Verbosity_MAX); - } - - if (HasOption(options, "--record")) - EnableRecording(options["--record"]); - - if (HasOption(options, "--clang-sanity-check")) { - language_server = false; - ClangSanityCheck(); - } - - if (HasOption(options, "--test-unit")) { - language_server = false; - doctest::Context context; - context.applyCommandLine(argc, argv); - int res = context.run(); - if (res != 0 || context.shouldExit()) - return res; - } - - if (HasOption(options, "--test-index")) { - language_server = false; - if (!RunIndexTests(options["--test-index"], !HasOption(options, "--ci"))) - return 1; - } - - if (language_server) { - if (HasOption(options, "--init")) { - // We check syntax error here but override client-side - // initializationOptions in messages/initialize.cc - g_init_options = options["--init"]; - rapidjson::Document reader; - rapidjson::ParseResult ok = reader.Parse(g_init_options.c_str()); - if (!ok) { - std::cerr << "Failed to parse --init as JSON: " - << rapidjson::GetParseError_En(ok.Code()) << " (" - << ok.Offset() << ")\n"; - return 1; - } - JsonReader json_reader{&reader}; - try { - Config config; - Reflect(json_reader, config); - } catch (std::invalid_argument& e) { - std::cerr << "Fail to parse --init " - << static_cast(json_reader).GetPath() - << ", expected " << e.what() << "\n"; - return 1; - } - } - - // std::cerr << "Running language server" << std::endl; - auto config = std::make_unique(); - LanguageServerMain(argv[0], config.get(), &querydb_waiter, &indexer_waiter, - &stdout_waiter); - } - - if (HasOption(options, "--wait-for-input")) { - std::cerr << std::endl << "[Enter] to exit" << std::endl; - getchar(); - } - - return 0; -} diff --git a/src/config.cc b/src/config.cc new file mode 100644 index 000000000..fd607730d --- /dev/null +++ b/src/config.cc @@ -0,0 +1,31 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "config.hh" + +namespace ccls { +Config *g_config; + +void DoPathMapping(std::string &arg) { + for (const std::string &mapping : g_config->clang.pathMappings) { + auto colon = mapping.find(':'); + if (colon != std::string::npos) { + auto p = arg.find(mapping.substr(0, colon)); + if (p != std::string::npos) + arg.replace(p, colon, mapping.substr(colon + 1)); + } + } +} +} diff --git a/src/config.h b/src/config.h deleted file mode 100644 index f06397a5c..000000000 --- a/src/config.h +++ /dev/null @@ -1,281 +0,0 @@ -#pragma once - -#include "serializer.h" - -#include - -/* -The language client plugin needs to send initialization options in the -`initialize` request to the cquery language server. The only required option is -`cacheDirectory`, which is where index files will be stored. - - { - "initializationOptions": { - "cacheDirectory": "/tmp/cquery" - } - } - -If necessary, the command line option --init can be used to override -initialization options specified by the client. For example, in shell syntax: - - '--init={"index": {"comments": 2, "whitelist": ["."]}}' -*/ -struct Config { - // Root directory of the project. **Not available for configuration** - std::string projectRoot; - // If specified, this option overrides compile_commands.json and this - // external command will be executed with an option |projectRoot|. - // The initialization options will be provided as stdin. - // The stdout of the command should be the JSON compilation database. - std::string compilationDatabaseCommand; - // Directory containing compile_commands.json. - std::string compilationDatabaseDirectory; - // Cache directory for indexed files. - std::string cacheDirectory; - // Cache serialization format. - // - // "json" generates `cacheDirectory/.../xxx.json` files which can be pretty - // printed with jq. - // - // "msgpack" uses a compact binary serialization format (the underlying wire - // format is [MessagePack](https://msgpack.org/index.html)) which typically - // takes only 60% of the corresponding JSON size, but is difficult to inspect. - // msgpack does not store map keys and you need to re-index whenever a struct - // member has changed. - SerializeFormat cacheFormat = SerializeFormat::Json; - // Value to use for clang -resource-dir if not present in - // compile_commands.json. - // - // cquery includes a resource directory, this should not need to be configured - // unless you're using an esoteric configuration. Consider reporting a bug and - // fixing upstream instead of configuring this. - // - // Example value: "/path/to/lib/clang/5.0.1/" - std::string resourceDirectory; - - // Additional arguments to pass to clang. - std::vector extraClangArguments; - - // If true, cquery will send progress reports while indexing - // How often should cquery send progress report messages? - // -1: never - // 0: as often as possible - // xxx: at most every xxx milliseconds - // - // Empty progress reports (ie, idle) are delivered as often as they are - // available and may exceed this value. - // - // This does not guarantee a progress report will be delivered every - // interval; it could take significantly longer if cquery is completely idle. - int progressReportFrequencyMs = 500; - - // If true, document links are reported for #include directives. - bool showDocumentLinksOnIncludes = true; - - // Version of the client. If undefined the version check is skipped. Used to - // inform users their vscode client is too old and needs to be updated. - optional clientVersion; - - struct ClientCapability { - // TextDocumentClientCapabilities.completion.completionItem.snippetSupport - bool snippetSupport = false; - }; - ClientCapability client; - - struct CodeLens { - // Enables code lens on parameter and function variables. - bool localVariables = true; - } codeLens; - - struct Completion { - // Some completion UI, such as Emacs' completion-at-point and company-lsp, - // display completion item label and detail side by side. - // This does not look right, when you see things like: - // "foo" "int foo()" - // "bar" "void bar(int i = 0)" - // When this option is enabled, the completion item label is very detailed, - // it shows the full signature of the candidate. - // The detail just contains the completion item parent context. - // Also, in this mode, functions with default arguments, - // generates one more item per default argument - // so that the right function call can be selected. - // That is, you get something like: - // "int foo()" "Foo" - // "void bar()" "Foo" - // "void bar(int i = 0)" "Foo" - // Be wary, this is quickly quite verbose, - // items can end up truncated by the UIs. - bool detailedLabel = false; - - // On large projects, completion can take a long time. By default if cquery - // receives multiple completion requests while completion is still running - // it will only service the newest request. If this is set to false then all - // completion requests will be serviced. - bool dropOldRequests = true; - - // If true, filter and sort completion response. cquery filters and sorts - // completions to try to be nicer to clients that can't handle big numbers - // of completion candidates. This behaviour can be disabled by specifying - // false for the option. This option is the most useful for LSP clients - // that implement their own filtering and sorting logic. - bool filterAndSort = true; - - // Regex patterns to match include completion candidates against. They - // receive the absolute file path. - // - // For example, to hide all files in a /CACHE/ folder, use ".*/CACHE/.*" - std::vector includeBlacklist; - - // Maximum path length to show in completion results. Paths longer than this - // will be elided with ".." put at the front. Set to 0 or a negative number - // to disable eliding. - int includeMaxPathSize = 30; - - // Whitelist that file paths will be tested against. If a file path does not - // end in one of these values, it will not be considered for - // auto-completion. An example value is { ".h", ".hpp" } - // - // This is significantly faster than using a regex. - std::vector includeSuffixWhitelist = {".h", ".hpp", ".hh"}; - - std::vector includeWhitelist; - } completion; - - struct Diagnostics { - // Like index.{whitelist,blacklist}, don't publish diagnostics to - // blacklisted files. - std::vector blacklist; - - // How often should cquery publish diagnostics in completion? - // -1: never - // 0: as often as possible - // xxx: at most every xxx milliseconds - int frequencyMs = 0; - - // If true, diagnostics from a full document parse will be reported. - bool onParse = true; - - std::vector whitelist; - } diagnostics; - - // Semantic highlighting - struct Highlight { - // Like index.{whitelist,blacklist}, don't publish semantic highlighting to - // blacklisted files. - std::vector blacklist; - - std::vector whitelist; - } highlight; - - struct Index { - // Attempt to convert calls of make* functions to constructors based on - // hueristics. - // - // For example, this will show constructor calls for std::make_unique - // invocations. Specifically, cquery will try to attribute a ctor call - // whenever the function name starts with make (ignoring case). - bool attributeMakeCallsToCtor = true; - - // If a translation unit's absolute path matches any EMCAScript regex in the - // whitelist, or does not match any regex in the blacklist, it will be - // indexed. To only index files in the whitelist, add ".*" to the blacklist. - // `std::regex_search(path, regex, std::regex_constants::match_any)` - // - // Example: `ash/.*\.cc` - std::vector blacklist; - - // 0: none, 1: Doxygen, 2: all comments - // Plugin support for clients: - // - https://github.com/emacs-lsp/lsp-ui - // - https://github.com/autozimu/LanguageClient-neovim/issues/224 - int comments = 2; - - // If false, the indexer will be disabled. - bool enabled = true; - - // If true, project paths that were skipped by the whitelist/blacklist will - // be logged. - bool logSkippedPaths = false; - - // Number of indexer threads. If 0, 80% of cores are used. - int threads = 0; - - std::vector whitelist; - } index; - - struct WorkspaceSymbol { - // Maximum workspace search results. - int maxNum = 1000; - // If true, workspace search results will be dynamically rescored/reordered - // as the search progresses. Some clients do their own ordering and assume - // that the results stay sorted in the same order as the search progresses. - bool sort = true; - } workspaceSymbol; - - struct Xref { - // If true, |Location[]| response will include lexical container. - bool container = false; - // Maximum number of definition/reference/... results. - int maxNum = 2000; - } xref; - - //// For debugging - - // Dump AST after parsing if some pattern matches the source filename. - std::vector dumpAST; -}; -MAKE_REFLECT_STRUCT(Config::ClientCapability, snippetSupport); -MAKE_REFLECT_STRUCT(Config::CodeLens, localVariables); -MAKE_REFLECT_STRUCT(Config::Completion, - detailedLabel, - filterAndSort, - includeBlacklist, - includeMaxPathSize, - includeSuffixWhitelist, - includeWhitelist); -MAKE_REFLECT_STRUCT(Config::Diagnostics, - blacklist, - frequencyMs, - onParse, - whitelist) -MAKE_REFLECT_STRUCT(Config::Highlight, - blacklist, - whitelist) -MAKE_REFLECT_STRUCT(Config::Index, - attributeMakeCallsToCtor, - blacklist, - comments, - enabled, - logSkippedPaths, - threads, - whitelist); -MAKE_REFLECT_STRUCT(Config::WorkspaceSymbol, maxNum, sort); -MAKE_REFLECT_STRUCT(Config::Xref, container, maxNum); -MAKE_REFLECT_STRUCT(Config, - compilationDatabaseCommand, - compilationDatabaseDirectory, - cacheDirectory, - cacheFormat, - resourceDirectory, - - extraClangArguments, - - progressReportFrequencyMs, - - showDocumentLinksOnIncludes, - - clientVersion, - - client, - codeLens, - completion, - diagnostics, - highlight, - index, - workspaceSymbol, - xref, - - dumpAST); - -// Expected client version. We show an error if this doesn't match. -constexpr const int kExpectedClientVersion = 3; diff --git a/src/config.hh b/src/config.hh new file mode 100644 index 000000000..b0f5ff4f5 --- /dev/null +++ b/src/config.hh @@ -0,0 +1,291 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "serializer.hh" + +#include + +namespace ccls { +/* +The language client plugin needs to send initialization options in the +`initialize` request to the ccls language server. + +If necessary, the command line option --init can be used to override +initialization options specified by the client. For example, in shell syntax: + + '--init={"index": {"comments": 2, "whitelist": ["."]}}' +*/ +struct Config { + // **Not available for configuration** + std::string fallbackFolder; + std::vector workspaceFolders; + // If specified, this option overrides compile_commands.json and this + // external command will be executed with an option |projectRoot|. + // The initialization options will be provided as stdin. + // The stdout of the command should be the JSON compilation database. + std::string compilationDatabaseCommand; + // Directory containing compile_commands.json. + std::string compilationDatabaseDirectory; + // Cache directory for indexed files, either absolute or relative to the + // project root. + // If empty, cache will be stored in memory. + std::string cacheDirectory = ".ccls-cache"; + // Cache serialization format. + // + // "json" generates `cacheDirectory/.../xxx.json` files which can be pretty + // printed with jq. + // + // "binary" uses a compact binary serialization format. + // It is not schema-aware and you need to re-index whenever an internal struct + // member has changed. + SerializeFormat cacheFormat = SerializeFormat::Binary; + + struct Clang { + // Arguments that should be excluded, e.g. ["-fopenmp", "-Wall"] + // + // e.g. If your project is built by GCC and has an option thag clang does not understand. + std::vector excludeArgs; + + // Additional arguments to pass to clang. + std::vector extraArgs; + + // Translate absolute paths in compile_commands.json entries, .ccls options + // and cache files. This allows to reuse cache files built otherwhere if the + // source paths are different. + // + // This is a list of colon-separated strings, e.g. ["/container:/host"] + // + // An entry of "clang -I /container/include /container/a.cc" will be + // translated to "clang -I /host/include /host/a.cc". This is simple string + // replacement, so "clang /prefix/container/a.cc" will become "clang + // /prefix/host/a.cc". + std::vector pathMappings; + + // Value to use for clang -resource-dir if not specified. + // + // This option defaults to clang -print-resource-dir and should not be + // specified unless you are using an esoteric configuration. + std::string resourceDir; + } clang; + + struct ClientCapability { + // TextDocumentClientCapabilities.documentSymbol.hierarchicalDocumentSymbolSupport + bool hierarchicalDocumentSymbolSupport = true; + // TextDocumentClientCapabilities.completion.completionItem.snippetSupport + bool snippetSupport = true; + } client; + + struct CodeLens { + // Enables code lens on parameter and function variables. + bool localVariables = true; + } codeLens; + + struct Completion { + // 0: case-insensitive + // 1: case-folded, i.e. insensitive if no input character is uppercase. + // 2: case-sensitive + int caseSensitivity = 2; + + // Some completion UI, such as Emacs' completion-at-point and company-lsp, + // display completion item label and detail side by side. + // This does not look right, when you see things like: + // "foo" "int foo()" + // "bar" "void bar(int i = 0)" + // When this option is enabled, the completion item label is very detailed, + // it shows the full signature of the candidate. + // The detail just contains the completion item parent context. + bool detailedLabel = true; + + // On large projects, completion can take a long time. By default if ccls + // receives multiple completion requests while completion is still running + // it will only service the newest request. If this is set to false then all + // completion requests will be serviced. + bool dropOldRequests = true; + + // Functions with default arguments, generate one more item per default + // argument. That is, you get something like: + // "int foo()" "Foo" + // "void bar()" "Foo" + // "void bar(int i = 0)" "Foo" + // Be wary, this is quickly quite verbose, + // items can end up truncated by the UIs. + bool duplicateOptional = true; + + // If true, filter and sort completion response. ccls filters and sorts + // completions to try to be nicer to clients that can't handle big numbers + // of completion candidates. This behaviour can be disabled by specifying + // false for the option. This option is the most useful for LSP clients + // that implement their own filtering and sorting logic. + bool filterAndSort = true; + + // Maxmum number of results. + int maxNum = 100; + + struct Include { + // Regex patterns to match include completion candidates against. They + // receive the absolute file path. + // + // For example, to hide all files in a /CACHE/ folder, use ".*/CACHE/.*" + std::vector blacklist; + + // Maximum path length to show in completion results. Paths longer than + // this will be elided with ".." put at the front. Set to 0 or a negative + // number to disable eliding. + int maxPathSize = 30; + + // Whitelist that file paths will be tested against. If a file path does + // not end in one of these values, it will not be considered for + // auto-completion. An example value is { ".h", ".hpp" } + // + // This is significantly faster than using a regex. + std::vector suffixWhitelist = {".h", ".hpp", ".hh", ".inc"}; + + std::vector whitelist; + } include; + } completion; + + struct Diagnostics { + // Like index.{whitelist,blacklist}, don't publish diagnostics to + // blacklisted files. + std::vector blacklist; + + // Time to wait before computing diagnostics for textDocument/didChange. + // -1: disable diagnostics on change + // 0: immediately + // positive (e.g. 500): wait for 500 milliseconds. didChange requests in + // this period of time will only cause one computation. + int onChange = 1000; + + // Time to wait before computing diagnostics for textDocument/didOpen. + int onOpen = 0; + + // Time to wait before computing diagnostics for textDocument/didSave. + int onSave = 0; + + bool spellChecking = true; + + std::vector whitelist; + } diagnostics; + + // Semantic highlighting + struct Highlight { + // Disable semantic highlighting for files larger than the size. + int64_t largeFileSize = 2 * 1024 * 1024; + + // true: LSP line/character; false: position + bool lsRanges = false; + + // Like index.{whitelist,blacklist}, don't publish semantic highlighting to + // blacklisted files. + std::vector blacklist; + + std::vector whitelist; + } highlight; + + struct Index { + // If a translation unit's absolute path matches any EMCAScript regex in the + // whitelist, or does not match any regex in the blacklist, it will be + // indexed. To only index files in the whitelist, add ".*" to the blacklist. + // `std::regex_search(path, regex, std::regex_constants::match_any)` + // + // Example: `ash/.*\.cc` + std::vector blacklist; + + // 0: none, 1: Doxygen, 2: all comments + // Plugin support for clients: + // - https://github.com/emacs-lsp/lsp-ui + // - https://github.com/autozimu/LanguageClient-neovim/issues/224 + int comments = 2; + + // By default, all project entries will be indexed on initialization. Use + // these two options to exclude some. They can still be indexed after you + // open them. + std::vector initialBlacklist; + std::vector initialWhitelist; + + // If not 0, a file will be indexed in each tranlation unit that includes it. + int multiVersion = 0; + + // If multiVersion != 0, files that match blacklist but not whitelist will + // still only be indexed for one version. + std::vector multiVersionBlacklist; + std::vector multiVersionWhitelist; + + // Allow indexing on textDocument/didChange. + // May be too slow for big projects, so it is off by default. + bool onChange = false; + + // Number of indexer threads. If 0, 80% of cores are used. + int threads = 0; + + // Whether to reparse a file if write times of its dependencies have + // changed. The file will always be reparsed if its own write time changes. + // 0: no, 1: only during initial load of project, 2: yes + int trackDependency = 2; + + std::vector whitelist; + } index; + + struct Session { + int maxNum = 10; + } session; + + struct WorkspaceSymbol { + int caseSensitivity = 1; + // Maximum workspace search results. + int maxNum = 1000; + // If true, workspace search results will be dynamically rescored/reordered + // as the search progresses. Some clients do their own ordering and assume + // that the results stay sorted in the same order as the search progresses. + bool sort = true; + } workspaceSymbol; + + struct Xref { + // Maximum number of definition/reference/... results. + int maxNum = 2000; + } xref; +}; +REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, pathMappings, + resourceDir); +REFLECT_STRUCT(Config::ClientCapability, hierarchicalDocumentSymbolSupport, + snippetSupport); +REFLECT_STRUCT(Config::CodeLens, localVariables); +REFLECT_STRUCT(Config::Completion::Include, blacklist, maxPathSize, + suffixWhitelist, whitelist); +REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel, + dropOldRequests, duplicateOptional, filterAndSort, include, + maxNum); +REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave, + spellChecking, whitelist) +REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist, + whitelist) +REFLECT_STRUCT(Config::Index, blacklist, comments, initialBlacklist, + initialWhitelist, multiVersion, multiVersionBlacklist, + multiVersionWhitelist, onChange, threads, trackDependency, + whitelist); +REFLECT_STRUCT(Config::Session, maxNum); +REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort); +REFLECT_STRUCT(Config::Xref, maxNum); +REFLECT_STRUCT(Config, compilationDatabaseCommand, + compilationDatabaseDirectory, cacheDirectory, cacheFormat, + clang, client, codeLens, completion, diagnostics, highlight, + index, session, workspaceSymbol, xref); + +extern Config *g_config; + +void DoPathMapping(std::string &arg); +} diff --git a/src/diagnostics_engine.cc b/src/diagnostics_engine.cc deleted file mode 100644 index 8d66325f0..000000000 --- a/src/diagnostics_engine.cc +++ /dev/null @@ -1,33 +0,0 @@ -#include "diagnostics_engine.h" - -#include "queue_manager.h" - -#include - -void DiagnosticsEngine::Init(Config* config) { - frequencyMs_ = config->diagnostics.frequencyMs; - match_ = std::make_unique(config->diagnostics.whitelist, - config->diagnostics.blacklist); -} - -void DiagnosticsEngine::Publish(WorkingFiles* working_files, - std::string path, - std::vector diagnostics) { - // Cache diagnostics so we can show fixits. - working_files->DoActionOnFile(path, [&](WorkingFile* working_file) { - if (working_file) - working_file->diagnostics_ = diagnostics; - }); - - int64_t now = std::chrono::duration_cast( - std::chrono::high_resolution_clock::now().time_since_epoch()).count(); - if (frequencyMs_ >= 0 && (nextPublish_ <= now || diagnostics.empty()) && - match_->IsMatch(path)) { - nextPublish_ = now + frequencyMs_; - - Out_TextDocumentPublishDiagnostics out; - out.params.uri = lsDocumentUri::FromPath(path); - out.params.diagnostics = diagnostics; - QueueManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); - } -} diff --git a/src/diagnostics_engine.h b/src/diagnostics_engine.h deleted file mode 100644 index ec1066fbd..000000000 --- a/src/diagnostics_engine.h +++ /dev/null @@ -1,16 +0,0 @@ -#include "lsp_diagnostic.h" - -#include "match.h" -#include "working_files.h" - -class DiagnosticsEngine { - std::unique_ptr match_; - int64_t nextPublish_ = 0; - int frequencyMs_; - - public: - void Init(Config*); - void Publish(WorkingFiles* working_files, - std::string path, - std::vector diagnostics); -}; diff --git a/src/file_consumer.cc b/src/file_consumer.cc deleted file mode 100644 index 13042d795..000000000 --- a/src/file_consumer.cc +++ /dev/null @@ -1,107 +0,0 @@ -#include "file_consumer.h" - -#include "clang_utils.h" -#include "indexer.h" -#include "platform.h" -#include "utils.h" - -#include - -namespace { - -optional GetFileContents(const std::string& path, - FileContentsMap* file_contents) { - auto it = file_contents->find(path); - if (it == file_contents->end()) { - optional content = ReadContent(path); - if (content) - (*file_contents)[path] = FileContents(path, *content); - return content; - } - return it->second.content; -} - -} // namespace - -bool operator==(const CXFileUniqueID& a, const CXFileUniqueID& b) { - return a.data[0] == b.data[0] && a.data[1] == b.data[1] && - a.data[2] == b.data[2]; -} - -bool FileConsumerSharedState::Mark(const std::string& file) { - std::lock_guard lock(mutex); - return used_files.insert(file).second; -} - -void FileConsumerSharedState::Reset(const std::string& file) { - std::lock_guard lock(mutex); - auto it = used_files.find(file); - if (it != used_files.end()) - used_files.erase(it); -} - -FileConsumer::FileConsumer(FileConsumerSharedState* shared_state, - const std::string& parse_file) - : shared_(shared_state), parse_file_(parse_file) {} - -IndexFile* FileConsumer::TryConsumeFile(CXFile file, - bool* is_first_ownership, - FileContentsMap* file_contents_map) { - assert(is_first_ownership); - - CXFileUniqueID file_id; - if (clang_getFileUniqueID(file, &file_id) != 0) { - EmitError(file); - return nullptr; - } - - // Try to find cached local result. - auto it = local_.find(file_id); - if (it != local_.end()) { - *is_first_ownership = false; - return it->second.get(); - } - - std::string file_name = FileName(file); - - // No result in local; we need to query global. - bool did_insert = shared_->Mark(file_name); - - // We did not take the file from global. Cache that we failed so we don't try - // again and return nullptr. - if (!did_insert) { - local_[file_id] = nullptr; - return nullptr; - } - - // Read the file contents, if we fail then we cannot index the file. - optional contents = - GetFileContents(file_name, file_contents_map); - if (!contents) { - *is_first_ownership = false; - return nullptr; - } - - // Build IndexFile instance. - *is_first_ownership = true; - local_[file_id] = std::make_unique(file_name, *contents); - return local_[file_id].get(); -} - -std::vector> FileConsumer::TakeLocalState() { - std::vector> result; - for (auto& entry : local_) { - if (entry.second) - result.push_back(std::move(entry.second)); - } - return result; -} - -void FileConsumer::EmitError(CXFile file) const { - std::string file_name = ToString(clang_getFileName(file)); - // TODO: Investigate this more, why can we get an empty file name? - if (!file_name.empty()) { - LOG_S(ERROR) << "Could not get unique file id for " << file_name - << " when parsing " << parse_file_; - } -} \ No newline at end of file diff --git a/src/file_consumer.h b/src/file_consumer.h deleted file mode 100644 index 6ac94eddb..000000000 --- a/src/file_consumer.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "file_contents.h" -#include "utils.h" - -#include - -#include -#include -#include -#include - -struct IndexFile; - -// Needed for unordered_map usage below. -MAKE_HASHABLE(CXFileUniqueID, t.data[0], t.data[1], t.data[2]); -bool operator==(const CXFileUniqueID& a, const CXFileUniqueID& b); - -struct FileConsumerSharedState { - mutable std::unordered_set used_files; - mutable std::mutex mutex; - - // Mark the file as used. Returns true if the file was not previously used. - bool Mark(const std::string& file); - // Reset the used state (ie, mark the file as unused). - void Reset(const std::string& file); -}; - -// FileConsumer is used by the indexer. When it encouters a file, it tries to -// take ownership over it. If the indexer has ownership over a file, it will -// produce an index, otherwise, it will emit nothing for that declarations -// and references coming from that file. -// -// The indexer does this because header files do not have their own translation -// units but we still want to index them. -struct FileConsumer { - FileConsumer(FileConsumerSharedState* shared_state, - const std::string& parse_file); - - // Returns true if this instance owns given |file|. This will also attempt to - // take ownership over |file|. - // - // Returns IndexFile for the file or nullptr. |is_first_ownership| is set - // to true iff the function just took ownership over the file. Otherwise it - // is set to false. - // - // note: file_contents is passed as a parameter instead of as a member - // variable since it is large and we do not want to copy it. - IndexFile* TryConsumeFile(CXFile file, - bool* is_first_ownership, - FileContentsMap* file_contents); - - // Returns and passes ownership of all local state. - std::vector> TakeLocalState(); - - private: - void EmitError(CXFile file) const; - - std::unordered_map> local_; - FileConsumerSharedState* shared_; - std::string parse_file_; -}; \ No newline at end of file diff --git a/src/file_contents.cc b/src/file_contents.cc deleted file mode 100644 index 4e716e65a..000000000 --- a/src/file_contents.cc +++ /dev/null @@ -1,29 +0,0 @@ -#include "file_contents.h" - -FileContents::FileContents() : line_offsets_{0} {} - -FileContents::FileContents(const std::string& path, const std::string& content) - : path(path), content(content) { - line_offsets_.push_back(0); - for (size_t i = 0; i < content.size(); i++) { - if (content[i] == '\n') - line_offsets_.push_back(i + 1); - } -} - -optional FileContents::ToOffset(Position p) const { - if (0 <= p.line && size_t(p.line) < line_offsets_.size()) { - int ret = line_offsets_[p.line] + p.column; - if (size_t(ret) < content.size()) - return ret; - } - return nullopt; -} - -optional FileContents::ContentsInRange(Range range) const { - optional start_offset = ToOffset(range.start), - end_offset = ToOffset(range.end); - if (start_offset && end_offset && *start_offset < *end_offset) - return content.substr(*start_offset, *end_offset - *start_offset); - return nullopt; -} diff --git a/src/file_contents.h b/src/file_contents.h deleted file mode 100644 index 0a6cffb51..000000000 --- a/src/file_contents.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "position.h" - -#include "optional.h" - -#include -#include -#include - -struct FileContents { - FileContents(); - FileContents(const std::string& path, const std::string& content); - - optional ToOffset(Position p) const; - optional ContentsInRange(Range range) const; - - std::string path; - std::string content; - // {0, 1 + position of first newline, 1 + position of second newline, ...} - std::vector line_offsets_; -}; - -using FileContentsMap = std::unordered_map; diff --git a/src/filesystem.cc b/src/filesystem.cc new file mode 100644 index 000000000..fb463b989 --- /dev/null +++ b/src/filesystem.cc @@ -0,0 +1,71 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "filesystem.hh" +using namespace llvm; + +#include "utils.hh" + +#include +#include + +void GetFilesInFolder(std::string folder, bool recursive, bool dir_prefix, + const std::function &handler) { + ccls::EnsureEndsInSlash(folder); + sys::fs::file_status Status; + if (sys::fs::status(folder, Status, true)) + return; + sys::fs::UniqueID ID; + std::vector curr{folder}; + std::vector> succ; + std::set seen{Status.getUniqueID()}; + while (curr.size() || succ.size()) { + if (curr.empty()) { + for (auto &it : succ) + if (!seen.count(it.second.getUniqueID())) + curr.push_back(std::move(it.first)); + succ.clear(); + } else { + std::error_code ec; + std::string folder1 = curr.back(); + curr.pop_back(); + for (sys::fs::directory_iterator I(folder1, ec, false), E; I != E && !ec; + I.increment(ec)) { + std::string path = I->path(), filename = sys::path::filename(path); + if ((filename[0] == '.' && filename != ".ccls") || + sys::fs::status(path, Status, false)) + continue; + if (sys::fs::is_symlink_file(Status)) { + if (sys::fs::status(path, Status, true)) + continue; + if (sys::fs::is_directory(Status)) { + if (recursive) + succ.emplace_back(path, Status); + continue; + } + } + if (sys::fs::is_regular_file(Status)) { + if (!dir_prefix) + path = path.substr(folder.size()); + handler(sys::path::convert_to_slash(path)); + } else if (recursive && sys::fs::is_directory(Status) && + !seen.count(ID = Status.getUniqueID())) { + curr.push_back(path); + seen.insert(ID); + } + } + } + } +} diff --git a/src/filesystem.hh b/src/filesystem.hh new file mode 100644 index 000000000..334810fc6 --- /dev/null +++ b/src/filesystem.hh @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +#include +#include + +void GetFilesInFolder(std::string folder, + bool recursive, + bool add_folder_to_path, + const std::function& handler); diff --git a/src/fuzzy_match.cc b/src/fuzzy_match.cc index bda1a6883..680aa4acb 100644 --- a/src/fuzzy_match.cc +++ b/src/fuzzy_match.cc @@ -1,120 +1,199 @@ -#include "fuzzy_match.h" +/* Copyright 2017-2018 ccls Authors -#include -#include -#include +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 -// Penalty of dropping a leading character in str -constexpr int kLeadingGapScore = -4; -// Penalty of dropping a non-leading character in str -constexpr int kGapScore = -5; -// Bonus of aligning with an initial character of a word in pattern. Must be -// greater than 1 -constexpr int kPatternStartMultiplier = 2; +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ -constexpr int kWordStartScore = 50; -constexpr int kNonWordScore = 40; -constexpr int kCaseMatchScore = 2; +#include "fuzzy_match.hh" -// Less than kWordStartScore -constexpr int kConsecutiveScore = kWordStartScore + kGapScore; -// Slightly less than kConsecutiveScore -constexpr int kCamelScore = kWordStartScore + kGapScore - 1; +#include +#include +#include +#include -enum class CharClass { Lower, Upper, Digit, NonWord }; +namespace ccls { +namespace { +enum CharClass { Other, Lower, Upper }; +enum CharRole { None, Tail, Head }; -static CharClass GetCharClass(int c) { +CharClass GetCharClass(int c) { if (islower(c)) - return CharClass::Lower; + return Lower; if (isupper(c)) - return CharClass::Upper; - if (isdigit(c)) - return CharClass::Digit; - return CharClass::NonWord; + return Upper; + return Other; } -static int GetScoreFor(CharClass prev, CharClass curr) { - if (prev == CharClass::NonWord && curr != CharClass::NonWord) - return kWordStartScore; - if ((prev == CharClass::Lower && curr == CharClass::Upper) || - (prev != CharClass::Digit && curr == CharClass::Digit)) - return kCamelScore; - if (curr == CharClass::NonWord) - return kNonWordScore; - return 0; +void CalculateRoles(std::string_view s, int roles[], int *class_set) { + if (s.empty()) { + *class_set = 0; + return; + } + CharClass pre = Other, cur = GetCharClass(s[0]), suc; + *class_set = 1 << cur; + auto fn = [&]() { + if (cur == Other) + return None; + // U(U)L is Head while U(U)U is Tail + return pre == Other || (cur == Upper && (pre == Lower || suc == Lower)) + ? Head + : Tail; + }; + for (size_t i = 0; i < s.size() - 1; i++) { + suc = GetCharClass(s[i + 1]); + *class_set |= 1 << suc; + roles[i] = fn(); + pre = cur; + cur = suc; + } + roles[s.size() - 1] = fn(); +} +} // namespace + +int FuzzyMatcher::MissScore(int j, bool last) { + int s = -3; + if (last) + s -= 10; + if (text_role[j] == Head) + s -= 10; + return s; } -/* -fuzzyEvaluate implements a global sequence alignment algorithm to find the -maximum accumulated score by aligning `pattern` to `str`. It applies when -`pattern` is a subsequence of `str`. - -Scoring criteria -- Prefer matches at the start of a word, or the start of subwords in -CamelCase/camelCase/camel123 words. See kWordStartScore/kCamelScore -- Non-word characters matter. See kNonWordScore -- The first characters of words of `pattern` receive bonus because they usually -have more significance than the rest. See kPatternStartMultiplier -- Superfluous characters in `str` will reduce the score (gap penalty). See -kGapScore -- Prefer early occurrence of the first character. See kLeadingGapScore/kGapScore - -The recurrence of the dynamic programming: -dp[i][j]: maximum accumulated score by aligning pattern[0..i] to str[0..j] -dp[0][j] = leading_gap_penalty(0, j) + score[j] -dp[i][j] = max(dp[i-1][j-1] + CONSECUTIVE_SCORE, max(dp[i-1][k] + -gap_penalty(k+1, j) + score[j] : k < j)) -The first dimension can be suppressed since we do not need a matching scheme, -which reduces the space complexity from O(N*M) to O(M) -*/ -int FuzzyEvaluate(std::string_view pattern, - std::string_view str, - std::vector& score, - std::vector& dp) { - bool pfirst = true, // aligning the first character of pattern - pstart = true; // whether we are aligning the start of a word in pattern - int uleft = 0, // value of the upper left cell - ulefts = 0, // maximum value of uleft and cells on the left - left, lefts; // similar to uleft/ulefts, but for the next row - - // Calculate position score for each character in str. - CharClass prev = CharClass::NonWord; - for (int i = 0; i < int(str.size()); i++) { - CharClass cur = GetCharClass(str[i]); - score[i] = GetScoreFor(prev, cur); - prev = cur; +int FuzzyMatcher::MatchScore(int i, int j, bool last) { + int s = 0; + // Case matching. + if (pat[i] == text[j]) { + s++; + // pat contains uppercase letters or prefix matching. + if ((pat_set & 1 << Upper) || i == j) + s++; } - std::fill_n(dp.begin(), str.size(), kMinScore); + if (pat_role[i] == Head) { + if (text_role[j] == Head) + s += 30; + else if (text_role[j] == Tail) + s -= 10; + } + // Matching a tail while previous char wasn't matched. + if (text_role[j] == Tail && i && !last) + s -= 30; + // First char of pat matches a tail. + if (i == 0 && text_role[j] == Tail) + s -= 40; + return s; +} - // Align each character of pattern. - for (unsigned char pc : pattern) { - if (isspace(pc)) { - pstart = true; - continue; +FuzzyMatcher::FuzzyMatcher(std::string_view pattern, int sensitivity) { + CalculateRoles(pattern, pat_role, &pat_set); + if (sensitivity == 1) + sensitivity = pat_set & 1 << Upper ? 2 : 0; + case_sensitivity = sensitivity; + size_t n = 0; + for (size_t i = 0; i < pattern.size(); i++) + if (pattern[i] != ' ') { + pat += pattern[i]; + low_pat[n] = ::tolower(pattern[i]); + pat_role[n] = pat_role[i]; + n++; } - lefts = kMinScore; - // Enumerate the character in str to be aligned with pc. - for (int i = 0; i < int(str.size()); i++) { - left = dp[i]; - lefts = std::max(lefts + kGapScore, left); - // Use lower() if case-insensitive - if (tolower(pc) == tolower(str[i])) { - int t = score[i] * (pstart ? kPatternStartMultiplier : 1); - dp[i] = (pfirst ? kLeadingGapScore * i + t - : std::max(uleft + kConsecutiveScore, ulefts + t)) + - (pc == str[i] ? kCaseMatchScore : 0); - } else - dp[i] = kMinScore; - uleft = left; - ulefts = lefts; +} + +int FuzzyMatcher::Match(std::string_view text) { + int n = int(text.size()); + if (n > kMaxText) + return kMinScore + 1; + this->text = text; + for (int i = 0; i < n; i++) + low_text[i] = ::tolower(text[i]); + CalculateRoles(text, text_role, &text_set); + dp[0][0][0] = dp[0][0][1] = 0; + for (int j = 0; j < n; j++) { + dp[0][j + 1][0] = dp[0][j][0] + MissScore(j, false); + dp[0][j + 1][1] = kMinScore * 2; + } + for (int i = 0; i < int(pat.size()); i++) { + int(*pre)[2] = dp[i & 1]; + int(*cur)[2] = dp[(i + 1) & 1]; + cur[i][0] = cur[i][1] = kMinScore; + for (int j = i; j < n; j++) { + cur[j + 1][0] = std::max(cur[j][0] + MissScore(j, false), + cur[j][1] + MissScore(j, true)); + // For the first char of pattern, apply extra restriction to filter bad + // candidates (e.g. |int| in |PRINT|) + cur[j + 1][1] = (case_sensitivity ? pat[i] == text[j] + : low_pat[i] == low_text[j] && + (i || text_role[j] != Tail || + pat[i] == text[j])) + ? std::max(pre[j][0] + MatchScore(i, j, false), + pre[j][1] + MatchScore(i, j, true)) + : kMinScore * 2; } - pfirst = pstart = false; } // Enumerate the end position of the match in str. Each removed trailing - // character has a penulty of kGapScore. - lefts = kMinScore; - for (int i = 0; i < int(str.size()); i++) - lefts = std::max(lefts + kGapScore, dp[i]); - return lefts; + // character has a penulty. + int ret = kMinScore; + for (int j = pat.size(); j <= n; j++) + ret = std::max(ret, dp[pat.size() & 1][j][1] - 2 * (n - j)); + return ret; +} +} // namespace ccls + +#if 0 +TEST_SUITE("fuzzy_match") { + bool Ranks(std::string_view pat, std::vector texts) { + FuzzyMatcher fuzzy(pat, 0); + std::vector scores; + for (auto text : texts) + scores.push_back(fuzzy.Match(text)); + bool ret = true; + for (size_t i = 0; i < texts.size() - 1; i++) + if (scores[i] < scores[i + 1]) { + ret = false; + break; + } + if (!ret) { + for (size_t i = 0; i < texts.size(); i++) + printf("%s %d ", texts[i], scores[i]); + puts(""); + } + return ret; + } + + TEST_CASE("test") { + FuzzyMatcher fuzzy("", 0); + CHECK(fuzzy.Match("") == 0); + CHECK(fuzzy.Match("aaa") < 0); + + // case + CHECK(Ranks("monad", {"monad", "Monad", "mONAD"})); + // initials + CHECK(Ranks("ab", {"ab", "aoo_boo", "acb"})); + CHECK(Ranks("CC", {"CamelCase", "camelCase", "camelcase"})); + CHECK(Ranks("cC", {"camelCase", "CamelCase", "camelcase"})); + CHECK(Ranks("c c", {"camelCase", "camel case", "CamelCase", "camelcase", + "camel ace"})); + CHECK(Ranks("Da.Te", + {"Data.Text", "Data.Text.Lazy", "Data.Aeson.Encoding.text"})); + CHECK(Ranks("foo bar.h", {"foo/bar.h", "foobar.h"})); + // prefix + CHECK(Ranks("is", {"isIEEE", "inSuf"})); + // shorter + CHECK(Ranks("ma", {"map", "many", "maximum"})); + CHECK(Ranks("print", {"printf", "sprintf"})); + // score(PRINT) = kMinScore + CHECK(Ranks("ast", {"ast", "AST", "INT_FAST16_MAX"})); + // score(PRINT) > kMinScore + CHECK(Ranks("Int", {"int", "INT", "PRINT"})); + } } +#endif diff --git a/src/fuzzy_match.h b/src/fuzzy_match.h deleted file mode 100644 index 3e38a5a76..000000000 --- a/src/fuzzy_match.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include - -#include -#include - -// Negative but far from INT_MIN so that intermediate results are hard to -// overflow -constexpr int kMinScore = INT_MIN / 2; - -// Evaluate the score matching |pattern| against |str|, the larger the better. -// |score| and |dp| must be at least as long as |str|. -int FuzzyEvaluate(std::string_view pattern, - std::string_view str, - std::vector& score, - std::vector& dp); diff --git a/src/fuzzy_match.hh b/src/fuzzy_match.hh new file mode 100644 index 000000000..9153c6c01 --- /dev/null +++ b/src/fuzzy_match.hh @@ -0,0 +1,46 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include +#include +#include + +namespace ccls { +class FuzzyMatcher { +public: + constexpr static int kMaxPat = 100; + constexpr static int kMaxText = 200; + // Negative but far from INT_MIN so that intermediate results are hard to + // overflow. + constexpr static int kMinScore = INT_MIN / 4; + + FuzzyMatcher(std::string_view pattern, int case_sensitivity); + int Match(std::string_view text); + +private: + int case_sensitivity; + std::string pat; + std::string_view text; + int pat_set, text_set; + char low_pat[kMaxPat], low_text[kMaxText]; + int pat_role[kMaxPat], text_role[kMaxText]; + int dp[2][kMaxText + 1][2]; + + int MatchScore(int i, int j, bool last); + int MissScore(int j, bool last); +}; +} // namespace ccls diff --git a/src/hierarchy.hh b/src/hierarchy.hh new file mode 100644 index 000000000..de1b17514 --- /dev/null +++ b/src/hierarchy.hh @@ -0,0 +1,44 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "lsp.hh" + +#include +#include + +namespace ccls { +template +std::vector FlattenHierarchy(const std::optional &root) { + if (!root) + return {}; + std::vector ret; + std::queue q; + for (auto &entry : root->children) + q.push(&entry); + while (q.size()) { + auto *entry = q.front(); + q.pop(); + if (entry->location.uri.raw_uri.size()) + ret.push_back({entry->location}); + for (auto &entry1 : entry->children) + q.push(&entry1); + } + std::sort(ret.begin(), ret.end()); + ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); + return ret; +} +} // namespace ccls diff --git a/src/iindexer.cc b/src/iindexer.cc deleted file mode 100644 index e07584100..000000000 --- a/src/iindexer.cc +++ /dev/null @@ -1,92 +0,0 @@ -#include "iindexer.h" - -#include "indexer.h" - -namespace { -struct ClangIndexer : IIndexer { - ~ClangIndexer() override = default; - - optional>> Index( - Config* config, - FileConsumerSharedState* file_consumer_shared, - std::string file, - const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf) override { - bool dump_ast = false; - for (const std::string& pattern : config->dumpAST) - if (file.find(pattern) != std::string::npos) { - dump_ast = true; - break; - } - return Parse(config, file_consumer_shared, file, args, file_contents, perf, - &index, dump_ast); - } - - // Note: constructing this acquires a global lock - ClangIndex index; -}; - -struct TestIndexer : IIndexer { - static std::unique_ptr FromEntries( - const std::vector& entries) { - auto result = std::make_unique(); - - for (const TestEntry& entry : entries) { - std::vector> indexes; - - if (entry.num_indexes > 0) - indexes.push_back(std::make_unique(entry.path, "")); - for (int i = 1; i < entry.num_indexes; ++i) { - indexes.push_back(std::make_unique( - entry.path + "_extra_" + std::to_string(i) + ".h", "")); - } - - result->indexes.insert(std::make_pair(entry.path, std::move(indexes))); - } - - return result; - } - - ~TestIndexer() override = default; - - optional>> Index( - Config* config, - FileConsumerSharedState* file_consumer_shared, - std::string file, - const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf) override { - auto it = indexes.find(file); - if (it == indexes.end()) { - // Don't return any indexes for unexpected data. - assert(false && "no indexes"); - return nullopt; - } - - // FIXME: allow user to control how many times we return the index for a - // specific file (atm it is always 1) - auto result = std::move(it->second); - indexes.erase(it); - return std::move(result); - } - - std::unordered_map>> - indexes; -}; - -} // namespace - -IIndexer::TestEntry::TestEntry(const std::string& path, int num_indexes) - : path(path), num_indexes(num_indexes) {} - -// static -std::unique_ptr IIndexer::MakeClangIndexer() { - return std::make_unique(); -} - -// static -std::unique_ptr IIndexer::MakeTestIndexer( - std::initializer_list entries) { - return TestIndexer::FromEntries(entries); -} diff --git a/src/iindexer.h b/src/iindexer.h deleted file mode 100644 index f8a453472..000000000 --- a/src/iindexer.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -// TODO: -// - rename indexer.h to clang_indexer.h and pull out non-clang specific code -// like IndexFile -// - rename this file to indexer.h - -struct Config; -struct IndexFile; -struct FileContents; -struct FileConsumerSharedState; -struct PerformanceImportFile; - -// Abstracts away the actual indexing process. Each IIndexer instance is -// per-thread and constructing an instance may be extremely expensive (ie, -// acquire a lock) and should be done as rarely as possible. -struct IIndexer { - struct TestEntry { - std::string path; - int num_indexes = 0; - - TestEntry(const std::string& path, int num_indexes); - }; - - static std::unique_ptr MakeClangIndexer(); - static std::unique_ptr MakeTestIndexer( - std::initializer_list entries); - - virtual ~IIndexer() = default; - virtual optional>> Index( - Config* config, - FileConsumerSharedState* file_consumer_shared, - std::string file, - const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf) = 0; -}; diff --git a/src/import_manager.cc b/src/import_manager.cc deleted file mode 100644 index 11b13f296..000000000 --- a/src/import_manager.cc +++ /dev/null @@ -1,14 +0,0 @@ -#include "import_manager.h" - -bool ImportManager::TryMarkDependencyImported(const std::string& path) { - std::lock_guard lock(dependency_mutex_); - return dependency_imported_.insert(path).second; -} - -bool ImportManager::StartQueryDbImport(const std::string& path) { - return querydb_processing_.insert(path).second; -} - -void ImportManager::DoneQueryDbImport(const std::string& path) { - querydb_processing_.erase(path); -} diff --git a/src/import_manager.h b/src/import_manager.h deleted file mode 100644 index 57dc7ffe5..000000000 --- a/src/import_manager.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include -#include - -// Manages files inside of the indexing pipeline so we don't have the same file -// being imported multiple times. -// -// NOTE: This is not thread safe and should only be used on the querydb thread. -struct ImportManager { - // Try to mark the given dependency as imported. A dependency can only ever be - // imported once. - bool TryMarkDependencyImported(const std::string& path); - - // Try to import the given file into querydb. We should only ever be - // importing a file into querydb once per file. Returns true if the file - // can be imported. - bool StartQueryDbImport(const std::string& path); - - // The file has been fully imported and can be imported again later on. - void DoneQueryDbImport(const std::string& path); - - std::unordered_set querydb_processing_; - - // TODO: use std::shared_mutex so we can have multiple readers. - std::mutex dependency_mutex_; - std::unordered_set dependency_imported_; -}; \ No newline at end of file diff --git a/src/import_pipeline.cc b/src/import_pipeline.cc deleted file mode 100644 index dc84f642a..000000000 --- a/src/import_pipeline.cc +++ /dev/null @@ -1,913 +0,0 @@ -#include "import_pipeline.h" - -#include "cache_manager.h" -#include "config.h" -#include "diagnostics_engine.h" -#include "iindexer.h" -#include "import_manager.h" -#include "lsp.h" -#include "message_handler.h" -#include "platform.h" -#include "project.h" -#include "query_utils.h" -#include "queue_manager.h" -#include "timer.h" -#include "timestamp_manager.h" - -#include -#include - -#include -#include -#include -#include - -namespace { - -struct Out_Progress : public lsOutMessage { - struct Params { - int indexRequestCount = 0; - int doIdMapCount = 0; - int loadPreviousIndexCount = 0; - int onIdMappedCount = 0; - int onIndexedCount = 0; - int activeThreads = 0; - }; - std::string method = "$cquery/progress"; - Params params; -}; -MAKE_REFLECT_STRUCT(Out_Progress::Params, - indexRequestCount, - doIdMapCount, - loadPreviousIndexCount, - onIdMappedCount, - onIndexedCount, - activeThreads); -MAKE_REFLECT_STRUCT(Out_Progress, jsonrpc, method, params); - -struct IModificationTimestampFetcher { - virtual ~IModificationTimestampFetcher() = default; - virtual optional GetModificationTime(const std::string& path) = 0; -}; -struct RealModificationTimestampFetcher : IModificationTimestampFetcher { - ~RealModificationTimestampFetcher() override = default; - - // IModificationTimestamp: - optional GetModificationTime(const std::string& path) override { - return GetLastModificationTime(path); - } -}; -struct FakeModificationTimestampFetcher : IModificationTimestampFetcher { - std::unordered_map> entries; - - ~FakeModificationTimestampFetcher() override = default; - - // IModificationTimestamp: - optional GetModificationTime(const std::string& path) override { - auto it = entries.find(path); - assert(it != entries.end()); - return it->second; - } -}; - -long long GetCurrentTimeInMilliseconds() { - auto time_since_epoch = Timer::Clock::now().time_since_epoch(); - long long elapsed_milliseconds = - std::chrono::duration_cast(time_since_epoch) - .count(); - return elapsed_milliseconds; -} - -struct ActiveThread { - ActiveThread(Config* config, ImportPipelineStatus* status) - : config_(config), status_(status) { - if (config_->progressReportFrequencyMs < 0) - return; - - ++status_->num_active_threads; - } - ~ActiveThread() { - if (config_->progressReportFrequencyMs < 0) - return; - - --status_->num_active_threads; - EmitProgress(); - } - - // Send indexing progress to client if reporting is enabled. - void EmitProgress() { - auto* queue = QueueManager::instance(); - Out_Progress out; - out.params.indexRequestCount = queue->index_request.Size(); - out.params.doIdMapCount = queue->do_id_map.Size(); - out.params.loadPreviousIndexCount = queue->load_previous_index.Size(); - out.params.onIdMappedCount = queue->on_id_mapped.Size(); - out.params.onIndexedCount = queue->on_indexed.Size(); - out.params.activeThreads = status_->num_active_threads; - - // Ignore this progress update if the last update was too recent. - if (config_->progressReportFrequencyMs != 0) { - // Make sure we output a status update if queue lengths are zero. - bool all_zero = - out.params.indexRequestCount == 0 && out.params.doIdMapCount == 0 && - out.params.loadPreviousIndexCount == 0 && - out.params.onIdMappedCount == 0 && out.params.onIndexedCount == 0 && - out.params.activeThreads == 0; - if (!all_zero && - GetCurrentTimeInMilliseconds() < status_->next_progress_output) - return; - status_->next_progress_output = - GetCurrentTimeInMilliseconds() + config_->progressReportFrequencyMs; - } - - QueueManager::WriteStdout(IpcId::Unknown, out); - } - - Config* config_; - ImportPipelineStatus* status_; -}; - -enum class ShouldParse { Yes, No, NoSuchFile }; - -// Checks if |path| needs to be reparsed. This will modify cached state -// such that calling this function twice with the same path may return true -// the first time but will return false the second. -// -// |from|: The file which generated the parse request for this file. -ShouldParse FileNeedsParse( - bool is_interactive, - TimestampManager* timestamp_manager, - IModificationTimestampFetcher* modification_timestamp_fetcher, - ImportManager* import_manager, - const std::shared_ptr& cache_manager, - IndexFile* opt_previous_index, - const std::string& path, - const std::vector& args, - const optional& from) { - auto unwrap_opt = [](const optional& opt) -> std::string { - if (opt) - return " (via " + *opt + ")"; - return ""; - }; - - // If the file is a dependency but another file as already imported it, - // don't bother. - if (!is_interactive && from && - !import_manager->TryMarkDependencyImported(path)) { - return ShouldParse::No; - } - - optional modification_timestamp = - modification_timestamp_fetcher->GetModificationTime(path); - - // Cannot find file. - if (!modification_timestamp) - return ShouldParse::NoSuchFile; - - optional last_cached_modification = - timestamp_manager->GetLastCachedModificationTime(cache_manager.get(), - path); - - // File has been changed. - if (!last_cached_modification || - modification_timestamp != *last_cached_modification) { - LOG_S(INFO) << "Timestamp has changed for " << path << unwrap_opt(from); - return ShouldParse::Yes; - } - - // Command-line arguments changed. - auto is_file = [](const std::string& arg) { - return EndsWithAny(arg, {".h", ".c", ".cc", ".cpp", ".hpp", ".m", ".mm"}); - }; - if (opt_previous_index) { - auto& prev_args = opt_previous_index->args; - bool same = prev_args.size() == args.size(); - for (size_t i = 0; i < args.size() && same; ++i) { - same = prev_args[i] == args[i] || - (is_file(prev_args[i]) && is_file(args[i])); - } - if (!same) { - LOG_S(INFO) << "Arguments have changed for " << path << unwrap_opt(from); - return ShouldParse::Yes; - } - } - - // File has not changed, do not parse it. - return ShouldParse::No; -}; - -enum CacheLoadResult { Parse, DoNotParse }; -CacheLoadResult TryLoadFromCache( - FileConsumerSharedState* file_consumer_shared, - TimestampManager* timestamp_manager, - IModificationTimestampFetcher* modification_timestamp_fetcher, - ImportManager* import_manager, - const std::shared_ptr& cache_manager, - bool is_interactive, - const Project::Entry& entry, - const std::string& path_to_index) { - // Always run this block, even if we are interactive, so we can check - // dependencies and reset files in |file_consumer_shared|. - IndexFile* previous_index = cache_manager->TryLoad(path_to_index); - if (!previous_index) - return CacheLoadResult::Parse; - - // If none of the dependencies have changed and the index is not - // interactive (ie, requested by a file save), skip parsing and just load - // from cache. - - // Check timestamps and update |file_consumer_shared|. - ShouldParse path_state = FileNeedsParse( - is_interactive, timestamp_manager, modification_timestamp_fetcher, - import_manager, cache_manager, previous_index, path_to_index, entry.args, - nullopt); - if (path_state == ShouldParse::Yes) - file_consumer_shared->Reset(path_to_index); - - // Target file does not exist on disk, do not emit any indexes. - // TODO: Dependencies should be reassigned to other files. We can do this by - // updating the "primary_file" if it doesn't exist. Might not actually be a - // problem in practice. - if (path_state == ShouldParse::NoSuchFile) - return CacheLoadResult::DoNotParse; - - bool needs_reparse = is_interactive || path_state == ShouldParse::Yes; - - for (const std::string& dependency : previous_index->dependencies) { - assert(!dependency.empty()); - - if (FileNeedsParse(is_interactive, timestamp_manager, - modification_timestamp_fetcher, import_manager, - cache_manager, previous_index, dependency, entry.args, - previous_index->path) == ShouldParse::Yes) { - needs_reparse = true; - - // Do not break here, as we need to update |file_consumer_shared| for - // every dependency that needs to be reparsed. - file_consumer_shared->Reset(dependency); - } - } - - // FIXME: should we still load from cache? - if (needs_reparse) - return CacheLoadResult::Parse; - - // No timestamps changed - load directly from cache. - LOG_S(INFO) << "Skipping parse; no timestamp change for " << path_to_index; - - // TODO/FIXME: real perf - PerformanceImportFile perf; - - std::vector result; - result.push_back(Index_DoIdMap(cache_manager->TakeOrLoad(path_to_index), - cache_manager, perf, is_interactive, - false /*write_to_disk*/)); - for (const std::string& dependency : previous_index->dependencies) { - // Only load a dependency if it is not already loaded. - // - // This is important for perf in large projects where there are lots of - // dependencies shared between many files. - if (!file_consumer_shared->Mark(dependency)) - continue; - - LOG_S(INFO) << "Emitting index result for " << dependency << " (via " - << previous_index->path << ")"; - - std::unique_ptr dependency_index = - cache_manager->TryTakeOrLoad(dependency); - - // |dependency_index| may be null if there is no cache for it but - // another file has already started importing it. - if (!dependency_index) - continue; - - result.push_back(Index_DoIdMap(std::move(dependency_index), cache_manager, - perf, is_interactive, - false /*write_to_disk*/)); - } - - QueueManager::instance()->do_id_map.EnqueueAll(std::move(result)); - return CacheLoadResult::DoNotParse; -} - -std::vector PreloadFileContents( - const std::shared_ptr& cache_manager, - const Project::Entry& entry, - const std::string& entry_contents, - const std::string& path_to_index) { - // Load file contents for all dependencies into memory. If the dependencies - // for the file changed we may not end up using all of the files we - // preloaded. If a new dependency was added the indexer will grab the file - // contents as soon as possible. - // - // We do this to minimize the race between indexing a file and capturing the - // file contents. - // - // TODO: We might be able to optimize perf by only copying for files in - // working_files. We can pass that same set of files to the indexer as - // well. We then default to a fast file-copy if not in working set. - - // index->file_contents comes from cache, so we need to check if that cache is - // still valid. if so, we can use it, otherwise we need to load from disk. - auto get_latest_content = [](const std::string& path, int64_t cached_time, - const std::string& cached) -> std::string { - optional mod_time = GetLastModificationTime(path); - if (!mod_time) - return ""; - - if (*mod_time == cached_time) - return cached; - - optional fresh_content = ReadContent(path); - if (!fresh_content) { - LOG_S(ERROR) << "Failed to load content for " << path; - return ""; - } - return *fresh_content; - }; - - std::vector file_contents; - file_contents.push_back(FileContents(entry.filename, entry_contents)); - cache_manager->IterateLoadedCaches([&](IndexFile* index) { - if (index->path == entry.filename) - return; - file_contents.push_back(FileContents( - index->path, - get_latest_content(index->path, index->last_modification_time, - index->file_contents))); - }); - - return file_contents; -} - -void ParseFile(Config* config, - DiagnosticsEngine* diag_engine, - WorkingFiles* working_files, - FileConsumerSharedState* file_consumer_shared, - TimestampManager* timestamp_manager, - IModificationTimestampFetcher* modification_timestamp_fetcher, - ImportManager* import_manager, - IIndexer* indexer, - const Index_Request& request, - const Project::Entry& entry) { - // If the file is inferred, we may not actually be able to parse that file - // directly (ie, a header file, which are not listed in the project). If this - // file is inferred, then try to use the file which originally imported it. - std::string path_to_index = entry.filename; - if (entry.is_inferred) { - IndexFile* entry_cache = request.cache_manager->TryLoad(entry.filename); - if (entry_cache) - path_to_index = entry_cache->import_file; - } - - // Try to load the file from cache. - if (TryLoadFromCache(file_consumer_shared, timestamp_manager, - modification_timestamp_fetcher, import_manager, - request.cache_manager, request.is_interactive, entry, - path_to_index) == CacheLoadResult::DoNotParse) { - return; - } - - LOG_S(INFO) << "Parsing " << path_to_index; - std::vector file_contents = PreloadFileContents( - request.cache_manager, entry, request.contents, path_to_index); - - std::vector result; - PerformanceImportFile perf; - auto indexes = indexer->Index(config, file_consumer_shared, path_to_index, - entry.args, file_contents, &perf); - - if (!indexes) { - if (config->index.enabled && - !std::holds_alternative(request.id)) { - Out_Error out; - out.id = request.id; - out.error.code = lsErrorCodes::InternalError; - out.error.message = "Failed to index " + path_to_index; - QueueManager::WriteStdout(IpcId::Unknown, out); - } - return; - } - - for (std::unique_ptr& new_index : *indexes) { - Timer time; - - // Only emit diagnostics for non-interactive sessions, which makes it easier - // to identify indexing problems. For interactive sessions, diagnostics are - // handled by code completion. - if (!request.is_interactive) - diag_engine->Publish(working_files, new_index->path, - new_index->diagnostics_); - - // When main thread does IdMap request it will request the previous index if - // needed. - LOG_S(INFO) << "Emitting index result for " << new_index->path; - result.push_back(Index_DoIdMap(std::move(new_index), request.cache_manager, - perf, request.is_interactive, - true /*write_to_disk*/)); - } - - QueueManager::instance()->do_id_map.EnqueueAll(std::move(result), - request.is_interactive); -} - -bool IndexMain_DoParse( - Config* config, - DiagnosticsEngine* diag_engine, - WorkingFiles* working_files, - FileConsumerSharedState* file_consumer_shared, - TimestampManager* timestamp_manager, - IModificationTimestampFetcher* modification_timestamp_fetcher, - ImportManager* import_manager, - IIndexer* indexer) { - auto* queue = QueueManager::instance(); - optional request = queue->index_request.TryPopFront(); - if (!request) - return false; - - Project::Entry entry; - entry.filename = request->path; - entry.args = request->args; - ParseFile(config, diag_engine, working_files, file_consumer_shared, - timestamp_manager, modification_timestamp_fetcher, import_manager, - indexer, request.value(), entry); - return true; -} - -bool IndexMain_DoCreateIndexUpdate(TimestampManager* timestamp_manager) { - auto* queue = QueueManager::instance(); - optional response = queue->on_id_mapped.TryPopFront(); - if (!response) - return false; - - Timer time; - - IdMap* previous_id_map = nullptr; - IndexFile* previous_index = nullptr; - if (response->previous) { - previous_id_map = response->previous->ids.get(); - previous_index = response->previous->file.get(); - } - - // Build delta update. - IndexUpdate update = - IndexUpdate::CreateDelta(previous_id_map, response->current->ids.get(), - previous_index, response->current->file.get()); - response->perf.index_make_delta = time.ElapsedMicrosecondsAndReset(); - LOG_S(INFO) << "Built index update for " << response->current->file->path - << " (is_delta=" << !!response->previous << ")"; - - // Write current index to disk if requested. - if (response->write_to_disk) { - LOG_S(INFO) << "Writing cached index to disk for " - << response->current->file->path; - time.Reset(); - response->cache_manager->WriteToCache(*response->current->file); - response->perf.index_save_to_disk = time.ElapsedMicrosecondsAndReset(); - timestamp_manager->UpdateCachedModificationTime( - response->current->file->path, - response->current->file->last_modification_time); - } - -#if false -#define PRINT_SECTION(name) \ - if (response->perf.name) { \ - total += response->perf.name; \ - output << " " << #name << ": " << FormatMicroseconds(response->perf.name); \ - } - std::stringstream output; - long long total = 0; - output << "[perf]"; - PRINT_SECTION(index_parse); - PRINT_SECTION(index_build); - PRINT_SECTION(index_save_to_disk); - PRINT_SECTION(index_load_cached); - PRINT_SECTION(querydb_id_map); - PRINT_SECTION(index_make_delta); - output << "\n total: " << FormatMicroseconds(total); - output << " path: " << response->current_index->path; - LOG_S(INFO) << output.rdbuf(); -#undef PRINT_SECTION - - if (response->is_interactive) - LOG_S(INFO) << "Applying IndexUpdate" << std::endl << update.ToString(); -#endif - - Index_OnIndexed reply(std::move(update), response->perf); - queue->on_indexed.PushBack(std::move(reply), response->is_interactive); - - return true; -} - -bool IndexMain_LoadPreviousIndex() { - auto* queue = QueueManager::instance(); - optional response = queue->load_previous_index.TryPopFront(); - if (!response) - return false; - - response->previous = - response->cache_manager->TryTakeOrLoad(response->current->path); - LOG_IF_S(ERROR, !response->previous) - << "Unable to load previous index for already imported index " - << response->current->path; - - queue->do_id_map.PushBack(std::move(*response)); - return true; -} - -bool IndexMergeIndexUpdates() { - auto* queue = QueueManager::instance(); - optional root = queue->on_indexed.TryPopBack(); - if (!root) - return false; - - bool did_merge = false; - while (true) { - optional to_join = queue->on_indexed.TryPopBack(); - if (!to_join) { - queue->on_indexed.PushFront(std::move(*root)); - return did_merge; - } - - did_merge = true; - Timer time; - root->update.Merge(std::move(to_join->update)); - // time.ResetAndPrint("Joined querydb updates for files: " + - // StringJoinMap(root->update.files_def_update, - //[](const QueryFile::DefUpdate& update) { - // return update.path; - //})); - } -} - -} // namespace - -ImportPipelineStatus::ImportPipelineStatus() - : num_active_threads(0), next_progress_output(0) {} - -// Index a file using an already-parsed translation unit from code completion. -// Since most of the time for indexing a file comes from parsing, we can do -// real-time indexing. -// TODO: add option to disable this. -void IndexWithTuFromCodeCompletion( - Config* config, - FileConsumerSharedState* file_consumer_shared, - ClangTranslationUnit* tu, - const std::vector& file_contents, - const std::string& path, - const std::vector& args) { - file_consumer_shared->Reset(path); - - PerformanceImportFile perf; - ClangIndex index; - auto indexes = ParseWithTu(config, file_consumer_shared, &perf, tu, &index, - path, args, file_contents); - if (!indexes) - return; - - std::vector result; - for (std::unique_ptr& new_index : *indexes) { - Timer time; - - std::shared_ptr cache_manager; - assert(false && "FIXME cache_manager"); - // When main thread does IdMap request it will request the previous index if - // needed. - LOG_S(INFO) << "Emitting index result for " << new_index->path; - result.push_back(Index_DoIdMap(std::move(new_index), cache_manager, perf, - true /*is_interactive*/, - true /*write_to_disk*/)); - } - - LOG_IF_S(WARNING, result.size() > 1) - << "Code completion index update generated more than one index"; - - QueueManager::instance()->do_id_map.EnqueueAll(std::move(result)); -} - -void Indexer_Main(Config* config, - DiagnosticsEngine* diag_engine, - FileConsumerSharedState* file_consumer_shared, - TimestampManager* timestamp_manager, - ImportManager* import_manager, - ImportPipelineStatus* status, - Project* project, - WorkingFiles* working_files, - MultiQueueWaiter* waiter) { - RealModificationTimestampFetcher modification_timestamp_fetcher; - auto* queue = QueueManager::instance(); - // Build one index per-indexer, as building the index acquires a global lock. - auto indexer = IIndexer::MakeClangIndexer(); - - while (true) { - bool did_work = false; - - { - ActiveThread active_thread(config, status); - - // TODO: process all off IndexMain_DoIndex before calling - // IndexMain_DoCreateIndexUpdate for better icache behavior. We need to - // have some threads spinning on both though otherwise memory usage will - // get bad. - - // We need to make sure to run both IndexMain_DoParse and - // IndexMain_DoCreateIndexUpdate so we don't starve querydb from doing any - // work. Running both also lets the user query the partially constructed - // index. - did_work = IndexMain_DoParse(config, diag_engine, working_files, - file_consumer_shared, timestamp_manager, - &modification_timestamp_fetcher, - import_manager, indexer.get()) || - did_work; - - did_work = IndexMain_DoCreateIndexUpdate(timestamp_manager) || did_work; - - did_work = IndexMain_LoadPreviousIndex() || did_work; - - // Nothing to index and no index updates to create, so join some already - // created index updates to reduce work on querydb thread. - if (!did_work) - did_work = IndexMergeIndexUpdates() || did_work; - } - - // We didn't do any work, so wait for a notification. - if (!did_work) { - waiter->Wait(&queue->on_indexed, &queue->index_request, - &queue->on_id_mapped, &queue->load_previous_index); - } - } -} - -namespace { -void QueryDb_DoIdMap(QueueManager* queue, - QueryDatabase* db, - ImportManager* import_manager, - Index_DoIdMap* request) { - assert(request->current); - - // If the request does not have previous state and we have already imported - // it, load the previous state from disk and rerun IdMap logic later. Do not - // do this if we have already attempted in the past. - if (!request->load_previous && !request->previous && - db->usr_to_file.find(NormalizedPath(request->current->path)) != - db->usr_to_file.end()) { - assert(!request->load_previous); - request->load_previous = true; - queue->load_previous_index.PushBack(std::move(*request)); - return; - } - - // Check if the file is already being imported into querydb. If it is, drop - // the request. - // - // Note, we must do this *after* we have checked for the previous index, - // otherwise we will never actually generate the IdMap. - if (!import_manager->StartQueryDbImport(request->current->path)) { - LOG_S(INFO) << "Dropping index as it is already being imported for " - << request->current->path; - return; - } - - Index_OnIdMapped response(request->cache_manager, request->perf, - request->is_interactive, request->write_to_disk); - Timer time; - - auto make_map = [db](std::unique_ptr file) - -> std::unique_ptr { - if (!file) - return nullptr; - - auto id_map = std::make_unique(db, file->id_cache); - return std::make_unique(std::move(file), - std::move(id_map)); - }; - response.current = make_map(std::move(request->current)); - response.previous = make_map(std::move(request->previous)); - response.perf.querydb_id_map = time.ElapsedMicrosecondsAndReset(); - - queue->on_id_mapped.PushBack(std::move(response)); -} - -void QueryDb_OnIndexed(QueueManager* queue, - QueryDatabase* db, - ImportManager* import_manager, - ImportPipelineStatus* status, - SemanticHighlightSymbolCache* semantic_cache, - WorkingFiles* working_files, - Index_OnIndexed* response) { - Timer time; - db->ApplyIndexUpdate(&response->update); - time.ResetAndPrint("Applying index update for " + - StringJoinMap(response->update.files_def_update, - [](const QueryFile::DefUpdate& value) { - return value.value.path; - })); - - // Update indexed content, inactive lines, and semantic highlighting. - for (auto& updated_file : response->update.files_def_update) { - WorkingFile* working_file = - working_files->GetFileByFilename(updated_file.value.path); - if (working_file) { - // Update indexed content. - working_file->SetIndexContent(updated_file.file_content); - - // Inactive lines. - EmitInactiveLines(working_file, updated_file.value.inactive_regions); - - // Semantic highlighting. - QueryFileId file_id = - db->usr_to_file[NormalizedPath(working_file->filename)]; - QueryFile* file = &db->files[file_id.id]; - EmitSemanticHighlighting(db, semantic_cache, working_file, file); - } - - // Mark the files as being done in querydb stage after we apply the index - // update. - import_manager->DoneQueryDbImport(updated_file.value.path); - } -} -} // namespace - -bool QueryDb_ImportMain(Config* config, - QueryDatabase* db, - ImportManager* import_manager, - ImportPipelineStatus* status, - SemanticHighlightSymbolCache* semantic_cache, - WorkingFiles* working_files) { - auto* queue = QueueManager::instance(); - - ActiveThread active_thread(config, status); - - bool did_work = false; - - while (true) { - optional request = queue->do_id_map.TryPopFront(); - if (!request) - break; - did_work = true; - QueryDb_DoIdMap(queue, db, import_manager, &*request); - } - - while (true) { - optional response = queue->on_indexed.TryPopFront(); - if (!response) - break; - did_work = true; - QueryDb_OnIndexed(queue, db, import_manager, status, semantic_cache, - working_files, &*response); - } - - return did_work; -} - -TEST_SUITE("ImportPipeline") { - struct Fixture { - Fixture() { - QueueManager::Init(&querydb_waiter, &indexer_waiter, &stdout_waiter); - - queue = QueueManager::instance(); - cache_manager = ICacheManager::MakeFake({}); - indexer = IIndexer::MakeTestIndexer({}); - diag_engine.Init(&config); - } - - bool PumpOnce() { - return IndexMain_DoParse(&config, &diag_engine, &working_files, - &file_consumer_shared, ×tamp_manager, - &modification_timestamp_fetcher, &import_manager, - indexer.get()); - } - - void MakeRequest(const std::string& path, - const std::vector& args = {}, - bool is_interactive = false, - const std::string& contents = "void foo();") { - queue->index_request.PushBack( - Index_Request(path, args, is_interactive, contents, cache_manager)); - } - - MultiQueueWaiter querydb_waiter; - MultiQueueWaiter indexer_waiter; - MultiQueueWaiter stdout_waiter; - - QueueManager* queue = nullptr; - Config config; - DiagnosticsEngine diag_engine; - WorkingFiles working_files; - FileConsumerSharedState file_consumer_shared; - TimestampManager timestamp_manager; - FakeModificationTimestampFetcher modification_timestamp_fetcher; - ImportManager import_manager; - std::shared_ptr cache_manager; - std::unique_ptr indexer; - }; - - TEST_CASE_FIXTURE(Fixture, "FileNeedsParse") { - auto check = [&](const std::string& file, bool is_dependency = false, - bool is_interactive = false, - const std::vector& old_args = {}, - const std::vector& new_args = {}) { - std::unique_ptr opt_previous_index; - if (!old_args.empty()) { - opt_previous_index = std::make_unique("---.cc", ""); - opt_previous_index->args = old_args; - } - optional from; - if (is_dependency) - from = std::string("---.cc"); - return FileNeedsParse(is_interactive /*is_interactive*/, - ×tamp_manager, &modification_timestamp_fetcher, - &import_manager, cache_manager, - opt_previous_index.get(), file, new_args, from); - }; - - // A file with no timestamp is not imported, since this implies the file no - // longer exists on disk. - modification_timestamp_fetcher.entries["bar.h"] = nullopt; - REQUIRE(check("bar.h", false /*is_dependency*/) == ShouldParse::NoSuchFile); - - // A dependency is only imported once. - modification_timestamp_fetcher.entries["foo.h"] = 5; - REQUIRE(check("foo.h", true /*is_dependency*/) == ShouldParse::Yes); - REQUIRE(check("foo.h", true /*is_dependency*/) == ShouldParse::No); - - // An interactive dependency is imported. - REQUIRE(check("foo.h", true /*is_dependency*/) == ShouldParse::No); - REQUIRE(check("foo.h", true /*is_dependency*/, true /*is_interactive*/) == - ShouldParse::Yes); - - // A file whose timestamp has not changed is not imported. When the - // timestamp changes (either forward or backward) it is reimported. - auto check_timestamp_change = [&](int64_t timestamp) { - modification_timestamp_fetcher.entries["aa.cc"] = timestamp; - REQUIRE(check("aa.cc") == ShouldParse::Yes); - REQUIRE(check("aa.cc") == ShouldParse::Yes); - REQUIRE(check("aa.cc") == ShouldParse::Yes); - timestamp_manager.UpdateCachedModificationTime("aa.cc", timestamp); - REQUIRE(check("aa.cc") == ShouldParse::No); - }; - check_timestamp_change(5); - check_timestamp_change(6); - check_timestamp_change(5); - check_timestamp_change(4); - - // Argument change implies reimport, even if timestamp has not changed. - timestamp_manager.UpdateCachedModificationTime("aa.cc", 5); - modification_timestamp_fetcher.entries["aa.cc"] = 5; - REQUIRE(check("aa.cc", false /*is_dependency*/, false /*is_interactive*/, - {"b"} /*old_args*/, - {"b", "a"} /*new_args*/) == ShouldParse::Yes); - } - - // FIXME: validate other state like timestamp_manager, etc. - // FIXME: add more interesting tests that are not the happy path - // FIXME: test - // - IndexMain_DoCreateIndexUpdate - // - IndexMain_LoadPreviousIndex - // - QueryDb_ImportMain - - TEST_CASE_FIXTURE(Fixture, "index request with zero results") { - indexer = IIndexer::MakeTestIndexer({IIndexer::TestEntry{"foo.cc", 0}}); - - MakeRequest("foo.cc"); - - REQUIRE(queue->index_request.Size() == 1); - REQUIRE(queue->do_id_map.Size() == 0); - PumpOnce(); - REQUIRE(queue->index_request.Size() == 0); - REQUIRE(queue->do_id_map.Size() == 0); - - REQUIRE(file_consumer_shared.used_files.empty()); - } - - TEST_CASE_FIXTURE(Fixture, "one index request") { - indexer = IIndexer::MakeTestIndexer({IIndexer::TestEntry{"foo.cc", 100}}); - - MakeRequest("foo.cc"); - - REQUIRE(queue->index_request.Size() == 1); - REQUIRE(queue->do_id_map.Size() == 0); - PumpOnce(); - REQUIRE(queue->index_request.Size() == 0); - REQUIRE(queue->do_id_map.Size() == 100); - - REQUIRE(file_consumer_shared.used_files.empty()); - } - - TEST_CASE_FIXTURE(Fixture, "multiple index requests") { - indexer = IIndexer::MakeTestIndexer( - {IIndexer::TestEntry{"foo.cc", 100}, IIndexer::TestEntry{"bar.cc", 5}}); - - MakeRequest("foo.cc"); - MakeRequest("bar.cc"); - - REQUIRE(queue->index_request.Size() == 2); - REQUIRE(queue->do_id_map.Size() == 0); - while (PumpOnce()) { - } - REQUIRE(queue->index_request.Size() == 0); - REQUIRE(queue->do_id_map.Size() == 105); - - REQUIRE(file_consumer_shared.used_files.empty()); - } -} diff --git a/src/import_pipeline.h b/src/import_pipeline.h deleted file mode 100644 index 7ede6ae63..000000000 --- a/src/import_pipeline.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -// FIXME: do not include clang-c outside of clang_ files. -#include - -#include -#include -#include -#include - -struct ClangTranslationUnit; -struct Config; -class DiagnosticsEngine; -struct FileConsumerSharedState; -struct ImportManager; -struct MultiQueueWaiter; -struct Project; -struct QueryDatabase; -struct SemanticHighlightSymbolCache; -struct TimestampManager; -struct WorkingFiles; - -struct ImportPipelineStatus { - std::atomic num_active_threads; - std::atomic next_progress_output; - - ImportPipelineStatus(); -}; - -void IndexWithTuFromCodeCompletion( - Config* config, - FileConsumerSharedState* file_consumer_shared, - ClangTranslationUnit* tu, - const std::vector& file_contents, - const std::string& path, - const std::vector& args); - -void Indexer_Main(Config* config, - DiagnosticsEngine* diag_engine, - FileConsumerSharedState* file_consumer_shared, - TimestampManager* timestamp_manager, - ImportManager* import_manager, - ImportPipelineStatus* status, - Project* project, - WorkingFiles* working_files, - MultiQueueWaiter* waiter); - -bool QueryDb_ImportMain(Config* config, - QueryDatabase* db, - ImportManager* import_manager, - ImportPipelineStatus* status, - SemanticHighlightSymbolCache* semantic_cache, - WorkingFiles* working_files); diff --git a/src/include_complete.cc b/src/include_complete.cc index 9f4476c02..05439ad4e 100644 --- a/src/include_complete.cc +++ b/src/include_complete.cc @@ -1,148 +1,158 @@ -#include "include_complete.h" +/* Copyright 2017-2018 ccls Authors -#include "match.h" -#include "platform.h" -#include "project.h" -#include "standard_includes.h" -#include "timer.h" -#include "work_thread.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "include_complete.hh" + +#include "filesystem.hh" +#include "platform.hh" +#include "project.hh" + +#include +#include +#include + +#include +using namespace llvm; #include +namespace ccls { namespace { struct CompletionCandidate { std::string absolute_path; - lsCompletionItem completion_item; + CompletionItem completion_item; }; -std::string ElideLongPath(Config* config, const std::string& path) { - if (config->completion.includeMaxPathSize <= 0) - return path; - - if ((int)path.size() <= config->completion.includeMaxPathSize) +std::string ElideLongPath(const std::string &path) { + if (g_config->completion.include.maxPathSize <= 0 || + (int)path.size() <= g_config->completion.include.maxPathSize) return path; - size_t start = path.size() - config->completion.includeMaxPathSize; + size_t start = path.size() - g_config->completion.include.maxPathSize; return ".." + path.substr(start + 2); } -size_t TrimCommonPathPrefix(const std::string& result, - const std::string& trimmer) { - size_t i = 0; - while (i < result.size() && i < trimmer.size()) { - char a = result[i]; - char b = trimmer[i]; -#if defined(_WIN32) - a = (char)tolower(a); - b = (char)tolower(b); +size_t TrimCommonPathPrefix(const std::string &result, + const std::string &trimmer) { +#ifdef _WIN32 + std::string s = result, t = trimmer; + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + std::transform(t.begin(), t.end(), t.begin(), ::tolower); + if (s.compare(0, t.size(), t) == 0) + return t.size(); +#else + if (result.compare(0, trimmer.size(), trimmer) == 0) + return trimmer.size(); #endif - if (a != b) - break; - ++i; - } - - if (i == trimmer.size()) - return i; return 0; } // Returns true iff angle brackets should be used. -bool TrimPath(Project* project, - const std::string& project_root, - std::string* insert_path) { - size_t start = 0; +bool TrimPath(Project *project, std::string &path) { + size_t pos = 0; bool angle = false; - - size_t len = TrimCommonPathPrefix(*insert_path, project_root); - if (len > start) - start = len; - - for (auto& include_dir : project->quote_include_directories) { - len = TrimCommonPathPrefix(*insert_path, include_dir); - if (len > start) - start = len; - } - - for (auto& include_dir : project->angle_include_directories) { - len = TrimCommonPathPrefix(*insert_path, include_dir); - if (len > start) { - start = len; + for (auto &[root, folder] : project->root2folder) { + size_t pos1 = 0; + for (auto &search : folder.angle_search_list) + pos1 = std::max(pos1, TrimCommonPathPrefix(path, search)); + if (pos1 > pos) { + pos = pos1; angle = true; } - } - *insert_path = insert_path->substr(start); + pos1 = TrimCommonPathPrefix(path, root); + for (auto &search : folder.quote_search_list) + pos1 = std::max(pos1, TrimCommonPathPrefix(path, search)); + if (pos1 > pos) { + pos = pos1; + angle = false; + } + } + path = path.substr(pos); return angle; } -lsCompletionItem BuildCompletionItem(Config* config, - const std::string& path, - bool use_angle_brackets, - bool is_stl) { - lsCompletionItem item; - item.label = ElideLongPath(config, path); - item.detail = path; // the include path, used in de-duplicating - item.textEdit = lsTextEdit(); - item.textEdit->newText = path; - item.insertTextFormat = lsInsertTextFormat::PlainText; +CompletionItem BuildCompletionItem(const std::string &path, + bool use_angle_brackets) { + CompletionItem item; + item.label = ElideLongPath(path); + item.detail = path; // the include path, used in de-duplicating + item.textEdit.newText = path; + item.insertTextFormat = InsertTextFormat::PlainText; item.use_angle_brackets_ = use_angle_brackets; - if (is_stl) { - item.kind = lsCompletionItemKind::Module; - item.priority_ = 2; - } else { - item.kind = lsCompletionItemKind::File; - item.priority_ = 1; - } + item.kind = CompletionItemKind::File; + item.priority_ = 0; return item; } +} // namespace -} // namespace +IncludeComplete::IncludeComplete(Project *project) + : is_scanning(false), project_(project) {} -IncludeComplete::IncludeComplete(Config* config, Project* project) - : is_scanning(false), config_(config), project_(project) {} +IncludeComplete::~IncludeComplete() { + // Spin until the scanning has completed. + while (is_scanning.load()) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} void IncludeComplete::Rescan() { - if (is_scanning) + if (is_scanning || LLVM_VERSION_MAJOR >= 8) return; completion_items.clear(); absolute_path_to_completion_item.clear(); inserted_paths.clear(); - if (!match_ && (!config_->completion.includeWhitelist.empty() || - !config_->completion.includeBlacklist.empty())) - match_ = std::make_unique(config_->completion.includeWhitelist, - config_->completion.includeBlacklist); + if (!match_ && (g_config->completion.include.whitelist.size() || + g_config->completion.include.blacklist.size())) + match_ = + std::make_unique(g_config->completion.include.whitelist, + g_config->completion.include.blacklist); is_scanning = true; - WorkThread::StartThread("scan_includes", [this]() { - Timer timer; - - InsertStlIncludes(); - InsertIncludesFromDirectory(config_->projectRoot, - false /*use_angle_brackets*/); - for (const std::string& dir : project_->quote_include_directories) - InsertIncludesFromDirectory(dir, false /*use_angle_brackets*/); - for (const std::string& dir : project_->angle_include_directories) - InsertIncludesFromDirectory(dir, true /*use_angle_brackets*/); - - timer.ResetAndPrint("[perf] Scanning for includes"); + std::thread([this]() { + set_thread_name("include"); + std::unordered_set angle_set, quote_set; + for (auto &[root, folder] : project_->root2folder) { + for (const std::string &search : folder.angle_search_list) + if (angle_set.insert(search).second) + InsertIncludesFromDirectory(search, true); + for (const std::string &search : folder.quote_search_list) + if (quote_set.insert(search).second) + InsertIncludesFromDirectory(search, false); + } + is_scanning = false; - }); + }) + .detach(); } -void IncludeComplete::InsertCompletionItem(const std::string& absolute_path, - lsCompletionItem&& item) { +void IncludeComplete::InsertCompletionItem(const std::string &absolute_path, + CompletionItem &&item) { if (inserted_paths.insert({item.detail, inserted_paths.size()}).second) { completion_items.push_back(item); // insert if not found or with shorter include path auto it = absolute_path_to_completion_item.find(absolute_path); if (it == absolute_path_to_completion_item.end() || - completion_items[it->second].detail.length() > item.detail.length()) - absolute_path_to_completion_item[absolute_path] = completion_items.size(); + completion_items[it->second].detail.length() > item.detail.length()) { + absolute_path_to_completion_item[absolute_path] = + completion_items.size() - 1; + } } else { - lsCompletionItem& inserted_item = + CompletionItem &inserted_item = completion_items[inserted_paths[item.detail]]; // Update |use_angle_brackets_|, prefer quotes. if (!item.use_angle_brackets_) @@ -150,71 +160,68 @@ void IncludeComplete::InsertCompletionItem(const std::string& absolute_path, } } -void IncludeComplete::AddFile(const std::string& absolute_path) { - if (!EndsWithAny(absolute_path, config_->completion.includeSuffixWhitelist)) +void IncludeComplete::AddFile(const std::string &path) { + bool ok = false; + for (StringRef suffix : g_config->completion.include.suffixWhitelist) + if (StringRef(path).endswith(suffix)) + ok = true; + if (!ok) return; - if (match_ && !match_->IsMatch(absolute_path)) + if (match_ && !match_->Matches(path)) return; - std::string trimmed_path = absolute_path; - bool use_angle_brackets = - TrimPath(project_, config_->projectRoot, &trimmed_path); - lsCompletionItem item = BuildCompletionItem( - config_, trimmed_path, use_angle_brackets, false /*is_stl*/); + std::string trimmed_path = path; + bool use_angle_brackets = TrimPath(project_, trimmed_path); + CompletionItem item = BuildCompletionItem(trimmed_path, use_angle_brackets); std::unique_lock lock(completion_items_mutex, std::defer_lock); if (is_scanning) lock.lock(); - InsertCompletionItem(absolute_path, std::move(item)); - if (lock) - lock.unlock(); + InsertCompletionItem(path, std::move(item)); } void IncludeComplete::InsertIncludesFromDirectory(std::string directory, bool use_angle_brackets) { directory = NormalizePath(directory); EnsureEndsInSlash(directory); - if (match_ && !match_->IsMatch(directory)) { - // Don't even enter the directory if it fails the patterns. + if (match_ && !match_->Matches(directory)) return; - } + bool include_cpp = directory.find("include/c++") != std::string::npos; std::vector results; GetFilesInFolder( directory, true /*recursive*/, false /*add_folder_to_path*/, - [&](const std::string& path) { - if (!EndsWithAny(path, config_->completion.includeSuffixWhitelist)) + [&](const std::string &path) { + bool ok = include_cpp; + for (StringRef suffix : g_config->completion.include.suffixWhitelist) + if (StringRef(path).endswith(suffix)) + ok = true; + if (!ok) return; - if (match_ && !match_->IsMatch(directory + path)) + if (match_ && !match_->Matches(directory + path)) return; CompletionCandidate candidate; candidate.absolute_path = directory + path; - candidate.completion_item = BuildCompletionItem( - config_, path, use_angle_brackets, false /*is_stl*/); + candidate.completion_item = + BuildCompletionItem(path, use_angle_brackets); results.push_back(candidate); }); std::lock_guard lock(completion_items_mutex); - for (CompletionCandidate& result : results) + for (CompletionCandidate &result : results) InsertCompletionItem(result.absolute_path, std::move(result.completion_item)); } -void IncludeComplete::InsertStlIncludes() { - std::lock_guard lock(completion_items_mutex); - for (const char* stl_header : kStandardLibraryIncludes) { - completion_items.push_back(BuildCompletionItem( - config_, stl_header, true /*use_angle_brackets*/, true /*is_stl*/)); - } -} - -optional IncludeComplete::FindCompletionItemForAbsolutePath( - const std::string& absolute_path) { +std::optional +IncludeComplete::FindCompletionItemForAbsolutePath( + const std::string &absolute_path) { std::lock_guard lock(completion_items_mutex); auto it = absolute_path_to_completion_item.find(absolute_path); if (it == absolute_path_to_completion_item.end()) - return nullopt; + return std::nullopt; return completion_items[it->second]; } +} // namespace ccls diff --git a/src/include_complete.h b/src/include_complete.h deleted file mode 100644 index 9806cffe7..000000000 --- a/src/include_complete.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include "lsp_completion.h" - -#include -#include -#include - -struct GroupMatch; -struct Project; - -struct IncludeComplete { - IncludeComplete(Config* config, Project* project); - - // Starts scanning directories. Clears existing cache. - void Rescan(); - - // Ensures the one-off file is inside |completion_items|. - void AddFile(const std::string& absolute_path); - - // Scans the given directory and inserts all includes from this. This is a - // blocking function and should be run off the querydb thread. - void InsertIncludesFromDirectory(std::string directory, - bool use_angle_brackets); - void InsertStlIncludes(); - - optional FindCompletionItemForAbsolutePath( - const std::string& absolute_path); - - // Insert item to |completion_items|. - // Update |absolute_path_to_completion_item| and |inserted_paths|. - void InsertCompletionItem(const std::string& absolute_path, - lsCompletionItem&& item); - - // Guards |completion_items| when |is_scanning| is true. - std::mutex completion_items_mutex; - std::atomic is_scanning; - std::vector completion_items; - - // Absolute file path to the completion item in |completion_items|. - // Keep the one with shortest include path. - std::unordered_map absolute_path_to_completion_item; - - // Only one completion item per include path. - std::unordered_map inserted_paths; - - // Cached references - Config* config_; - Project* project_; - std::unique_ptr match_; -}; diff --git a/src/include_complete.hh b/src/include_complete.hh new file mode 100644 index 000000000..003dc7efd --- /dev/null +++ b/src/include_complete.hh @@ -0,0 +1,66 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "message_handler.hh" + +#include +#include + +namespace ccls { +struct GroupMatch; +struct Project; + +struct IncludeComplete { + IncludeComplete(Project *project); + ~IncludeComplete(); + + // Starts scanning directories. Clears existing cache. + void Rescan(); + + // Ensures the one-off file is inside |completion_items|. + void AddFile(const std::string &absolute_path); + + // Scans the given directory and inserts all includes from this. This is a + // blocking function and should be run off the querydb thread. + void InsertIncludesFromDirectory(std::string directory, + bool use_angle_brackets); + + std::optional + FindCompletionItemForAbsolutePath(const std::string &absolute_path); + + // Insert item to |completion_items|. + // Update |absolute_path_to_completion_item| and |inserted_paths|. + void InsertCompletionItem(const std::string &absolute_path, + ccls::CompletionItem &&item); + + // Guards |completion_items| when |is_scanning| is true. + std::mutex completion_items_mutex; + std::atomic is_scanning; + std::vector completion_items; + + // Absolute file path to the completion item in |completion_items|. + // Keep the one with shortest include path. + std::unordered_map absolute_path_to_completion_item; + + // Only one completion item per include path. + std::unordered_map inserted_paths; + + // Cached references + Project *project_; + std::unique_ptr match_; +}; +} // namespace ccls diff --git a/src/indexer.cc b/src/indexer.cc new file mode 100644 index 000000000..644834c75 --- /dev/null +++ b/src/indexer.cc @@ -0,0 +1,1681 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "indexer.hh" + +#include "clang_tu.hh" +#include "log.hh" +#include "pipeline.hh" +#include "platform.hh" +#include "sema_manager.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace clang; + +namespace ccls { +namespace { + +constexpr int kInitializerMaxLines = 3; + +GroupMatch *multiVersionMatcher; + +struct File { + std::string path; + int64_t mtime; + std::string content; + std::unique_ptr db; +}; + +struct IndexParam { + std::unordered_map UID2File; + std::unordered_map UID2multi; + struct DeclInfo { + Usr usr; + std::string short_name; + std::string qualified; + }; + std::unordered_map Decl2Info; + + VFS &vfs; + ASTContext *Ctx; + IndexParam(VFS &vfs) : vfs(vfs) {} + + void SeenFile(const FileEntry &File) { + // If this is the first time we have seen the file (ignoring if we are + // generating an index for it): + auto [it, inserted] = UID2File.try_emplace(File.getUniqueID()); + if (inserted) { + std::string path = PathFromFileEntry(File); + it->second.path = path; + it->second.mtime = File.getModificationTime(); + if (!it->second.mtime) + if (auto tim = LastWriteTime(path)) + it->second.mtime = *tim; + if (std::optional content = ReadContent(path)) + it->second.content = *content; + + if (!vfs.Stamp(path, it->second.mtime, 1)) + return; + it->second.db = std::make_unique(File.getUniqueID(), path, + it->second.content); + } + } + + IndexFile *ConsumeFile(const FileEntry &FE) { + SeenFile(FE); + return UID2File[FE.getUniqueID()].db.get(); + } + + bool UseMultiVersion(const FileEntry &FE) { + auto it = UID2multi.try_emplace(FE.getUniqueID()); + if (it.second) + it.first->second = multiVersionMatcher->Matches(PathFromFileEntry(FE)); + return it.first->second; + } +}; + +StringRef GetSourceInRange(const SourceManager &SM, const LangOptions &LangOpts, + SourceRange R) { + SourceLocation BLoc = R.getBegin(), ELoc = R.getEnd(); + std::pair BInfo = SM.getDecomposedLoc(BLoc), + EInfo = SM.getDecomposedLoc(ELoc); + bool invalid = false; + StringRef Buf = SM.getBufferData(BInfo.first, &invalid); + if (invalid) + return ""; + return Buf.substr(BInfo.second, + EInfo.second + + Lexer::MeasureTokenLength(ELoc, SM, LangOpts) - + BInfo.second); +} + +Kind GetKind(const Decl *D, SymbolKind &kind) { + switch (D->getKind()) { + case Decl::LinkageSpec: + return Kind::Invalid; + case Decl::Namespace: + kind = SymbolKind::Namespace; + return Kind::Type; + case Decl::NamespaceAlias: + kind = SymbolKind::TypeAlias; + return Kind::Type; + case Decl::ObjCCategory: + case Decl::ObjCImplementation: + case Decl::ObjCInterface: + case Decl::ObjCProtocol: + kind = SymbolKind::Interface; + return Kind::Type; + case Decl::ObjCMethod: + kind = SymbolKind::Method; + return Kind::Func; + case Decl::ObjCProperty: + kind = SymbolKind::Property; + return Kind::Type; + case Decl::ClassTemplate: + kind = SymbolKind::Class; + return Kind::Type; + case Decl::FunctionTemplate: + kind = SymbolKind::Function; + return Kind::Func; + case Decl::TypeAliasTemplate: + kind = SymbolKind::TypeAlias; + return Kind::Type; + case Decl::VarTemplate: + kind = SymbolKind::Variable; + return Kind::Var; + case Decl::TemplateTemplateParm: + kind = SymbolKind::TypeParameter; + return Kind::Type; + case Decl::Enum: + kind = SymbolKind::Enum; + return Kind::Type; + case Decl::CXXRecord: + case Decl::Record: + kind = SymbolKind::Class; + // spec has no Union, use Class + if (auto *RD = dyn_cast(D)) + if (RD->getTagKind() == TTK_Struct) + kind = SymbolKind::Struct; + return Kind::Type; + case Decl::ClassTemplateSpecialization: + case Decl::ClassTemplatePartialSpecialization: + kind = SymbolKind::Class; + return Kind::Type; + case Decl::TypeAlias: + case Decl::Typedef: + case Decl::UnresolvedUsingTypename: + kind = SymbolKind::TypeAlias; + return Kind::Type; + case Decl::Binding: + kind = SymbolKind::Variable; + return Kind::Var; + case Decl::Field: + case Decl::ObjCIvar: + kind = SymbolKind::Field; + return Kind::Var; + case Decl::Function: + kind = SymbolKind::Function; + return Kind::Func; + case Decl::CXXMethod: { + const auto *MD = cast(D); + kind = MD->isStatic() ? SymbolKind::StaticMethod : SymbolKind::Method; + return Kind::Func; + } + case Decl::CXXConstructor: + kind = SymbolKind::Constructor; + return Kind::Func; + case Decl::CXXConversion: + case Decl::CXXDestructor: + kind = SymbolKind::Method; + return Kind::Func; + case Decl::Var: + case Decl::Decomposition: + kind = SymbolKind::Variable; + return Kind::Var; + case Decl::ImplicitParam: + case Decl::ParmVar: + // ccls extension + kind = SymbolKind::Parameter; + return Kind::Var; + case Decl::VarTemplateSpecialization: + case Decl::VarTemplatePartialSpecialization: + kind = SymbolKind::Variable; + return Kind::Var; + case Decl::EnumConstant: + kind = SymbolKind::EnumMember; + return Kind::Var; + case Decl::UnresolvedUsingValue: + kind = SymbolKind::Variable; + return Kind::Var; + case Decl::TranslationUnit: + return Kind::Invalid; + + default: + LOG_S(INFO) << "unhandled " << int(D->getKind()); + return Kind::Invalid; + } +} + +LanguageId GetDeclLanguage(const Decl *D) { + switch (D->getKind()) { + default: + return LanguageId::C; + case Decl::ImplicitParam: + case Decl::ObjCAtDefsField: + case Decl::ObjCCategory: + case Decl::ObjCCategoryImpl: + case Decl::ObjCCompatibleAlias: + case Decl::ObjCImplementation: + case Decl::ObjCInterface: + case Decl::ObjCIvar: + case Decl::ObjCMethod: + case Decl::ObjCProperty: + case Decl::ObjCPropertyImpl: + case Decl::ObjCProtocol: + case Decl::ObjCTypeParam: + return LanguageId::ObjC; + case Decl::CXXConstructor: + case Decl::CXXConversion: + case Decl::CXXDestructor: + case Decl::CXXMethod: + case Decl::CXXRecord: + case Decl::ClassTemplate: + case Decl::ClassTemplatePartialSpecialization: + case Decl::ClassTemplateSpecialization: + case Decl::Friend: + case Decl::FriendTemplate: + case Decl::FunctionTemplate: + case Decl::LinkageSpec: + case Decl::Namespace: + case Decl::NamespaceAlias: + case Decl::NonTypeTemplateParm: + case Decl::StaticAssert: + case Decl::TemplateTemplateParm: + case Decl::TemplateTypeParm: + case Decl::UnresolvedUsingTypename: + case Decl::UnresolvedUsingValue: + case Decl::Using: + case Decl::UsingDirective: + case Decl::UsingShadow: + return LanguageId::Cpp; + } +} + +// clang/lib/AST/DeclPrinter.cpp +QualType GetBaseType(QualType T, bool deduce_auto) { + QualType BaseType = T; + while (!BaseType.isNull() && !BaseType->isSpecifierType()) { + if (const PointerType *PTy = BaseType->getAs()) + BaseType = PTy->getPointeeType(); + else if (const BlockPointerType *BPy = BaseType->getAs()) + BaseType = BPy->getPointeeType(); + else if (const ArrayType *ATy = dyn_cast(BaseType)) + BaseType = ATy->getElementType(); + else if (const VectorType *VTy = BaseType->getAs()) + BaseType = VTy->getElementType(); + else if (const ReferenceType *RTy = BaseType->getAs()) + BaseType = RTy->getPointeeType(); + else if (const ParenType *PTy = BaseType->getAs()) + BaseType = PTy->desugar(); + else if (deduce_auto) { + if (const AutoType *ATy = BaseType->getAs()) + BaseType = ATy->getDeducedType(); + else + break; + } else + break; + } + return BaseType; +} + +const Decl *GetTypeDecl(QualType T, bool *specialization = nullptr) { + Decl *D = nullptr; + T = GetBaseType(T.getUnqualifiedType(), true); + const Type *TP = T.getTypePtrOrNull(); + if (!TP) + return nullptr; + +try_again: + switch (TP->getTypeClass()) { + case Type::Typedef: + D = cast(TP)->getDecl(); + break; + case Type::ObjCObject: + D = cast(TP)->getInterface(); + break; + case Type::ObjCInterface: + D = cast(TP)->getDecl(); + break; + case Type::Record: + case Type::Enum: + D = cast(TP)->getDecl(); + break; + case Type::TemplateTypeParm: + D = cast(TP)->getDecl(); + break; + case Type::TemplateSpecialization: + if (specialization) + *specialization = true; + if (const RecordType *Record = TP->getAs()) + D = Record->getDecl(); + else + D = cast(TP) + ->getTemplateName() + .getAsTemplateDecl(); + break; + + case Type::Auto: + case Type::DeducedTemplateSpecialization: + TP = cast(TP)->getDeducedType().getTypePtrOrNull(); + if (TP) + goto try_again; + break; + + case Type::InjectedClassName: + D = cast(TP)->getDecl(); + break; + + // FIXME: Template type parameters! + + case Type::Elaborated: + TP = cast(TP)->getNamedType().getTypePtrOrNull(); + goto try_again; + + default: + break; + } + return D; +} + +const Decl *GetAdjustedDecl(const Decl *D) { + while (D) { + if (auto *R = dyn_cast(D)) { + if (auto *S = dyn_cast(R)) { + if (!S->getTypeAsWritten()) { + llvm::PointerUnion + Result = S->getSpecializedTemplateOrPartial(); + if (Result.is()) + D = Result.get(); + else + D = Result.get(); + continue; + } + } else if (auto *D1 = R->getInstantiatedFromMemberClass()) { + D = D1; + continue; + } + } else if (auto *ED = dyn_cast(D)) { + if (auto *D1 = ED->getInstantiatedFromMemberEnum()) { + D = D1; + continue; + } + } + break; + } + return D; +} + +bool ValidateRecord(const RecordDecl *RD) { + for (const auto *I : RD->fields()) { + QualType FQT = I->getType(); + if (FQT->isIncompleteType() || FQT->isDependentType()) + return false; + if (const RecordType *ChildType = I->getType()->getAs()) + if (const RecordDecl *Child = ChildType->getDecl()) + if (!ValidateRecord(Child)) + return false; + } + return true; +} + +class IndexDataConsumer : public index::IndexDataConsumer { +public: + ASTContext *Ctx; + IndexParam ¶m; + + std::string GetComment(const Decl *D) { + SourceManager &SM = Ctx->getSourceManager(); + const RawComment *RC = Ctx->getRawCommentForAnyRedecl(D); + if (!RC) + return ""; + StringRef Raw = RC->getRawText(Ctx->getSourceManager()); + SourceRange R = RC->getSourceRange(); + std::pair BInfo = SM.getDecomposedLoc(R.getBegin()); + unsigned start_column = SM.getLineNumber(BInfo.first, BInfo.second); + std::string ret; + int pad = -1; + for (const char *p = Raw.data(), *E = Raw.end(); p < E;) { + // The first line starts with a comment marker, but the rest needs + // un-indenting. + unsigned skip = start_column - 1; + for (; skip > 0 && p < E && (*p == ' ' || *p == '\t'); p++) + skip--; + const char *q = p; + while (q < E && *q != '\n') + q++; + if (q < E) + q++; + // A minimalist approach to skip Doxygen comment markers. + // See https://www.stack.nl/~dimitri/doxygen/manual/docblocks.html + if (pad < 0) { + // First line, detect the length of comment marker and put into |pad| + const char *begin = p; + while (p < E && (*p == '/' || *p == '*' || *p == '-' || *p == '=')) + p++; + if (p < E && (*p == '<' || *p == '!')) + p++; + if (p < E && *p == ' ') + p++; + if (p + 1 == q) + p++; + else + pad = int(p - begin); + } else { + // Other lines, skip |pad| bytes + int prefix = pad; + while (prefix > 0 && p < E && + (*p == ' ' || *p == '/' || *p == '*' || *p == '<' || *p == '!')) + prefix--, p++; + } + ret.insert(ret.end(), p, q); + p = q; + } + while (ret.size() && isspace(ret.back())) + ret.pop_back(); + if (StringRef(ret).endswith("*/")) + ret.resize(ret.size() - 2); + else if (StringRef(ret).endswith("\n/")) + ret.resize(ret.size() - 2); + while (ret.size() && isspace(ret.back())) + ret.pop_back(); + return ret; + } + + Usr GetUsr(const Decl *D, IndexParam::DeclInfo **info = nullptr) const { + D = D->getCanonicalDecl(); + auto [it, inserted] = param.Decl2Info.try_emplace(D); + if (inserted) { + SmallString<256> USR; + index::generateUSRForDecl(D, USR); + auto &info = it->second; + info.usr = HashUsr(USR); + if (auto *ND = dyn_cast(D)) { + info.short_name = ND->getNameAsString(); + info.qualified = ND->getQualifiedNameAsString(); + SimplifyAnonymous(info.qualified); + } + } + if (info) + *info = &it->second; + return it->second.usr; + } + + PrintingPolicy GetDefaultPolicy() const { + PrintingPolicy PP(Ctx->getLangOpts()); + PP.AnonymousTagLocations = false; + PP.TerseOutput = true; + PP.PolishForDeclaration = true; + PP.ConstantsAsWritten = true; + PP.SuppressTagKeyword = true; + PP.SuppressInitializers = true; + PP.FullyQualifiedName = false; + return PP; + } + + static void SimplifyAnonymous(std::string &name) { + for (std::string::size_type i = 0;;) { + if ((i = name.find("(anonymous ", i)) == std::string::npos) + break; + i++; + if (name.size() - i > 19 && name.compare(i + 10, 9, "namespace") == 0) + name.replace(i, 19, "anon ns"); + else + name.replace(i, 9, "anon"); + } + } + + template + void SetName(const Decl *D, std::string_view short_name, + std::string_view qualified, Def &def) { + SmallString<256> Str; + llvm::raw_svector_ostream OS(Str); + D->print(OS, GetDefaultPolicy()); + + std::string name = OS.str(); + SimplifyAnonymous(name); + // Remove \n in DeclPrinter.cpp "{\n" + if(!TerseOutput)something + "}" + for (std::string::size_type i = 0;;) { + if ((i = name.find("{\n}", i)) == std::string::npos) + break; + name.replace(i, 3, "{}"); + } + auto i = name.find(short_name); + if (short_name.size()) + while (i != std::string::npos && ((i && isIdentifierBody(name[i - 1])) || + isIdentifierBody(name[i + short_name.size()]))) + i = name.find(short_name, i + short_name.size()); + if (i == std::string::npos) { + // e.g. operator type-parameter-1 + i = 0; + def.short_name_offset = 0; + } else if (short_name.empty() || (i >= 2 && name[i - 2] == ':')) { + // Don't replace name with qualified name in ns::name Cls::*name + def.short_name_offset = i; + } else { + name.replace(i, short_name.size(), qualified); + def.short_name_offset = i + qualified.size() - short_name.size(); + } + def.short_name_size = short_name.size(); + for (int paren = 0; i; i--) { + // Skip parentheses in "(anon struct)::name" + if (name[i - 1] == ')') + paren++; + else if (name[i - 1] == '(') + paren--; + else if (!(paren > 0 || isIdentifierBody(name[i - 1]) || + name[i - 1] == ':')) + break; + } + def.qual_name_offset = i; + def.detailed_name = Intern(name); + } + + void SetVarName(const Decl *D, std::string_view short_name, + std::string_view qualified, IndexVar::Def &def) { + QualType T; + const Expr *init = nullptr; + bool deduced = false; + if (auto *VD = dyn_cast(D)) { + T = VD->getType(); + init = VD->getAnyInitializer(); + def.storage = VD->getStorageClass(); + } else if (auto *FD = dyn_cast(D)) { + T = FD->getType(); + init = FD->getInClassInitializer(); + } else if (auto *BD = dyn_cast(D)) { + T = BD->getType(); + deduced = true; + } + if (!T.isNull()) { + if (T->getContainedDeducedType()) { + deduced = true; + } else if (auto *DT = dyn_cast(T)) { + // decltype(y) x; + while (DT && !DT->getUnderlyingType().isNull()) { + T = DT->getUnderlyingType(); + DT = dyn_cast(T); + } + deduced = true; + } + } + if (!T.isNull() && deduced) { + SmallString<256> Str; + llvm::raw_svector_ostream OS(Str); + PrintingPolicy PP = GetDefaultPolicy(); + T.print(OS, PP); + if (Str.size() && + (Str.back() != ' ' && Str.back() != '*' && Str.back() != '&')) + Str += ' '; + def.qual_name_offset = Str.size(); + def.short_name_offset = Str.size() + qualified.size() - short_name.size(); + def.short_name_size = short_name.size(); + Str += StringRef(qualified.data(), qualified.size()); + def.detailed_name = Intern(Str); + } else { + SetName(D, short_name, qualified, def); + } + if (init) { + SourceManager &SM = Ctx->getSourceManager(); + const LangOptions &Lang = Ctx->getLangOpts(); + SourceRange R = SM.getExpansionRange(init->getSourceRange()) +#if LLVM_VERSION_MAJOR >= 7 + .getAsRange() +#endif + ; + SourceLocation L = D->getLocation(); + if (L.isMacroID() || !SM.isBeforeInTranslationUnit(L, R.getBegin())) + return; + StringRef Buf = GetSourceInRange(SM, Lang, R); + Twine Init = Buf.count('\n') <= kInitializerMaxLines - 1 + ? Buf.size() && Buf[0] == ':' ? Twine(" ", Buf) + : Twine(" = ", Buf) + : Twine(); + Twine T = def.detailed_name + Init; + def.hover = + def.storage == SC_Static && strncmp(def.detailed_name, "static ", 7) + ? Intern(("static " + T).str()) + : Intern(T.str()); + } + } + + static int GetFileLID(IndexFile *db, SourceManager &SM, const FileEntry &FE) { + auto [it, inserted] = db->uid2lid_and_path.try_emplace(FE.getUniqueID()); + if (inserted) { + it->second.first = db->uid2lid_and_path.size() - 1; + SmallString<256> Path = FE.tryGetRealPathName(); + if (Path.empty()) + Path = FE.getName(); + if (!llvm::sys::path::is_absolute(Path) && + !SM.getFileManager().makeAbsolutePath(Path)) + return -1; + it->second.second = llvm::sys::path::convert_to_slash(Path.str()); + } + return it->second.first; + } + + void AddMacroUse(IndexFile *db, SourceManager &SM, Usr usr, Kind kind, + SourceLocation Spell) const { + const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(Spell)); + if (!FE) + return; + int lid = GetFileLID(db, SM, *FE); + if (lid < 0) + return; + Range spell = + FromTokenRange(SM, Ctx->getLangOpts(), SourceRange(Spell, Spell)); + Use use{{spell, Role::Dynamic}, lid}; + switch (kind) { + case Kind::Func: + db->ToFunc(usr).uses.push_back(use); + break; + case Kind::Type: + db->ToType(usr).uses.push_back(use); + break; + case Kind::Var: + db->ToVar(usr).uses.push_back(use); + break; + default: + llvm_unreachable(""); + } + } + + void CollectRecordMembers(IndexType &type, const RecordDecl *RD) { + SmallVector, 2> Stack{{RD, 0}}; + llvm::DenseSet Seen; + Seen.insert(RD); + while (Stack.size()) { + int offset; + std::tie(RD, offset) = Stack.back(); + Stack.pop_back(); + if (!RD->isCompleteDefinition() || RD->isDependentType() || + RD->isInvalidDecl() || !ValidateRecord(RD)) + offset = -1; + for (FieldDecl *FD : RD->fields()) { + int offset1 = offset < 0 ? -1 : offset + Ctx->getFieldOffset(FD); + if (FD->getIdentifier()) + type.def.vars.emplace_back(GetUsr(FD), offset1); + else if (const auto *RT1 = FD->getType()->getAs()) { + if (const RecordDecl *RD1 = RT1->getDecl()) + if (Seen.insert(RD1).second) + Stack.push_back({RD1, offset1}); + } + } + } + } + +public: + std::unordered_set visited_decls; + + IndexDataConsumer(IndexParam ¶m) : param(param) {} + void initialize(ASTContext &Ctx) override { + this->Ctx = param.Ctx = &Ctx; + SourceManager &SM = Ctx.getSourceManager(); + (void)param.ConsumeFile(*SM.getFileEntryForID(SM.getMainFileID())); + } + bool handleDeclOccurence(const Decl *D, index::SymbolRoleSet Roles, + ArrayRef Relations, +#if LLVM_VERSION_MAJOR >= 7 + SourceLocation Loc, +#else + FileID LocFID, unsigned LocOffset, +#endif + ASTNodeInfo ASTNode) override { + SourceManager &SM = Ctx->getSourceManager(); + const LangOptions &Lang = Ctx->getLangOpts(); +#if LLVM_VERSION_MAJOR < 7 + SourceLocation Loc; + { + const SrcMgr::SLocEntry &Entry = SM.getSLocEntry(LocFID); + unsigned off = Entry.getOffset() + LocOffset; + if (!Entry.isFile()) + off |= 1u << 31; + Loc = SourceLocation::getFromRawEncoding(off); + } +#else + FileID LocFID; +#endif + SourceLocation Spell = SM.getSpellingLoc(Loc); + const FileEntry *FE; + Range loc; +#if LLVM_VERSION_MAJOR < 7 + CharSourceRange R; + if (SM.isMacroArgExpansion(Loc)) + R = CharSourceRange::getTokenRange(Spell); + else { + auto P = SM.getExpansionRange(Loc); + R = CharSourceRange::getTokenRange(P.first, P.second); + } +#else + auto R = SM.isMacroArgExpansion(Loc) ? CharSourceRange::getTokenRange(Spell) + : SM.getExpansionRange(Loc); +#endif + loc = FromCharSourceRange(SM, Lang, R); + LocFID = SM.getFileID(R.getBegin()); + FE = SM.getFileEntryForID(LocFID); + if (!FE) + return true; + int lid = -1; + IndexFile *db; + if (g_config->index.multiVersion && param.UseMultiVersion(*FE)) { + db = param.ConsumeFile(*SM.getFileEntryForID(SM.getMainFileID())); + if (!db) + return true; + param.SeenFile(*FE); + if (!SM.isInMainFile(R.getBegin())) + lid = GetFileLID(db, SM, *FE); + } else { + db = param.ConsumeFile(*FE); + if (!db) + return true; + } + + // spell, extent, comments use OrigD while most others use adjusted |D|. + const Decl *OrigD = ASTNode.OrigD; + const DeclContext *SemDC = OrigD->getDeclContext()->getRedeclContext(); + const DeclContext *LexDC = ASTNode.ContainerDC->getRedeclContext(); + { + const NamespaceDecl *ND; + while ((ND = dyn_cast(cast(SemDC))) && + ND->isAnonymousNamespace()) + SemDC = ND->getDeclContext()->getRedeclContext(); + while ((ND = dyn_cast(cast(LexDC))) && + ND->isAnonymousNamespace()) + LexDC = ND->getDeclContext()->getRedeclContext(); + } + Role role = static_cast(Roles); + db->language = LanguageId((int)db->language | (int)GetDeclLanguage(D)); + + bool is_decl = Roles & uint32_t(index::SymbolRole::Declaration); + bool is_def = Roles & uint32_t(index::SymbolRole::Definition); + if (is_decl && D->getKind() == Decl::Binding) + is_def = true; + IndexFunc *func = nullptr; + IndexType *type = nullptr; + IndexVar *var = nullptr; + SymbolKind ls_kind; + Kind kind = GetKind(D, ls_kind); + + if (is_def) + switch (D->getKind()) { + case Decl::CXXConversion: // *operator* int => *operator int* + case Decl::CXXDestructor: // *~*A => *~A* + case Decl::CXXMethod: // *operator*= => *operator=* + case Decl::Function: // operator delete + if (Loc.isFileID()) { + SourceRange R = + cast(OrigD)->getNameInfo().getSourceRange(); + if (R.getEnd().isFileID()) + loc = FromTokenRange(SM, Lang, R); + } + break; + default: + break; + } + else { + // e.g. typedef Foo gg; => Foo has an unadjusted `D` + const Decl *D1 = GetAdjustedDecl(D); + if (D1 && D1 != D) + D = D1; + } + + IndexParam::DeclInfo *info; + Usr usr = GetUsr(D, &info); + + if (true) { + class FuncVisitor : public RecursiveASTVisitor { + public: + FuncVisitor(IndexDataConsumer *consumer, IndexFile *db, SourceManager &SM, + const LangOptions &Lang, int lid) + : consumer(consumer), db(db), SM(SM), Lang(Lang), lid(lid) {} + + std::stack function_stack; + + bool TraverseDecl(Decl *decl) { + if (decl->hasBody() && !consumer->visited_decls.insert(decl).second) + return true; + return RecursiveASTVisitor::TraverseDecl(decl); + } + #define TRAVERSE_FUNCTION_DECL_CHILD(TYPE) \ + bool Traverse##TYPE(TYPE *decl) {\ + function_stack.push(decl);\ + bool ret = RecursiveASTVisitor::Traverse##TYPE(decl);\ + function_stack.pop();\ + return ret;\ + } + TRAVERSE_FUNCTION_DECL_CHILD(FunctionDecl) + TRAVERSE_FUNCTION_DECL_CHILD(CXXDeductionGuideDecl) + TRAVERSE_FUNCTION_DECL_CHILD(CXXMethodDecl) + TRAVERSE_FUNCTION_DECL_CHILD(CXXConstructorDecl) + TRAVERSE_FUNCTION_DECL_CHILD(CXXConversionDecl) + TRAVERSE_FUNCTION_DECL_CHILD(CXXDestructorDecl) + + bool VisitUnaryOperator(UnaryOperator *unop) { + if (unop->isIncrementDecrementOp()) { + AddDataFlow(unop->getSubExpr(), unop, unop->getSourceRange()); + } + return RecursiveASTVisitor::VisitUnaryOperator(unop); + } + bool VisitBinaryOperator(BinaryOperator *binop) { + if (binop->getOpcode() == BinaryOperator::Opcode::BO_Assign) { + AddDataFlow(binop->getLHS(), binop->getRHS(), binop->getSourceRange()); + } else if (binop->isAssignmentOp()) { + AddDataFlow(binop->getLHS(), binop, binop->getSourceRange()); + } + return RecursiveASTVisitor::VisitBinaryOperator(binop); + } + bool VisitVarDecl(VarDecl *decl) { + if (decl->hasInit()) { + AddDataFlow(decl, decl->getInit(), decl->getSourceRange()); + } + return RecursiveASTVisitor::VisitVarDecl(decl); + } + bool VisitFieldDecl(FieldDecl *decl) { + if (decl->hasInClassInitializer()) { + if (decl->getInClassInitializer()) { + AddDataFlow(decl, decl->getInClassInitializer(), decl->getSourceRange()); + } else { + // Allow re-visit of this decl. + consumer->visited_decls.erase(decl); + } + } + return RecursiveASTVisitor::VisitFieldDecl(decl); + } + bool VisitCXXConstructorDecl(CXXConstructorDecl *decl) { + for (const auto &init : decl->inits()) { + if (init->isMemberInitializer()) { + if (auto *defaultInit = + dyn_cast(init->getInit())) { + AddDataFlow(init->getMember(), defaultInit->getExpr(), defaultInit->getSourceRange()); + } else { + AddDataFlow(init->getMember(), init->getInit(), init->getSourceRange()); + } + } + + if (auto *constructorInit = + dyn_cast(init->getInit())) { + if (CXXConstructorDecl *decl = constructorInit->getConstructor()) { + int len = std::min(constructorInit->getNumArgs(), decl->getNumParams()); + for (int i = 0; i < len; ++i) { + AddDataFlow(decl->getParamDecl(i), constructorInit->getArg(i), constructorInit->getSourceRange()); + } + } + } + } + return RecursiveASTVisitor::VisitCXXConstructorDecl(decl); + } + bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *expr) { + AddDataFlow(expr->getField(), expr->getExpr(), expr->getSourceRange()); + return RecursiveASTVisitor::VisitCXXDefaultInitExpr(expr); + } + + bool VisitDeclStmt(DeclStmt *stmt) { + for (auto &decl : stmt->decls()) { + if (VarDecl *named = dyn_cast(decl)) { + if (named->hasInit()) { + AddDataFlow(named, named->getInit(), named->getSourceRange()); + } + } + } + return RecursiveASTVisitor::VisitDeclStmt(stmt); + } + + bool VisitCallExpr(CallExpr *expr) { + if (FunctionDecl *decl = expr->getDirectCallee()) { + int len = std::min(expr->getNumArgs(), decl->getNumParams()); + for (int i = 0; i < len; ++i) { + AddDataFlow(decl->getParamDecl(i), expr->getArg(i), expr->getSourceRange()); + } + } + return RecursiveASTVisitor::VisitCallExpr(expr); + } + bool VisitCXXConstructExpr(CXXConstructExpr *expr) { + if (CXXConstructorDecl *decl = expr->getConstructor()) { + int len = std::min(expr->getNumArgs(), decl->getNumParams()); + for (int i = 0; i < len; ++i) { + AddDataFlow(decl->getParamDecl(i), expr->getArg(i), expr->getSourceRange()); + } + } + return RecursiveASTVisitor::VisitCXXConstructExpr(expr); + } + + bool VisitReturnStmt(ReturnStmt *stmt) { + if (!function_stack.empty()) { + if (Expr *retVal = stmt->getRetValue()) { + if (const FunctionDecl *func = dyn_cast(function_stack.top())) + AddDataFlow(func, retVal, stmt->getSourceRange()); + } + } + return RecursiveASTVisitor::VisitReturnStmt(stmt); + } + + private: + void AddDataFlow(const Expr *target, const Expr *source, SourceRange flowSourceRange) { + if (!source) + target->dumpColor(); + if (const auto *targetDeclRef = + dyn_cast(target->IgnoreParenCasts())) { + AddDataFlow(targetDeclRef->getDecl(), source, flowSourceRange); + } else if (const auto *targetFieldRef = + dyn_cast(target->IgnoreParenCasts())) { + AddDataFlow(targetFieldRef->getMemberDecl(), source, flowSourceRange); + } + } + void AddDataFlow(const ValueDecl *target, const Expr *source, SourceRange flowSourceRange) { + if (!source) + target->dumpColor(); + auto& var = db->ToVar(consumer->GetUsr(target)); + var.data_flow_into.push_back(GetDataFlow(source, flowSourceRange)); + } + void AddDataFlow(const FunctionDecl *target, const Expr *source, SourceRange flowSourceRange) { + if (!source) + target->dumpColor(); + auto& func = db->ToFunc(consumer->GetUsr(target)); + func.data_flow_into_return.push_back(GetDataFlow(source, flowSourceRange)); + } + DataFlow GetDataFlow(const Expr *source, SourceRange flowSourceRange) { + auto data_flowRange = FromCharSourceRange( + SM, Lang, + CharSourceRange::getTokenRange(flowSourceRange)); + Use write{{data_flowRange, Role::None}, lid}; + + if (const auto *data_flowExpr = + dyn_cast(source->IgnoreParenCasts())) { + auto *data_flow_decl = data_flowExpr->getDecl(); + write.role = Role::Read; + return DataFlow{consumer->GetUsr(data_flow_decl), write}; + } else if (const auto *data_flowExpr = + dyn_cast(source->IgnoreParenCasts())) { + auto *data_flow_decl = data_flowExpr->getMemberDecl(); + write.role = Role::Read; + return DataFlow{consumer->GetUsr(data_flow_decl), write}; + } else if (const auto *data_flowExpr = + dyn_cast(source->IgnoreParenCasts())) { + if (auto *data_flow_callee = data_flowExpr->getDirectCallee()) { + write.role = Role::Call; + return DataFlow{consumer->GetUsr(data_flow_callee), write}; + } + } + + return DataFlow{0, write}; + } + + IndexDataConsumer *consumer; + IndexFile *db; + SourceManager &SM; + const LangOptions ⟪ + int lid; + }; + FuncVisitor funcVisitor(this, db, SM, Lang, lid); + funcVisitor.TraverseDecl(const_cast(ASTNode.OrigD)); + } + + auto do_def_decl = [&](auto *entity) { + Use use{{loc, role}, lid}; + if (is_def) { + SourceRange R = OrigD->getSourceRange(); + entity->def.spell = {use, + FromTokenRangeDefaulted(SM, Lang, R, FE, loc)}; + GetKind(cast(SemDC), entity->def.parent_kind); + } else if (is_decl) { + SourceRange R = OrigD->getSourceRange(); + entity->declarations.push_back( + {use, FromTokenRangeDefaulted(SM, Lang, R, FE, loc)}); + } else { + entity->uses.push_back(use); + return; + } + if (entity->def.comments[0] == '\0' && g_config->index.comments) + entity->def.comments = Intern(GetComment(OrigD)); + }; + switch (kind) { + case Kind::Invalid: + LOG_S(INFO) << "Unhandled " << int(D->getKind()) << " " << info->qualified + << " in " << db->path << ":" << loc.start.line + 1; + return true; + case Kind::File: + return true; + case Kind::Func: + func = &db->ToFunc(usr); + func->def.kind = ls_kind; + // Mark as Role::Implicit to span one more column to the left/right. + if (!is_def && !is_decl && + (D->getKind() == Decl::CXXConstructor || + D->getKind() == Decl::CXXConversion)) + role = Role(role | Role::Implicit); + do_def_decl(func); + if (Spell != Loc) + AddMacroUse(db, SM, usr, Kind::Func, Spell); + if (func->def.detailed_name[0] == '\0') + SetName(D, info->short_name, info->qualified, func->def); + if (is_def || is_decl) { + const Decl *DC = cast(SemDC); + if (GetKind(DC, ls_kind) == Kind::Type) + db->ToType(GetUsr(DC)).def.funcs.push_back(usr); + } else { + const Decl *DC = cast(LexDC); + if (GetKind(DC, ls_kind) == Kind::Func) + db->ToFunc(GetUsr(DC)) + .def.callees.push_back({loc, usr, Kind::Func, role}); + } + break; + case Kind::Type: + type = &db->ToType(usr); + type->def.kind = ls_kind; + do_def_decl(type); + if (Spell != Loc) + AddMacroUse(db, SM, usr, Kind::Type, Spell); + if (type->def.detailed_name[0] == '\0' && info->short_name.size()) + SetName(D, info->short_name, info->qualified, type->def); + if (is_def || is_decl) { + const Decl *DC = cast(SemDC); + if (GetKind(DC, ls_kind) == Kind::Type) + db->ToType(GetUsr(DC)).def.types.push_back(usr); + } + break; + case Kind::Var: + var = &db->ToVar(usr); + var->def.kind = ls_kind; + do_def_decl(var); + if (Spell != Loc) + AddMacroUse(db, SM, usr, Kind::Var, Spell); + if (var->def.detailed_name[0] == '\0') + SetVarName(D, info->short_name, info->qualified, var->def); + QualType T; + if (auto *VD = dyn_cast(D)) + T = VD->getType(); + if (is_def || is_decl) { + const Decl *DC = cast(SemDC); + Kind kind = GetKind(DC, var->def.parent_kind); + if (kind == Kind::Func) + db->ToFunc(GetUsr(DC)).def.vars.push_back(usr); + else if (kind == Kind::Type && !isa(SemDC)) + db->ToType(GetUsr(DC)).def.vars.emplace_back(usr, -1); + if (!T.isNull()) { + if (auto *BT = T->getAs()) { + Usr usr1 = static_cast(BT->getKind()); + var->def.type = usr1; + if (!isa(D)) + db->ToType(usr1).instances.push_back(usr); + } else if (const Decl *D1 = GetAdjustedDecl(GetTypeDecl(T))) { + if (isa(D1)) { + // e.g. TemplateTypeParmDecl is not handled by + // handleDeclOccurence. + SourceRange R1 = D1->getSourceRange(); + if (SM.getFileID(R1.getBegin()) == LocFID) { + IndexParam::DeclInfo *info1; + Usr usr1 = GetUsr(D1, &info1); + IndexType &type1 = db->ToType(usr1); + SourceLocation L1 = D1->getLocation(); + type1.def.spell = { + Use{{FromTokenRange(SM, Lang, {L1, L1}), Role::Definition}, + lid}, + FromTokenRange(SM, Lang, R1)}; + type1.def.detailed_name = Intern(info1->short_name); + type1.def.short_name_size = int16_t(info1->short_name.size()); + type1.def.kind = SymbolKind::TypeParameter; + type1.def.parent_kind = SymbolKind::Class; + var->def.type = usr1; + type1.instances.push_back(usr); + break; + } + } + + IndexParam::DeclInfo *info1; + Usr usr1 = GetUsr(D1, &info1); + var->def.type = usr1; + if (!isa(D)) + db->ToType(usr1).instances.push_back(usr); + } + } + } else if (!var->def.spell && var->declarations.empty()) { + // e.g. lambda parameter + SourceLocation L = D->getLocation(); + if (SM.getFileID(L) == LocFID) { + var->def.spell = { + Use{{FromTokenRange(SM, Lang, {L, L}), Role::Definition}, lid}, + FromTokenRange(SM, Lang, D->getSourceRange())}; + var->def.parent_kind = SymbolKind::Method; + } + } + break; + } + + switch (D->getKind()) { + case Decl::Namespace: + if (D->isFirstDecl()) { + auto *ND = cast(D); + auto *ND1 = cast(ND->getParent()); + if (isa(ND1)) { + Usr usr1 = GetUsr(ND1); + type->def.bases.push_back(usr1); + db->ToType(usr1).derived.push_back(usr); + } + } + break; + case Decl::NamespaceAlias: { + auto *NAD = cast(D); + if (const NamespaceDecl *ND = NAD->getNamespace()) { + Usr usr1 = GetUsr(ND); + type->def.alias_of = usr1; + (void)db->ToType(usr1); + } + break; + } + case Decl::CXXRecord: + if (is_def) { + auto *RD = dyn_cast(D); + if (RD && RD->hasDefinition()) + for (const CXXBaseSpecifier &Base : RD->bases()) + if (const Decl *BaseD = + GetAdjustedDecl(GetTypeDecl(Base.getType()))) { + Usr usr1 = GetUsr(BaseD); + type->def.bases.push_back(usr1); + db->ToType(usr1).derived.push_back(usr); + } + } + [[fallthrough]]; + case Decl::Record: + if (auto *RD = dyn_cast(D)) { + if (type->def.detailed_name[0] == '\0' && info->short_name.empty()) { + StringRef Tag; + switch (RD->getTagKind()) { + case TTK_Struct: Tag = "struct"; break; + case TTK_Interface: Tag = "__interface"; break; + case TTK_Union: Tag = "union"; break; + case TTK_Class: Tag = "class"; break; + case TTK_Enum: Tag = "enum"; break; + } + if (TypedefNameDecl *TD = RD->getTypedefNameForAnonDecl()) { + StringRef Name = TD->getName(); + std::string name = ("anon " + Tag + " " + Name).str(); + type->def.detailed_name = Intern(name); + type->def.short_name_size = name.size(); + } else { + std::string name = ("anon " + Tag).str(); + type->def.detailed_name = Intern(name); + type->def.short_name_size = name.size(); + } + } + if (is_def) + if (auto *ORD = dyn_cast(OrigD)) + CollectRecordMembers(*type, ORD); + } + break; + case Decl::ClassTemplateSpecialization: + case Decl::ClassTemplatePartialSpecialization: + type->def.kind = SymbolKind::Class; + if (is_def) { + if (auto *ORD = dyn_cast(OrigD)) + CollectRecordMembers(*type, ORD); + if (auto *RD = dyn_cast(D)) { + Decl *D1 = nullptr; + if (auto *SD = dyn_cast(RD)) + D1 = SD->getSpecializedTemplate(); + else if (auto *SD = dyn_cast(RD)) { + llvm::PointerUnion + Result = SD->getSpecializedTemplateOrPartial(); + if (Result.is()) + D1 = Result.get(); + else + D1 = Result.get(); + + } else + D1 = RD->getInstantiatedFromMemberClass(); + if (D1) { + Usr usr1 = GetUsr(D1); + type->def.bases.push_back(usr1); + db->ToType(usr1).derived.push_back(usr); + } + } + } + break; + case Decl::TypeAlias: + case Decl::Typedef: + case Decl::UnresolvedUsingTypename: + if (auto *TD = dyn_cast(D)) { + bool specialization = false; + QualType T = TD->getUnderlyingType(); + if (const Decl *D1 = GetAdjustedDecl(GetTypeDecl(T, &specialization))) { + Usr usr1 = GetUsr(D1); + IndexType &type1 = db->ToType(usr1); + type->def.alias_of = usr1; + // Not visited template struct B {typedef A t;}; + if (specialization) { + const TypeSourceInfo *TSI = TD->getTypeSourceInfo(); + SourceLocation L1 = TSI->getTypeLoc().getBeginLoc(); + if (SM.getFileID(L1) == LocFID) + type1.uses.push_back( + {{FromTokenRange(SM, Lang, {L1, L1}), Role::Reference}, lid}); + } + } + } + break; + case Decl::CXXMethod: + if (is_def || is_decl) { + if (auto *ND = dyn_cast(D)) { + SmallVector OverDecls; + Ctx->getOverriddenMethods(ND, OverDecls); + for (const auto *ND1 : OverDecls) { + Usr usr1 = GetUsr(ND1); + func->def.bases.push_back(usr1); + db->ToFunc(usr1).derived.push_back(usr); + } + } + } + break; + case Decl::EnumConstant: + if (is_def && strchr(var->def.detailed_name, '=') == nullptr) { + auto *ECD = cast(D); + const auto &Val = ECD->getInitVal(); + std::string init = + " = " + (Val.isSigned() ? std::to_string(Val.getSExtValue()) + : std::to_string(Val.getZExtValue())); + var->def.hover = Intern(var->def.detailed_name + init); + } + break; + default: + break; + } + return true; + } +}; + +class IndexPPCallbacks : public PPCallbacks { + SourceManager &SM; + IndexParam ¶m; + + std::pair GetMacro(const Token &Tok) const { + StringRef Name = Tok.getIdentifierInfo()->getName(); + SmallString<256> USR("@macro@"); + USR += Name; + return {Name, HashUsr(USR)}; + } + +public: + IndexPPCallbacks(SourceManager &SM, IndexParam ¶m) + : SM(SM), param(param) {} + void InclusionDirective(SourceLocation HashLoc, const Token &Tok, + StringRef Included, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const Module *Imported +#if LLVM_VERSION_MAJOR >= 7 + , + SrcMgr::CharacteristicKind FileType +#endif + ) override { + if (!File) + return; + llvm::sys::fs::UniqueID UniqueID; + auto spell = FromCharSourceRange(SM, param.Ctx->getLangOpts(), + FilenameRange, &UniqueID); + const FileEntry *FE = + SM.getFileEntryForID(SM.getFileID(FilenameRange.getBegin())); + if (!FE) + return; + if (IndexFile *db = param.ConsumeFile(*FE)) { + std::string path = PathFromFileEntry(*File); + if (path.size()) + db->includes.push_back({spell.start.line, Intern(path)}); + } + } + void MacroDefined(const Token &Tok, const MacroDirective *MD) override { + llvm::sys::fs::UniqueID UniqueID; + const LangOptions &Lang = param.Ctx->getLangOpts(); + SourceLocation L = MD->getLocation(); + const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(L)); + if (!FE) + return; + if (IndexFile *db = param.ConsumeFile(*FE)) { + auto [Name, usr] = GetMacro(Tok); + IndexVar &var = db->ToVar(usr); + Range range = FromTokenRange(SM, Lang, {L, L}, &UniqueID); + var.def.kind = SymbolKind::Macro; + var.def.parent_kind = SymbolKind::File; + if (var.def.spell) + var.declarations.push_back(*var.def.spell); + const MacroInfo *MI = MD->getMacroInfo(); + SourceRange R(MI->getDefinitionLoc(), MI->getDefinitionEndLoc()); + Range extent = FromTokenRange(SM, param.Ctx->getLangOpts(), R); + var.def.spell = {Use{{range, Role::Definition}}, extent}; + if (var.def.detailed_name[0] == '\0') { + var.def.detailed_name = Intern(Name); + var.def.short_name_size = Name.size(); + StringRef Buf = GetSourceInRange(SM, Lang, R); + var.def.hover = + Intern(Buf.count('\n') <= kInitializerMaxLines - 1 + ? Twine("#define ", GetSourceInRange(SM, Lang, R)).str() + : Twine("#define ", Name).str()); + } + } + } + void MacroExpands(const Token &Tok, const MacroDefinition &MD, SourceRange R, + const MacroArgs *Args) override { + llvm::sys::fs::UniqueID UniqueID; + SourceLocation L = SM.getSpellingLoc(R.getBegin()); + const FileEntry *FE = SM.getFileEntryForID(SM.getFileID(L)); + if (!FE) + return; + if (IndexFile *db = param.ConsumeFile(*FE)) { + IndexVar &var = db->ToVar(GetMacro(Tok).second); + var.uses.push_back( + {{FromTokenRange(SM, param.Ctx->getLangOpts(), {L, L}, &UniqueID), + Role::Dynamic}}); + } + } + void MacroUndefined(const Token &Tok, const MacroDefinition &MD, + const MacroDirective *UD) override { + if (UD) { + SourceLocation L = UD->getLocation(); + MacroExpands(Tok, MD, {L, L}, nullptr); + } + } + void SourceRangeSkipped(SourceRange Range, SourceLocation EndifLoc) override { + llvm::sys::fs::UniqueID UniqueID; + auto range = FromCharRange(SM, param.Ctx->getLangOpts(), Range, &UniqueID); + if (const FileEntry *FE = + SM.getFileEntryForID(SM.getFileID(Range.getBegin()))) + if (IndexFile *db = param.ConsumeFile(*FE)) + db->skipped_ranges.push_back(range); + } +}; + +class IndexFrontendAction : public ASTFrontendAction { + IndexParam ¶m; + +public: + IndexFrontendAction(IndexParam ¶m) : param(param) {} + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef InFile) override { + Preprocessor &PP = CI.getPreprocessor(); + PP.addPPCallbacks( + std::make_unique(PP.getSourceManager(), param)); + return std::make_unique(); + } +}; +} // namespace + +const int IndexFile::kMajorVersion = 20; +const int IndexFile::kMinorVersion = 0; + +IndexFile::IndexFile(llvm::sys::fs::UniqueID UniqueID, const std::string &path, + const std::string &contents) + : UniqueID(UniqueID), path(path), file_contents(contents) {} + +IndexFunc &IndexFile::ToFunc(Usr usr) { + auto [it, inserted] = usr2func.try_emplace(usr); + if (inserted) + it->second.usr = usr; + return it->second; +} + +IndexType &IndexFile::ToType(Usr usr) { + auto [it, inserted] = usr2type.try_emplace(usr); + if (inserted) + it->second.usr = usr; + return it->second; +} + +IndexVar &IndexFile::ToVar(Usr usr) { + auto [it, inserted] = usr2var.try_emplace(usr); + if (inserted) + it->second.usr = usr; + return it->second; +} + +std::string IndexFile::ToString() { + return ccls::Serialize(SerializeFormat::Json, *this); +} + +template void Uniquify(std::vector &a) { + std::unordered_set seen; + size_t n = 0; + for (size_t i = 0; i < a.size(); i++) + if (seen.insert(a[i]).second) + a[n++] = a[i]; + a.resize(n); +} + +namespace idx { +void Init() { + multiVersionMatcher = new GroupMatch(g_config->index.multiVersionWhitelist, + g_config->index.multiVersionBlacklist); +} + +std::vector> +Index(SemaManager *manager, WorkingFiles *wfiles, VFS *vfs, + const std::string &opt_wdir, const std::string &file, + const std::vector &args, + const std::vector> &remapped, + bool &ok) { + ok = true; + auto PCH = std::make_shared(); + llvm::IntrusiveRefCntPtr FS = llvm::vfs::getRealFileSystem(); + std::shared_ptr CI = BuildCompilerInvocation(args, FS); + // e.g. .s + if (!CI) + return {}; + ok = false; + // -fparse-all-comments enables documentation in the indexer and in + // code completion. + if (g_config->index.comments > 1) + CI->getLangOpts()->CommentOpts.ParseAllComments = true; + { + // FileSystemOptions& FSOpts = CI->getFileSystemOpts(); + // if (FSOpts.WorkingDir.empty()) + // FSOpts.WorkingDir = opt_wdir; + // HeaderSearchOptions &HSOpts = CI->getHeaderSearchOpts(); + // llvm::errs() << HSOpts.ResourceDir << "\n"; + // // lib/clang/7.0.0 is incorrect + // if (HSOpts.ResourceDir.compare(0, 3, "lib") == 0 && + // HSOpts.UseBuiltinIncludes) + // HSOpts.ResourceDir = g_config->clang.resourceDir; + } + std::string buf = wfiles->GetContent(file); + std::vector> Bufs; + if (buf.size()) { + // If there is a completion session, reuse its preamble if exists. + bool done_remap = false; +#if 0 + std::shared_ptr session = + manager->TryGetSession(file, false, false); + if (session) + if (auto preamble = session->GetPreamble()) { + Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(buf)); + auto Bounds = ComputePreambleBounds(*CI->getLangOpts(), Bufs.back().get(), 0); + if (preamble->Preamble.CanReuse(*CI, Bufs.back().get(), Bounds, + FS.get())) { + preamble->Preamble.AddImplicitPreamble(*CI, FS, Bufs.back().get()); + done_remap = true; + } + } +#endif + for (auto &[filename, content] : remapped) { + if (filename == file && done_remap) + continue; + Bufs.push_back(llvm::MemoryBuffer::getMemBuffer(content)); + CI->getPreprocessorOpts().addRemappedFile( + filename == file ? CI->getFrontendOpts().Inputs[0].getFile() + : StringRef(filename), + Bufs.back().get()); + } + } + + DiagnosticConsumer DC; + auto Clang = std::make_unique(PCH); + Clang->setInvocation(std::move(CI)); + Clang->setVirtualFileSystem(FS); + Clang->createDiagnostics(&DC, false); + Clang->setTarget(TargetInfo::CreateTargetInfo( + Clang->getDiagnostics(), Clang->getInvocation().TargetOpts)); + if (!Clang->hasTarget()) + return {}; + + IndexParam param(*vfs); + auto DataConsumer = std::make_shared(param); + + index::IndexingOptions IndexOpts; + IndexOpts.SystemSymbolFilter = + index::IndexingOptions::SystemSymbolFilterKind::All; + IndexOpts.IndexFunctionLocals = true; +#if LLVM_VERSION_MAJOR >= 7 + IndexOpts.IndexImplicitInstantiation = true; +#endif + + std::unique_ptr Action = createIndexingAction( + DataConsumer, IndexOpts, std::make_unique(param)); + + { + llvm::CrashRecoveryContext CRC; + auto parse = [&]() { + if (!Action->BeginSourceFile(*Clang, Clang->getFrontendOpts().Inputs[0])) + return; + if (!Action->Execute()) + return; + Action->EndSourceFile(); + ok = true; + }; + if (!CRC.RunSafely(parse)) { + LOG_S(ERROR) << "clang crashed for " << file; + return {}; + } + } + if (!ok) { + LOG_S(ERROR) << "failed to index " << file; + return {}; + } + for (auto &Buf : Bufs) + Buf.release(); + + std::vector> result; + for (auto &it : param.UID2File) { + if (!it.second.db) + continue; + std::unique_ptr &entry = it.second.db; + entry->import_file = file; + entry->args = args; + for (auto &[_, it] : entry->uid2lid_and_path) + entry->lid2path.emplace_back(it.first, std::move(it.second)); + entry->uid2lid_and_path.clear(); + for (auto &it : entry->usr2func) { + // e.g. declaration + out-of-line definition + Uniquify(it.second.derived); + Uniquify(it.second.uses); + Uniquify(it.second.data_flow_into_return); + } + for (auto &it : entry->usr2type) { + Uniquify(it.second.derived); + Uniquify(it.second.uses); + // e.g. declaration + out-of-line definition + Uniquify(it.second.def.bases); + Uniquify(it.second.def.funcs); + } + for (auto &it : entry->usr2var) { + Uniquify(it.second.uses); + Uniquify(it.second.data_flow_into); + } + + // Update dependencies for the file. + for (auto &[_, file] : param.UID2File) { + const std::string &path = file.path; + if (path == entry->path) + entry->mtime = file.mtime; + else if (path != entry->import_file) + entry->dependencies[llvm::CachedHashStringRef(Intern(path))] = + file.mtime; + } + result.push_back(std::move(entry)); + } + + return result; +} +} // namespace idx + +void Reflect(JsonReader &vis, SymbolRef &v) { + std::string t = vis.GetString(); + char *s = const_cast(t.c_str()); + v.range = Range::FromString(s); + s = strchr(s, '|'); + v.usr = strtoull(s + 1, &s, 10); + v.kind = static_cast(strtol(s + 1, &s, 10)); + v.role = static_cast(strtol(s + 1, &s, 10)); +} +void Reflect(JsonReader &vis, Use &v) { + std::string t = vis.GetString(); + char *s = const_cast(t.c_str()); + v.range = Range::FromString(s); + s = strchr(s, '|'); + v.role = static_cast(strtol(s + 1, &s, 10)); + v.file_id = static_cast(strtol(s + 1, &s, 10)); +} +void Reflect(JsonReader &vis, DeclRef &v) { + std::string t = vis.GetString(); + char *s = const_cast(t.c_str()); + v.range = Range::FromString(s); + s = strchr(s, '|') + 1; + v.extent = Range::FromString(s); + s = strchr(s, '|'); + v.role = static_cast(strtol(s + 1, &s, 10)); + v.file_id = static_cast(strtol(s + 1, &s, 10)); +} + +void Reflect(JsonReader &vis, DataFlow &v) { + std::string t = vis.GetString(); + char *s = const_cast(t.c_str()); + v.from = strtoull(s + 1, &s, 10); + s = strchr(s, '|') + 1; + v.use.range = Range::FromString(s); + s = strchr(s, '|'); + v.use.role = static_cast(strtol(s + 1, &s, 10)); + v.use.file_id = static_cast(strtol(s + 1, &s, 10)); +} + +void Reflect(JsonWriter &vis, SymbolRef &v) { + char buf[99]; + snprintf(buf, sizeof buf, "%s|%" PRIu64 "|%d|%d", v.range.ToString().c_str(), + v.usr, int(v.kind), int(v.role)); + std::string s(buf); + Reflect(vis, s); +} +void Reflect(JsonWriter &vis, Use &v) { + char buf[99]; + snprintf(buf, sizeof buf, "%s|%d|%d", v.range.ToString().c_str(), int(v.role), + v.file_id); + std::string s(buf); + Reflect(vis, s); +} +void Reflect(JsonWriter &vis, DeclRef &v) { + char buf[99]; + snprintf(buf, sizeof buf, "%s|%s|%d|%d", v.range.ToString().c_str(), + v.extent.ToString().c_str(), int(v.role), v.file_id); + std::string s(buf); + Reflect(vis, s); +} +void Reflect(JsonWriter &vis, DataFlow &v) { + char buf[99]; + snprintf(buf, sizeof buf, "%" PRIu64 "|%s|%d|%d", v.from, + v.use.range.ToString().c_str(), int(v.use.role), v.use.file_id); + std::string s(buf); + Reflect(vis, s); +} + +void Reflect(BinaryReader &vis, SymbolRef &v) { + Reflect(vis, v.range); + Reflect(vis, v.usr); + Reflect(vis, v.kind); + Reflect(vis, v.role); +} +void Reflect(BinaryReader &vis, Use &v) { + Reflect(vis, v.range); + Reflect(vis, v.role); + Reflect(vis, v.file_id); +} +void Reflect(BinaryReader &vis, DeclRef &v) { + Reflect(vis, static_cast(v)); + Reflect(vis, v.extent); +} +void Reflect(BinaryReader &vis, DataFlow &v) { + Reflect(vis, v.from); + Reflect(vis, v.use); +} + +void Reflect(BinaryWriter &vis, SymbolRef &v) { + Reflect(vis, v.range); + Reflect(vis, v.usr); + Reflect(vis, v.kind); + Reflect(vis, v.role); +} +void Reflect(BinaryWriter &vis, Use &v) { + Reflect(vis, v.range); + Reflect(vis, v.role); + Reflect(vis, v.file_id); +} +void Reflect(BinaryWriter &vis, DeclRef &v) { + Reflect(vis, static_cast(v)); + Reflect(vis, v.extent); +} +void Reflect(BinaryWriter &vis, DataFlow &v) { + Reflect(vis, v.from); + Reflect(vis, v.use); +} + +} // namespace ccls diff --git a/src/indexer.h b/src/indexer.h deleted file mode 100644 index c671fca37..000000000 --- a/src/indexer.h +++ /dev/null @@ -1,550 +0,0 @@ -#pragma once - -#include "clang_cursor.h" -#include "clang_index.h" -#include "clang_translation_unit.h" -#include "clang_utils.h" -#include "file_consumer.h" -#include "file_contents.h" -#include "language.h" -#include "lsp.h" -#include "maybe.h" -#include "nt_string.h" -#include "performance.h" -#include "position.h" -#include "serializer.h" -#include "symbol.h" -#include "utils.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -struct IndexFile; -struct IndexType; -struct IndexFunc; -struct IndexVar; -struct QueryFile; - -using RawId = uint32_t; - -template -struct Id { - RawId id; - - // Invalid id. - Id() : id(-1) {} - explicit Id(RawId id) : id(id) {} - // Id -> Id or Id -> Id is allowed implicitly. - template ::value || - std::is_same::value, - bool>::type = false> - Id(Id o) : id(o.id) {} - template ::value || - std::is_same::value), - bool>::type = false> - explicit Id(Id o) : id(o.id) {} - - // Needed for google::dense_hash_map. - explicit operator RawId() const { return id; } - - bool HasValueForMaybe_() const { return id != RawId(-1); } - - bool operator==(const Id& o) const { return id == o.id; } - bool operator!=(const Id& o) const { return id != o.id; } - bool operator<(const Id& o) const { return id < o.id; } -}; - -namespace std { -template -struct hash> { - size_t operator()(const Id& k) const { return hash()(k.id); } -}; -} // namespace std - -template -void Reflect(TVisitor& visitor, Id& id) { - Reflect(visitor, id.id); -} - -using IndexFileId = Id; -using IndexTypeId = Id; -using IndexFuncId = Id; -using IndexVarId = Id; - -struct SymbolIdx { - Id id; - SymbolKind kind; - - bool operator==(const SymbolIdx& o) const { - return id == o.id && kind == o.kind; - } - bool operator!=(const SymbolIdx& o) const { return !(*this == o); } - bool operator<(const SymbolIdx& o) const { - if (id != o.id) - return id < o.id; - return kind < o.kind; - } -}; -MAKE_REFLECT_STRUCT(SymbolIdx, kind, id); -MAKE_HASHABLE(SymbolIdx, t.kind, t.id); - -struct Reference { - Range range; - Id id; - SymbolKind kind; - Role role; - - bool HasValueForMaybe_() const { return range.HasValueForMaybe_(); } - operator SymbolIdx() const { return {id, kind}; } - std::tuple, SymbolKind, Role> ToTuple() const { - return std::make_tuple(range, id, kind, role); - } - bool operator==(const Reference& o) const { return ToTuple() == o.ToTuple(); } - bool operator<(const Reference& o) const { return ToTuple() < o.ToTuple(); } -}; - -// |id,kind| refer to the referenced entity. -struct SymbolRef : Reference { - SymbolRef() = default; - SymbolRef(Range range, Id id, SymbolKind kind, Role role) - : Reference{range, id, kind, role} {} -}; - -// Represents an occurrence of a variable/type, |id,kind| refer to the lexical -// parent. -struct Use : Reference { - // |file| is used in Query* but not in Index* - Id file; - Use() = default; - Use(Range range, Id id, SymbolKind kind, Role role, Id file) - : Reference{range, id, kind, role}, file(file) {} -}; -// Used by |HANDLE_MERGEABLE| so only |range| is needed. -MAKE_HASHABLE(Use, t.range); - -void Reflect(Reader& visitor, Reference& value); -void Reflect(Writer& visitor, Reference& value); - -struct IndexFamily { - using FileId = Id; - using FuncId = Id; - using TypeId = Id; - using VarId = Id; - using Range = ::Range; -}; - -template -struct TypeDefDefinitionData { - // General metadata. - std::string detailed_name; - NtString hover; - NtString comments; - - // While a class/type can technically have a separate declaration/definition, - // it doesn't really happen in practice. The declaration never contains - // comments or insightful information. The user always wants to jump from - // the declaration to the definition - never the other way around like in - // functions and (less often) variables. - // - // It's also difficult to identify a `class Foo;` statement with the clang - // indexer API (it's doable using cursor AST traversal), so we don't bother - // supporting the feature. - Maybe spell; - Maybe extent; - - // Immediate parent types. - std::vector bases; - - // Types, functions, and variables defined in this type. - std::vector types; - std::vector funcs; - std::vector vars; - - typename F::FileId file; - // If set, then this is the same underlying type as the given value (ie, this - // type comes from a using or typedef statement). - Maybe alias_of; - - int16_t short_name_offset = 0; - int16_t short_name_size = 0; - lsSymbolKind kind = lsSymbolKind::Unknown; - - bool operator==(const TypeDefDefinitionData& o) const { - return detailed_name == o.detailed_name && spell == o.spell && - extent == o.extent && alias_of == o.alias_of && - bases == o.bases && types == o.types && funcs == o.funcs && - vars == o.vars && kind == o.kind && hover == o.hover && - comments == o.comments; - } - bool operator!=(const TypeDefDefinitionData& o) const { - return !(*this == o); - } - - std::string_view ShortName() const { - return std::string_view(detailed_name.c_str() + short_name_offset, - short_name_size); - } - // Used by cquery_inheritance_hierarchy.cc:Expand generic lambda - std::string_view DetailedName(bool) const { - return detailed_name; - } -}; -template -void Reflect(TVisitor& visitor, TypeDefDefinitionData& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(detailed_name); - REFLECT_MEMBER(short_name_offset); - REFLECT_MEMBER(short_name_size); - REFLECT_MEMBER(kind); - REFLECT_MEMBER(hover); - REFLECT_MEMBER(comments); - REFLECT_MEMBER(spell); - REFLECT_MEMBER(extent); - REFLECT_MEMBER(file); - REFLECT_MEMBER(alias_of); - REFLECT_MEMBER(bases); - REFLECT_MEMBER(types); - REFLECT_MEMBER(funcs); - REFLECT_MEMBER(vars); - REFLECT_MEMBER_END(); -} - -struct IndexType { - using Def = TypeDefDefinitionData; - - Usr usr; - IndexTypeId id; - - Def def; - std::vector declarations; - - // Immediate derived types. - std::vector derived; - - // Declared variables of this type. - std::vector instances; - - // Every usage, useful for things like renames. - // NOTE: Do not insert directly! Use AddUsage instead. - std::vector uses; - - IndexType() {} // For serialization. - IndexType(IndexTypeId id, Usr usr); - - bool operator<(const IndexType& other) const { return id < other.id; } -}; -MAKE_HASHABLE(IndexType, t.id); - -template -struct FuncDefDefinitionData { - // General metadata. - std::string detailed_name; - NtString hover; - NtString comments; - Maybe spell; - Maybe extent; - - // Method this method overrides. - std::vector bases; - - // Local variables or parameters. - std::vector vars; - - // Functions that this function calls. - std::vector callees; - - typename F::FileId file; - // Type which declares this one (ie, it is a method) - Maybe declaring_type; - int16_t short_name_offset = 0; - int16_t short_name_size = 0; - lsSymbolKind kind = lsSymbolKind::Unknown; - StorageClass storage = StorageClass::Invalid; - - bool operator==(const FuncDefDefinitionData& o) const { - return detailed_name == o.detailed_name && spell == o.spell && - extent == o.extent && declaring_type == o.declaring_type && - bases == o.bases && vars == o.vars && callees == o.callees && - kind == o.kind && storage == o.storage && hover == o.hover && - comments == o.comments; - } - bool operator!=(const FuncDefDefinitionData& o) const { - return !(*this == o); - } - - std::string_view ShortName() const { - return std::string_view(detailed_name.c_str() + short_name_offset, - short_name_size); - } - std::string_view DetailedName(bool params) const { - if (params) - return detailed_name; - return std::string_view(detailed_name) - .substr(0, short_name_offset + short_name_size); - } -}; - -template -void Reflect(TVisitor& visitor, FuncDefDefinitionData& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(detailed_name); - REFLECT_MEMBER(short_name_offset); - REFLECT_MEMBER(short_name_size); - REFLECT_MEMBER(kind); - REFLECT_MEMBER(storage); - REFLECT_MEMBER(hover); - REFLECT_MEMBER(comments); - REFLECT_MEMBER(spell); - REFLECT_MEMBER(extent); - REFLECT_MEMBER(file); - REFLECT_MEMBER(declaring_type); - REFLECT_MEMBER(bases); - REFLECT_MEMBER(vars); - REFLECT_MEMBER(callees); - REFLECT_MEMBER_END(); -} - -struct IndexFunc { - using Def = FuncDefDefinitionData; - - Usr usr; - IndexFuncId id; - - Def def; - - struct Declaration { - // Range of only the function name. - Use spell; - // Location of the parameter names. - std::vector param_spellings; - }; - - // Places the function is forward-declared. - std::vector declarations; - - // Methods which directly override this one. - std::vector derived; - - // Calls/usages of this function. If the call is coming from outside a - // function context then the FuncRef will not have an associated id. - // - // To get all usages, also include the ranges inside of declarations and - // def.spell. - std::vector uses; - - IndexFunc() {} // For serialization. - IndexFunc(IndexFuncId id, Usr usr) : usr(usr), id(id) {} - - bool operator<(const IndexFunc& other) const { return id < other.id; } -}; -MAKE_HASHABLE(IndexFunc, t.id); -MAKE_REFLECT_STRUCT(IndexFunc::Declaration, spell, param_spellings); - -template -struct VarDefDefinitionData { - // General metadata. - std::string detailed_name; - NtString hover; - NtString comments; - // TODO: definitions should be a list of ranges, since there can be more - // than one - when?? - Maybe spell; - Maybe extent; - - typename F::FileId file; - // Type of the variable. - Maybe type; - - // Function/type which declares this one. - int16_t short_name_offset = 0; - int16_t short_name_size = 0; - - lsSymbolKind kind = lsSymbolKind::Unknown; - // Note a variable may have instances of both |None| and |Extern| - // (declaration). - StorageClass storage = StorageClass::Invalid; - - bool is_local() const { return kind == lsSymbolKind::Variable; } - - bool operator==(const VarDefDefinitionData& o) const { - return detailed_name == o.detailed_name && spell == o.spell && - extent == o.extent && type == o.type && kind == o.kind && - storage == o.storage && hover == o.hover && comments == o.comments; - } - bool operator!=(const VarDefDefinitionData& o) const { return !(*this == o); } - - std::string_view ShortName() const { - return std::string_view(detailed_name.c_str() + short_name_offset, - short_name_size); - } - std::string DetailedName(bool qualified) const { - if (qualified) - return detailed_name; - int i = short_name_offset; - for (int paren = 0; i; i--) { - // Skip parentheses in "(anon struct)::name" - if (detailed_name[i - 1] == ')') - paren++; - else if (detailed_name[i - 1] == '(') - paren--; - else if (!(paren > 0 || isalnum(detailed_name[i - 1]) || - detailed_name[i - 1] == '_' || detailed_name[i - 1] == ':')) - break; - } - return detailed_name.substr(0, i) + detailed_name.substr(short_name_offset); - } -}; - -template -void Reflect(TVisitor& visitor, VarDefDefinitionData& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(detailed_name); - REFLECT_MEMBER(short_name_size); - REFLECT_MEMBER(short_name_offset); - REFLECT_MEMBER(hover); - REFLECT_MEMBER(comments); - REFLECT_MEMBER(spell); - REFLECT_MEMBER(extent); - REFLECT_MEMBER(file); - REFLECT_MEMBER(type); - REFLECT_MEMBER(kind); - REFLECT_MEMBER(storage); - REFLECT_MEMBER_END(); -} - -struct IndexVar { - using Def = VarDefDefinitionData; - - Usr usr; - IndexVarId id; - - Def def; - - std::vector declarations; - std::vector uses; - - IndexVar() {} // For serialization. - IndexVar(IndexVarId id, Usr usr) : usr(usr), id(id) {} - - bool operator<(const IndexVar& other) const { return id < other.id; } -}; -MAKE_HASHABLE(IndexVar, t.id); - -struct IdCache { - std::string primary_file; - std::unordered_map usr_to_type_id; - std::unordered_map usr_to_func_id; - std::unordered_map usr_to_var_id; - std::unordered_map type_id_to_usr; - std::unordered_map func_id_to_usr; - std::unordered_map var_id_to_usr; - - IdCache(const std::string& primary_file); -}; - -struct IndexInclude { - // Line that has the include directive. We don't have complete range - // information - a line is good enough for clicking. - int line = 0; - // Absolute path to the index. - std::string resolved_path; -}; - -struct IndexFile { - IdCache id_cache; - - // For both JSON and MessagePack cache files. - static const int kMajorVersion; - // For MessagePack cache files. - // JSON has good forward compatibility because field addition/deletion do not - // harm but currently no efforts have been made to make old MessagePack cache - // files accepted by newer cquery. - static const int kMinorVersion; - - std::string path; - std::vector args; - int64_t last_modification_time = 0; - LanguageId language = LanguageId::Unknown; - - // The path to the translation unit cc file which caused the creation of this - // IndexFile. When parsing a translation unit we generate many IndexFile - // instances (ie, each header has a separate one). When the user edits a - // header we need to lookup the original translation unit and reindex that. - std::string import_file; - - // Source ranges that were not processed. - std::vector skipped_by_preprocessor; - - std::vector includes; - std::vector dependencies; - std::vector types; - std::vector funcs; - std::vector vars; - - // Diagnostics found when indexing this file. Not serialized. - std::vector diagnostics_; - // File contents at the time of index. Not serialized. - std::string file_contents; - - IndexFile(const std::string& path, const std::string& contents); - - IndexTypeId ToTypeId(Usr usr); - IndexFuncId ToFuncId(Usr usr); - IndexVarId ToVarId(Usr usr); - IndexTypeId ToTypeId(const CXCursor& usr); - IndexFuncId ToFuncId(const CXCursor& usr); - IndexVarId ToVarId(const CXCursor& usr); - IndexType* Resolve(IndexTypeId id); - IndexFunc* Resolve(IndexFuncId id); - IndexVar* Resolve(IndexVarId id); - - std::string ToString(); -}; - -struct NamespaceHelper { - std::unordered_map - container_cursor_to_qualified_name; - - std::string QualifiedName(const CXIdxContainerInfo* container, - std::string_view unqualified_name); -}; - -// |import_file| is the cc file which is what gets passed to clang. -// |desired_index_file| is the (h or cc) file which has actually changed. -// |dependencies| are the existing dependencies of |import_file| if this is a -// reparse. -optional>> Parse( - Config* config, - FileConsumerSharedState* file_consumer_shared, - std::string file, - const std::vector& args, - const std::vector& file_contents, - PerformanceImportFile* perf, - ClangIndex* index, - bool dump_ast = false); -optional>> ParseWithTu( - Config* config, - FileConsumerSharedState* file_consumer_shared, - PerformanceImportFile* perf, - ClangTranslationUnit* tu, - ClangIndex* index, - const std::string& file, - const std::vector& args, - const std::vector& file_contents); - -void ConcatTypeAndName(std::string& type, const std::string& name); - -void IndexInit(); - -void ClangSanityCheck(); - -std::string GetClangVersion(); diff --git a/src/indexer.hh b/src/indexer.hh new file mode 100644 index 000000000..e6fd578d5 --- /dev/null +++ b/src/indexer.hh @@ -0,0 +1,370 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "lsp.hh" +#include "position.hh" +#include "serializer.hh" +#include "utils.hh" + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace std { +template <> struct hash { + std::size_t operator()(llvm::sys::fs::UniqueID ID) const { + size_t ret = ID.getDevice(); + ccls::hash_combine(ret, ID.getFile()); + return ret; + } +}; +} // namespace std + +namespace ccls { +using Usr = uint64_t; + +// The order matters. In FindSymbolsAtLocation, we want Var/Func ordered in +// front of others. +enum class Kind : uint8_t { Invalid, File, Type, Func, Var }; +REFLECT_UNDERLYING_B(Kind); + +enum class Role : uint16_t { + None = 0, + Declaration = 1 << 0, + Definition = 1 << 1, + Reference = 1 << 2, + Read = 1 << 3, + Write = 1 << 4, + Call = 1 << 5, + Dynamic = 1 << 6, + Address = 1 << 7, + Implicit = 1 << 8, + All = (1 << 9) - 1, +}; +REFLECT_UNDERLYING_B(Role); +inline uint16_t operator&(Role lhs, Role rhs) { + return uint16_t(lhs) & uint16_t(rhs); +} +inline Role operator|(Role lhs, Role rhs) { + return Role(uint16_t(lhs) | uint16_t(rhs)); +} + +struct SymbolIdx { + Usr usr; + Kind kind; + + bool operator==(const SymbolIdx &o) const { + return usr == o.usr && kind == o.kind; + } + bool operator<(const SymbolIdx &o) const { + return usr != o.usr ? usr < o.usr : kind < o.kind; + } +}; + +// |id,kind| refer to the referenced entity. +struct SymbolRef { + Range range; + Usr usr; + Kind kind; + Role role; + operator SymbolIdx() const { return {usr, kind}; } + std::tuple ToTuple() const { + return std::make_tuple(range, usr, kind, role); + } + bool operator==(const SymbolRef &o) const { return ToTuple() == o.ToTuple(); } + bool Valid() const { return range.Valid(); } +}; + +struct ExtentRef : SymbolRef { + Range extent; + std::tuple ToTuple() const { + return std::make_tuple(range, usr, kind, role, extent); + } + bool operator==(const ExtentRef &o) const { return ToTuple() == o.ToTuple(); } +}; + +struct Ref { + Range range; + Role role; + + bool Valid() const { return range.Valid(); } + std::tuple ToTuple() const { + return std::make_tuple(range, role); + } + bool operator==(const Ref &o) const { return ToTuple() == o.ToTuple(); } + bool operator<(const Ref &o) const { return ToTuple() < o.ToTuple(); } +}; + +// Represents an occurrence of a variable/type, |usr,kind| refer to the lexical +// parent. +struct Use : Ref { + // |file| is used in Query* but not in Index* + int file_id = -1; + bool operator==(const Use &o) const { + // lexical container info is ignored. + return range == o.range && file_id == o.file_id; + } +}; + +struct DeclRef : Use { + Range extent; +}; + +struct DataFlow { + Usr from; + Use use; + + bool operator==(const DataFlow &o) const { + return from == o.from && use == o.use; + } + bool operator<(const DataFlow &o) const { + return from != o.from ? from < o.from : use < o.use; + } +}; + +void Reflect(JsonReader &visitor, SymbolRef &value); +void Reflect(JsonReader &visitor, Use &value); +void Reflect(JsonReader &visitor, DeclRef &value); +void Reflect(JsonReader &visitor, DataFlow &value); +void Reflect(JsonWriter &visitor, SymbolRef &value); +void Reflect(JsonWriter &visitor, Use &value); +void Reflect(JsonWriter &visitor, DeclRef &value); +void Reflect(JsonWriter &visitor, DataFlow &value); +void Reflect(BinaryReader &visitor, SymbolRef &value); +void Reflect(BinaryReader &visitor, Use &value); +void Reflect(BinaryReader &visitor, DeclRef &value); +void Reflect(BinaryReader &visitor, DataFlow &value); +void Reflect(BinaryWriter &visitor, SymbolRef &value); +void Reflect(BinaryWriter &visitor, Use &value); +void Reflect(BinaryWriter &visitor, DeclRef &value); +void Reflect(BinaryWriter &visitor, DataFlow &value); + +template struct NameMixin { + std::string_view Name(bool qualified) const { + auto self = static_cast(this); + return qualified + ? std::string_view(self->detailed_name + self->qual_name_offset, + self->short_name_offset - + self->qual_name_offset + + self->short_name_size) + : std::string_view(self->detailed_name + self->short_name_offset, + self->short_name_size); + } +}; + +struct FuncDef : NameMixin { + // General metadata. + const char *detailed_name = ""; + const char *hover = ""; + const char *comments = ""; + Maybe spell; + + // Method this method overrides. + std::vector bases; + // Local variables or parameters. + std::vector vars; + // Functions that this function calls. + std::vector callees; + + int file_id = -1; // not serialized + int16_t qual_name_offset = 0; + int16_t short_name_offset = 0; + int16_t short_name_size = 0; + SymbolKind kind = SymbolKind::Unknown; + SymbolKind parent_kind = SymbolKind::Unknown; + uint8_t storage = clang::SC_None; + + std::vector GetBases() const { return bases; } +}; +REFLECT_STRUCT(FuncDef, detailed_name, hover, comments, spell, bases, vars, + callees, qual_name_offset, short_name_offset, + short_name_size, kind, parent_kind, storage); + +struct IndexFunc : NameMixin { + using Def = FuncDef; + Usr usr; + Def def; + std::vector declarations; + std::vector derived; + std::vector uses; + std::vector data_flow_into_return; +}; + +struct TypeDef : NameMixin { + const char *detailed_name = ""; + const char *hover = ""; + const char *comments = ""; + Maybe spell; + + std::vector bases; + // Types, functions, and variables defined in this type. + std::vector funcs; + std::vector types; + std::vector> vars; + + // If set, then this is the same underlying type as the given value (ie, this + // type comes from a using or typedef statement). + Usr alias_of = 0; + int file_id = -1; // not serialized + int16_t qual_name_offset = 0; + int16_t short_name_offset = 0; + int16_t short_name_size = 0; + SymbolKind kind = SymbolKind::Unknown; + SymbolKind parent_kind = SymbolKind::Unknown; + + std::vector GetBases() const { return bases; } +}; +REFLECT_STRUCT(TypeDef, detailed_name, hover, comments, spell, bases, + funcs, types, vars, alias_of, qual_name_offset, + short_name_offset, short_name_size, kind, parent_kind); + +struct IndexType { + using Def = TypeDef; + Usr usr; + Def def; + std::vector declarations; + std::vector derived; + std::vector instances; + std::vector uses; +}; + +struct VarDef : NameMixin { + // General metadata. + const char *detailed_name = ""; + const char *hover = ""; + const char *comments = ""; + Maybe spell; + + // Type of the variable. + Usr type = 0; + int file_id = -1; // not serialized + int16_t qual_name_offset = 0; + int16_t short_name_offset = 0; + int16_t short_name_size = 0; + SymbolKind kind = SymbolKind::Unknown; + SymbolKind parent_kind = SymbolKind::Unknown; + // Note a variable may have instances of both |None| and |Extern| + // (declaration). + uint8_t storage = clang::SC_None; + + bool is_local() const { + return spell && + (parent_kind == SymbolKind::Function || + parent_kind == SymbolKind::Method || + parent_kind == SymbolKind::StaticMethod || + parent_kind == SymbolKind::Constructor) && + storage == clang::SC_None; + } + + std::vector GetBases() const { return {}; } +}; +REFLECT_STRUCT(VarDef, detailed_name, hover, comments, spell, type, + qual_name_offset, short_name_offset, short_name_size, kind, + parent_kind, storage); + +struct IndexVar { + using Def = VarDef; + Usr usr; + Def def; + std::vector declarations; + std::vector uses; + std::vector data_flow_into; +}; + +struct IndexInclude { + // Line that has the include directive. We don't have complete range + // information - a line is good enough for clicking. + int line = 0; + // Absolute path to the index. + const char *resolved_path; +}; + +struct IndexFile { + // For both JSON and MessagePack cache files. + static const int kMajorVersion; + // For MessagePack cache files. + // JSON has good forward compatibility because field addition/deletion do not + // harm but currently no efforts have been made to make old MessagePack cache + // files accepted by newer ccls. + static const int kMinorVersion; + + llvm::sys::fs::UniqueID UniqueID; + std::string path; + std::vector args; + // This is unfortunately time_t as used by clang::FileEntry + int64_t mtime = 0; + LanguageId language = LanguageId::C; + + // uid2lid_and_path is used to generate lid2path, but not serialized. + std::unordered_map> + uid2lid_and_path; + std::vector> lid2path; + + // The path to the translation unit cc file which caused the creation of this + // IndexFile. When parsing a translation unit we generate many IndexFile + // instances (ie, each header has a separate one). When the user edits a + // header we need to lookup the original translation unit and reindex that. + std::string import_file; + + // Source ranges that were not processed. + std::vector skipped_ranges; + + std::vector includes; + llvm::DenseMap dependencies; + std::unordered_map usr2func; + std::unordered_map usr2type; + std::unordered_map usr2var; + + // File contents at the time of index. Not serialized. + std::string file_contents; + + IndexFile(llvm::sys::fs::UniqueID UniqueID, const std::string &path, + const std::string &contents); + + IndexFunc &ToFunc(Usr usr); + IndexType &ToType(Usr usr); + IndexVar &ToVar(Usr usr); + + std::string ToString(); +}; + +struct SemaManager; +struct WorkingFiles; +struct VFS; + +namespace idx { +void Init(); +std::vector> +Index(SemaManager *complete, WorkingFiles *wfiles, VFS *vfs, + const std::string &opt_wdir, const std::string &file, + const std::vector &args, + const std::vector> &remapped, + bool &ok); +} // namespace idx +} // namespace ccls + +MAKE_HASHABLE(ccls::SymbolRef, t.range, t.usr, t.kind, t.role); +MAKE_HASHABLE(ccls::ExtentRef, t.range, t.usr, t.kind, t.role, t.extent); +MAKE_HASHABLE(ccls::Use, t.range, t.file_id) +MAKE_HASHABLE(ccls::DeclRef, t.range, t.file_id) +MAKE_HASHABLE(ccls::DataFlow, t.from, t.use) diff --git a/src/ipc.cc b/src/ipc.cc deleted file mode 100644 index 27598672d..000000000 --- a/src/ipc.cc +++ /dev/null @@ -1,31 +0,0 @@ -#include "ipc.h" - -#include - -const char* IpcIdToString(IpcId id) { - switch (id) { - case IpcId::CancelRequest: - return "$/cancelRequest"; - case IpcId::Initialized: - return "initialized"; - case IpcId::Exit: - return "exit"; - -#define CASE(name, method) case IpcId::name: return method; - #include "methods.inc" -#undef CASE - - case IpcId::Unknown: - return "$unknown"; - } - - CQUERY_UNREACHABLE("missing IpcId string name"); -} - -BaseIpcMessage::BaseIpcMessage(IpcId method_id) : method_id(method_id) {} - -BaseIpcMessage::~BaseIpcMessage() = default; - -lsRequestId BaseIpcMessage::GetRequestId() { - return std::monostate(); -} diff --git a/src/ipc.h b/src/ipc.h deleted file mode 100644 index 55a1139a8..000000000 --- a/src/ipc.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "serializer.h" -#include "utils.h" - -#include - -using lsRequestId = std::variant; - -enum class IpcId : int { - // Language server specific requests. - CancelRequest = 0, - Initialized, - Exit, - -#define CASE(x, _) x, - #include "methods.inc" -#undef CASE - - // Internal implementation detail. - Unknown, -}; -MAKE_ENUM_HASHABLE(IpcId) -MAKE_REFLECT_TYPE_PROXY(IpcId) -const char* IpcIdToString(IpcId id); - -struct BaseIpcMessage { - const IpcId method_id; - BaseIpcMessage(IpcId method_id); - virtual ~BaseIpcMessage(); - - virtual lsRequestId GetRequestId(); - - template - T* As() { - assert(method_id == T::kIpcId); - return static_cast(this); - } -}; - -template -struct RequestMessage : public BaseIpcMessage { - // number | string, actually no null - lsRequestId id; - RequestMessage() : BaseIpcMessage(T::kIpcId) {} - - lsRequestId GetRequestId() override { return id; } -}; - -// NotificationMessage does not have |id|. -template -struct NotificationMessage : public BaseIpcMessage { - NotificationMessage() : BaseIpcMessage(T::kIpcId) {} -}; diff --git a/src/language.h b/src/language.h deleted file mode 100644 index 393423cf3..000000000 --- a/src/language.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include "serializer.h" - -// Used to identify the language at a file level. The ordering is important, as -// a file previously identified as `C`, will be changed to `Cpp` if it -// encounters a c++ declaration. -enum class LanguageId { Unknown = 0, C = 1, Cpp = 2, ObjC = 3, ObjCpp = 4 }; -MAKE_REFLECT_TYPE_PROXY(LanguageId); diff --git a/src/lex_utils.cc b/src/lex_utils.cc deleted file mode 100644 index 9ab0ef4b9..000000000 --- a/src/lex_utils.cc +++ /dev/null @@ -1,403 +0,0 @@ -#include "lex_utils.h" - -#include - -#include -#include - -// VSCode (UTF-16) disagrees with Emacs lsp-mode (UTF-8) on how to represent -// text documents. -// We use a UTF-8 iterator to approximate UTF-16 in the specification (weird). -// This is good enough and fails only for UTF-16 surrogate pairs. -int GetOffsetForPosition(lsPosition position, std::string_view content) { - size_t i = 0; - for (; position.line > 0 && i < content.size(); i++) - if (content[i] == '\n') - position.line--; - for (; position.character > 0 && i < content.size(); position.character--) - if (uint8_t(content[i++]) >= 128) { - // Skip 0b10xxxxxx - while (i < content.size() && uint8_t(content[i]) >= 128 && - uint8_t(content[i]) < 192) - i++; - } - return int(i); -} - -lsPosition CharPos(std::string_view search, - char character, - int character_offset) { - lsPosition result; - size_t index = 0; - while (index < search.size()) { - char c = search[index]; - if (c == character) - break; - if (c == '\n') { - result.line += 1; - result.character = 0; - } else { - result.character += 1; - } - ++index; - } - assert(index < search.size()); - result.character += character_offset; - return result; -} - -// TODO: eliminate |line_number| param. -optional ExtractQuotedRange(int line_number, const std::string& line) { - // Find starting and ending quote. - int start = 0; - while (start < (int)line.size()) { - char c = line[start]; - ++start; - if (c == '"' || c == '<') - break; - } - if (start == (int)line.size()) - return nullopt; - - int end = (int)line.size(); - while (end > 0) { - char c = line[end]; - if (c == '"' || c == '>') - break; - --end; - } - - if (start >= end) - return nullopt; - - return lsRange(lsPosition(line_number, start), lsPosition(line_number, end)); -} - -void LexFunctionDeclaration(const std::string& buffer_content, - lsPosition declaration_spelling, - optional type_name, - std::string* insert_text, - int* newlines_after_name) { - int name_start = GetOffsetForPosition(declaration_spelling, buffer_content); - - bool parse_return_type = true; - // We need to check if we have a return type (ctors and dtors do not). - if (type_name) { - int name_end = name_start; - while (name_end < buffer_content.size()) { - char c = buffer_content[name_end]; - if (isspace(c) || c == '(') - break; - ++name_end; - } - - std::string func_name = - buffer_content.substr(name_start, name_end - name_start); - if (func_name == *type_name || func_name == ("~" + *type_name)) - parse_return_type = false; - } - - // We need to fetch the return type. This can get complex, ie, - // - // std::vector foo(); - // - int return_start = name_start; - if (parse_return_type) { - int paren_balance = 0; - int angle_balance = 0; - bool expect_token = true; - while (return_start > 0) { - char c = buffer_content[return_start - 1]; - if (paren_balance == 0 && angle_balance == 0) { - if (isspace(c) && !expect_token) { - break; - } - if (!isspace(c)) - expect_token = false; - } - - if (c == ')') - ++paren_balance; - if (c == '(') { - --paren_balance; - expect_token = true; - } - - if (c == '>') - ++angle_balance; - if (c == '<') { - --angle_balance; - expect_token = true; - } - - return_start -= 1; - } - } - - // We need to fetch the arguments. Just scan for the next ';'. - *newlines_after_name = 0; - int end = name_start; - while (end < buffer_content.size()) { - char c = buffer_content[end]; - if (c == ';') - break; - if (c == '\n') - *newlines_after_name += 1; - ++end; - } - - std::string result; - result += buffer_content.substr(return_start, name_start - return_start); - if (type_name && !type_name->empty()) - result += *type_name + "::"; - result += buffer_content.substr(name_start, end - name_start); - TrimEndInPlace(result); - result += " {\n}"; - *insert_text = result; -} - -std::string_view LexIdentifierAroundPos(lsPosition position, - std::string_view content) { - int start = GetOffsetForPosition(position, content); - int end = start + 1; - char c; - - // We search for :: before the cursor but not after to get the qualifier. - for (; start > 0; start--) { - c = content[start - 1]; - if (isalnum(c) || c == '_') - ; - else if (c == ':' && start > 1 && content[start - 2] == ':') - start--; - else - break; - } - - for (; end < (int)content.size(); end++) - if (c = content[end], !(isalnum(c) || c == '_')) - break; - - return content.substr(start, end - start); -} - -bool SubsequenceMatchIgnoreCase(std::string_view search, std::string_view content) { - size_t j = 0; - for (size_t i = 0; i < search.size(); i++) { - char search_char = tolower(search[i]); - while (j < content.size() && tolower(content[j]) != search_char) - j++; - if (j == content.size()) - return false; - j++; - } - return true; -} - -// Find discontinous |search| in |content|. -// Return |found| and the count of skipped chars before found. -std::tuple SubsequenceCountSkip(std::string_view search, - std::string_view content) { - bool hasUppercaseLetter = std::any_of(search.begin(), search.end(), isupper); - int skip = 0; - size_t j = 0; - for (char c : search) { - while (j < content.size() && - (hasUppercaseLetter ? content[j] != c - : tolower(content[j]) != tolower(c))) - ++j, ++skip; - if (j == content.size()) - return std::make_tuple(false, skip); - ++j; - } - return std::make_tuple(true, skip); -} - -TEST_SUITE("Offset") { - TEST_CASE("past end") { - std::string content = "foo"; - int offset = GetOffsetForPosition(lsPosition(10, 10), content); - REQUIRE(offset <= content.size()); - } - - TEST_CASE("in middle of content") { - std::string content = "abcdefghijk"; - for (int i = 0; i < content.size(); ++i) { - int offset = GetOffsetForPosition(lsPosition(0, i), content); - REQUIRE(i == offset); - } - } - - TEST_CASE("at end of content") { - REQUIRE(GetOffsetForPosition(lsPosition(0, 0), "") == 0); - REQUIRE(GetOffsetForPosition(lsPosition(0, 1), "a") == 1); - } -} - -TEST_SUITE("Substring") { - TEST_CASE("match") { - // Empty string matches anything. - REQUIRE(SubsequenceMatchIgnoreCase("", "")); - REQUIRE(SubsequenceMatchIgnoreCase("", "aa")); - - // Match in start/middle/end. - REQUIRE(SubsequenceMatchIgnoreCase("a", "abbbb")); - REQUIRE(SubsequenceMatchIgnoreCase("a", "bbabb")); - REQUIRE(SubsequenceMatchIgnoreCase("a", "bbbba")); - REQUIRE(SubsequenceMatchIgnoreCase("aa", "aabbb")); - REQUIRE(SubsequenceMatchIgnoreCase("aa", "bbaab")); - REQUIRE(SubsequenceMatchIgnoreCase("aa", "bbbaa")); - - // Capitalization. - REQUIRE(SubsequenceMatchIgnoreCase("aa", "aA")); - REQUIRE(SubsequenceMatchIgnoreCase("aa", "Aa")); - REQUIRE(SubsequenceMatchIgnoreCase("aa", "AA")); - - // Token skipping. - REQUIRE(SubsequenceMatchIgnoreCase("ad", "abcd")); - REQUIRE(SubsequenceMatchIgnoreCase("ad", "ABCD")); - - // Ordering. - REQUIRE(!SubsequenceMatchIgnoreCase("ad", "dcba")); - } - - TEST_CASE("skip") { - REQUIRE(SubsequenceCountSkip("a", "a") == std::make_tuple(true, 0)); - REQUIRE(SubsequenceCountSkip("b", "a") == std::make_tuple(false, 1)); - REQUIRE(SubsequenceCountSkip("", "") == std::make_tuple(true, 0)); - REQUIRE(SubsequenceCountSkip("a", "ba") == std::make_tuple(true, 1)); - REQUIRE(SubsequenceCountSkip("aa", "aba") == std::make_tuple(true, 1)); - REQUIRE(SubsequenceCountSkip("aa", "baa") == std::make_tuple(true, 1)); - REQUIRE(SubsequenceCountSkip("aA", "aA") == std::make_tuple(true, 0)); - REQUIRE(SubsequenceCountSkip("aA", "aa") == std::make_tuple(false, 1)); - REQUIRE(SubsequenceCountSkip("incstdioh", "include ") == - std::make_tuple(true, 7)); - } -} - -TEST_SUITE("LexFunctionDeclaration") { - TEST_CASE("simple") { - std::string buffer_content = " void Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "void Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "void Type::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("ctor") { - std::string buffer_content = " Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, std::string("Foo"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "Foo::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("dtor") { - std::string buffer_content = " ~Foo(); "; - lsPosition declaration = CharPos(buffer_content, '~'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, std::string("Foo"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "Foo::~Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("complex return type") { - std::string buffer_content = " std::vector Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "std::vector Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "std::vector Type::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("extra complex return type") { - std::string buffer_content = " std::function < int() > \n Foo(); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "std::function < int() > \n Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "std::function < int() > \n Type::Foo() {\n}"); - REQUIRE(newlines_after_name == 0); - } - - TEST_CASE("parameters") { - std::string buffer_content = "void Foo(int a,\n\n int b); "; - lsPosition declaration = CharPos(buffer_content, 'F'); - std::string insert_text; - int newlines_after_name = 0; - - LexFunctionDeclaration(buffer_content, declaration, nullopt, &insert_text, - &newlines_after_name); - REQUIRE(insert_text == "void Foo(int a,\n\n int b) {\n}"); - REQUIRE(newlines_after_name == 2); - - LexFunctionDeclaration(buffer_content, declaration, std::string("Type"), - &insert_text, &newlines_after_name); - REQUIRE(insert_text == "void Type::Foo(int a,\n\n int b) {\n}"); - REQUIRE(newlines_after_name == 2); - } -} - -TEST_SUITE("LexWordAroundPos") { - TEST_CASE("edges") { - std::string content = "Foobar"; - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'F'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'o'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'b'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'a'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'r'), content) == "Foobar"); - } - - TEST_CASE("simple") { - std::string content = " Foobar "; - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'F'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'o'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'b'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'a'), content) == "Foobar"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'r'), content) == "Foobar"); - } - - TEST_CASE("underscores, numbers and ::") { - std::string content = " file:ns::_my_t5ype7 "; - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'f'), content) == "file"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 's'), content) == "ns"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, 'y'), content) == "ns::_my_t5ype7"); - } - - TEST_CASE("dot, dash, colon are skipped") { - std::string content = "1. 2- 3:"; - REQUIRE(LexIdentifierAroundPos(CharPos(content, '1'), content) == "1"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, '2'), content) == "2"); - REQUIRE(LexIdentifierAroundPos(CharPos(content, '3'), content) == "3"); - } -} diff --git a/src/lex_utils.h b/src/lex_utils.h deleted file mode 100644 index f95035e67..000000000 --- a/src/lex_utils.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "lsp.h" - -#include - -#include -#include - -// Utility method to map |position| to an offset inside of |content|. -int GetOffsetForPosition(lsPosition position, std::string_view content); -// Utility method to find a position for the given character. -lsPosition CharPos(std::string_view search, - char character, - int character_offset = 0); - -// TODO: eliminate |line_number| param. -optional ExtractQuotedRange(int line_number, const std::string& line); - -void LexFunctionDeclaration(const std::string& buffer_content, - lsPosition declaration_spelling, - optional type_name, - std::string* insert_text, - int* newlines_after_name); - -std::string_view LexIdentifierAroundPos(lsPosition position, - std::string_view content); - -// Case-insensitive subsequence matching. -bool SubsequenceMatchIgnoreCase(std::string_view search, std::string_view content); - -std::tuple SubsequenceCountSkip(std::string_view search, - std::string_view content); diff --git a/src/log.cc b/src/log.cc new file mode 100644 index 000000000..a10a4a7df --- /dev/null +++ b/src/log.cc @@ -0,0 +1,78 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "log.hh" + +#include +#include + +#include +#include +#include +#include +#include + +namespace ccls::log { +static std::mutex mtx; +FILE *file; +Verbosity verbosity; + +Message::Message(Verbosity verbosity, const char *file, int line) + : verbosity_(verbosity) { + using namespace llvm; + time_t tim = time(NULL); + struct tm t; + { + std::lock_guard lock(mtx); + t = *localtime(&tim); + } + char buf[16]; + snprintf(buf, sizeof buf, "%02d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec); + stream_ << buf; + { + SmallString<32> Name; + get_thread_name(Name); + stream_ << std::left << std::setw(13) << Name.c_str(); + } + { + const char *p = strrchr(file, '/'); + if (p) + file = p + 1; + stream_ << std::right << std::setw(15) << file << ':' << std::left + << std::setw(3) << line; + } + stream_ << ' '; + // clang-format off + switch (verbosity_) { + case Verbosity_FATAL: stream_ << 'F'; break; + case Verbosity_ERROR: stream_ << 'E'; break; + case Verbosity_WARNING: stream_ << 'W'; break; + case Verbosity_INFO: stream_ << 'I'; break; + default: stream_ << "V(" << int(verbosity_) << ')'; + } + // clang-format on + stream_ << ' '; +} + +Message::~Message() { + if (!file) + return; + std::lock_guard lock(mtx); + stream_ << '\n'; + fputs(stream_.str().c_str(), file); + if (verbosity_ == Verbosity_FATAL) + abort(); +} +} // namespace ccls::log diff --git a/src/log.hh b/src/log.hh new file mode 100644 index 000000000..64fc63d6b --- /dev/null +++ b/src/log.hh @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace ccls::log { +extern FILE* file; + +struct Voidify { + void operator&(const std::ostream&) {} +}; + +enum Verbosity { + Verbosity_FATAL = -3, + Verbosity_ERROR = -2, + Verbosity_WARNING = -1, + Verbosity_INFO = 0, +}; +extern Verbosity verbosity; + +struct Message { + std::stringstream stream_; + int verbosity_; + + Message(Verbosity verbosity, const char* file, int line); + ~Message(); +}; +} + +#define LOG_IF(v, cond) \ + !(cond) ? void(0) \ + : ccls::log::Voidify() & \ + ccls::log::Message(v, __FILE__, __LINE__).stream_ +#define LOG_S(v) \ + LOG_IF(ccls::log::Verbosity_##v, \ + ccls::log::Verbosity_##v <= ccls::log::verbosity) +#define LOG_IF_S(v, cond) \ + LOG_IF(ccls::log::Verbosity_##v, \ + (cond) && ccls::log::Verbosity_##v <= ccls::log::verbosity) +#define LOG_V(v) LOG_IF(ccls::log::Verbosity(v), v <= ccls::log::verbosity) diff --git a/src/lru_cache.h b/src/lru_cache.h deleted file mode 100644 index 5539cc784..000000000 --- a/src/lru_cache.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -// Cache that evicts old entries which have not been used recently. Implemented -// using array/linear search so this works well for small array sizes. -template -struct LruCache { - explicit LruCache(int max_entries); - - // Fetches an entry for |key|. If it does not exist, |allocator| will be - // invoked to create one. - template - std::shared_ptr Get(const TKey& key, TAllocator allocator); - // Fetches the entry for |filename| and updates it's usage so it is less - // likely to be evicted. - std::shared_ptr TryGet(const TKey& key); - // TryGetEntry, except the entry is removed from the cache. - std::shared_ptr TryTake(const TKey& key); - // Inserts an entry. Evicts the oldest unused entry if there is no space. - void Insert(const TKey& key, const std::shared_ptr& value); - - // Call |func| on existing entries. If |func| returns false iteration - // temrinates early. - template - void IterateValues(TFunc func); - - private: - // There is a global score counter, when we access an element we increase - // its score to the current global value, so it has the highest overall - // score. This means that the oldest/least recently accessed value has the - // lowest score. - // - // There is a bit of special logic to handle score overlow. - struct Entry { - uint32_t score = 0; - TKey key; - std::shared_ptr value; - bool operator<(const Entry& other) const { return score < other.score; } - }; - - void IncrementScore(); - - std::vector entries_; - int max_entries_ = 1; - uint32_t next_score_ = 0; -}; - -template -LruCache::LruCache(int max_entries) : max_entries_(max_entries) { - assert(max_entries > 0); -} - -template -template -std::shared_ptr LruCache::Get(const TKey& key, - TAllocator allocator) { - std::shared_ptr result = TryGet(key); - if (!result) - Insert(key, result = allocator()); - return result; -} - -template -std::shared_ptr LruCache::TryGet(const TKey& key) { - // Assign new score. - for (Entry& entry : entries_) { - if (entry.key == key) { - entry.score = next_score_; - IncrementScore(); - return entry.value; - } - } - - return nullptr; -} - -template -std::shared_ptr LruCache::TryTake(const TKey& key) { - for (size_t i = 0; i < entries_.size(); ++i) { - if (entries_[i].key == key) { - std::shared_ptr copy = entries_[i].value; - entries_.erase(entries_.begin() + i); - return copy; - } - } - - return nullptr; -} - -template -void LruCache::Insert(const TKey& key, - const std::shared_ptr& value) { - if ((int)entries_.size() >= max_entries_) - entries_.erase(std::min_element(entries_.begin(), entries_.end())); - - Entry entry; - entry.score = next_score_; - IncrementScore(); - entry.key = key; - entry.value = value; - entries_.push_back(entry); -} - -template -template -void LruCache::IterateValues(TFunc func) { - for (Entry& entry : entries_) { - if (!func(entry.value)) - break; - } -} - -template -void LruCache::IncrementScore() { - // Overflow. - if (++next_score_ == 0) { - std::sort(entries_.begin(), entries_.end()); - for (Entry& entry : entries_) - entry.score = next_score_++; - } -} diff --git a/src/lsp.cc b/src/lsp.cc index 47d869eab..caf7eec82 100644 --- a/src/lsp.cc +++ b/src/lsp.cc @@ -1,221 +1,75 @@ -#include "lsp.h" +/* Copyright 2017-2018 ccls Authors -#include "recorder.h" -#include "serializers/json.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#include -#include -#include + http://www.apache.org/licenses/LICENSE-2.0 -#include -#include - -MessageRegistry* MessageRegistry::instance_ = nullptr; - -lsTextDocumentIdentifier -lsVersionedTextDocumentIdentifier::AsTextDocumentIdentifier() const { - lsTextDocumentIdentifier result; - result.uri = uri; - return result; -} - -// Reads a JsonRpc message. |read| returns the next input character. -optional ReadJsonRpcContentFrom( - std::function()> read) { - // Read the content length. It is terminated by the "\r\n" sequence. - int exit_seq = 0; - std::string stringified_content_length; - while (true) { - optional opt_c = read(); - if (!opt_c) { - LOG_S(INFO) << "No more input when reading content length header"; - return nullopt; - } - char c = *opt_c; - - if (exit_seq == 0 && c == '\r') - ++exit_seq; - if (exit_seq == 1 && c == '\n') - break; - - stringified_content_length += c; - } - const char* kContentLengthStart = "Content-Length: "; - assert(StartsWith(stringified_content_length, kContentLengthStart)); - int content_length = - atoi(stringified_content_length.c_str() + strlen(kContentLengthStart)); - - // There is always a "\r\n" sequence before the actual content. - auto expect_char = [&](char expected) { - optional opt_c = read(); - return opt_c && *opt_c == expected; - }; - if (!expect_char('\r') || !expect_char('\n')) { - LOG_S(INFO) << "Unexpected token (expected \r\n sequence)"; - return nullopt; - } - - // Read content. - std::string content; - content.reserve(content_length); - for (int i = 0; i < content_length; ++i) { - optional c = read(); - if (!c) { - LOG_S(INFO) << "No more input when reading content body"; - return nullopt; - } - content += *c; - } - - RecordInput(content); +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ - return content; -} - -std::function()> MakeContentReader(std::string* content, - bool can_be_empty) { - return [content, can_be_empty]() -> optional { - if (!can_be_empty) - REQUIRE(!content->empty()); - if (content->empty()) - return nullopt; - char c = (*content)[0]; - content->erase(content->begin()); - return c; - }; -} - -TEST_SUITE("FindIncludeLine") { - TEST_CASE("ReadContentFromSource") { - auto parse_correct = [](std::string content) -> std::string { - auto reader = MakeContentReader(&content, false /*can_be_empty*/); - auto got = ReadJsonRpcContentFrom(reader); - REQUIRE(got); - return got.value(); - }; +#include "lsp.hh" - auto parse_incorrect = [](std::string content) -> optional { - auto reader = MakeContentReader(&content, true /*can_be_empty*/); - return ReadJsonRpcContentFrom(reader); - }; +#include "log.hh" - REQUIRE(parse_correct("Content-Length: 0\r\n\r\n") == ""); - REQUIRE(parse_correct("Content-Length: 1\r\n\r\na") == "a"); - REQUIRE(parse_correct("Content-Length: 4\r\n\r\nabcd") == "abcd"); +#include - REQUIRE(parse_incorrect("ggg") == optional()); - REQUIRE(parse_incorrect("Content-Length: 0\r\n") == - optional()); - REQUIRE(parse_incorrect("Content-Length: 5\r\n\r\nab") == - optional()); - } -} - -optional ReadCharFromStdinBlocking() { - // We do not use std::cin because it does not read bytes once stuck in - // cin.bad(). We can call cin.clear() but C++ iostream has other annoyance - // like std::{cin,cout} is tied by default, which causes undesired cout flush - // for cin operations. - int c = getchar(); - if (c >= 0) - return c; - return nullopt; -} +#include +#include -optional MessageRegistry::ReadMessageFromStdin( - std::unique_ptr* message) { - optional content = - ReadJsonRpcContentFrom(&ReadCharFromStdinBlocking); - if (!content) { - LOG_S(ERROR) << "Failed to read JsonRpc input; exiting"; - exit(1); +namespace ccls { +void Reflect(JsonReader &vis, RequestId &v) { + if (vis.m->IsInt64()) { + v.type = RequestId::kInt; + v.value = int(vis.m->GetInt64()); + } else if (vis.m->IsInt()) { + v.type = RequestId::kInt; + v.value = vis.m->GetInt(); + } else if (vis.m->IsString()) { + v.type = RequestId::kString; + v.value = atoll(vis.m->GetString()); + } else { + v.type = RequestId::kNone; + v.value = -1; } - - rapidjson::Document document; - document.Parse(content->c_str(), content->length()); - assert(!document.HasParseError()); - - JsonReader json_reader{&document}; - return Parse(json_reader, message); } -optional MessageRegistry::Parse( - Reader& visitor, - std::unique_ptr* message) { - if (!visitor.HasMember("jsonrpc") || - std::string(visitor["jsonrpc"]->GetString()) != "2.0") { - LOG_S(FATAL) << "Bad or missing jsonrpc version"; - exit(1); - } - - std::string method; - ReflectMember(visitor, "method", method); - - if (allocators.find(method) == allocators.end()) - return std::string("Unable to find registered handler for method '") + - method + "'"; - - Allocator& allocator = allocators[method]; - try { - allocator(visitor, message); - return nullopt; - } catch (std::invalid_argument& e) { - // *message is partially deserialized but some field (e.g. |id|) are likely - // available. - return std::string("Fail to parse '") + method + "' " + - static_cast(visitor).GetPath() + ", expected " + - e.what(); +void Reflect(JsonWriter &visitor, RequestId &value) { + switch (value.type) { + case RequestId::kNone: + visitor.Null(); + break; + case RequestId::kInt: + visitor.Int(value.value); + break; + case RequestId::kString: + auto s = std::to_string(value.value); + visitor.String(s.c_str(), s.size()); + break; } } -MessageRegistry* MessageRegistry::instance() { - if (!instance_) - instance_ = new MessageRegistry(); - - return instance_; -} - -lsBaseOutMessage::~lsBaseOutMessage() = default; - -void lsBaseOutMessage::Write(std::ostream& out) { - rapidjson::StringBuffer output; - rapidjson::Writer writer(output); - JsonWriter json_writer{&writer}; - ReflectWriter(json_writer); - - out << "Content-Length: " << output.GetSize() << "\r\n\r\n" - << output.GetString(); - out.flush(); -} - -void lsResponseError::Write(Writer& visitor) { - auto& value = *this; - int code2 = static_cast(this->code); - - visitor.StartObject(); - REFLECT_MEMBER2("code", code2); - REFLECT_MEMBER(message); - visitor.EndObject(); -} - -lsDocumentUri lsDocumentUri::FromPath(const std::string& path) { - lsDocumentUri result; +DocumentUri DocumentUri::FromPath(const std::string &path) { + DocumentUri result; result.SetPath(path); return result; } -lsDocumentUri::lsDocumentUri() {} - -bool lsDocumentUri::operator==(const lsDocumentUri& other) const { +bool DocumentUri::operator==(const DocumentUri &other) const { return raw_uri == other.raw_uri; } -void lsDocumentUri::SetPath(const std::string& path) { +void DocumentUri::SetPath(const std::string &path) { // file:///c%3A/Users/jacob/Desktop/superindex/indexer/full_tests raw_uri = path; size_t index = raw_uri.find(":"); - if (index == 1) { // widows drive letters must always be 1 char + if (index == 1) { // widows drive letters must always be 1 char raw_uri.replace(raw_uri.begin() + index, raw_uri.begin() + index + 1, "%3A"); } @@ -251,11 +105,16 @@ void lsDocumentUri::SetPath(const std::string& path) { raw_uri = std::move(t); } -std::string lsDocumentUri::GetPath() const { - if (raw_uri.compare(0, 8, "file:///")) +std::string DocumentUri::GetPath() const { + if (raw_uri.compare(0, 7, "file://")) { + LOG_S(WARNING) + << "Received potentially bad URI (not starting with file://): " + << raw_uri; return raw_uri; + } std::string ret; #ifdef _WIN32 + // Skipping the initial "/" on Windows size_t i = 8; #else size_t i = 7; @@ -268,58 +127,18 @@ std::string lsDocumentUri::GetPath() const { ret.push_back(from_hex(raw_uri[i + 1]) * 16 + from_hex(raw_uri[i + 2])); i += 2; } else - ret.push_back(raw_uri[i] == '\\' ? '/' : raw_uri[i]); + ret.push_back(raw_uri[i]); + } +#ifdef _WIN32 + std::replace(ret.begin(), ret.end(), '\\', '/'); + if (ret.size() > 1 && ret[0] >= 'a' && ret[0] <= 'z' && ret[1] == ':') { + ret[0] = toupper(ret[0]); } +#endif return ret; } -lsPosition::lsPosition() {} -lsPosition::lsPosition(int line, int character) - : line(line), character(character) {} - -bool lsPosition::operator==(const lsPosition& other) const { - return line == other.line && character == other.character; -} - -bool lsPosition::operator<(const lsPosition& other) const { - return line != other.line ? line < other.line : character < other.character; -} - -std::string lsPosition::ToString() const { +std::string Position::ToString() const { return std::to_string(line) + ":" + std::to_string(character); } -const lsPosition lsPosition::kZeroPosition = lsPosition(); - -lsRange::lsRange() {} -lsRange::lsRange(lsPosition start, lsPosition end) : start(start), end(end) {} - -bool lsRange::operator==(const lsRange& o) const { - return start == o.start && end == o.end; -} - -bool lsRange::operator<(const lsRange& o) const { - return !(start == o.start) ? start < o.start : end < o.end; -} - -lsLocation::lsLocation() {} -lsLocation::lsLocation(lsDocumentUri uri, lsRange range) - : uri(uri), range(range) {} - -bool lsLocation::operator==(const lsLocation& o) const { - return uri == o.uri && range == o.range; -} - -bool lsLocation::operator<(const lsLocation& o) const { - return std::make_tuple(uri.raw_uri, range) < - std::make_tuple(o.uri.raw_uri, o.range); -} - -bool lsTextEdit::operator==(const lsTextEdit& that) { - return range == that.range && newText == that.newText; -} - -std::string Out_ShowLogMessage::method() { - if (display_type == DisplayType::Log) - return "window/logMessage"; - return "window/showMessage"; -} +} // namespace ccls diff --git a/src/lsp.h b/src/lsp.h deleted file mode 100644 index b44024cea..000000000 --- a/src/lsp.h +++ /dev/null @@ -1,410 +0,0 @@ -#pragma once - -#include "config.h" -#include "ipc.h" -#include "serializer.h" -#include "utils.h" - -#include -#include - -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////// OUTGOING MESSAGES ///////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// - -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////// INCOMING MESSAGES ///////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// - -#define REGISTER_IPC_MESSAGE(type) \ - static MessageRegistryRegister type##message_handler_instance_; - -struct MessageRegistry { - static MessageRegistry* instance_; - static MessageRegistry* instance(); - - using Allocator = - std::function*)>; - std::unordered_map allocators; - - optional ReadMessageFromStdin( - std::unique_ptr* message); - optional Parse(Reader& visitor, - std::unique_ptr* message); -}; - -template -struct MessageRegistryRegister { - MessageRegistryRegister() { - std::string method_name = IpcIdToString(T::kIpcId); - MessageRegistry::instance()->allocators[method_name] = - [](Reader& visitor, std::unique_ptr* message) { - *message = std::make_unique(); - // Reflect may throw and *message will be partially deserialized. - Reflect(visitor, static_cast(**message)); - }; - } -}; - -struct lsBaseOutMessage { - virtual ~lsBaseOutMessage(); - virtual void ReflectWriter(Writer&) = 0; - - // Send the message to the language client by writing it to stdout. - void Write(std::ostream& out); -}; - -template -struct lsOutMessage : lsBaseOutMessage { - // All derived types need to reflect on the |jsonrpc| member. - std::string jsonrpc = "2.0"; - - void ReflectWriter(Writer& writer) override { - Reflect(writer, static_cast(*this)); - } -}; - -struct lsResponseError { - enum class lsErrorCodes : int { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - serverErrorStart = -32099, - serverErrorEnd = -32000, - ServerNotInitialized = -32002, - UnknownErrorCode = -32001, - RequestCancelled = -32800, - }; - - lsErrorCodes code; - // Short description. - std::string message; - - void Write(Writer& visitor); -}; - -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -////////////////////////////// PRIMITIVE TYPES ////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// - -struct lsDocumentUri { - static lsDocumentUri FromPath(const std::string& path); - - lsDocumentUri(); - bool operator==(const lsDocumentUri& other) const; - - void SetPath(const std::string& path); - std::string GetPath() const; - - std::string raw_uri; -}; -MAKE_HASHABLE(lsDocumentUri, t.raw_uri); - -template -void Reflect(TVisitor& visitor, lsDocumentUri& value) { - Reflect(visitor, value.raw_uri); -} - -struct lsPosition { - lsPosition(); - lsPosition(int line, int character); - - bool operator==(const lsPosition& other) const; - bool operator<(const lsPosition& other) const; - - std::string ToString() const; - - // Note: these are 0-based. - int line = 0; - int character = 0; - static const lsPosition kZeroPosition; -}; -MAKE_HASHABLE(lsPosition, t.line, t.character); -MAKE_REFLECT_STRUCT(lsPosition, line, character); - -struct lsRange { - lsRange(); - lsRange(lsPosition start, lsPosition end); - - bool operator==(const lsRange& other) const; - bool operator<(const lsRange& other) const; - - lsPosition start; - lsPosition end; -}; -MAKE_HASHABLE(lsRange, t.start, t.end); -MAKE_REFLECT_STRUCT(lsRange, start, end); - -struct lsLocation { - lsLocation(); - lsLocation(lsDocumentUri uri, lsRange range); - - bool operator==(const lsLocation& other) const; - bool operator<(const lsLocation& o) const; - - lsDocumentUri uri; - lsRange range; -}; -MAKE_HASHABLE(lsLocation, t.uri, t.range); -MAKE_REFLECT_STRUCT(lsLocation, uri, range); - -enum class lsSymbolKind : uint8_t { - Unknown = 0, - - File = 1, - Module = 2, - Namespace = 3, - Package = 4, - Class = 5, - Method = 6, - Property = 7, - Field = 8, - Constructor = 9, - Enum = 10, - Interface = 11, - Function = 12, - Variable = 13, - Constant = 14, - String = 15, - Number = 16, - Boolean = 17, - Array = 18, - Object = 19, - Key = 20, - Null = 21, - EnumMember = 22, - Struct = 23, - Event = 24, - Operator = 25, - - // For C++, this is interpreted as "template parameter" (including - // non-type template parameters). - TypeParameter = 26, - - // cquery extensions - // See also https://github.com/Microsoft/language-server-protocol/issues/344 - // for new SymbolKind clang/Index/IndexSymbol.h clang::index::SymbolKind - TypeAlias = 252, - Parameter = 253, - StaticMethod = 254, - Macro = 255, -}; -MAKE_REFLECT_TYPE_PROXY(lsSymbolKind); - -// cquery extension -struct lsLocationEx : lsLocation { - optional containerName; - optional parentKind; - // Avoid circular dependency on symbol.h - optional role; -}; -MAKE_REFLECT_STRUCT(lsLocationEx, uri, range, containerName, parentKind, role); - -template -struct lsCommand { - // Title of the command (ie, 'save') - std::string title; - // Actual command identifier. - std::string command; - // Arguments to run the command with. - // **NOTE** This must be serialized as an array. Use - // MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY. - T arguments; -}; -template -void Reflect(TVisitor& visitor, lsCommand& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(title); - REFLECT_MEMBER(command); - REFLECT_MEMBER(arguments); - REFLECT_MEMBER_END(); -} - -template -struct lsCodeLens { - // The range in which this code lens is valid. Should only span a single line. - lsRange range; - // The command this code lens represents. - optional> command; - // A data entry field that is preserved on a code lens item between - // a code lens and a code lens resolve request. - TData data; -}; -template -void Reflect(TVisitor& visitor, lsCodeLens& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(range); - REFLECT_MEMBER(command); - REFLECT_MEMBER(data); - REFLECT_MEMBER_END(); -} - -struct lsTextDocumentIdentifier { - lsDocumentUri uri; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentIdentifier, uri); - -struct lsVersionedTextDocumentIdentifier { - lsDocumentUri uri; - // The version number of this document. number | null - std::variant version; - - lsTextDocumentIdentifier AsTextDocumentIdentifier() const; -}; -MAKE_REFLECT_STRUCT(lsVersionedTextDocumentIdentifier, uri, version); - -struct lsTextDocumentPositionParams { - // The text document. - lsTextDocumentIdentifier textDocument; - - // The position inside the text document. - lsPosition position; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentPositionParams, textDocument, position); - -struct lsTextEdit { - // The range of the text document to be manipulated. To insert - // text into a document create a range where start === end. - lsRange range; - - // The string to be inserted. For delete operations use an - // empty string. - std::string newText; - - bool operator==(const lsTextEdit& that); -}; -MAKE_REFLECT_STRUCT(lsTextEdit, range, newText); - -struct lsTextDocumentItem { - // The text document's URI. - lsDocumentUri uri; - - // The text document's language identifier. - std::string languageId; - - // The version number of this document (it will strictly increase after each - // change, including undo/redo). - int version; - - // The content of the opened text document. - std::string text; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentItem, uri, languageId, version, text); - -struct lsTextDocumentEdit { - // The text document to change. - lsVersionedTextDocumentIdentifier textDocument; - - // The edits to be applied. - std::vector edits; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentEdit, textDocument, edits); - -struct lsWorkspaceEdit { - // Holds changes to existing resources. - // changes ? : { [uri:string]: TextEdit[]; }; - // std::unordered_map> changes; - - // An array of `TextDocumentEdit`s to express changes to specific a specific - // version of a text document. Whether a client supports versioned document - // edits is expressed via `WorkspaceClientCapabilites.versionedWorkspaceEdit`. - std::vector documentChanges; -}; -MAKE_REFLECT_STRUCT(lsWorkspaceEdit, documentChanges); - -struct lsFormattingOptions { - // Size of a tab in spaces. - int tabSize; - // Prefer spaces over tabs. - bool insertSpaces; -}; -MAKE_REFLECT_STRUCT(lsFormattingOptions, tabSize, insertSpaces); - -// Cancel an existing request. -struct Ipc_CancelRequest : public RequestMessage { - static const IpcId kIpcId = IpcId::CancelRequest; -}; -MAKE_REFLECT_STRUCT(Ipc_CancelRequest, id); - -// MarkedString can be used to render human readable text. It is either a -// markdown string or a code-block that provides a language and a code snippet. -// The language identifier is sematically equal to the optional language -// identifier in fenced code blocks in GitHub issues. See -// https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting -// -// The pair of a language and a value is an equivalent to markdown: -// ```${language} -// ${value} -// ``` -// -// Note that markdown strings will be sanitized - that means html will be -// escaped. -struct lsMarkedString1 { - std::string_view language; - std::string_view value; -}; -using lsMarkedString = std::variant; -MAKE_REFLECT_STRUCT(lsMarkedString1, language, value); - -struct lsTextDocumentContentChangeEvent { - // The range of the document that changed. - optional range; - // The length of the range that got replaced. - optional rangeLength; - // The new text of the range/document. - std::string text; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentContentChangeEvent, range, rangeLength, text); - -struct lsTextDocumentDidChangeParams { - lsVersionedTextDocumentIdentifier textDocument; - std::vector contentChanges; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentDidChangeParams, - textDocument, - contentChanges); - -// Show a message to the user. -enum class lsMessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 }; -MAKE_REFLECT_TYPE_PROXY(lsMessageType) -struct Out_ShowLogMessageParams { - lsMessageType type = lsMessageType::Error; - std::string message; -}; -MAKE_REFLECT_STRUCT(Out_ShowLogMessageParams, type, message); -struct Out_ShowLogMessage : public lsOutMessage { - enum class DisplayType { Show, Log }; - DisplayType display_type = DisplayType::Show; - - std::string method(); - Out_ShowLogMessageParams params; -}; -template -void Reflect(TVisitor& visitor, Out_ShowLogMessage& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(jsonrpc); - std::string method = value.method(); - REFLECT_MEMBER2("method", method); - REFLECT_MEMBER(params); - REFLECT_MEMBER_END(); -} - -struct Out_LocationList : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_LocationList, jsonrpc, id, result); diff --git a/src/lsp.hh b/src/lsp.hh new file mode 100644 index 000000000..57854804f --- /dev/null +++ b/src/lsp.hh @@ -0,0 +1,236 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "config.hh" +#include "serializer.hh" +#include "utils.hh" + +#include + +#include +#include + +namespace ccls { +struct RequestId { + // The client can send the request id as an int or a string. We should output + // the same format we received. + enum Type { kNone, kInt, kString }; + Type type = kNone; + + int value = -1; + + bool Valid() const { return type != kNone; } +}; +void Reflect(JsonReader &visitor, RequestId &value); +void Reflect(JsonWriter &visitor, RequestId &value); + +struct InMessage { + RequestId id; + std::string method; + std::unique_ptr message; + std::unique_ptr document; +}; + +enum class ErrorCode { + // Defined by JSON RPC + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + serverErrorStart = -32099, + serverErrorEnd = -32000, + ServerNotInitialized = -32002, + UnknownErrorCode = -32001, + + // Defined by the protocol. + RequestCancelled = -32800, +}; + +struct ResponseError { + ErrorCode code; + std::string message; +}; + +constexpr char ccls_xref[] = "ccls.xref"; +constexpr char window_showMessage[] = "window/showMessage"; + +struct DocumentUri { + static DocumentUri FromPath(const std::string &path); + + bool operator==(const DocumentUri &other) const; + + void SetPath(const std::string &path); + std::string GetPath() const; + + std::string raw_uri; +}; + +struct Position { + int line = 0; + int character = 0; + bool operator==(const Position &o) const { + return line == o.line && character == o.character; + } + bool operator<(const Position &o) const { + return line != o.line ? line < o.line : character < o.character; + } + bool operator<=(const Position &o) const { + return line != o.line ? line < o.line : character <= o.character; + } + std::string ToString() const; +}; + +struct lsRange { + Position start; + Position end; + bool operator==(const lsRange &o) const { + return start == o.start && end == o.end; + } + bool operator<(const lsRange &o) const { + return !(start == o.start) ? start < o.start : end < o.end; + } + bool Includes(const lsRange &o) const { + return start <= o.start && o.end <= end; + } +}; + +struct Location { + DocumentUri uri; + lsRange range; + bool operator==(const Location &o) const { + return uri == o.uri && range == o.range; + } + bool operator<(const Location &o) const { + return !(uri.raw_uri == o.uri.raw_uri) ? uri.raw_uri < o.uri.raw_uri + : range < o.range; + } +}; + +enum class SymbolKind : uint8_t { + Unknown = 0, + + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + + // For C++, this is interpreted as "template parameter" (including + // non-type template parameters). + TypeParameter = 26, + + // ccls extensions + // See also https://github.com/Microsoft/language-server-protocol/issues/344 + // for new SymbolKind clang/Index/IndexSymbol.h clang::index::SymbolKind + TypeAlias = 252, + Parameter = 253, + StaticMethod = 254, + Macro = 255, +}; + +struct SymbolInformation { + std::string_view name; + SymbolKind kind; + Location location; + std::optional containerName; +}; + +struct TextDocumentIdentifier { + DocumentUri uri; +}; + +struct VersionedTextDocumentIdentifier { + DocumentUri uri; + // The version number of this document. number | null + std::optional version; +}; + +struct TextEdit { + lsRange range; + std::string newText; +}; + +struct TextDocumentItem { + DocumentUri uri; + std::string languageId; + int version; + std::string text; +}; + +struct TextDocumentContentChangeEvent { + // The range of the document that changed. + std::optional range; + // The length of the range that got replaced. + std::optional rangeLength; + // The new text of the range/document. + std::string text; +}; + +struct TextDocumentDidChangeParam { + VersionedTextDocumentIdentifier textDocument; + std::vector contentChanges; +}; + +struct WorkspaceFolder { + DocumentUri uri; + std::string name; +}; + +enum class MessageType : int { Error = 1, Warning = 2, Info = 3, Log = 4 }; +REFLECT_UNDERLYING(MessageType) + +struct Diagnostic { + lsRange range; + int severity = 0; + int code = 0; + std::string source = "ccls"; + std::string message; + std::vector fixits_; +}; + +struct ShowMessageParam { + MessageType type = MessageType::Error; + std::string message; +}; + +// Used to identify the language at a file level. The ordering is important, as +// a file previously identified as `C`, will be changed to `Cpp` if it +// encounters a c++ declaration. +enum class LanguageId { Unknown = -1, C = 0, Cpp = 1, ObjC = 2, ObjCpp = 3 }; + +} // namespace ccls diff --git a/src/lsp_code_action.h b/src/lsp_code_action.h deleted file mode 100644 index 30c049a86..000000000 --- a/src/lsp_code_action.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include "lsp.h" - -// codeAction -struct CommandArgs { - lsDocumentUri textDocumentUri; - std::vector edits; -}; -MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY(CommandArgs, textDocumentUri, edits); - -// codeLens -struct lsCodeLensUserData {}; -MAKE_REFLECT_EMPTY_STRUCT(lsCodeLensUserData); - -struct lsCodeLensCommandArguments { - lsDocumentUri uri; - lsPosition position; - std::vector locations; -}; - -// FIXME Don't use array in vscode-cquery -inline void Reflect(Writer& visitor, lsCodeLensCommandArguments& value) { - visitor.StartArray(3); - Reflect(visitor, value.uri); - Reflect(visitor, value.position); - Reflect(visitor, value.locations); - visitor.EndArray(); -} - -inline void Reflect(Reader& visitor, lsCodeLensCommandArguments& value) { - int i = 0; - visitor.IterArray([&](Reader& visitor) { - switch (i++) { - case 0: - Reflect(visitor, value.uri); - break; - case 1: - Reflect(visitor, value.position); - break; - case 2: - Reflect(visitor, value.locations); - break; - } - }); -} diff --git a/src/lsp_completion.h b/src/lsp_completion.h deleted file mode 100644 index e06b4bd91..000000000 --- a/src/lsp_completion.h +++ /dev/null @@ -1,138 +0,0 @@ -#pragma once -#include "lsp.h" - -// The kind of a completion entry. -enum class lsCompletionItemKind { - Text = 1, - Method = 2, - Function = 3, - Constructor = 4, - Field = 5, - Variable = 6, - Class = 7, - Interface = 8, - Module = 9, - Property = 10, - Unit = 11, - Value = 12, - Enum = 13, - Keyword = 14, - Snippet = 15, - Color = 16, - File = 17, - Reference = 18, - Folder = 19, - EnumMember = 20, - Constant = 21, - Struct = 22, - Event = 23, - Operator = 24, - TypeParameter = 25, -}; -MAKE_REFLECT_TYPE_PROXY(lsCompletionItemKind); - -// Defines whether the insert text in a completion item should be interpreted as -// plain text or a snippet. -enum class lsInsertTextFormat { - // The primary text to be inserted is treated as a plain string. - PlainText = 1, - - // The primary text to be inserted is treated as a snippet. - // - // A snippet can define tab stops and placeholders with `$1`, `$2` - // and `${3:foo}`. `$0` defines the final tab stop, it defaults to - // the end of the snippet. Placeholders with equal identifiers are linked, - // that is typing in one will update others too. - // - // See also: - // https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/snippet/common/snippet.md - Snippet = 2 -}; -MAKE_REFLECT_TYPE_PROXY(lsInsertTextFormat); - -struct lsCompletionItem { - // A set of function parameters. Used internally for signature help. Not sent - // to vscode. - std::vector parameters_; - - // The label of this completion item. By default - // also the text that is inserted when selecting - // this completion. - std::string label; - - // The kind of this completion item. Based of the kind - // an icon is chosen by the editor. - lsCompletionItemKind kind = lsCompletionItemKind::Text; - - // A human-readable string with additional information - // about this item, like type or symbol information. - std::string detail; - - // A human-readable string that represents a doc-comment. - optional documentation; - - // Internal information to order candidates. - bool found_; - std::string::size_type skip_; - unsigned priority_; - - // Use <> or "" by default as include path. - bool use_angle_brackets_ = false; - - // A string that shoud be used when comparing this item - // with other items. When `falsy` the label is used. - std::string sortText; - - // A string that should be used when filtering a set of - // completion items. When `falsy` the label is used. - optional filterText; - - // A string that should be inserted a document when selecting - // this completion. When `falsy` the label is used. - std::string insertText; - - // The format of the insert text. The format applies to both the `insertText` - // property and the `newText` property of a provided `textEdit`. - lsInsertTextFormat insertTextFormat = lsInsertTextFormat::PlainText; - - // An edit which is applied to a document when selecting this completion. When - // an edit is provided the value of `insertText` is ignored. - // - // *Note:* The range of the edit must be a single line range and it must - // contain the position at which completion has been requested. - optional textEdit; - - // An optional array of additional text edits that are applied when - // selecting this completion. Edits must not overlap with the main edit - // nor with themselves. - // std::vector additionalTextEdits; - - // An optional command that is executed *after* inserting this completion. - // *Note* that additional modifications to the current document should be - // described with the additionalTextEdits-property. Command command; - - // An data entry field that is preserved on a completion item between - // a completion and a completion resolve request. - // data ? : any - - // Use this helper to figure out what content the completion item will insert - // into the document, as it could live in either |textEdit|, |insertText|, or - // |label|. - const std::string& InsertedContent() const { - if (textEdit) - return textEdit->newText; - if (!insertText.empty()) - return insertText; - return label; - } -}; -MAKE_REFLECT_STRUCT(lsCompletionItem, - label, - kind, - detail, - documentation, - sortText, - insertText, - filterText, - insertTextFormat, - textEdit); diff --git a/src/lsp_diagnostic.cc b/src/lsp_diagnostic.cc deleted file mode 100644 index e6a000d76..000000000 --- a/src/lsp_diagnostic.cc +++ /dev/null @@ -1,5 +0,0 @@ -#include "lsp_diagnostic.h" - -#include "match.h" -#include "queue_manager.h" -#include "working_files.h" diff --git a/src/lsp_diagnostic.h b/src/lsp_diagnostic.h deleted file mode 100644 index 000980cdb..000000000 --- a/src/lsp_diagnostic.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include "lsp.h" - -enum class lsDiagnosticSeverity { - // Reports an error. - Error = 1, - // Reports a warning. - Warning = 2, - // Reports an information. - Information = 3, - // Reports a hint. - Hint = 4 -}; -MAKE_REFLECT_TYPE_PROXY(lsDiagnosticSeverity); - -struct lsDiagnostic { - // The range at which the message applies. - lsRange range; - - // The diagnostic's severity. Can be omitted. If omitted it is up to the - // client to interpret diagnostics as error, warning, info or hint. - optional severity; - - // The diagnostic's code. Can be omitted. - int code = 0; - - // A human-readable string describing the source of this - // diagnostic, e.g. 'typescript' or 'super lint'. - std::string source = "cquery"; - - // The diagnostic's message. - std::string message; - - // Non-serialized set of fixits. - std::vector fixits_; -}; -MAKE_REFLECT_STRUCT(lsDiagnostic, range, severity, source, message); - -enum class lsErrorCodes { - // Defined by JSON RPC - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - serverErrorStart = -32099, - serverErrorEnd = -32000, - ServerNotInitialized = -32002, - UnknownErrorCode = -32001, - - // Defined by the protocol. - RequestCancelled = -32800, -}; -MAKE_REFLECT_TYPE_PROXY(lsErrorCodes); -struct Out_Error : public lsOutMessage { - struct lsResponseError { - // A number indicating the error type that occurred. - lsErrorCodes code; - - // A string providing a short description of the error. - std::string message; - - // A Primitive or Structured value that contains additional - // information about the error. Can be omitted. - // optional data; - }; - - lsRequestId id; - - // The error object in case a request fails. - lsResponseError error; -}; -MAKE_REFLECT_STRUCT(Out_Error::lsResponseError, code, message); -MAKE_REFLECT_STRUCT(Out_Error, jsonrpc, id, error); - -// Diagnostics -struct Out_TextDocumentPublishDiagnostics - : public lsOutMessage { - struct Params { - // The URI for which diagnostic information is reported. - lsDocumentUri uri; - - // An array of diagnostic information items. - std::vector diagnostics; - }; - - Params params; -}; -template -void Reflect(TVisitor& visitor, Out_TextDocumentPublishDiagnostics& value) { - std::string method = "textDocument/publishDiagnostics"; - REFLECT_MEMBER_START(); - REFLECT_MEMBER(jsonrpc); - REFLECT_MEMBER2("method", method); - REFLECT_MEMBER(params); - REFLECT_MEMBER_END(); -} -MAKE_REFLECT_STRUCT(Out_TextDocumentPublishDiagnostics::Params, - uri, - diagnostics); diff --git a/src/main.cc b/src/main.cc new file mode 100644 index 000000000..9a4ea630b --- /dev/null +++ b/src/main.cc @@ -0,0 +1,160 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "log.hh" +#include "pipeline.hh" +#include "platform.hh" +#include "serializer.hh" +#include "test.hh" +#include "working_files.hh" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +using namespace ccls; +using namespace llvm; +using namespace llvm::cl; + +namespace ccls { +std::string g_init_options; +} + +namespace { +OptionCategory C("ccls options"); + +opt opt_help("h", desc("Alias for -help"), cat(C)); +opt opt_verbose("v", desc("verbosity"), init(0), cat(C)); +opt opt_test_index("test-index", ValueOptional, init("!"), + desc("run index tests"), cat(C)); + +opt opt_index("index", + desc("standalone mode: index a project and exit"), + value_desc("root"), cat(C)); +opt opt_init("init", desc("extra initialization options in JSON"), + cat(C)); +opt opt_log_file("log-file", desc("log"), value_desc("filename"), + cat(C)); +opt opt_log_file_append("log-file-append", desc("log"), + value_desc("filename"), cat(C)); + +void CloseLog() { fclose(ccls::log::file); } + +} // namespace + +int main(int argc, char **argv) { + TraceMe(); + sys::PrintStackTraceOnErrorSignal(argv[0]); + cl::SetVersionPrinter([](raw_ostream &OS) { + OS << clang::getClangToolFullVersion("ccls") << "\n"; + }); + + for (auto &I : TopLevelSubCommand->OptionsMap) + if (I.second->Category != &C) + I.second->setHiddenFlag(ReallyHidden); + + ParseCommandLineOptions(argc, argv, + "C/C++/Objective-C language server\n\n" + "See more on https://github.com/MaskRay/ccls/wiki"); + + if (opt_help) { + PrintHelpMessage(); + return 0; + } + + pipeline::Init(); + const char *env = getenv("CCLS_CRASH_RECOVERY"); + if (!env || strcmp(env, "0") != 0) + CrashRecoveryContext::Enable(); + + bool language_server = true; + + if (opt_log_file.size() || opt_log_file_append.size()) { + ccls::log::file = opt_log_file.size() + ? fopen(opt_log_file.c_str(), "wb") + : fopen(opt_log_file_append.c_str(), "ab"); + if (!ccls::log::file) { + fprintf( + stderr, "failed to open %s\n", + (opt_log_file.size() ? opt_log_file : opt_log_file_append).c_str()); + return 2; + } + setbuf(ccls::log::file, NULL); + atexit(CloseLog); + } + + if (opt_test_index != "!") { + language_server = false; + if (!ccls::RunIndexTests(opt_test_index, sys::Process::StandardInIsUserInput())) + return 1; + } + + if (language_server) { + if (!opt_init.empty()) { + // We check syntax error here but override client-side + // initializationOptions in messages/initialize.cc + g_init_options = opt_init.getValue(); + rapidjson::Document reader; + rapidjson::ParseResult ok = reader.Parse(g_init_options.c_str()); + if (!ok) { + fprintf(stderr, "Failed to parse --init as JSON: %s (%zd)\n", + rapidjson::GetParseError_En(ok.Code()), ok.Offset()); + return 1; + } + JsonReader json_reader{&reader}; + try { + Config config; + Reflect(json_reader, config); + } catch (std::invalid_argument &e) { + fprintf(stderr, "Failed to parse --init %s, expected %s\n", + static_cast(json_reader).GetPath().c_str(), + e.what()); + return 1; + } + } + + sys::ChangeStdinToBinary(); + sys::ChangeStdoutToBinary(); + if (opt_index.size()) { + SmallString<256> Root(opt_index); + sys::fs::make_absolute(Root); + pipeline::Standalone(Root.str()); + } else { + // The thread that reads from stdin and dispatchs commands to the main + // thread. + pipeline::LaunchStdin(); + // The thread that writes responses from the main thread to stdout. + pipeline::LaunchStdout(); + // Main thread which also spawns indexer threads upon the "initialize" + // request. + pipeline::MainLoop(); + } + } + + return 0; +} diff --git a/src/match.cc b/src/match.cc deleted file mode 100644 index bec67dcf3..000000000 --- a/src/match.cc +++ /dev/null @@ -1,88 +0,0 @@ -#include "match.h" - -#include "lsp.h" -#include "queue_manager.h" - -#include - -// static -optional Matcher::Create(const std::string& search) { - /* - std::string real_search; - real_search.reserve(search.size() * 3 + 2); - for (auto c : search) { - real_search += ".*"; - real_search += c; - } - real_search += ".*"; - */ - - try { - Matcher m; - m.regex_string = search; - m.regex = std::regex( - search, std::regex_constants::ECMAScript | std::regex_constants::icase | - std::regex_constants::optimize - // std::regex_constants::nosubs - ); - return m; - } catch (std::exception e) { - Out_ShowLogMessage out; - out.display_type = Out_ShowLogMessage::DisplayType::Show; - out.params.type = lsMessageType::Error; - out.params.message = "cquery: Parsing EMCAScript regex \"" + search + - "\" failed; " + e.what(); - QueueManager::WriteStdout(IpcId::Unknown, out); - return nullopt; - } -} - -bool Matcher::IsMatch(const std::string& value) const { - // std::smatch match; - // return std::regex_match(value, match, regex); - return std::regex_search(value, regex, std::regex_constants::match_any); -} - -GroupMatch::GroupMatch(const std::vector& whitelist, - const std::vector& blacklist) { - for (const std::string& entry : whitelist) { - optional m = Matcher::Create(entry); - if (m) - this->whitelist.push_back(*m); - } - for (const std::string& entry : blacklist) { - optional m = Matcher::Create(entry); - if (m) - this->blacklist.push_back(*m); - } -} - -bool GroupMatch::IsMatch(const std::string& value, - std::string* match_failure_reason) const { - for (const Matcher& m : whitelist) { - if (m.IsMatch(value)) - return true; - } - - for (const Matcher& m : blacklist) { - if (m.IsMatch(value)) { - if (match_failure_reason) - *match_failure_reason = "blacklist \"" + m.regex_string + "\""; - return false; - } - } - - return true; -} - -TEST_SUITE("Matcher") { - TEST_CASE("sanity") { - // Matcher m("abc"); - // TODO: check case - // CHECK(m.IsMatch("abc")); - // CHECK(m.IsMatch("fooabc")); - // CHECK(m.IsMatch("abc")); - // CHECK(m.IsMatch("abcfoo")); - // CHECK(m.IsMatch("11a11b11c11")); - } -} diff --git a/src/match.h b/src/match.h deleted file mode 100644 index 6e7be8f97..000000000 --- a/src/match.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -struct Matcher { - static optional Create(const std::string& search); - - bool IsMatch(const std::string& value) const; - - std::string regex_string; - std::regex regex; -}; - -// Check multiple |Matcher| instances at the same time. -struct GroupMatch { - GroupMatch(const std::vector& whitelist, - const std::vector& blacklist); - - bool IsMatch(const std::string& value, - std::string* match_failure_reason = nullptr) const; - - std::vector whitelist; - std::vector blacklist; -}; \ No newline at end of file diff --git a/src/maybe.h b/src/maybe.h deleted file mode 100644 index 09d07f8f9..000000000 --- a/src/maybe.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -#include - -// Like optional, but the stored data is responsible for containing the empty -// state. T should define a function `bool T::HasValueForMaybe_()`. -template -class Maybe { - T storage; - - public: - constexpr Maybe() = default; - Maybe(const Maybe&) = default; - Maybe(std::nullopt_t) {} - Maybe(const T& x) : storage(x) {} - Maybe(T&& x) : storage(std::forward(x)) {} - - Maybe& operator=(const Maybe&) = default; - Maybe& operator=(const T& x) { - storage = x; - return *this; - } - - const T* operator->() const { return &storage; } - T* operator->() { return &storage; } - const T& operator*() const { return storage; } - T& operator*() { return storage; } - - bool HasValue() const { return storage.HasValueForMaybe_(); } - explicit operator bool() const { return HasValue(); } - operator optional() const { - if (HasValue()) - return storage; - return nullopt; - } - - void operator=(optional&& o) { storage = o ? *o : T(); } - - // Does not test if has_value() - bool operator==(const Maybe& o) const { return storage == o.storage; } - bool operator!=(const Maybe& o) const { return !(*this == o); } -}; diff --git a/src/message_handler.cc b/src/message_handler.cc index 35ceae777..cceb35a6a 100644 --- a/src/message_handler.cc +++ b/src/message_handler.cc @@ -1,232 +1,375 @@ -#include "message_handler.h" +/* Copyright 2017-2018 ccls Authors -#include "lex_utils.h" -#include "project.h" -#include "query_utils.h" -#include "queue_manager.h" -#include "semantic_highlight_symbol_cache.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#include + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" + +#include "log.hh" +#include "pipeline.hh" +#include "project.hh" +#include "query.hh" + +#include +#include #include +#include + +using namespace clang; + +MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind); + +namespace ccls { +REFLECT_STRUCT(CodeActionParam::Context, diagnostics); +REFLECT_STRUCT(CodeActionParam, textDocument, range, context); +void Reflect(JsonReader &, EmptyParam &) {} +REFLECT_STRUCT(TextDocumentParam, textDocument); +REFLECT_STRUCT(DidOpenTextDocumentParam, textDocument); +REFLECT_STRUCT(TextDocumentContentChangeEvent, range, rangeLength, text); +REFLECT_STRUCT(TextDocumentDidChangeParam, textDocument, contentChanges); +REFLECT_STRUCT(TextDocumentPositionParam, textDocument, position); +REFLECT_STRUCT(RenameParam, textDocument, position, newName); + +// completion +REFLECT_UNDERLYING(CompletionTriggerKind); +REFLECT_STRUCT(CompletionContext, triggerKind, triggerCharacter); +REFLECT_STRUCT(CompletionParam, textDocument, position, context); + +// formatting +REFLECT_STRUCT(FormattingOptions, tabSize, insertSpaces); +REFLECT_STRUCT(DocumentFormattingParam, textDocument, options); +REFLECT_STRUCT(DocumentOnTypeFormattingParam, textDocument, position, ch, + options); +REFLECT_STRUCT(DocumentRangeFormattingParam, textDocument, range, options); + +// workspace +REFLECT_UNDERLYING(FileChangeType); +REFLECT_STRUCT(DidChangeWatchedFilesParam::Event, uri, type); +REFLECT_STRUCT(DidChangeWatchedFilesParam, changes); +REFLECT_STRUCT(DidChangeWorkspaceFoldersParam::Event, added, removed); +REFLECT_STRUCT(DidChangeWorkspaceFoldersParam, event); +REFLECT_STRUCT(WorkspaceSymbolParam, query, folders); namespace { +struct CclsSemanticHighlightSymbol { + int id = 0; + SymbolKind parentKind; + SymbolKind kind; + uint8_t storage; + std::vector> ranges; -struct Out_CquerySetInactiveRegion - : public lsOutMessage { - struct Params { - lsDocumentUri uri; - std::vector inactiveRegions; - }; - std::string method = "$cquery/setInactiveRegions"; - Params params; + // `lsRanges` is used to compute `ranges`. + std::vector lsRanges; +}; + +struct CclsSemanticHighlight { + DocumentUri uri; + std::vector symbols; }; -MAKE_REFLECT_STRUCT(Out_CquerySetInactiveRegion::Params, uri, inactiveRegions); -MAKE_REFLECT_STRUCT(Out_CquerySetInactiveRegion, jsonrpc, method, params); +REFLECT_STRUCT(CclsSemanticHighlightSymbol, id, parentKind, kind, storage, + ranges, lsRanges); +REFLECT_STRUCT(CclsSemanticHighlight, uri, symbols); + +struct CclsSetSkippedRanges { + DocumentUri uri; + std::vector skippedRanges; +}; +REFLECT_STRUCT(CclsSetSkippedRanges, uri, skippedRanges); struct ScanLineEvent { - lsPosition pos; - lsPosition end_pos; // Second key when there is a tie for insertion events. + Position pos; + Position end_pos; // Second key when there is a tie for insertion events. int id; - Out_CqueryPublishSemanticHighlighting::Symbol* symbol; - bool operator<(const ScanLineEvent& other) const { + CclsSemanticHighlightSymbol *symbol; + bool operator<(const ScanLineEvent &o) const { // See the comments below when insertion/deletion events are inserted. - if (!(pos == other.pos)) - return pos < other.pos; - if (!(other.end_pos == end_pos)) - return other.end_pos < end_pos; + if (!(pos == o.pos)) + return pos < o.pos; + if (!(o.end_pos == end_pos)) + return o.end_pos < end_pos; // This comparison essentially order Macro after non-Macro, // So that macros will not be rendered as Var/Type/... - return symbol->kind < other.symbol->kind; + if (symbol->kind != o.symbol->kind) + return symbol->kind < o.symbol->kind; + // If symbol A and B occupy the same place, we want one to be placed + // before the other consistantly. + return symbol->id < o.symbol->id; } }; -} // namespace +} // namespace -MessageHandler::MessageHandler() { - // Dynamically allocate |message_handlers|, otherwise there will be static - // initialization order races. - if (!message_handlers) - message_handlers = new std::vector(); - message_handlers->push_back(this); +void ReplyOnce::NotReady(bool file) { + if (file) + Error(ErrorCode::InvalidRequest, "not opened"); + else + Error(ErrorCode::InternalError, "not indexed"); } -// static -std::vector* MessageHandler::message_handlers = nullptr; +void MessageHandler::Bind(const char *method, + void (MessageHandler::*handler)(JsonReader &)) { + method2notification[method] = [this, handler](JsonReader &reader) { + (this->*handler)(reader); + }; +} -bool FindFileOrFail(QueryDatabase* db, - const Project* project, - optional id, - const std::string& absolute_path, - QueryFile** out_query_file, - QueryFileId* out_file_id) { - *out_query_file = nullptr; +template +void MessageHandler::Bind(const char *method, + void (MessageHandler::*handler)(Param &)) { + method2notification[method] = [this, handler](JsonReader &reader) { + Param param{}; + Reflect(reader, param); + (this->*handler)(param); + }; +} - auto it = db->usr_to_file.find(NormalizedPath(absolute_path)); - if (it != db->usr_to_file.end()) { - QueryFile& file = db->files[it->second.id]; - if (file.def) { - *out_query_file = &file; - if (out_file_id) - *out_file_id = QueryFileId(it->second.id); - return true; - } - } +void MessageHandler::Bind(const char *method, + void (MessageHandler::*handler)(JsonReader &, + ReplyOnce &)) { + method2request[method] = [this, handler](JsonReader &reader, + ReplyOnce &reply) { + (this->*handler)(reader, reply); + }; +} - if (out_file_id) - *out_file_id = QueryFileId(); +template +void MessageHandler::Bind(const char *method, + void (MessageHandler::*handler)(Param &, + ReplyOnce &)) { + method2request[method] = [this, handler](JsonReader &reader, + ReplyOnce &reply) { + Param param{}; + Reflect(reader, param); + (this->*handler)(param, reply); + }; +} - bool indexing = project->absolute_path_to_entry_index_.find(absolute_path) != - project->absolute_path_to_entry_index_.end(); - if (indexing) - LOG_S(INFO) << "\"" << absolute_path << "\" is being indexed."; - else - LOG_S(INFO) << "Unable to find file \"" << absolute_path << "\""; - /* - LOG_S(INFO) << "Files (size=" << db->usr_to_file.size() << "): " - << StringJoinMap(db->usr_to_file, - [](const std::pair& entry) { - return entry.first; - }); - */ - - if (id) { - Out_Error out; - out.id = *id; - if (indexing) { - out.error.code = lsErrorCodes::ServerNotInitialized; - out.error.message = absolute_path + " is being indexed."; +MessageHandler::MessageHandler() { + Bind("$ccls/call", &MessageHandler::ccls_call); + Bind("$ccls/fileInfo", &MessageHandler::ccls_fileInfo); + Bind("$ccls/info", &MessageHandler::ccls_info); + Bind("$ccls/inheritance", &MessageHandler::ccls_inheritance); + Bind("$ccls/member", &MessageHandler::ccls_member); + Bind("$ccls/navigate", &MessageHandler::ccls_navigate); + Bind("$ccls/reload", &MessageHandler::ccls_reload); + Bind("$ccls/vars", &MessageHandler::ccls_vars); + Bind("$ccls/dataFlowInto", &MessageHandler::ccls_dataFlowInto); + Bind("exit", &MessageHandler::exit); + Bind("initialize", &MessageHandler::initialize); + Bind("shutdown", &MessageHandler::shutdown); + Bind("textDocument/codeAction", &MessageHandler::textDocument_codeAction); + Bind("textDocument/codeLens", &MessageHandler::textDocument_codeLens); + Bind("textDocument/completion", &MessageHandler::textDocument_completion); + Bind("textDocument/definition", &MessageHandler::textDocument_definition); + Bind("textDocument/didChange", &MessageHandler::textDocument_didChange); + Bind("textDocument/didClose", &MessageHandler::textDocument_didClose); + Bind("textDocument/didOpen", &MessageHandler::textDocument_didOpen); + Bind("textDocument/didSave", &MessageHandler::textDocument_didSave); + Bind("textDocument/documentHighlight", + &MessageHandler::textDocument_documentHighlight); + Bind("textDocument/documentLink", &MessageHandler::textDocument_documentLink); + Bind("textDocument/documentSymbol", + &MessageHandler::textDocument_documentSymbol); + Bind("textDocument/foldingRange", &MessageHandler::textDocument_foldingRange); + Bind("textDocument/formatting", &MessageHandler::textDocument_formatting); + Bind("textDocument/hover", &MessageHandler::textDocument_hover); + Bind("textDocument/implementation", + &MessageHandler::textDocument_implementation); + Bind("textDocument/onTypeFormatting", + &MessageHandler::textDocument_onTypeFormatting); + Bind("textDocument/rangeFormatting", + &MessageHandler::textDocument_rangeFormatting); + Bind("textDocument/references", &MessageHandler::textDocument_references); + Bind("textDocument/rename", &MessageHandler::textDocument_rename); + Bind("textDocument/signatureHelp", + &MessageHandler::textDocument_signatureHelp); + Bind("textDocument/typeDefinition", + &MessageHandler::textDocument_typeDefinition); + Bind("workspace/didChangeConfiguration", + &MessageHandler::workspace_didChangeConfiguration); + Bind("workspace/didChangeWatchedFiles", + &MessageHandler::workspace_didChangeWatchedFiles); + Bind("workspace/didChangeWorkspaceFolders", + &MessageHandler::workspace_didChangeWorkspaceFolders); + Bind("workspace/executeCommand", &MessageHandler::workspace_executeCommand); + Bind("workspace/symbol", &MessageHandler::workspace_symbol); +} + +void MessageHandler::Run(InMessage &msg) { + rapidjson::Document &doc = *msg.document; + rapidjson::Value param; + auto it = doc.FindMember("params"); + if (it != doc.MemberEnd()) + param = it->value; + JsonReader reader(¶m); + if (msg.id.Valid()) { + ReplyOnce reply{msg.id}; + auto it = method2request.find(msg.method); + if (it != method2request.end()) { + try { + it->second(reader, reply); + } catch (std::invalid_argument &ex) { + reply.Error(ErrorCode::InvalidParams, + "invalid params of " + msg.method + ": " + ex.what()); + } catch (...) { + reply.Error(ErrorCode::InternalError, "failed to process " + msg.method); + } } else { - out.error.code = lsErrorCodes::InternalError; - out.error.message = "Unable to find file " + absolute_path; + reply.Error(ErrorCode::MethodNotFound, "unknown request " + msg.method); } - QueueManager::WriteStdout(IpcId::Unknown, out); + } else { + auto it = method2notification.find(msg.method); + if (it != method2notification.end()) + try { + it->second(reader); + } catch (...) { + ShowMessageParam param{MessageType::Error, + std::string("failed to process ") + msg.method}; + pipeline::Notify(window_showMessage, param); + } } - - return false; } -void EmitInactiveLines(WorkingFile* working_file, - const std::vector& inactive_regions) { - Out_CquerySetInactiveRegion out; - out.params.uri = lsDocumentUri::FromPath(working_file->filename); - for (Range skipped : inactive_regions) { - optional ls_skipped = GetLsRange(working_file, skipped); - if (ls_skipped) - out.params.inactiveRegions.push_back(*ls_skipped); +QueryFile *MessageHandler::FindFile(const std::string &path, int *out_file_id) { + QueryFile *ret = nullptr; + auto it = db->name2file_id.find(LowerPathIfInsensitive(path)); + if (it != db->name2file_id.end()) { + QueryFile &file = db->files[it->second]; + if (file.def) { + ret = &file; + if (out_file_id) + *out_file_id = it->second; + return ret; + } } - QueueManager::WriteStdout(IpcId::CqueryPublishInactiveRegions, out); + if (out_file_id) + *out_file_id = -1; + return ret; } -void EmitSemanticHighlighting(QueryDatabase* db, - SemanticHighlightSymbolCache* semantic_cache, - WorkingFile* working_file, - QueryFile* file) { - assert(file->def); - if (!semantic_cache->match_->IsMatch(file->def->path)) +void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file) { + CclsSetSkippedRanges params; + params.uri = DocumentUri::FromPath(wfile->filename); + for (Range skipped : file.def->skipped_ranges) + if (auto ls_skipped = GetLsRange(wfile, skipped)) + params.skippedRanges.push_back(*ls_skipped); + pipeline::Notify("$ccls/publishSkippedRanges", params); +} + +void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) { + static GroupMatch match(g_config->highlight.whitelist, + g_config->highlight.blacklist); + assert(file.def); + if (wfile->buffer_content.size() > g_config->highlight.largeFileSize || + !match.Matches(file.def->path)) return; - auto semantic_cache_for_file = - semantic_cache->GetCacheForFile(file->def->path); // Group symbols together. - std::unordered_map - grouped_symbols; - for (SymbolRef sym : file->def->all_symbols) { + std::unordered_map grouped_symbols; + for (auto [sym, refcnt] : file.symbol2refcnt) { + if (refcnt <= 0) continue; std::string_view detailed_name; - lsSymbolKind parent_kind = lsSymbolKind::Unknown; - lsSymbolKind kind = lsSymbolKind::Unknown; - StorageClass storage = StorageClass::Invalid; + SymbolKind parent_kind = SymbolKind::Unknown; + SymbolKind kind = SymbolKind::Unknown; + uint8_t storage = SC_None; + int idx; // This switch statement also filters out symbols that are not highlighted. switch (sym.kind) { - case SymbolKind::Func: { - const QueryFunc& func = db->GetFunc(sym); - const QueryFunc::Def* def = func.AnyDef(); - if (!def) - continue; // applies to for loop - if (def->spell) - parent_kind = GetSymbolKind(db, *def->spell); - if (parent_kind == lsSymbolKind::Unknown) { - for (Use use: func.declarations) { - parent_kind = GetSymbolKind(db, use); - break; - } - } - // Don't highlight overloadable operators or implicit lambda -> - // std::function constructor. - std::string_view short_name = def->ShortName(); - if (short_name.compare(0, 8, "operator") == 0 || - short_name.compare(0, 27, "functionspell) - parent_kind = GetSymbolKind(db, *def->spell); - kind = def->kind; - storage = def->storage; - detailed_name = short_name; - - // Check whether the function name is actually there. - // If not, do not publish the semantic highlight. - // E.g. copy-initialization of constructors should not be highlighted - // but we still want to keep the range for jumping to definition. - std::string_view concise_name = - detailed_name.substr(0, detailed_name.find('<')); - int16_t start_line = sym.range.start.line; - int16_t start_col = sym.range.start.column; - if (start_line >= 0 && start_line < working_file->index_lines.size()) { - std::string_view line = working_file->index_lines[start_line]; - sym.range.end.line = start_line; - if (start_col + concise_name.size() <= line.size() && - line.compare(start_col, concise_name.size(), concise_name) == 0) - sym.range.end.column = start_col + concise_name.size(); - else - continue; // applies to for loop + case Kind::Func: { + auto func_it = db->func_usr.find(sym.usr); + if (func_it == db->func_usr.end()) + continue; + idx = func_it->second; + const QueryFunc &func = db->funcs[idx]; + const QueryFunc::Def *def = func.AnyDef(); + if (!def) + continue; // applies to for loop + // Don't highlight overloadable operators or implicit lambda -> + // std::function constructor. + std::string_view short_name = def->Name(false); + if (short_name.compare(0, 8, "operator") == 0) + continue; // applies to for loop + kind = def->kind; + storage = def->storage; + detailed_name = short_name; + parent_kind = def->parent_kind; + + // Check whether the function name is actually there. + // If not, do not publish the semantic highlight. + // E.g. copy-initialization of constructors should not be highlighted + // but we still want to keep the range for jumping to definition. + std::string_view concise_name = + detailed_name.substr(0, detailed_name.find('<')); + int16_t start_line = sym.range.start.line; + int16_t start_col = sym.range.start.column; + if (start_line < 0 || start_line >= wfile->index_lines.size()) + continue; + std::string_view line = wfile->index_lines[start_line]; + sym.range.end.line = start_line; + if (!(start_col + concise_name.size() <= line.size() && + line.compare(start_col, concise_name.size(), concise_name) == 0)) + continue; + sym.range.end.column = start_col + concise_name.size(); + break; + } + case Kind::Type: { + auto type_it = db->type_usr.find(sym.usr); + if (type_it == db->type_usr.end()) + continue; + idx = type_it->second; + const QueryType &type = db->types[idx]; + for (auto &def : type.def) { + kind = def.kind; + detailed_name = def.detailed_name; + if (def.spell) { + parent_kind = def.parent_kind; + break; } - break; } - case SymbolKind::Type: - for (auto& def : db->GetType(sym).def) { - kind = def.kind; - detailed_name = def.detailed_name; - if (def.spell) { - parent_kind = GetSymbolKind(db, *def.spell); - break; - } - } - break; - case SymbolKind::Var: { - const QueryVar& var = db->GetVar(sym); - for (auto& def : var.def) { - kind = def.kind; - storage = def.storage; - detailed_name = def.detailed_name; - if (def.spell) { - parent_kind = GetSymbolKind(db, *def.spell); - break; - } - } - if (parent_kind == lsSymbolKind::Unknown) { - for (Use use : var.declarations) { - parent_kind = GetSymbolKind(db, use); - break; - } + break; + } + case Kind::Var: { + auto var_it = db->var_usr.find(sym.usr); + if (var_it == db->var_usr.end()) + continue; + idx = var_it->second; + const QueryVar &var = db->vars[idx]; + for (auto &def : var.def) { + kind = def.kind; + storage = def.storage; + detailed_name = def.detailed_name; + if (def.spell) { + parent_kind = def.parent_kind; + break; } - break; } - default: - continue; // applies to for loop + break; + } + default: + continue; // applies to for loop } - optional loc = GetLsRange(working_file, sym.range); - if (loc) { + if (std::optional loc = GetLsRange(wfile, sym.range)) { auto it = grouped_symbols.find(sym); if (it != grouped_symbols.end()) { - it->second.ranges.push_back(*loc); + it->second.lsRanges.push_back(*loc); } else { - Out_CqueryPublishSemanticHighlighting::Symbol symbol; - symbol.stableId = semantic_cache_for_file->GetStableId( - sym.kind, std::string(detailed_name)); + CclsSemanticHighlightSymbol symbol; + symbol.id = idx; symbol.parentKind = parent_kind; symbol.kind = kind; symbol.storage = storage; - symbol.ranges.push_back(*loc); + symbol.lsRanges.push_back(*loc); grouped_symbols[sym] = symbol; } } @@ -235,9 +378,9 @@ void EmitSemanticHighlighting(QueryDatabase* db, // Make ranges non-overlapping using a scan line algorithm. std::vector events; int id = 0; - for (auto& entry : grouped_symbols) { - Out_CqueryPublishSemanticHighlighting::Symbol& symbol = entry.second; - for (auto& loc : symbol.ranges) { + for (auto &entry : grouped_symbols) { + CclsSemanticHighlightSymbol &symbol = entry.second; + for (auto &loc : symbol.lsRanges) { // For ranges sharing the same start point, the one with leftmost end // point comes first. events.push_back({loc.start, loc.end, id, &symbol}); @@ -247,7 +390,7 @@ void EmitSemanticHighlighting(QueryDatabase* db, events.push_back({loc.end, loc.end, ~id, &symbol}); id++; } - symbol.ranges.clear(); + symbol.lsRanges.clear(); } std::sort(events.begin(), events.end()); @@ -263,23 +406,61 @@ void EmitSemanticHighlighting(QueryDatabase* db, // Attribute range [events[i-1].pos, events[i].pos) to events[top-1].symbol // . if (top && !(events[i - 1].pos == events[i].pos)) - events[top - 1].symbol->ranges.emplace_back(events[i - 1].pos, - events[i].pos); + events[top - 1].symbol->lsRanges.push_back( + {events[i - 1].pos, events[i].pos}); if (events[i].id >= 0) events[top++] = events[i]; else deleted[~events[i].id] = 1; } - // Publish. - Out_CqueryPublishSemanticHighlighting out; - out.params.uri = lsDocumentUri::FromPath(working_file->filename); - for (auto& entry : grouped_symbols) - if (entry.second.ranges.size()) - out.params.symbols.push_back(entry.second); - QueueManager::WriteStdout(IpcId::CqueryPublishSemanticHighlighting, out); -} + CclsSemanticHighlight params; + params.uri = DocumentUri::FromPath(wfile->filename); + // Transform lsRange into pair (offset pairs) + if (!g_config->highlight.lsRanges) { + std::vector> scratch; + for (auto &entry : grouped_symbols) { + for (auto &range : entry.second.lsRanges) + scratch.emplace_back(range, &entry.second); + entry.second.lsRanges.clear(); + } + std::sort(scratch.begin(), scratch.end(), + [](auto &l, auto &r) { return l.first.start < r.first.start; }); + const auto &buf = wfile->buffer_content; + int l = 0, c = 0, i = 0, p = 0; + auto mov = [&](int line, int col) { + if (l < line) + c = 0; + for (; l < line && i < buf.size(); i++) { + if (buf[i] == '\n') + l++; + if (uint8_t(buf[i]) < 128 || 192 <= uint8_t(buf[i])) + p++; + } + if (l < line) + return true; + for (; c < col && i < buf.size() && buf[i] != '\n'; c++) + if (p++, uint8_t(buf[i++]) >= 128) + // Skip 0b10xxxxxx + while (i < buf.size() && uint8_t(buf[i]) >= 128 && + uint8_t(buf[i]) < 192) + i++; + return c < col; + }; + for (auto &entry : scratch) { + lsRange &r = entry.first; + if (mov(r.start.line, r.start.character)) + continue; + int beg = p; + if (mov(r.end.line, r.end.character)) + continue; + entry.second->ranges.emplace_back(beg, p); + } + } -bool ShouldIgnoreFileForIndexing(const std::string& path) { - return StartsWith(path, "git:"); + for (auto &entry : grouped_symbols) + if (entry.second.ranges.size() || entry.second.lsRanges.size()) + params.symbols.push_back(std::move(entry.second)); + pipeline::Notify("$ccls/publishSemanticHighlight", params); } +} // namespace ccls diff --git a/src/message_handler.h b/src/message_handler.h deleted file mode 100644 index d5b79d48a..000000000 --- a/src/message_handler.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include "ipc.h" -#include "lsp.h" -#include "query.h" - -#include - -#include -#include - -struct ClangCompleteManager; -struct CodeCompleteCache; -struct Config; -class DiagnosticsEngine; -struct FileConsumerSharedState; -struct ImportManager; -struct ImportPipelineStatus; -struct IncludeComplete; -struct MultiQueueWaiter; -struct Project; -struct QueryDatabase; -struct SemanticHighlightSymbolCache; -struct TimestampManager; -struct WorkingFile; -struct WorkingFiles; - -struct Out_CqueryPublishSemanticHighlighting - : public lsOutMessage { - struct Symbol { - int stableId = 0; - lsSymbolKind parentKind; - lsSymbolKind kind; - StorageClass storage; - std::vector ranges; - }; - struct Params { - lsDocumentUri uri; - std::vector symbols; - }; - std::string method = "$cquery/publishSemanticHighlighting"; - Params params; -}; -MAKE_REFLECT_STRUCT(Out_CqueryPublishSemanticHighlighting::Symbol, - stableId, - parentKind, - kind, - storage, - ranges); -MAKE_REFLECT_STRUCT(Out_CqueryPublishSemanticHighlighting::Params, - uri, - symbols); -MAKE_REFLECT_STRUCT(Out_CqueryPublishSemanticHighlighting, - jsonrpc, - method, - params); - -// Usage: -// -// struct FooHandler : MessageHandler { -// // ... -// }; -// REGISTER_MESSAGE_HANDLER(FooHandler); -// -// Then there will be a global FooHandler instance in -// |MessageHandler::message_handlers|. - -#define REGISTER_MESSAGE_HANDLER(type) \ - static type type##message_handler_instance_; - -struct MessageHandler { - Config* config = nullptr; - QueryDatabase* db = nullptr; - MultiQueueWaiter* waiter = nullptr; - Project* project = nullptr; - DiagnosticsEngine* diag_engine = nullptr; - FileConsumerSharedState* file_consumer_shared = nullptr; - ImportManager* import_manager = nullptr; - ImportPipelineStatus* import_pipeline_status = nullptr; - TimestampManager* timestamp_manager = nullptr; - SemanticHighlightSymbolCache* semantic_cache = nullptr; - WorkingFiles* working_files = nullptr; - ClangCompleteManager* clang_complete = nullptr; - IncludeComplete* include_complete = nullptr; - CodeCompleteCache* global_code_complete_cache = nullptr; - CodeCompleteCache* non_global_code_complete_cache = nullptr; - CodeCompleteCache* signature_cache = nullptr; - - virtual IpcId GetId() const = 0; - virtual void Run(std::unique_ptr message) = 0; - - static std::vector* message_handlers; - - protected: - MessageHandler(); -}; - -template -struct BaseMessageHandler : MessageHandler { - virtual void Run(TMessage* message) = 0; - - // MessageHandler: - IpcId GetId() const override { return TMessage::kIpcId; } - void Run(std::unique_ptr message) override { - Run(message->As()); - } -}; - -bool FindFileOrFail(QueryDatabase* db, - const Project* project, - optional id, - const std::string& absolute_path, - QueryFile** out_query_file, - QueryFileId* out_file_id = nullptr); - -void EmitInactiveLines(WorkingFile* working_file, - const std::vector& inactive_regions); - -void EmitSemanticHighlighting(QueryDatabase* db, - SemanticHighlightSymbolCache* semantic_cache, - WorkingFile* working_file, - QueryFile* file); - -bool ShouldIgnoreFileForIndexing(const std::string& path); diff --git a/src/message_handler.hh b/src/message_handler.hh new file mode 100644 index 000000000..6d362a7df --- /dev/null +++ b/src/message_handler.hh @@ -0,0 +1,286 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "lsp.hh" +#include "query.hh" + +#include +#include +#include +#include + +namespace ccls { +struct SemaManager; +struct VFS; +struct IncludeComplete; +struct Project; +struct WorkingFile; +struct WorkingFiles; + +namespace pipeline { +void Reply(RequestId id, const std::function &fn); +void ReplyError(RequestId id, const std::function &fn); +} + +struct CodeActionParam { + TextDocumentIdentifier textDocument; + lsRange range; + struct Context { + std::vector diagnostics; + } context; +}; +struct EmptyParam {}; +struct DidOpenTextDocumentParam { + TextDocumentItem textDocument; +}; +struct RenameParam { + TextDocumentIdentifier textDocument; + Position position; + std::string newName; +}; +struct TextDocumentParam { + TextDocumentIdentifier textDocument; +}; +struct TextDocumentPositionParam { + TextDocumentIdentifier textDocument; + Position position; +}; +struct TextDocumentEdit { + VersionedTextDocumentIdentifier textDocument; + std::vector edits; +}; +REFLECT_STRUCT(TextDocumentEdit, textDocument, edits); +struct WorkspaceEdit { + std::vector documentChanges; +}; +REFLECT_STRUCT(WorkspaceEdit, documentChanges); + +// completion +enum class CompletionTriggerKind { + Invoked = 1, + TriggerCharacter = 2, + TriggerForIncompleteCompletions = 3, +}; +struct CompletionContext { + CompletionTriggerKind triggerKind = CompletionTriggerKind::Invoked; + std::optional triggerCharacter; +}; +struct CompletionParam : TextDocumentPositionParam { + CompletionContext context; +}; +enum class CompletionItemKind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, +}; +enum class InsertTextFormat { + PlainText = 1, + Snippet = 2 +}; +struct CompletionItem { + std::string label; + CompletionItemKind kind = CompletionItemKind::Text; + std::string detail; + std::string documentation; + std::string sortText; + std::string filterText; + InsertTextFormat insertTextFormat = InsertTextFormat::PlainText; + TextEdit textEdit; + std::vector additionalTextEdits; + + std::vector parameters_; + int score_; + unsigned priority_; + bool use_angle_brackets_ = false; +}; + +// formatting +struct FormattingOptions { + int tabSize; + bool insertSpaces; +}; +struct DocumentFormattingParam { + TextDocumentIdentifier textDocument; + FormattingOptions options; +}; +struct DocumentOnTypeFormattingParam { + TextDocumentIdentifier textDocument; + Position position; + std::string ch; + FormattingOptions options; +}; +struct DocumentRangeFormattingParam { + TextDocumentIdentifier textDocument; + lsRange range; + FormattingOptions options; +}; + +// workspace +enum class FileChangeType { + Created = 1, + Changed = 2, + Deleted = 3, +}; +struct DidChangeWatchedFilesParam { + struct Event { + DocumentUri uri; + FileChangeType type; + }; + std::vector changes; +}; +struct DidChangeWorkspaceFoldersParam { + struct Event { + std::vector added, removed; + } event; +}; +struct WorkspaceSymbolParam { + std::string query; + + // ccls extensions + std::vector folders; +}; +REFLECT_STRUCT(WorkspaceFolder, uri, name); + +inline void Reflect(JsonReader &visitor, DocumentUri &value) { + Reflect(visitor, value.raw_uri); +} +inline void Reflect(JsonWriter &visitor, DocumentUri &value) { + Reflect(visitor, value.raw_uri); +} + +REFLECT_UNDERLYING(ErrorCode); +REFLECT_STRUCT(ResponseError, code, message); +REFLECT_STRUCT(Position, line, character); +REFLECT_STRUCT(lsRange, start, end); +REFLECT_STRUCT(Location, uri, range); +REFLECT_UNDERLYING_B(SymbolKind); +REFLECT_STRUCT(TextDocumentIdentifier, uri); +REFLECT_STRUCT(TextDocumentItem, uri, languageId, version, text); +REFLECT_STRUCT(TextEdit, range, newText); +REFLECT_STRUCT(VersionedTextDocumentIdentifier, uri, version); +REFLECT_STRUCT(Diagnostic, range, severity, code, source, message); +REFLECT_STRUCT(ShowMessageParam, type, message); +REFLECT_UNDERLYING_B(LanguageId); + +struct ReplyOnce { + RequestId id; + template void operator()(Res &&result) const { + if (id.Valid()) + pipeline::Reply(id, [&](JsonWriter &w) { Reflect(w, result); }); + } + void Error(ErrorCode code, std::string message) const { + ResponseError err{code, std::move(message)}; + if (id.Valid()) + pipeline::ReplyError(id, [&](JsonWriter &w) { Reflect(w, err); }); + } + void NotReady(bool file); +}; + +struct MessageHandler { + SemaManager *manager = nullptr; + DB *db = nullptr; + IncludeComplete *include_complete = nullptr; + Project *project = nullptr; + VFS *vfs = nullptr; + WorkingFiles *wfiles = nullptr; + + llvm::StringMap> method2notification; + llvm::StringMap> + method2request; + + MessageHandler(); + void Run(InMessage &msg); + QueryFile *FindFile(const std::string &path, int *out_file_id = nullptr); + +private: + void Bind(const char *method, void (MessageHandler::*handler)(JsonReader &)); + template + void Bind(const char *method, void (MessageHandler::*handler)(Param &)); + void Bind(const char *method, + void (MessageHandler::*handler)(JsonReader &, ReplyOnce &)); + template + void Bind(const char *method, + void (MessageHandler::*handler)(Param &, ReplyOnce &)); + + void ccls_call(JsonReader &, ReplyOnce &); + void ccls_fileInfo(TextDocumentParam &, ReplyOnce &); + void ccls_info(EmptyParam &, ReplyOnce &); + void ccls_inheritance(JsonReader &, ReplyOnce &); + void ccls_member(JsonReader &, ReplyOnce &); + void ccls_navigate(JsonReader &, ReplyOnce &); + void ccls_reload(JsonReader &); + void ccls_vars(JsonReader &, ReplyOnce &); + void ccls_dataFlowInto(JsonReader &, ReplyOnce &); + void exit(EmptyParam &); + void initialize(JsonReader &, ReplyOnce &); + void shutdown(EmptyParam &, ReplyOnce &); + void textDocument_codeAction(CodeActionParam &, ReplyOnce &); + void textDocument_codeLens(TextDocumentParam &, ReplyOnce &); + void textDocument_completion(CompletionParam &, ReplyOnce &); + void textDocument_definition(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_didChange(TextDocumentDidChangeParam &); + void textDocument_didClose(TextDocumentParam &); + void textDocument_didOpen(DidOpenTextDocumentParam &); + void textDocument_didSave(TextDocumentParam &); + void textDocument_documentHighlight(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_documentLink(TextDocumentParam &, ReplyOnce &); + void textDocument_documentSymbol(JsonReader &, ReplyOnce &); + void textDocument_foldingRange(TextDocumentParam &, ReplyOnce &); + void textDocument_formatting(DocumentFormattingParam &, ReplyOnce &); + void textDocument_hover(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_implementation(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &, + ReplyOnce &); + void textDocument_rangeFormatting(DocumentRangeFormattingParam &, + ReplyOnce &); + void textDocument_references(JsonReader &, ReplyOnce &); + void textDocument_rename(RenameParam &, ReplyOnce &); + void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &); + void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &); + void workspace_didChangeConfiguration(EmptyParam &); + void workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam &); + void workspace_didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParam &); + void workspace_executeCommand(JsonReader &, ReplyOnce &); + void workspace_symbol(WorkspaceSymbolParam &, ReplyOnce &); +}; + +void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file); + +void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file); +} // namespace ccls diff --git a/src/messages/ccls_call.cc b/src/messages/ccls_call.cc new file mode 100644 index 000000000..5a0e6d8e2 --- /dev/null +++ b/src/messages/ccls_call.cc @@ -0,0 +1,221 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "hierarchy.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "query.hh" + +#include + +namespace ccls { + +namespace { + +enum class CallType : uint8_t { + Direct = 0, + Base = 1, + Derived = 2, + All = 1 | 2 +}; +REFLECT_UNDERLYING(CallType); + +bool operator&(CallType lhs, CallType rhs) { + return uint8_t(lhs) & uint8_t(rhs); +} + +struct Param : TextDocumentPositionParam { + // If id is specified, expand a node; otherwise textDocument+position should + // be specified for building the root and |levels| of nodes below. + Usr usr; + std::string id; + + // true: callee tree (functions called by this function); false: caller tree + // (where this function is called) + bool callee = false; + // Base: include base functions; All: include both base and derived + // functions. + CallType callType = CallType::All; + bool qualified = true; + int levels = 1; + bool hierarchy = false; +}; +REFLECT_STRUCT(Param, textDocument, position, id, callee, callType, qualified, + levels, hierarchy); + +struct Out_cclsCall { + Usr usr; + std::string id; + std::string_view name; + Location location; + CallType callType = CallType::Direct; + int numChildren; + // Empty if the |levels| limit is reached. + std::vector children; + bool operator==(const Out_cclsCall &o) const { + return location == o.location; + } + bool operator<(const Out_cclsCall &o) const { return location < o.location; } +}; +REFLECT_STRUCT(Out_cclsCall, id, name, location, callType, numChildren, + children); + +bool Expand(MessageHandler *m, Out_cclsCall *entry, bool callee, + CallType call_type, bool qualified, int levels) { + const QueryFunc &func = m->db->Func(entry->usr); + const QueryFunc::Def *def = func.AnyDef(); + entry->numChildren = 0; + if (!def) + return false; + auto handle = [&](SymbolRef sym, int file_id, CallType call_type1) { + entry->numChildren++; + if (levels > 0) { + Out_cclsCall entry1; + entry1.id = std::to_string(sym.usr); + entry1.usr = sym.usr; + if (auto loc = GetLsLocation(m->db, m->wfiles, + Use{{sym.range, sym.role}, file_id})) + entry1.location = *loc; + entry1.callType = call_type1; + if (Expand(m, &entry1, callee, call_type, qualified, levels - 1)) + entry->children.push_back(std::move(entry1)); + } + }; + auto handle_uses = [&](const QueryFunc &func, CallType call_type) { + if (callee) { + if (const auto *def = func.AnyDef()) + for (SymbolRef sym : def->callees) + if (sym.kind == Kind::Func) + handle(sym, def->file_id, call_type); + } else { + for (Use use : func.uses) { + const QueryFile &file1 = m->db->files[use.file_id]; + Maybe best; + for (auto [sym, refcnt] : file1.symbol2refcnt) + if (refcnt > 0 && sym.extent.Valid() && sym.kind == Kind::Func && + sym.extent.start <= use.range.start && + use.range.end <= sym.extent.end && + (!best || best->extent.start < sym.extent.start)) + best = sym; + if (best) + handle(*best, use.file_id, call_type); + } + } + }; + + std::unordered_set seen; + seen.insert(func.usr); + std::vector stack; + entry->name = def->Name(qualified); + handle_uses(func, CallType::Direct); + + // Callers/callees of base functions. + if (call_type & CallType::Base) { + stack.push_back(&func); + while (stack.size()) { + const QueryFunc &func1 = *stack.back(); + stack.pop_back(); + if (auto *def1 = func1.AnyDef()) { + EachDefinedFunc(m->db, def1->bases, [&](QueryFunc &func2) { + if (!seen.count(func2.usr)) { + seen.insert(func2.usr); + stack.push_back(&func2); + handle_uses(func2, CallType::Base); + } + }); + } + } + } + + // Callers/callees of derived functions. + if (call_type & CallType::Derived) { + stack.push_back(&func); + while (stack.size()) { + const QueryFunc &func1 = *stack.back(); + stack.pop_back(); + EachDefinedFunc(m->db, func1.derived, [&](QueryFunc &func2) { + if (!seen.count(func2.usr)) { + seen.insert(func2.usr); + stack.push_back(&func2); + handle_uses(func2, CallType::Derived); + } + }); + } + } + + std::sort(entry->children.begin(), entry->children.end()); + entry->children.erase( + std::unique(entry->children.begin(), entry->children.end()), + entry->children.end()); + return true; +} + +std::optional BuildInitial(MessageHandler *m, Usr root_usr, + bool callee, CallType call_type, + bool qualified, int levels) { + const auto *def = m->db->Func(root_usr).AnyDef(); + if (!def) + return {}; + + Out_cclsCall entry; + entry.id = std::to_string(root_usr); + entry.usr = root_usr; + entry.callType = CallType::Direct; + if (def->spell) { + if (auto loc = GetLsLocation(m->db, m->wfiles, *def->spell)) + entry.location = *loc; + } + Expand(m, &entry, callee, call_type, qualified, levels); + return entry; +} +} // namespace + +void MessageHandler::ccls_call(JsonReader &reader, ReplyOnce &reply) { + Param param; + Reflect(reader, param); + std::optional result; + if (param.id.size()) { + try { + param.usr = std::stoull(param.id); + } catch (...) { + return; + } + result.emplace(); + result->id = std::to_string(param.usr); + result->usr = param.usr; + result->callType = CallType::Direct; + if (db->HasFunc(param.usr)) + Expand(this, &*result, param.callee, param.callType, param.qualified, + param.levels); + } else { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) + return; + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + if (sym.kind == Kind::Func) { + result = BuildInitial(this, sym.usr, param.callee, param.callType, + param.qualified, param.levels); + break; + } + } + } + + if (param.hierarchy) + reply(result); + else + reply(FlattenHierarchy(result)); +} +} // namespace ccls diff --git a/src/messages/ccls_dataFlowInto.cc b/src/messages/ccls_dataFlowInto.cc new file mode 100644 index 000000000..5a78eb76c --- /dev/null +++ b/src/messages/ccls_dataFlowInto.cc @@ -0,0 +1,108 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "query.hh" + +#include + +namespace ccls { +namespace { +struct Param : TextDocumentPositionParam { +}; +REFLECT_STRUCT(Param, textDocument, position); +} // namespace + +struct Out_cclsDataFlowInto { + int id; + Location location; + // Empty if the |levels| limit is reached. + std::vector children; +}; +REFLECT_STRUCT(Out_cclsDataFlowInto, id ,location, children); + +Out_cclsDataFlowInto BuildDataFlow(Usr usr, Kind kind, Use use, std::unordered_set seen, DB* db, WorkingFiles* wfiles, int& id) { + Out_cclsDataFlowInto result = {id++}; + if (auto loc = GetLsLocation(db, wfiles, use)) { + result.location = *loc; + } + if (!seen.insert(usr).second) return result; + + const std::vector* data_flow_sources = nullptr; + if (kind == Kind::Var) { + auto& var = db->Var(usr); + data_flow_sources = &var.data_flow_into; + } else if (kind == Kind::Func) { + auto& func = db->Func(usr); + data_flow_sources = &func.data_flow_into_return; + } + if (data_flow_sources != nullptr) { + if (data_flow_sources->empty()) { + if (kind == Kind::Var) { + auto& var = db->Var(usr); + for (auto& def : var.def) { + if (def.spell) { + if (auto loc = GetLsLocation(db, wfiles, *def.spell)) { + result.children.push_back({id++, *loc}); + } + } + } + } else if (kind == Kind::Func) { + auto& func = db->Func(usr); + for (auto& def : func.def) { + if (def.spell) { + if (auto loc = GetLsLocation(db, wfiles, *def.spell)) { + result.children.push_back({id++, *loc}); + } + } + } + } + } else { + for (auto& write : *data_flow_sources) { + if (write.use.role == Role::Read) { + result.children.push_back(BuildDataFlow(write.from, Kind::Var, write.use, seen, db, wfiles, id)); + } else if (write.use.role == Role::Call) { + result.children.push_back(BuildDataFlow(write.from, Kind::Func, write.use, seen, db, wfiles, id)); + } else if (auto loc = GetLsLocation(db, wfiles, write.use)) { + result.children.push_back({id++, *loc}); + } + } + } + } + return result; +} + +void MessageHandler::ccls_dataFlowInto(JsonReader &reader, ReplyOnce &reply) { + Param param; + Reflect(reader, param); + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + std::optional result; + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + // Found symbol. Return references. + int id = 0; + std::unordered_set seen = {}; + result = BuildDataFlow(sym.usr, sym.kind, Use{{sym.range, sym.role},file->id}, seen, db, wfiles, id); + break; + } + reply(result); +} +} // namespace ccls diff --git a/src/messages/ccls_info.cc b/src/messages/ccls_info.cc new file mode 100644 index 000000000..9befa32fa --- /dev/null +++ b/src/messages/ccls_info.cc @@ -0,0 +1,70 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "project.hh" +#include "query.hh" + +namespace ccls { +REFLECT_STRUCT(QueryFile::Def, path, args, language, skipped_ranges, + dependencies); + +namespace { +struct Out_cclsInfo { + struct DB { + int files, funcs, types, vars; + } db; + struct Pipeline { + int pendingIndexRequests; + } pipeline; + struct Project { + int entries; + } project; +}; +REFLECT_STRUCT(Out_cclsInfo::DB, files, funcs, types, vars); +REFLECT_STRUCT(Out_cclsInfo::Pipeline, pendingIndexRequests); +REFLECT_STRUCT(Out_cclsInfo::Project, entries); +REFLECT_STRUCT(Out_cclsInfo, db, pipeline, project); +} // namespace + +void MessageHandler::ccls_info(EmptyParam &, ReplyOnce &reply) { + Out_cclsInfo result; + result.db.files = db->files.size(); + result.db.funcs = db->funcs.size(); + result.db.types = db->types.size(); + result.db.vars = db->vars.size(); + result.pipeline.pendingIndexRequests = pipeline::pending_index_requests; + result.project.entries = 0; + for (auto &[_, folder] : project->root2folder) + result.project.entries += folder.entries.size(); + reply(result); +} + +void MessageHandler::ccls_fileInfo(TextDocumentParam ¶m, ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + if (!file) + return; + + QueryFile::Def result; + // Expose some fields of |QueryFile::Def|. + result.path = file->def->path; + result.args = file->def->args; + result.language = file->def->language; + result.includes = file->def->includes; + result.skipped_ranges = file->def->skipped_ranges; + reply(result); +} +} // namespace ccls diff --git a/src/messages/ccls_inheritance.cc b/src/messages/ccls_inheritance.cc new file mode 100644 index 000000000..b7520ddf7 --- /dev/null +++ b/src/messages/ccls_inheritance.cc @@ -0,0 +1,182 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "hierarchy.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "query.hh" + +#include + +namespace ccls { +namespace { +struct Param : TextDocumentPositionParam { + // If id+kind are specified, expand a node; otherwise textDocument+position + // should be specified for building the root and |levels| of nodes below. + Usr usr; + std::string id; + Kind kind = Kind::Invalid; + + // true: derived classes/functions; false: base classes/functions + bool derived = false; + bool qualified = true; + int levels = 1; + bool hierarchy = false; +}; + +REFLECT_STRUCT(Param, textDocument, position, id, kind, derived, qualified, + levels, hierarchy); + +struct Out_cclsInheritance { + Usr usr; + std::string id; + Kind kind; + std::string_view name; + Location location; + // For unexpanded nodes, this is an upper bound because some entities may be + // undefined. If it is 0, there are no members. + int numChildren; + // Empty if the |levels| limit is reached. + std::vector children; +}; +REFLECT_STRUCT(Out_cclsInheritance, id, kind, name, location, numChildren, + children); + +bool Expand(MessageHandler *m, Out_cclsInheritance *entry, bool derived, + bool qualified, int levels); + +template +bool ExpandHelper(MessageHandler *m, Out_cclsInheritance *entry, bool derived, + bool qualified, int levels, Q &entity) { + const auto *def = entity.AnyDef(); + if (def) { + entry->name = def->Name(qualified); + if (def->spell) { + if (auto loc = GetLsLocation(m->db, m->wfiles, *def->spell)) + entry->location = *loc; + } else if (entity.declarations.size()) { + if (auto loc = GetLsLocation(m->db, m->wfiles, entity.declarations[0])) + entry->location = *loc; + } + } else if (!derived) { + entry->numChildren = 0; + return false; + } + std::unordered_set seen; + if (derived) { + if (levels > 0) { + for (auto usr : entity.derived) { + if (!seen.insert(usr).second) + continue; + Out_cclsInheritance entry1; + entry1.id = std::to_string(usr); + entry1.usr = usr; + entry1.kind = entry->kind; + if (Expand(m, &entry1, derived, qualified, levels - 1)) + entry->children.push_back(std::move(entry1)); + } + entry->numChildren = int(entry->children.size()); + } else + entry->numChildren = int(entity.derived.size()); + } else { + if (levels > 0) { + for (auto usr : def->bases) { + if (!seen.insert(usr).second) + continue; + Out_cclsInheritance entry1; + entry1.id = std::to_string(usr); + entry1.usr = usr; + entry1.kind = entry->kind; + if (Expand(m, &entry1, derived, qualified, levels - 1)) + entry->children.push_back(std::move(entry1)); + } + entry->numChildren = int(entry->children.size()); + } else + entry->numChildren = int(def->bases.size()); + } + return true; +} + +bool Expand(MessageHandler *m, Out_cclsInheritance *entry, bool derived, + bool qualified, int levels) { + if (entry->kind == Kind::Func) + return ExpandHelper(m, entry, derived, qualified, levels, + m->db->Func(entry->usr)); + else + return ExpandHelper(m, entry, derived, qualified, levels, + m->db->Type(entry->usr)); +} + +std::optional BuildInitial(MessageHandler *m, SymbolRef sym, bool derived, + bool qualified, int levels) { + Out_cclsInheritance entry; + entry.id = std::to_string(sym.usr); + entry.usr = sym.usr; + entry.kind = sym.kind; + Expand(m, &entry, derived, qualified, levels); + return entry; +} + +void Inheritance(MessageHandler *m, Param ¶m, ReplyOnce &reply) { + std::optional result; + if (param.id.size()) { + try { + param.usr = std::stoull(param.id); + } catch (...) { + return; + } + result.emplace(); + result->id = std::to_string(param.usr); + result->usr = param.usr; + result->kind = param.kind; + if (!(((param.kind == Kind::Func && m->db->HasFunc(param.usr)) || + (param.kind == Kind::Type && m->db->HasType(param.usr))) && + Expand(m, &*result, param.derived, param.qualified, param.levels))) + result.reset(); + } else { + QueryFile *file = m->FindFile(param.textDocument.uri.GetPath()); + if (!file) + return; + WorkingFile *wf = m->wfiles->GetFile(file->def->path); + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) + if (sym.kind == Kind::Func || sym.kind == Kind::Type) { + result = BuildInitial(m, sym, param.derived, param.qualified, + param.levels); + break; + } + } + + if (param.hierarchy) + reply(result); + else + reply(FlattenHierarchy(result)); +} +} // namespace + +void MessageHandler::ccls_inheritance(JsonReader &reader, ReplyOnce &reply) { + Param param; + Reflect(reader, param); + Inheritance(this, param, reply); +} + +void MessageHandler::textDocument_implementation( + TextDocumentPositionParam ¶m, ReplyOnce &reply) { + Param param1; + param1.textDocument = param.textDocument; + param1.position = param.position; + param1.derived = true; + Inheritance(this, param1, reply); +} +} // namespace ccls diff --git a/src/messages/ccls_member.cc b/src/messages/ccls_member.cc new file mode 100644 index 000000000..d94a3d88d --- /dev/null +++ b/src/messages/ccls_member.cc @@ -0,0 +1,316 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "clang_tu.hh" +#include "hierarchy.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "query.hh" + +#include +#include + +#include + +namespace ccls { +using namespace clang; + +namespace { +struct Param : TextDocumentPositionParam { + // If id is specified, expand a node; otherwise textDocument+position should + // be specified for building the root and |levels| of nodes below. + Usr usr; + std::string id; + + bool qualified = false; + int levels = 1; + // If Kind::Func and the point is at a type, list member functions instead of + // member variables. + Kind kind = Kind::Var; + bool hierarchy = false; +}; + +REFLECT_STRUCT(Param, textDocument, position, id, qualified, levels, kind, + hierarchy); + +struct Out_cclsMember { + Usr usr; + std::string id; + std::string_view name; + std::string fieldName; + Location location; + // For unexpanded nodes, this is an upper bound because some entities may be + // undefined. If it is 0, there are no members. + int numChildren = 0; + // Empty if the |levels| limit is reached. + std::vector children; +}; +REFLECT_STRUCT(Out_cclsMember, id, name, fieldName, location, numChildren, + children); + +bool Expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, + int levels, Kind memberKind); + +// Add a field to |entry| which is a Func/Type. +void DoField(MessageHandler *m, Out_cclsMember *entry, const QueryVar &var, + int64_t offset, bool qualified, int levels) { + const QueryVar::Def *def1 = var.AnyDef(); + if (!def1) + return; + Out_cclsMember entry1; + // With multiple inheritance, the offset is incorrect. + if (offset >= 0) { + if (offset / 8 < 10) + entry1.fieldName += ' '; + entry1.fieldName += std::to_string(offset / 8); + if (offset % 8) { + entry1.fieldName += '.'; + entry1.fieldName += std::to_string(offset % 8); + } + entry1.fieldName += ' '; + } + if (qualified) + entry1.fieldName += def1->detailed_name; + else { + entry1.fieldName += + std::string_view(def1->detailed_name).substr(0, def1->qual_name_offset); + entry1.fieldName += def1->Name(false); + } + if (def1->spell) { + if (std::optional loc = + GetLsLocation(m->db, m->wfiles, *def1->spell)) + entry1.location = *loc; + } + if (def1->type) { + entry1.id = std::to_string(def1->type); + entry1.usr = def1->type; + if (Expand(m, &entry1, qualified, levels, Kind::Var)) + entry->children.push_back(std::move(entry1)); + } else { + entry1.id = "0"; + entry1.usr = 0; + entry->children.push_back(std::move(entry1)); + } +} + +// Expand a type node by adding members recursively to it. +bool Expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, + int levels, Kind memberKind) { + if (0 < entry->usr && entry->usr <= BuiltinType::LastKind) { + entry->name = ClangBuiltinTypeName(int(entry->usr)); + return true; + } + const QueryType *type = &m->db->Type(entry->usr); + const QueryType::Def *def = type->AnyDef(); + // builtin types have no declaration and empty |qualified|. + if (!def) + return false; + entry->name = def->Name(qualified); + std::unordered_set seen; + if (levels > 0) { + std::vector stack; + seen.insert(type->usr); + stack.push_back(type); + while (stack.size()) { + type = stack.back(); + stack.pop_back(); + const auto *def = type->AnyDef(); + if (!def) + continue; + if (def->kind != SymbolKind::Namespace) + for (Usr usr : def->bases) { + auto &type1 = m->db->Type(usr); + if (type1.def.size()) { + seen.insert(type1.usr); + stack.push_back(&type1); + } + } + if (def->alias_of) { + const QueryType::Def *def1 = m->db->Type(def->alias_of).AnyDef(); + Out_cclsMember entry1; + entry1.id = std::to_string(def->alias_of); + entry1.usr = def->alias_of; + if (def1 && def1->spell) { + // The declaration of target type. + if (std::optional loc = + GetLsLocation(m->db, m->wfiles, *def1->spell)) + entry1.location = *loc; + } else if (def->spell) { + // Builtin types have no declaration but the typedef declaration + // itself is useful. + if (std::optional loc = + GetLsLocation(m->db, m->wfiles, *def->spell)) + entry1.location = *loc; + } + if (def1 && qualified) + entry1.fieldName = def1->detailed_name; + if (Expand(m, &entry1, qualified, levels - 1, memberKind)) { + // For builtin types |name| is set. + if (entry1.fieldName.empty()) + entry1.fieldName = std::string(entry1.name); + entry->children.push_back(std::move(entry1)); + } + } else if (memberKind == Kind::Func) { + llvm::DenseSet seen1; + for (auto &def : type->def) + for (Usr usr : def.funcs) + if (seen1.insert(usr).second) { + QueryFunc &func1 = m->db->Func(usr); + if (const QueryFunc::Def *def1 = func1.AnyDef()) { + Out_cclsMember entry1; + entry1.fieldName = def1->Name(false); + if (def1->spell) { + if (auto loc = GetLsLocation(m->db, m->wfiles, *def1->spell)) + entry1.location = *loc; + } else if (func1.declarations.size()) { + if (auto loc = GetLsLocation(m->db, m->wfiles, + func1.declarations[0])) + entry1.location = *loc; + } + entry->children.push_back(std::move(entry1)); + } + } + } else if (memberKind == Kind::Type) { + llvm::DenseSet seen1; + for (auto &def : type->def) + for (Usr usr : def.types) + if (seen1.insert(usr).second) { + QueryType &type1 = m->db->Type(usr); + if (const QueryType::Def *def1 = type1.AnyDef()) { + Out_cclsMember entry1; + entry1.fieldName = def1->Name(false); + if (def1->spell) { + if (auto loc = GetLsLocation(m->db, m->wfiles, *def1->spell)) + entry1.location = *loc; + } else if (type1.declarations.size()) { + if (auto loc = GetLsLocation(m->db, m->wfiles, + type1.declarations[0])) + entry1.location = *loc; + } + entry->children.push_back(std::move(entry1)); + } + } + } else { + llvm::DenseSet seen1; + for (auto &def : type->def) + for (auto it : def.vars) + if (seen1.insert(it.first).second) { + QueryVar &var = m->db->Var(it.first); + if (!var.def.empty()) + DoField(m, entry, var, it.second, qualified, levels - 1); + } + } + } + entry->numChildren = int(entry->children.size()); + } else + entry->numChildren = def->alias_of ? 1 : int(def->vars.size()); + return true; +} + +std::optional BuildInitial(MessageHandler *m, Kind kind, + Usr root_usr, bool qualified, + int levels, Kind memberKind) { + switch (kind) { + default: + return {}; + case Kind::Func: { + const auto *def = m->db->Func(root_usr).AnyDef(); + if (!def) + return {}; + + Out_cclsMember entry; + // Not type, |id| is invalid. + entry.name = def->Name(qualified); + if (def->spell) { + if (auto loc = GetLsLocation(m->db, m->wfiles, *def->spell)) + entry.location = *loc; + } + for (Usr usr : def->vars) { + auto &var = m->db->Var(usr); + if (var.def.size()) + DoField(m, &entry, var, -1, qualified, levels - 1); + } + return entry; + } + case Kind::Type: { + const auto *def = m->db->Type(root_usr).AnyDef(); + if (!def) + return {}; + + Out_cclsMember entry; + entry.id = std::to_string(root_usr); + entry.usr = root_usr; + if (def->spell) { + if (auto loc = GetLsLocation(m->db, m->wfiles, *def->spell)) + entry.location = *loc; + } + Expand(m, &entry, qualified, levels, memberKind); + return entry; + } + } +} +} // namespace + +void MessageHandler::ccls_member(JsonReader &reader, ReplyOnce &reply) { + Param param; + Reflect(reader, param); + std::optional result; + if (param.id.size()) { + try { + param.usr = std::stoull(param.id); + } catch (...) { + return; + } + result.emplace(); + result->id = std::to_string(param.usr); + result->usr = param.usr; + // entry.name is empty as it is known by the client. + if (!(db->HasType(param.usr) && Expand(this, &*result, param.qualified, + param.levels, param.kind))) + result.reset(); + } else { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + switch (sym.kind) { + case Kind::Func: + case Kind::Type: + result = BuildInitial(this, sym.kind, sym.usr, param.qualified, + param.levels, param.kind); + break; + case Kind::Var: { + const QueryVar::Def *def = db->GetVar(sym).AnyDef(); + if (def && def->type) + result = BuildInitial(this, Kind::Type, def->type, param.qualified, + param.levels, param.kind); + break; + } + default: + continue; + } + break; + } + } + + if (param.hierarchy) + reply(result); + else + reply(FlattenHierarchy(result)); +} +} // namespace ccls diff --git a/src/messages/ccls_navigate.cc b/src/messages/ccls_navigate.cc new file mode 100644 index 000000000..d09d57a83 --- /dev/null +++ b/src/messages/ccls_navigate.cc @@ -0,0 +1,107 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "query.hh" + +namespace ccls { +namespace { +struct Param { + TextDocumentIdentifier textDocument; + Position position; + std::string direction; +}; +REFLECT_STRUCT(Param, textDocument, position, direction); + +Maybe FindParent(QueryFile *file, Pos pos) { + Maybe parent; + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && sym.extent.Valid() && sym.extent.start <= pos && + pos < sym.extent.end && + (!parent || (parent->start == sym.extent.start + ? parent->end < sym.extent.end + : parent->start < sym.extent.start))) + parent = sym.extent; + return parent; +} +} // namespace + +void MessageHandler::ccls_navigate(JsonReader &reader, ReplyOnce &reply) { + Param param; + Reflect(reader, param); + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + Position ls_pos = param.position; + if (wf->index_lines.size()) + if (auto line = + wf->GetIndexPosFromBufferPos(ls_pos.line, &ls_pos.character, false)) + ls_pos.line = *line; + Pos pos{(int16_t)ls_pos.line, (int16_t)ls_pos.character}; + + Maybe res; + switch (param.direction[0]) { + case 'D': { + Maybe parent = FindParent(file, pos); + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && pos < sym.extent.start && + (!parent || sym.extent.end <= parent->end) && + (!res || sym.extent.start < res->start)) + res = sym.extent; + break; + } + case 'L': + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && sym.extent.Valid() && sym.extent.end <= pos && + (!res || (res->end == sym.extent.end ? sym.extent.start < res->start + : res->end < sym.extent.end))) + res = sym.extent; + break; + case 'R': { + Maybe parent = FindParent(file, pos); + if (parent && parent->start.line == pos.line && pos < parent->end) { + pos = parent->end; + if (pos.column) + pos.column--; + } + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && sym.extent.Valid() && pos < sym.extent.start && + (!res || + (sym.extent.start == res->start ? res->end < sym.extent.end + : sym.extent.start < res->start))) + res = sym.extent; + break; + } + case 'U': + default: + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && sym.extent.Valid() && sym.extent.start < pos && + pos < sym.extent.end && (!res || res->start < sym.extent.start)) + res = sym.extent; + break; + } + std::vector result; + if (res) + if (auto ls_range = GetLsRange(wf, *res)) { + Location &ls_loc = result.emplace_back(); + ls_loc.uri = param.textDocument.uri; + ls_loc.range = *ls_range; + } + reply(result); +} +} // namespace ccls diff --git a/src/messages/ccls_reload.cc b/src/messages/ccls_reload.cc new file mode 100644 index 000000000..3a4268c4f --- /dev/null +++ b/src/messages/ccls_reload.cc @@ -0,0 +1,47 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "project.hh" +#include "sema_manager.hh" +#include "working_files.hh" + +#include +#include + +namespace ccls { +namespace { +struct Param { + bool dependencies = true; + std::vector whitelist; + std::vector blacklist; +}; +REFLECT_STRUCT(Param, dependencies, whitelist, blacklist); +} // namespace + +void MessageHandler::ccls_reload(JsonReader &reader) { + Param param; + Reflect(reader, param); + // Send index requests for every file. + if (param.whitelist.empty() && param.blacklist.empty()) { + vfs->Clear(); + db->clear(); + project->Index(wfiles, RequestId()); + manager->Clear(); + return; + } +} +} // namespace ccls diff --git a/src/messages/ccls_vars.cc b/src/messages/ccls_vars.cc new file mode 100644 index 000000000..03941491c --- /dev/null +++ b/src/messages/ccls_vars.cc @@ -0,0 +1,63 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "query.hh" + +namespace ccls { +namespace { +struct Param : TextDocumentPositionParam { + // 1: field + // 2: local + // 4: parameter + unsigned kind = ~0u; +}; +REFLECT_STRUCT(Param, textDocument, position, kind); +} // namespace + +void MessageHandler::ccls_vars(JsonReader &reader, ReplyOnce &reply) { + Param param; + Reflect(reader, param); + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + std::vector result; + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + Usr usr = sym.usr; + switch (sym.kind) { + default: + break; + case Kind::Var: { + const QueryVar::Def *def = db->GetVar(sym).AnyDef(); + if (!def || !def->type) + continue; + usr = def->type; + [[fallthrough]]; + } + case Kind::Type: + result = GetLsLocations( + db, wfiles, + GetVarDeclarations(db, db->Type(usr).instances, param.kind)); + break; + } + } + reply(result); +} +} // namespace ccls diff --git a/src/messages/cquery_base.cc b/src/messages/cquery_base.cc deleted file mode 100644 index 3c970e124..000000000 --- a/src/messages/cquery_base.cc +++ /dev/null @@ -1,46 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct Ipc_CqueryBase : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryBase; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryBase, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryBase); - -struct CqueryBaseHandler : BaseMessageHandler { - void Run(Ipc_CqueryBase* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = request->id; - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - if (sym.kind == SymbolKind::Type) { - if (const auto* def = db->GetType(sym).AnyDef()) - out.result = - GetLsLocationExs(db, working_files, GetDeclarations(db, def->bases), - config->xref.container, config->xref.maxNum); - break; - } else if (sym.kind == SymbolKind::Func) { - if (const auto* def = db->GetFunc(sym).AnyDef()) - out.result = - GetLsLocationExs(db, working_files, GetDeclarations(db, def->bases), - config->xref.container, config->xref.maxNum); - break; - } - } - QueueManager::WriteStdout(IpcId::CqueryBase, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryBaseHandler); -} // namespace diff --git a/src/messages/cquery_call_hierarchy.cc b/src/messages/cquery_call_hierarchy.cc deleted file mode 100644 index aadac31b6..000000000 --- a/src/messages/cquery_call_hierarchy.cc +++ /dev/null @@ -1,211 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -#include - -namespace { -enum class CallType : uint8_t { Direct = 0, Base = 1, Derived = 2, All = 1 | 2 }; -MAKE_REFLECT_TYPE_PROXY(CallType); - -bool operator&(CallType lhs, CallType rhs) { - return uint8_t(lhs) & uint8_t(rhs); -} - -struct Ipc_CqueryCallHierarchy - : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryCallHierarchy; - struct Params { - // If id is specified, expand a node; otherwise textDocument+position should - // be specified for building the root and |levels| of nodes below. - lsTextDocumentIdentifier textDocument; - lsPosition position; - - Maybe id; - - // true: callee tree (functions called by this function); false: caller tree - // (where this function is called) - bool callee = false; - // Base: include base functions; All: include both base and derived - // functions. - CallType callType = CallType::All; - bool detailedName = false; - int levels = 1; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryCallHierarchy::Params, - textDocument, - position, - id, - callee, - callType, - detailedName, - levels); -MAKE_REFLECT_STRUCT(Ipc_CqueryCallHierarchy, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryCallHierarchy); - -struct Out_CqueryCallHierarchy : public lsOutMessage { - struct Entry { - QueryFuncId id; - std::string_view name; - lsLocation location; - CallType callType = CallType::Direct; - int numChildren; - // Empty if the |levels| limit is reached. - std::vector children; - }; - - lsRequestId id; - optional result; -}; -MAKE_REFLECT_STRUCT(Out_CqueryCallHierarchy::Entry, - id, - name, - location, - callType, - numChildren, - children); -MAKE_REFLECT_STRUCT(Out_CqueryCallHierarchy, jsonrpc, id, result); - -bool Expand(MessageHandler* m, - Out_CqueryCallHierarchy::Entry* entry, - bool callee, - CallType call_type, - bool detailed_name, - int levels) { - const QueryFunc& func = m->db->funcs[entry->id.id]; - const QueryFunc::Def* def = func.AnyDef(); - entry->numChildren = 0; - if (!def) - return false; - auto handle = [&](Use use, CallType call_type) { - entry->numChildren++; - if (levels > 0) { - Out_CqueryCallHierarchy::Entry entry1; - entry1.id = QueryFuncId(use.id); - if (auto loc = GetLsLocation(m->db, m->working_files, use)) - entry1.location = *loc; - entry1.callType = call_type; - if (Expand(m, &entry1, callee, call_type, detailed_name, levels - 1)) - entry->children.push_back(std::move(entry1)); - } - }; - auto handle_uses = [&](const QueryFunc& func, CallType call_type) { - if (callee) { - if (const auto* def = func.AnyDef()) - for (SymbolRef ref : def->callees) - if (ref.kind == SymbolKind::Func) - handle(Use(ref.range, ref.id, ref.kind, ref.role, def->file), call_type); - } else { - for (Use use : func.uses) - if (use.kind == SymbolKind::Func) - handle(use, call_type); - } - }; - - std::unordered_set seen; - seen.insert(func.usr); - std::vector stack; - if (detailed_name) - entry->name = def->detailed_name; - else - entry->name = def->ShortName(); - handle_uses(func, CallType::Direct); - - // Callers/callees of base functions. - if (call_type & CallType::Base) { - stack.push_back(&func); - while (stack.size()) { - const QueryFunc& func1 = *stack.back(); - stack.pop_back(); - if (auto* def1 = func1.AnyDef()) { - EachDefinedEntity(m->db->funcs, def1->bases, [&](QueryFunc& func2) { - if (!seen.count(func2.usr)) { - seen.insert(func2.usr); - stack.push_back(&func2); - handle_uses(func2, CallType::Base); - } - }); - } - } - } - - // Callers/callees of derived functions. - if (call_type & CallType::Derived) { - stack.push_back(&func); - while (stack.size()) { - const QueryFunc& func1 = *stack.back(); - stack.pop_back(); - EachDefinedEntity(m->db->funcs, func1.derived, [&](QueryFunc& func2) { - if (!seen.count(func2.usr)) { - seen.insert(func2.usr); - stack.push_back(&func2); - handle_uses(func2, CallType::Derived); - } - }); - } - } - return true; -} - -struct CqueryCallHierarchyHandler - : BaseMessageHandler { - optional BuildInitial(QueryFuncId root_id, - bool callee, - CallType call_type, - bool detailed_name, - int levels) { - const auto* def = db->funcs[root_id.id].AnyDef(); - if (!def) - return {}; - - Out_CqueryCallHierarchy::Entry entry; - entry.id = root_id; - entry.callType = CallType::Direct; - if (def->spell) { - if (optional loc = - GetLsLocation(db, working_files, *def->spell)) - entry.location = *loc; - } - Expand(this, &entry, callee, call_type, detailed_name, levels); - return entry; - } - - void Run(Ipc_CqueryCallHierarchy* request) override { - const auto& params = request->params; - Out_CqueryCallHierarchy out; - out.id = request->id; - - if (params.id) { - Out_CqueryCallHierarchy::Entry entry; - entry.id = *params.id; - entry.callType = CallType::Direct; - if (entry.id.id < db->funcs.size()) - Expand(this, &entry, params.callee, params.callType, - params.detailedName, params.levels); - out.result = std::move(entry); - } else { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - params.textDocument.uri.GetPath(), &file)) - return; - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, params.position)) { - if (sym.kind == SymbolKind::Func) { - out.result = - BuildInitial(QueryFuncId(sym.id), params.callee, params.callType, - params.detailedName, params.levels); - break; - } - } - } - - QueueManager::WriteStdout(IpcId::CqueryCallHierarchy, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryCallHierarchyHandler); - -} // namespace diff --git a/src/messages/cquery_callers.cc b/src/messages/cquery_callers.cc deleted file mode 100644 index 088b93137..000000000 --- a/src/messages/cquery_callers.cc +++ /dev/null @@ -1,45 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct Ipc_CqueryCallers : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryCallers; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryCallers, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryCallers); - -struct CqueryCallersHandler : BaseMessageHandler { - void Run(Ipc_CqueryCallers* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = request->id; - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - if (sym.kind == SymbolKind::Func) { - QueryFunc& func = db->GetFunc(sym); - std::vector uses = func.uses; - for (Use func_ref : GetUsesForAllBases(db, func)) - uses.push_back(func_ref); - for (Use func_ref : GetUsesForAllDerived(db, func)) - uses.push_back(func_ref); - out.result = - GetLsLocationExs(db, working_files, uses, config->xref.container, - config->xref.maxNum); - break; - } - } - QueueManager::WriteStdout(IpcId::CqueryCallers, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryCallersHandler); -} // namespace diff --git a/src/messages/cquery_derived.cc b/src/messages/cquery_derived.cc deleted file mode 100644 index 6e0565848..000000000 --- a/src/messages/cquery_derived.cc +++ /dev/null @@ -1,46 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct Ipc_CqueryDerived : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryDerived; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryDerived, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryDerived); - -struct CqueryDerivedHandler : BaseMessageHandler { - void Run(Ipc_CqueryDerived* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = request->id; - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - if (sym.kind == SymbolKind::Type) { - QueryType& type = db->GetType(sym); - out.result = - GetLsLocationExs(db, working_files, GetDeclarations(db, type.derived), - config->xref.container, config->xref.maxNum); - break; - } else if (sym.kind == SymbolKind::Func) { - QueryFunc& func = db->GetFunc(sym); - out.result = - GetLsLocationExs(db, working_files, GetDeclarations(db, func.derived), - config->xref.container, config->xref.maxNum); - break; - } - } - QueueManager::WriteStdout(IpcId::CqueryDerived, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryDerivedHandler); -} // namespace diff --git a/src/messages/cquery_did_view.cc b/src/messages/cquery_did_view.cc deleted file mode 100644 index 7b3949967..000000000 --- a/src/messages/cquery_did_view.cc +++ /dev/null @@ -1,38 +0,0 @@ -#include "clang_complete.h" -#include "message_handler.h" -#include "working_files.h" - -namespace { -struct Ipc_CqueryTextDocumentDidView - : public NotificationMessage { - const static IpcId kIpcId = IpcId::CqueryTextDocumentDidView; - struct Params { - lsDocumentUri textDocumentUri; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryTextDocumentDidView::Params, textDocumentUri); -MAKE_REFLECT_STRUCT(Ipc_CqueryTextDocumentDidView, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryTextDocumentDidView); - -struct CqueryDidViewHandler - : BaseMessageHandler { - void Run(Ipc_CqueryTextDocumentDidView* request) override { - std::string path = request->params.textDocumentUri.GetPath(); - - WorkingFile* working_file = working_files->GetFileByFilename(path); - if (!working_file) - return; - QueryFile* file = nullptr; - if (!FindFileOrFail(db, project, nullopt, path, &file)) - return; - - clang_complete->NotifyView(path); - if (file->def) { - EmitInactiveLines(working_file, file->def->inactive_regions); - EmitSemanticHighlighting(db, semantic_cache, working_file, file); - } - } -}; -REGISTER_MESSAGE_HANDLER(CqueryDidViewHandler); -} // namespace diff --git a/src/messages/cquery_file_info.cc b/src/messages/cquery_file_info.cc deleted file mode 100644 index 1fa9998b3..000000000 --- a/src/messages/cquery_file_info.cc +++ /dev/null @@ -1,44 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct lsDocumentSymbolParams { - lsTextDocumentIdentifier textDocument; -}; -MAKE_REFLECT_STRUCT(lsDocumentSymbolParams, textDocument); - -struct Ipc_CqueryFileInfo : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryFileInfo; - lsDocumentSymbolParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryFileInfo, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryFileInfo); - -struct Out_CqueryFileInfo : public lsOutMessage { - lsRequestId id; - QueryFile::Def result; -}; -MAKE_REFLECT_STRUCT(Out_CqueryFileInfo, jsonrpc, id, result); - -struct CqueryFileInfoHandler : BaseMessageHandler { - void Run(Ipc_CqueryFileInfo* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - Out_CqueryFileInfo out; - out.id = request->id; - // Expose some fields of |QueryFile::Def|. - out.result.path = file->def->path; - out.result.args = file->def->args; - out.result.language = file->def->language; - out.result.includes = file->def->includes; - out.result.inactive_regions = file->def->inactive_regions; - QueueManager::WriteStdout(IpcId::CqueryFileInfo, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryFileInfoHandler); -} // namespace diff --git a/src/messages/cquery_freshen_index.cc b/src/messages/cquery_freshen_index.cc deleted file mode 100644 index bb08b2a11..000000000 --- a/src/messages/cquery_freshen_index.cc +++ /dev/null @@ -1,94 +0,0 @@ -#include "cache_manager.h" -#include "match.h" -#include "message_handler.h" -#include "platform.h" -#include "project.h" -#include "queue_manager.h" -#include "timer.h" -#include "timestamp_manager.h" -#include "working_files.h" - -#include -#include -#include - -namespace { -struct Ipc_CqueryFreshenIndex - : public NotificationMessage { - const static IpcId kIpcId = IpcId::CqueryFreshenIndex; - struct Params { - bool dependencies = true; - std::vector whitelist; - std::vector blacklist; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryFreshenIndex::Params, - dependencies, - whitelist, - blacklist); -MAKE_REFLECT_STRUCT(Ipc_CqueryFreshenIndex, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryFreshenIndex); - -struct CqueryFreshenIndexHandler : BaseMessageHandler { - void Run(Ipc_CqueryFreshenIndex* request) override { - LOG_S(INFO) << "Freshening " << project->entries.size() << " files"; - - // TODO: think about this flow and test it more. - GroupMatch matcher(request->params.whitelist, request->params.blacklist); - - // Unmark all files whose timestamp has changed. - std::shared_ptr cache_manager = ICacheManager::Make(config); - - std::queue q; - // |need_index| stores every filename ever enqueued. - std::unordered_set need_index; - // Reverse dependency graph. - std::unordered_map> graph; - // filename -> QueryFile mapping for files haven't enqueued. - std::unordered_map path_to_file; - - for (const auto& file : db->files) - if (file.def) { - if (matcher.IsMatch(file.def->path)) - q.push(&file); - else - path_to_file[file.def->path] = &file; - for (const std::string& dependency : file.def->dependencies) - graph[dependency].push_back(file.def->path); - } - - while (!q.empty()) { - const QueryFile* file = q.front(); - q.pop(); - need_index.insert(file->def->path); - - optional modification_timestamp = - GetLastModificationTime(file->def->path); - if (!modification_timestamp) - continue; - optional cached_modification = - timestamp_manager->GetLastCachedModificationTime(cache_manager.get(), - file->def->path); - if (modification_timestamp != cached_modification) - file_consumer_shared->Reset(file->def->path); - - if (request->params.dependencies) - for (const std::string& path : graph[file->def->path]) { - auto it = path_to_file.find(path); - if (it != path_to_file.end()) { - q.push(it->second); - path_to_file.erase(it); - } - } - } - - Timer time; - // Send index requests for every file. - project->Index(config, QueueManager::instance(), working_files, - std::monostate()); - time.ResetAndPrint("[perf] Dispatched $cquery/freshenIndex index requests"); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryFreshenIndexHandler); -} // namespace diff --git a/src/messages/cquery_index_file.cc b/src/messages/cquery_index_file.cc deleted file mode 100644 index 59ef02f5b..000000000 --- a/src/messages/cquery_index_file.cc +++ /dev/null @@ -1,37 +0,0 @@ -#include "cache_manager.h" -#include "message_handler.h" -#include "platform.h" -#include "queue_manager.h" - -#include - -namespace { -struct Ipc_CqueryIndexFile : public NotificationMessage { - static constexpr IpcId kIpcId = IpcId::CqueryIndexFile; - struct Params { - std::string path; - std::vector args; - bool is_interactive = false; - std::string contents; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryIndexFile::Params, - path, - args, - is_interactive, - contents); -MAKE_REFLECT_STRUCT(Ipc_CqueryIndexFile, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryIndexFile); - -struct CqueryIndexFileHandler : BaseMessageHandler { - void Run(Ipc_CqueryIndexFile* request) override { - LOG_S(INFO) << "Indexing file " << request->params.path; - QueueManager::instance()->index_request.PushBack( - Index_Request(NormalizePath(request->params.path), request->params.args, - request->params.is_interactive, request->params.contents, - ICacheManager::Make(config))); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryIndexFileHandler); -} // namespace diff --git a/src/messages/cquery_inheritance_hierarchy.cc b/src/messages/cquery_inheritance_hierarchy.cc deleted file mode 100644 index 1d702a9cc..000000000 --- a/src/messages/cquery_inheritance_hierarchy.cc +++ /dev/null @@ -1,179 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct Ipc_CqueryInheritanceHierarchy - : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryInheritanceHierarchy; - struct Params { - // If id+kind are specified, expand a node; otherwise textDocument+position should - // be specified for building the root and |levels| of nodes below. - lsTextDocumentIdentifier textDocument; - lsPosition position; - - Maybe> id; - SymbolKind kind = SymbolKind::Invalid; - - // true: derived classes/functions; false: base classes/functions - bool derived = false; - bool detailedName = false; - int levels = 1; - }; - Params params; -}; - -MAKE_REFLECT_STRUCT(Ipc_CqueryInheritanceHierarchy::Params, - textDocument, - position, - id, - kind, - derived, - detailedName, - levels); -MAKE_REFLECT_STRUCT(Ipc_CqueryInheritanceHierarchy, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryInheritanceHierarchy); - -struct Out_CqueryInheritanceHierarchy - : public lsOutMessage { - struct Entry { - Id id; - SymbolKind kind; - std::string_view name; - lsLocation location; - // For unexpanded nodes, this is an upper bound because some entities may be - // undefined. If it is 0, there are no members. - int numChildren; - // Empty if the |levels| limit is reached. - std::vector children; - }; - lsRequestId id; - optional result; -}; -MAKE_REFLECT_STRUCT(Out_CqueryInheritanceHierarchy::Entry, - id, - kind, - name, - location, - numChildren, - children); -MAKE_REFLECT_STRUCT(Out_CqueryInheritanceHierarchy, jsonrpc, id, result); - -bool Expand(MessageHandler* m, - Out_CqueryInheritanceHierarchy::Entry* entry, - bool derived, - bool detailed_name, - int levels); - -template -bool ExpandHelper(MessageHandler* m, - Out_CqueryInheritanceHierarchy::Entry* entry, - bool derived, - bool detailed_name, - int levels, - Q& entity) { - const auto* def = entity.AnyDef(); - if (!def) { - entry->numChildren = 0; - return false; - } - if (detailed_name) - entry->name = def->DetailedName(false); - else - entry->name = def->ShortName(); - if (def->spell) { - if (optional loc = - GetLsLocation(m->db, m->working_files, *def->spell)) - entry->location = *loc; - } - if (derived) { - if (levels > 0) { - for (auto id : entity.derived) { - Out_CqueryInheritanceHierarchy::Entry entry1; - entry1.id = id; - entry1.kind = entry->kind; - if (Expand(m, &entry1, derived, detailed_name, levels - 1)) - entry->children.push_back(std::move(entry1)); - } - entry->numChildren = int(entry->children.size()); - } else - entry->numChildren = int(entity.derived.size()); - } else { - if (levels > 0) { - for (auto id : def->bases) { - Out_CqueryInheritanceHierarchy::Entry entry1; - entry1.id = id; - entry1.kind = entry->kind; - if (Expand(m, &entry1, derived, detailed_name, levels - 1)) - entry->children.push_back(std::move(entry1)); - } - entry->numChildren = int(entry->children.size()); - } else - entry->numChildren = int(def->bases.size()); - } - return true; -} - -bool Expand(MessageHandler* m, - Out_CqueryInheritanceHierarchy::Entry* entry, - bool derived, - bool detailed_name, - int levels) { - if (entry->kind == SymbolKind::Func) - return ExpandHelper(m, entry, derived, detailed_name, levels, - m->db->funcs[entry->id.id]); - else - return ExpandHelper(m, entry, derived, detailed_name, levels, - m->db->types[entry->id.id]); -} - -struct CqueryInheritanceHierarchyHandler - : BaseMessageHandler { - optional - BuildInitial(SymbolRef sym, bool derived, bool detailed_name, int levels) { - Out_CqueryInheritanceHierarchy::Entry entry; - entry.id = sym.id; - entry.kind = sym.kind; - Expand(this, &entry, derived, detailed_name, levels); - return entry; - } - - void Run(Ipc_CqueryInheritanceHierarchy* request) override { - const auto& params = request->params; - Out_CqueryInheritanceHierarchy out; - out.id = request->id; - - if (params.id) { - Out_CqueryInheritanceHierarchy::Entry entry; - entry.id = *params.id; - entry.kind = params.kind; - if (((entry.kind == SymbolKind::Func && entry.id.id < db->funcs.size()) || - (entry.kind == SymbolKind::Type && - entry.id.id < db->types.size())) && - Expand(this, &entry, params.derived, params.detailedName, - params.levels)) - out.result = std::move(entry); - } else { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - params.textDocument.uri.GetPath(), &file)) - return; - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - if (sym.kind == SymbolKind::Func || sym.kind == SymbolKind::Type) { - out.result = BuildInitial(sym, params.derived, params.detailedName, - params.levels); - break; - } - } - } - - QueueManager::WriteStdout(IpcId::CqueryInheritanceHierarchy, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryInheritanceHierarchyHandler); - -} // namespace diff --git a/src/messages/cquery_member_hierarchy.cc b/src/messages/cquery_member_hierarchy.cc deleted file mode 100644 index 0314a425d..000000000 --- a/src/messages/cquery_member_hierarchy.cc +++ /dev/null @@ -1,254 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct Ipc_CqueryMemberHierarchy - : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryMemberHierarchy; - struct Params { - // If id is specified, expand a node; otherwise textDocument+position should - // be specified for building the root and |levels| of nodes below. - lsTextDocumentIdentifier textDocument; - lsPosition position; - - Maybe id; - - bool detailedName = false; - int levels = 1; - }; - Params params; -}; - -MAKE_REFLECT_STRUCT(Ipc_CqueryMemberHierarchy::Params, - textDocument, - position, - id, - detailedName, - levels); -MAKE_REFLECT_STRUCT(Ipc_CqueryMemberHierarchy, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryMemberHierarchy); - -struct Out_CqueryMemberHierarchy - : public lsOutMessage { - struct Entry { - QueryTypeId id; - std::string_view name; - std::string fieldName; - lsLocation location; - // For unexpanded nodes, this is an upper bound because some entities may be - // undefined. If it is 0, there are no members. - int numChildren = 0; - // Empty if the |levels| limit is reached. - std::vector children; - }; - lsRequestId id; - optional result; -}; -MAKE_REFLECT_STRUCT(Out_CqueryMemberHierarchy::Entry, - id, - name, - fieldName, - location, - numChildren, - children); -MAKE_REFLECT_STRUCT(Out_CqueryMemberHierarchy, jsonrpc, id, result); - -bool Expand(MessageHandler* m, - Out_CqueryMemberHierarchy::Entry* entry, - bool detailed_name, - int levels); - -// Add a field to |entry| which is a Func/Type. -void DoField(MessageHandler* m, - Out_CqueryMemberHierarchy::Entry* entry, - const QueryVar& var, - bool detailed_name, - int levels) { - const QueryVar::Def* def1 = var.AnyDef(); - if (!def1) - return; - Out_CqueryMemberHierarchy::Entry entry1; - if (detailed_name) - entry1.fieldName = def1->DetailedName(false); - else - entry1.fieldName = std::string(def1->ShortName()); - if (def1->spell) { - if (optional loc = - GetLsLocation(m->db, m->working_files, *def1->spell)) - entry1.location = *loc; - } - if (def1->type) { - entry1.id = *def1->type; - if (Expand(m, &entry1, detailed_name, levels)) - entry->children.push_back(std::move(entry1)); - } else { - entry1.id = QueryTypeId(); - entry->children.push_back(std::move(entry1)); - } -} - -// Expand a type node by adding members recursively to it. -bool Expand(MessageHandler* m, - Out_CqueryMemberHierarchy::Entry* entry, - bool detailed_name, - int levels) { - const QueryType& type = m->db->types[entry->id.id]; - const QueryType::Def* def = type.AnyDef(); - // builtin types have no declaration and empty |detailed_name|. - if (CXType_FirstBuiltin <= type.usr && type.usr <= CXType_LastBuiltin) { - entry->name = ClangBuiltinTypeName(CXTypeKind(type.usr)); - return true; - } - if (!def) - return false; - if (detailed_name) - entry->name = def->detailed_name; - else - entry->name = def->ShortName(); - std::unordered_set seen; - if (levels > 0) { - std::vector stack; - seen.insert(type.usr); - stack.push_back(&type); - while (stack.size()) { - const auto* def = stack.back()->AnyDef(); - stack.pop_back(); - if (def) { - EachDefinedEntity(m->db->types, def->bases, [&](QueryType& type1) { - if (!seen.count(type1.usr)) { - seen.insert(type1.usr); - stack.push_back(&type1); - } - }); - if (def->alias_of) { - const QueryType::Def* def1 = m->db->types[def->alias_of->id].AnyDef(); - Out_CqueryMemberHierarchy::Entry entry1; - entry1.id = *def->alias_of; - if (def1 && def1->spell) { - // The declaration of target type. - if (optional loc = - GetLsLocation(m->db, m->working_files, *def1->spell)) - entry1.location = *loc; - } else if (def->spell) { - // Builtin types have no declaration but the typedef declaration - // itself is useful. - if (optional loc = - GetLsLocation(m->db, m->working_files, *def->spell)) - entry1.location = *loc; - } - if (def1 && detailed_name) - entry1.fieldName = def1->detailed_name; - if (Expand(m, &entry1, detailed_name, levels - 1)) { - // For builtin types |name| is set. - if (detailed_name && entry1.fieldName.empty()) - entry1.fieldName = std::string(entry1.name); - entry->children.push_back(std::move(entry1)); - } - } else { - EachDefinedEntity(m->db->vars, def->vars, [&](QueryVar& var) { - DoField(m, entry, var, detailed_name, levels - 1); - }); - } - } - } - entry->numChildren = int(entry->children.size()); - } else - entry->numChildren = def->alias_of ? 1 : int(def->vars.size()); - return true; -} - -struct CqueryMemberHierarchyHandler - : BaseMessageHandler { - optional BuildInitial(QueryFuncId root_id, - bool detailed_name, - int levels) { - const auto* def = db->funcs[root_id.id].AnyDef(); - if (!def) - return {}; - - Out_CqueryMemberHierarchy::Entry entry; - // Not type, |id| is invalid. - if (detailed_name) - entry.name = def->DetailedName(false); - else - entry.name = std::string(def->ShortName()); - if (def->spell) { - if (optional loc = - GetLsLocation(db, working_files, *def->spell)) - entry.location = *loc; - } - EachDefinedEntity(db->vars, def->vars, [&](QueryVar& var) { - DoField(this, &entry, var, detailed_name, levels - 1); - }); - return entry; - } - - optional BuildInitial(QueryTypeId root_id, - bool detailed_name, - int levels) { - const auto* def = db->types[root_id.id].AnyDef(); - if (!def) - return {}; - - Out_CqueryMemberHierarchy::Entry entry; - entry.id = root_id; - if (def->spell) { - if (optional loc = - GetLsLocation(db, working_files, *def->spell)) - entry.location = *loc; - } - Expand(this, &entry, detailed_name, levels); - return entry; - } - - void Run(Ipc_CqueryMemberHierarchy* request) override { - const auto& params = request->params; - Out_CqueryMemberHierarchy out; - out.id = request->id; - - if (params.id) { - Out_CqueryMemberHierarchy::Entry entry; - entry.id = *request->params.id; - // entry.name is empty as it is known by the client. - if (entry.id.id < db->types.size() && - Expand(this, &entry, params.detailedName, params.levels)) - out.result = std::move(entry); - } else { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - params.textDocument.uri.GetPath(), &file)) - return; - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, params.position)) { - switch (sym.kind) { - case SymbolKind::Func: - out.result = BuildInitial(QueryFuncId(sym.id), params.detailedName, - params.levels); - break; - case SymbolKind::Type: - out.result = BuildInitial(QueryTypeId(sym.id), params.detailedName, - params.levels); - break; - case SymbolKind::Var: { - const QueryVar::Def* def = db->GetVar(sym).AnyDef(); - if (def && def->type) - out.result = BuildInitial(QueryTypeId(*def->type), params.detailedName, - params.levels); - break; - } - default: - continue; - } - break; - } - } - - QueueManager::WriteStdout(IpcId::CqueryMemberHierarchy, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryMemberHierarchyHandler); - -} // namespace diff --git a/src/messages/cquery_random.cc b/src/messages/cquery_random.cc deleted file mode 100644 index 510088c08..000000000 --- a/src/messages/cquery_random.cc +++ /dev/null @@ -1,144 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -#include -#include -#include - -namespace { -struct Ipc_CqueryRandom : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryRandom; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryRandom, id); -REGISTER_IPC_MESSAGE(Ipc_CqueryRandom); - -const double kDeclWeight = 3; -const double kDamping = 0.1; - -template -struct Kind; -template <> -struct Kind { - static constexpr SymbolKind value = SymbolKind::Func; -}; -template <> -struct Kind { - static constexpr SymbolKind value = SymbolKind::Type; -}; -template <> -struct Kind { - static constexpr SymbolKind value = SymbolKind::Var; -}; - -template -void Add(const std::unordered_map& sym2id, - std::vector>& adj, - const std::vector>& ids, - int n, - double w = 1) { - for (Id id : ids) { - auto it = sym2id.find(SymbolIdx{id, Kind::value}); - if (it != sym2id.end()) - adj[it->second][n] += w; - } -} - -struct CqueryRandomHandler : BaseMessageHandler { - void Run(Ipc_CqueryRandom* request) override { - std::unordered_map sym2id; - std::vector syms; - int n = 0; - - for (RawId i = 0; i < db->funcs.size(); i++) - if (db->funcs[i].AnyDef()) { - syms.push_back(SymbolIdx{Id(i), SymbolKind::Func}); - sym2id[syms.back()] = n++; - } - for (RawId i = 0; i < db->types.size(); i++) - if (db->types[i].AnyDef()) { - syms.push_back(SymbolIdx{Id(i), SymbolKind::Type}); - sym2id[syms.back()] = n++; - } - for (RawId i = 0; i < db->vars.size(); i++) - if (db->vars[i].AnyDef()) { - syms.push_back(SymbolIdx{Id(i), SymbolKind::Var}); - sym2id[syms.back()] = n++; - } - - std::vector> adj(n); - auto add = [&](const std::vector& uses, double w) { - for (Use use : uses) { - auto it = sym2id.find(use); - if (it != sym2id.end()) - adj[it->second][n] += w; - } - }; - n = 0; - for (QueryFunc& func : db->funcs) - if (func.AnyDef()) { - add(func.declarations, kDeclWeight); - add(func.uses, 1); - Add(sym2id, adj, func.derived, n); - n++; - } - for (QueryType& type : db->types) - if (const auto* def = type.AnyDef()) { - add(type.uses, 1); - Add(sym2id, adj, type.instances, n); - Add(sym2id, adj, def->funcs, n); - Add(sym2id, adj, def->types, n); - Add(sym2id, adj, def->vars, n); - n++; - } - for (QueryVar& var : db->vars) - if (var.AnyDef()) { - add(var.declarations, kDeclWeight); - add(var.uses, 1); - n++; - } - for (int i = 0; i < n; i++) { - double sum = 0; - adj[i][i] += 1; - for (auto& it : adj[i]) - sum += it.second; - for (auto& it : adj[i]) - it.second = it.second / sum * (1 - kDamping); - } - - std::vector x(n, 1), y; - for (int j = 0; j < 8; j++) { - y.assign(n, kDamping); - for (int i = 0; i < n; i++) - for (auto& it : adj[i]) - y[it.first] += x[i] * it.second; - double d = 0; - for (int i = 0; i < n; i++) - d = std::max(d, fabs(x[i] - y[i])); - if (d < 1e-5) - break; - x.swap(y); - } - - double sum = std::accumulate(x.begin(), x.end(), 0.); - Out_LocationList out; - out.id = request->id; - double roulette = rand() / (RAND_MAX + 1.0) * sum; - sum = 0; - for (int i = 0; i < n; i++) { - sum += x[i]; - if (sum >= roulette) { - Maybe use = GetDefinitionExtent(db, syms[i]); - if (!use) - continue; - if (auto ls_loc = GetLsLocationEx(db, working_files, *use, - config->xref.container)) - out.result.push_back(*ls_loc); - break; - } - } - QueueManager::WriteStdout(IpcId::CqueryRandom, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryRandomHandler); -} // namespace diff --git a/src/messages/cquery_vars.cc b/src/messages/cquery_vars.cc deleted file mode 100644 index 1f6b9c568..000000000 --- a/src/messages/cquery_vars.cc +++ /dev/null @@ -1,52 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct Ipc_CqueryVars : public RequestMessage { - const static IpcId kIpcId = IpcId::CqueryVars; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_CqueryVars, id, params); -REGISTER_IPC_MESSAGE(Ipc_CqueryVars); - -struct CqueryVarsHandler : BaseMessageHandler { - void Run(Ipc_CqueryVars* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_LocationList out; - out.id = request->id; - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - Id id = sym.id; - switch (sym.kind) { - default: - break; - case SymbolKind::Var: { - const QueryVar::Def* def = db->GetVar(sym).AnyDef(); - if (!def || !def->type) - continue; - id = *def->type; - } - // fallthrough - case SymbolKind::Type: { - QueryType& type = db->types[id.id]; - out.result = GetLsLocationExs( - db, working_files, GetDeclarations(db, type.instances), - config->xref.container, config->xref.maxNum); - break; - } - } - } - QueueManager::WriteStdout(IpcId::CqueryVars, out); - } -}; -REGISTER_MESSAGE_HANDLER(CqueryVarsHandler); -} // namespace diff --git a/src/messages/cquery_wait.cc b/src/messages/cquery_wait.cc deleted file mode 100644 index 2e59e9972..000000000 --- a/src/messages/cquery_wait.cc +++ /dev/null @@ -1,44 +0,0 @@ -#include "import_manager.h" -#include "import_pipeline.h" -#include "message_handler.h" -#include "queue_manager.h" - -#include - -namespace { -struct Ipc_CqueryWait : public NotificationMessage { - static constexpr IpcId kIpcId = IpcId::CqueryWait; -}; -MAKE_REFLECT_EMPTY_STRUCT(Ipc_CqueryWait); -REGISTER_IPC_MESSAGE(Ipc_CqueryWait); - -struct CqueryWaitHandler : MessageHandler { - IpcId GetId() const override { return IpcId::CqueryWait; } - void Run(std::unique_ptr request) override { - // TODO: use status message system here, then run querydb as normal? Maybe - // this cannot be a normal message, ie, it needs to be re-entrant. - - LOG_S(INFO) << "Waiting for idle"; - int idle_count = 0; - while (true) { - bool has_work = false; - has_work |= import_pipeline_status->num_active_threads != 0; - has_work |= QueueManager::instance()->HasWork(); - has_work |= - QueryDb_ImportMain(config, db, import_manager, import_pipeline_status, - semantic_cache, working_files); - if (!has_work) - ++idle_count; - else - idle_count = 0; - - // There are race conditions between each of the three checks above, - // so we retry a bunch of times to try to avoid any. - if (idle_count > 10) - break; - } - LOG_S(INFO) << "Done waiting for idle"; - } -}; -REGISTER_MESSAGE_HANDLER(CqueryWaitHandler); -} // namespace diff --git a/src/messages/exit.cc b/src/messages/exit.cc deleted file mode 100644 index a0e403f42..000000000 --- a/src/messages/exit.cc +++ /dev/null @@ -1,21 +0,0 @@ -#include "message_handler.h" - -#include - -namespace { -struct Ipc_Exit : public NotificationMessage { - static const IpcId kIpcId = IpcId::Exit; -}; -MAKE_REFLECT_EMPTY_STRUCT(Ipc_Exit); -REGISTER_IPC_MESSAGE(Ipc_Exit); - -struct ExitHandler : MessageHandler { - IpcId GetId() const override { return IpcId::Exit; } - - void Run(std::unique_ptr request) override { - LOG_S(INFO) << "Exiting; got IpcId::Exit"; - exit(0); - } -}; -REGISTER_MESSAGE_HANDLER(ExitHandler); -} // namespace diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc index e08724491..474f01edb 100644 --- a/src/messages/initialize.cc +++ b/src/messages/initialize.cc @@ -1,631 +1,370 @@ -#include "cache_manager.h" -#include "diagnostics_engine.h" -#include "import_pipeline.h" -#include "include_complete.h" -#include "message_handler.h" -#include "platform.h" -#include "project.h" -#include "queue_manager.h" -#include "semantic_highlight_symbol_cache.h" -#include "serializers/json.h" -#include "timer.h" -#include "work_thread.h" -#include "working_files.h" - -#include - -#include +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "sema_manager.hh" +#include "filesystem.hh" +#include "include_complete.hh" +#include "log.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "platform.hh" +#include "project.hh" +#include "working_files.hh" + +#include +#include + +#include +#include + +#include #include #include +namespace ccls { +using namespace llvm; + // TODO Cleanup global variables extern std::string g_init_options; namespace { +enum class TextDocumentSyncKind { None = 0, Full = 1, Incremental = 2 }; +REFLECT_UNDERLYING(TextDocumentSyncKind) -// Code Lens options. -struct lsCodeLensOptions { - // Code lens has a resolve provider as well. - bool resolveProvider = false; -}; -MAKE_REFLECT_STRUCT(lsCodeLensOptions, resolveProvider); - -// Completion options. -struct lsCompletionOptions { - // The server provides support to resolve additional - // information for a completion item. - bool resolveProvider = false; - - // The characters that trigger completion automatically. - // vscode doesn't support trigger character sequences, so we use ':' - // for - // '::' and '>' for '->'. See - // https://github.com/Microsoft/language-server-protocol/issues/138. - std::vector triggerCharacters = {".", ":", ">", "#", - "<", "\"", "/"}; -}; -MAKE_REFLECT_STRUCT(lsCompletionOptions, resolveProvider, triggerCharacters); - -// Format document on type options -struct lsDocumentOnTypeFormattingOptions { - // A character on which formatting should be triggered, like `}`. - std::string firstTriggerCharacter; - - // More trigger characters. - std::vector moreTriggerCharacter; -}; -MAKE_REFLECT_STRUCT(lsDocumentOnTypeFormattingOptions, - firstTriggerCharacter, - moreTriggerCharacter); - -// Document link options -struct lsDocumentLinkOptions { - // Document links have a resolve provider as well. - bool resolveProvider = true; -}; -MAKE_REFLECT_STRUCT(lsDocumentLinkOptions, resolveProvider); - -// Execute command options. -struct lsExecuteCommandOptions { - // The commands to be executed on the server - std::vector commands; -}; -MAKE_REFLECT_STRUCT(lsExecuteCommandOptions, commands); - -// Save options. -struct lsSaveOptions { - // The client is supposed to include the content on save. - bool includeText = false; -}; -MAKE_REFLECT_STRUCT(lsSaveOptions, includeText); - -// Signature help options. -struct lsSignatureHelpOptions { - // The characters that trigger signature help automatically. - // NOTE: If updating signature help tokens make sure to also update - // WorkingFile::FindClosestCallNameInBuffer. - std::vector triggerCharacters = {"(", ","}; -}; -MAKE_REFLECT_STRUCT(lsSignatureHelpOptions, triggerCharacters); - -// Defines how the host (editor) should sync document changes to the language -// server. -enum class lsTextDocumentSyncKind { - // Documents should not be synced at all. - None = 0, - - // Documents are synced by always sending the full content - // of the document. - Full = 1, - - // Documents are synced by sending the full content on open. - // After that only incremental updates to the document are - // send. - Incremental = 2 -}; -MAKE_REFLECT_TYPE_PROXY(lsTextDocumentSyncKind) - -struct lsTextDocumentSyncOptions { - // Open and close notifications are sent to the server. - bool openClose = false; - // Change notificatins are sent to the server. See TextDocumentSyncKind.None, - // TextDocumentSyncKind.Full and TextDocumentSyncKindIncremental. - lsTextDocumentSyncKind change = lsTextDocumentSyncKind::Incremental; - // Will save notifications are sent to the server. - optional willSave; - // Will save wait until requests are sent to the server. - optional willSaveWaitUntil; - // Save notifications are sent to the server. - optional save; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentSyncOptions, - openClose, - change, - willSave, - willSaveWaitUntil, - save); - -struct lsServerCapabilities { - // Defines how text documents are synced. Is either a detailed structure - // defining each notification or for backwards compatibility the - // TextDocumentSyncKind number. - // TODO: It seems like the new API is broken and doesn't work. - // optional textDocumentSync; - lsTextDocumentSyncKind textDocumentSync = lsTextDocumentSyncKind::Incremental; +struct ServerCap { + struct SaveOptions { + bool includeText = false; + }; + struct TextDocumentSyncOptions { + bool openClose = true; + TextDocumentSyncKind change = TextDocumentSyncKind::Incremental; + bool willSave = false; + bool willSaveWaitUntil = false; + SaveOptions save; + } textDocumentSync; // The server provides hover support. bool hoverProvider = true; - // The server provides completion support. - lsCompletionOptions completionProvider; - // The server provides signature help support. - lsSignatureHelpOptions signatureHelpProvider; - // The server provides goto definition support. + struct CompletionOptions { + bool resolveProvider = false; + + // The characters that trigger completion automatically. + // vscode doesn't support trigger character sequences, so we use ':' + // for + // '::' and '>' for '->'. See + // https://github.com/Microsoft/language-server-protocol/issues/138. + std::vector triggerCharacters = {".", ":", ">", "#", + "<", "\"", "/"}; + } completionProvider; + struct SignatureHelpOptions { + std::vector triggerCharacters = {"(", ","}; + } signatureHelpProvider; bool definitionProvider = true; - // The server provides Goto Type Definition support. bool typeDefinitionProvider = true; - // The server provides find references support. + bool implementationProvider = true; bool referencesProvider = true; - // The server provides document highlight support. bool documentHighlightProvider = true; - // The server provides document symbol support. bool documentSymbolProvider = true; - // The server provides workspace symbol support. bool workspaceSymbolProvider = true; - // The server provides code actions. - bool codeActionProvider = true; - // The server provides code lens. - lsCodeLensOptions codeLensProvider; - // The server provides document formatting. - bool documentFormattingProvider = false; - // The server provides document range formatting. - bool documentRangeFormattingProvider = false; - // The server provides document formatting on typing. - optional documentOnTypeFormattingProvider; - // The server provides rename support. + struct CodeActionOptions { + std::vector codeActionKinds = {"quickfix"}; + } codeActionProvider; + struct CodeLensOptions { + bool resolveProvider = false; + } codeLensProvider; + bool documentFormattingProvider = true; + bool documentRangeFormattingProvider = true; + struct DocumentOnTypeFormattingOptions { + std::string firstTriggerCharacter = "}"; + std::vector moreTriggerCharacter; + } documentOnTypeFormattingProvider; bool renameProvider = true; - // The server provides document link support. - lsDocumentLinkOptions documentLinkProvider; + struct DocumentLinkOptions { + bool resolveProvider = true; + } documentLinkProvider; + bool foldingRangeProvider = true; // The server provides execute command support. - lsExecuteCommandOptions executeCommandProvider; + struct ExecuteCommandOptions { + std::vector commands = {ccls_xref}; + } executeCommandProvider; + struct Workspace { + struct WorkspaceFolders { + bool supported = true; + bool changeNotifications = true; + } workspaceFolders; + } workspace; +}; +REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds); +REFLECT_STRUCT(ServerCap::CodeLensOptions, resolveProvider); +REFLECT_STRUCT(ServerCap::CompletionOptions, resolveProvider, + triggerCharacters); +REFLECT_STRUCT(ServerCap::DocumentLinkOptions, resolveProvider); +REFLECT_STRUCT(ServerCap::DocumentOnTypeFormattingOptions, + firstTriggerCharacter, moreTriggerCharacter); +REFLECT_STRUCT(ServerCap::ExecuteCommandOptions, commands); +REFLECT_STRUCT(ServerCap::SaveOptions, includeText); +REFLECT_STRUCT(ServerCap::SignatureHelpOptions, triggerCharacters); +REFLECT_STRUCT(ServerCap::TextDocumentSyncOptions, openClose, change, willSave, + willSaveWaitUntil, save); +REFLECT_STRUCT(ServerCap::Workspace::WorkspaceFolders, supported, + changeNotifications); +REFLECT_STRUCT(ServerCap::Workspace, workspaceFolders); +REFLECT_STRUCT(ServerCap, textDocumentSync, hoverProvider, completionProvider, + signatureHelpProvider, definitionProvider, + implementationProvider, typeDefinitionProvider, + referencesProvider, documentHighlightProvider, + documentSymbolProvider, workspaceSymbolProvider, + codeActionProvider, codeLensProvider, documentFormattingProvider, + documentRangeFormattingProvider, + documentOnTypeFormattingProvider, renameProvider, + documentLinkProvider, foldingRangeProvider, + executeCommandProvider, workspace); + +struct DynamicReg { + bool dynamicRegistration = false; }; -MAKE_REFLECT_STRUCT(lsServerCapabilities, - textDocumentSync, - hoverProvider, - completionProvider, - signatureHelpProvider, - definitionProvider, - referencesProvider, - documentHighlightProvider, - documentSymbolProvider, - workspaceSymbolProvider, - codeActionProvider, - codeLensProvider, - documentFormattingProvider, - documentRangeFormattingProvider, - documentOnTypeFormattingProvider, - renameProvider, - documentLinkProvider, - executeCommandProvider); +REFLECT_STRUCT(DynamicReg, dynamicRegistration); // Workspace specific client capabilities. -struct lsWorkspaceClientCapabilites { +struct WorkspaceClientCap { // The client supports applying batch edits to the workspace. - optional applyEdit; + std::optional applyEdit; - struct lsWorkspaceEdit { + struct WorkspaceEdit { // The client supports versioned document changes in `WorkspaceEdit`s - optional documentChanges; + std::optional documentChanges; }; // Capabilities specific to `WorkspaceEdit`s - optional workspaceEdit; - - struct lsGenericDynamicReg { - // Did foo notification supports dynamic registration. - optional dynamicRegistration; - }; - - // Capabilities specific to the `workspace/didChangeConfiguration` - // notification. - optional didChangeConfiguration; - - // Capabilities specific to the `workspace/didChangeWatchedFiles` - // notification. - optional didChangeWatchedFiles; - - // Capabilities specific to the `workspace/symbol` request. - optional symbol; - - // Capabilities specific to the `workspace/executeCommand` request. - optional executeCommand; + std::optional workspaceEdit; + DynamicReg didChangeConfiguration; + DynamicReg didChangeWatchedFiles; + DynamicReg symbol; + DynamicReg executeCommand; }; -MAKE_REFLECT_STRUCT(lsWorkspaceClientCapabilites::lsWorkspaceEdit, - documentChanges); -MAKE_REFLECT_STRUCT(lsWorkspaceClientCapabilites::lsGenericDynamicReg, - dynamicRegistration); -MAKE_REFLECT_STRUCT(lsWorkspaceClientCapabilites, - applyEdit, - workspaceEdit, - didChangeConfiguration, - didChangeWatchedFiles, - symbol, - executeCommand); +REFLECT_STRUCT(WorkspaceClientCap::WorkspaceEdit, documentChanges); +REFLECT_STRUCT(WorkspaceClientCap, applyEdit, workspaceEdit, + didChangeConfiguration, didChangeWatchedFiles, symbol, + executeCommand); // Text document specific client capabilities. -struct lsTextDocumentClientCapabilities { - struct lsSynchronization { - // Whether text document synchronization supports dynamic registration. - optional dynamicRegistration; - - // The client supports sending will save notifications. - optional willSave; - - // The client supports sending a will save request and - // waits for a response providing text edits which will - // be applied to the document before it is saved. - optional willSaveWaitUntil; - - // The client supports did save notifications. - optional didSave; - }; - - lsSynchronization synchronization; - - struct lsCompletion { - // Whether completion supports dynamic registration. - optional dynamicRegistration; - - struct lsCompletionItem { +struct TextDocumentClientCap { + struct Completion { + struct CompletionItem { // Client supports snippets as insert text. // // A snippet can define tab stops and placeholders with `$1`, `$2` // and `${3:foo}`. `$0` defines the final tab stop, it defaults to // the end of the snippet. Placeholders with equal identifiers are linked, // that is typing in one will update others too. - optional snippetSupport; - }; - - // The client supports the following `CompletionItem` specific - // capabilities. - optional completionItem; - }; - // Capabilities specific to the `textDocument/completion` - optional completion; - - struct lsGenericDynamicReg { - // Whether foo supports dynamic registration. - optional dynamicRegistration; - }; - - // Capabilities specific to the `textDocument/hover` - optional hover; - - // Capabilities specific to the `textDocument/signatureHelp` - optional signatureHelp; - - // Capabilities specific to the `textDocument/references` - optional references; - - // Capabilities specific to the `textDocument/documentHighlight` - optional documentHighlight; - - // Capabilities specific to the `textDocument/documentSymbol` - optional documentSymbol; - - // Capabilities specific to the `textDocument/formatting` - optional formatting; - - // Capabilities specific to the `textDocument/rangeFormatting` - optional rangeFormatting; - - // Capabilities specific to the `textDocument/onTypeFormatting` - optional onTypeFormatting; - - // Capabilities specific to the `textDocument/definition` - optional definition; - - // Capabilities specific to the `textDocument/codeAction` - optional codeAction; + bool snippetSupport = false; + } completionItem; + } completion; - struct CodeLensRegistrationOptions : public lsGenericDynamicReg { - // Code lens has a resolve provider as well. - bool resolveProvider; - }; - - // Capabilities specific to the `textDocument/codeLens` - optional codeLens; - - // Capabilities specific to the `textDocument/documentLink` - optional documentLink; - - // Capabilities specific to the `textDocument/rename` - optional rename; + struct DocumentSymbol { + bool hierarchicalDocumentSymbolSupport = false; + } documentSymbol; }; -MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsSynchronization, - dynamicRegistration, - willSave, - willSaveWaitUntil, - didSave); -MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsCompletion, - dynamicRegistration, - completionItem); -MAKE_REFLECT_STRUCT( - lsTextDocumentClientCapabilities::lsCompletion::lsCompletionItem, - snippetSupport); -MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities::lsGenericDynamicReg, - dynamicRegistration); -MAKE_REFLECT_STRUCT( - lsTextDocumentClientCapabilities::CodeLensRegistrationOptions, - dynamicRegistration, - resolveProvider); -MAKE_REFLECT_STRUCT(lsTextDocumentClientCapabilities, - synchronization, - completion, - hover, - signatureHelp, - references, - documentHighlight, - documentSymbol, - formatting, - rangeFormatting, - onTypeFormatting, - definition, - codeAction, - codeLens, - documentLink, - rename); - -struct lsClientCapabilities { - // Workspace specific client capabilities. - optional workspace; - - // Text document specific client capabilities. - optional textDocument; - - /** - * Experimental client capabilities. - */ - // experimental?: any; // TODO +REFLECT_STRUCT(TextDocumentClientCap::Completion::CompletionItem, + snippetSupport); +REFLECT_STRUCT(TextDocumentClientCap::Completion, completionItem); +REFLECT_STRUCT(TextDocumentClientCap::DocumentSymbol, + hierarchicalDocumentSymbolSupport); +REFLECT_STRUCT(TextDocumentClientCap, completion, documentSymbol); + +struct ClientCap { + WorkspaceClientCap workspace; + TextDocumentClientCap textDocument; }; -MAKE_REFLECT_STRUCT(lsClientCapabilities, workspace, textDocument); - -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -////////////////////////////// INITIALIZATION /////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////////////////////////////////// - -struct lsInitializeParams { - // The process Id of the parent process that started - // the server. Is null if the process has not been started by another process. - // If the parent process is not alive then the server should exit (see exit - // notification) its process. - optional processId; - - // The rootPath of the workspace. Is null - // if no folder is open. - // - // @deprecated in favour of rootUri. - optional rootPath; +REFLECT_STRUCT(ClientCap, workspace, textDocument); +struct InitializeParam { // The rootUri of the workspace. Is null if no // folder is open. If both `rootPath` and `rootUri` are set // `rootUri` wins. - optional rootUri; - - // User provided initialization options. - optional initializationOptions; + std::optional rootUri; - // The capabilities provided by the client (editor or tool) - lsClientCapabilities capabilities; + Config initializationOptions; + ClientCap capabilities; - enum class lsTrace { + enum class Trace { // NOTE: serialized as a string, one of 'off' | 'messages' | 'verbose'; - Off, // off - Messages, // messages - Verbose // verbose + Off, // off + Messages, // messages + Verbose // verbose }; + Trace trace = Trace::Off; - // The initial trace setting. If omitted trace is disabled ('off'). - lsTrace trace = lsTrace::Off; + std::vector workspaceFolders; }; -void Reflect(Reader& reader, lsInitializeParams::lsTrace& value) { - if (!reader.IsString()) { - value = lsInitializeParams::lsTrace::Off; +void Reflect(JsonReader &reader, InitializeParam::Trace &value) { + if (!reader.m->IsString()) { + value = InitializeParam::Trace::Off; return; } - std::string v = reader.GetString(); + std::string v = reader.m->GetString(); if (v == "off") - value = lsInitializeParams::lsTrace::Off; + value = InitializeParam::Trace::Off; else if (v == "messages") - value = lsInitializeParams::lsTrace::Messages; + value = InitializeParam::Trace::Messages; else if (v == "verbose") - value = lsInitializeParams::lsTrace::Verbose; + value = InitializeParam::Trace::Verbose; } -#if 0 // unused -void Reflect(Writer& writer, lsInitializeParams::lsTrace& value) { - switch (value) { - case lsInitializeParams::lsTrace::Off: - writer.String("off"); - break; - case lsInitializeParams::lsTrace::Messages: - writer.String("messages"); - break; - case lsInitializeParams::lsTrace::Verbose: - writer.String("verbose"); - break; - } -} -#endif - -MAKE_REFLECT_STRUCT(lsInitializeParams, - processId, - rootPath, - rootUri, - initializationOptions, - capabilities, - trace); - -struct lsInitializeError { - // Indicates whether the client should retry to send the - // initilize request after showing the message provided - // in the ResponseError. - bool retry; -}; -MAKE_REFLECT_STRUCT(lsInitializeError, retry); +REFLECT_STRUCT(InitializeParam, rootUri, initializationOptions, capabilities, + trace, workspaceFolders); -struct Ipc_InitializeRequest : public RequestMessage { - const static IpcId kIpcId = IpcId::Initialize; - lsInitializeParams params; +struct InitializeResult { + ServerCap capabilities; }; -MAKE_REFLECT_STRUCT(Ipc_InitializeRequest, id, params); -REGISTER_IPC_MESSAGE(Ipc_InitializeRequest); - -struct Out_InitializeResponse : public lsOutMessage { - struct InitializeResult { - lsServerCapabilities capabilities; - }; - lsRequestId id; - InitializeResult result; -}; -MAKE_REFLECT_STRUCT(Out_InitializeResponse::InitializeResult, capabilities); -MAKE_REFLECT_STRUCT(Out_InitializeResponse, jsonrpc, id, result); +REFLECT_STRUCT(InitializeResult, capabilities); + +void *Indexer(void *arg_) { + MessageHandler *h; + int idx; + auto *arg = static_cast *>(arg_); + std::tie(h, idx) = *arg; + delete arg; + std::string name = "indexer" + std::to_string(idx); + set_thread_name(name.c_str()); + pipeline::Indexer_Main(h->manager, h->vfs, h->project, h->wfiles); + return nullptr; +} +} // namespace + +void Initialize(MessageHandler *m, InitializeParam ¶m, ReplyOnce &reply) { + std::string project_path = NormalizePath(param.rootUri->GetPath()); + LOG_S(INFO) << "initialize in directory " << project_path << " with uri " + << param.rootUri->raw_uri; + + { + g_config = new Config(param.initializationOptions); + rapidjson::Document reader; + reader.Parse(g_init_options.c_str()); + if (!reader.HasParseError()) { + JsonReader json_reader{&reader}; + try { + Reflect(json_reader, *g_config); + } catch (std::invalid_argument &) { + // This will not trigger because parse error is handled in + // MessageRegistry::Parse in lsp.cc + } + } -struct InitializeHandler : BaseMessageHandler { - void Run(Ipc_InitializeRequest* request) override { - // Log initialization parameters. rapidjson::StringBuffer output; rapidjson::Writer writer(output); JsonWriter json_writer(&writer); - Reflect(json_writer, request->params.initializationOptions); - LOG_S(INFO) << "Init parameters: " << output.GetString(); - - if (request->params.rootUri) { - std::string project_path = - NormalizePath(request->params.rootUri->GetPath()); - LOG_S(INFO) << "[querydb] Initialize in directory " << project_path - << " with uri " << request->params.rootUri->raw_uri; - - { - if (request->params.initializationOptions) - *config = *request->params.initializationOptions; - else - *config = Config(); - rapidjson::Document reader; - reader.Parse(g_init_options.c_str()); - if (!reader.HasParseError()) { - JsonReader json_reader{&reader}; - try { - Reflect(json_reader, *config); - } catch (std::invalid_argument&) { - // This will not trigger because parse error is handled in - // MessageRegistry::Parse in lsp.cc - } - } - - if (config->cacheDirectory.empty()) { - LOG_S(ERROR) << "cacheDirectory cannot be empty."; - exit(1); - } else { - config->cacheDirectory = NormalizePath(config->cacheDirectory); - EnsureEndsInSlash(config->cacheDirectory); - } - } - - // Client capabilities - if (request->params.capabilities.textDocument) { - const auto& cap = *request->params.capabilities.textDocument; - if (cap.completion && cap.completion->completionItem) - config->client.snippetSupport = - cap.completion->completionItem->snippetSupport.value_or(false); - } - - // Check client version. - if (config->clientVersion.has_value() && - *config->clientVersion != kExpectedClientVersion) { - Out_ShowLogMessage out; - out.display_type = Out_ShowLogMessage::DisplayType::Show; - out.params.type = lsMessageType::Error; - out.params.message = - "cquery client (v" + std::to_string(*config->clientVersion) + - ") and server (v" + std::to_string(kExpectedClientVersion) + - ") version mismatch. Please update "; - if (config->clientVersion > kExpectedClientVersion) - out.params.message += "the cquery binary."; - else - out.params.message += - "your extension client (VSIX file). Make sure to uninstall " - "the cquery extension and restart vscode before " - "reinstalling."; - out.Write(std::cout); - } + Reflect(json_writer, *g_config); + LOG_S(INFO) << "initializationOptions: " << output.GetString(); + + if (g_config->cacheDirectory.size()) { + SmallString<256> Path(g_config->cacheDirectory); + sys::fs::make_absolute(Path); + g_config->cacheDirectory = Path.str(); + EnsureEndsInSlash(g_config->cacheDirectory); + } + } - // Ensure there is a resource directory. - if (config->resourceDirectory.empty()) - config->resourceDirectory = GetDefaultResourceDirectory(); - LOG_S(INFO) << "Using -resource-dir=" << config->resourceDirectory; - - // Send initialization before starting indexers, so we don't send a - // status update too early. - // TODO: query request->params.capabilities.textDocument and support - // only things the client supports. - - Out_InitializeResponse out; - out.id = request->id; - - // out.result.capabilities.textDocumentSync = - // lsTextDocumentSyncOptions(); - // out.result.capabilities.textDocumentSync->openClose = true; - // out.result.capabilities.textDocumentSync->change = - // lsTextDocumentSyncKind::Full; - // out.result.capabilities.textDocumentSync->willSave = true; - // out.result.capabilities.textDocumentSync->willSaveWaitUntil = - // true; - -#if USE_CLANG_CXX - out.result.capabilities.documentFormattingProvider = true; - out.result.capabilities.documentRangeFormattingProvider = true; -#endif - - QueueManager::WriteStdout(IpcId::Initialize, out); - - // Set project root. - EnsureEndsInSlash(project_path); - config->projectRoot = project_path; + // Client capabilities + const auto &capabilities = param.capabilities; + g_config->client.snippetSupport &= + capabilities.textDocument.completion.completionItem.snippetSupport; + g_config->client.hierarchicalDocumentSymbolSupport &= + capabilities.textDocument.documentSymbol + .hierarchicalDocumentSymbolSupport; + + // Ensure there is a resource directory. + if (g_config->clang.resourceDir.empty()) + g_config->clang.resourceDir = GetDefaultResourceDirectory(); + DoPathMapping(g_config->clang.resourceDir); + LOG_S(INFO) << "use -resource-dir=" << g_config->clang.resourceDir; + + // Send initialization before starting indexers, so we don't send a + // status update too early. + reply(InitializeResult{}); + + // Set project root. + EnsureEndsInSlash(project_path); + g_config->fallbackFolder = project_path; + for (const WorkspaceFolder &wf : param.workspaceFolders) { + std::string path = wf.uri.GetPath(); + EnsureEndsInSlash(path); + g_config->workspaceFolders.push_back(path); + LOG_S(INFO) << "add workspace folder " << wf.name << ": " << path; + } + if (param.workspaceFolders.empty()) + g_config->workspaceFolders.push_back(project_path); + if (g_config->cacheDirectory.size()) + for (const std::string &folder : g_config->workspaceFolders) { // Create two cache directories for files inside and outside of the // project. - MakeDirectoryRecursive(config->cacheDirectory + - EscapeFileName(config->projectRoot)); - MakeDirectoryRecursive(config->cacheDirectory + '@' + - EscapeFileName(config->projectRoot)); - - Timer time; - diag_engine->Init(config); - semantic_cache->Init(config); - - // Open up / load the project. - project->Load(config, project_path); - time.ResetAndPrint("[perf] Loaded compilation entries (" + - std::to_string(project->entries.size()) + " files)"); - - // Start indexer threads. Start this after loading the project, as that - // may take a long time. Indexer threads will emit status/progress - // reports. - if (config->index.threads == 0) { - // If the user has not specified how many indexers to run, try to - // guess an appropriate value. Default to 80% utilization. - const float kDefaultTargetUtilization = 0.8f; - config->index.threads = (int)(std::thread::hardware_concurrency() * - kDefaultTargetUtilization); - if (config->index.threads <= 0) - config->index.threads = 1; - } - LOG_S(INFO) << "Starting " << config->index.threads << " indexers"; - for (int i = 0; i < config->index.threads; ++i) { - WorkThread::StartThread("indexer" + std::to_string(i), [=]() { - Indexer_Main(config, diag_engine, file_consumer_shared, - timestamp_manager, import_manager, - import_pipeline_status, project, working_files, waiter); - }); - } + std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1)); + sys::fs::create_directories(g_config->cacheDirectory + escaped); + sys::fs::create_directories(g_config->cacheDirectory + '@' + escaped); + } - // Start scanning include directories before dispatching project - // files, because that takes a long time. - include_complete->Rescan(); + idx::Init(); + for (const std::string &folder : g_config->workspaceFolders) + m->project->Load(folder); - time.Reset(); - project->Index(config, QueueManager::instance(), working_files, - request->id); - // We need to support multiple concurrent index processes. - time.ResetAndPrint("[perf] Dispatched initial index requests"); - } + // Start indexer threads. Start this after loading the project, as that + // may take a long time. Indexer threads will emit status/progress + // reports. + if (g_config->index.threads == 0) + g_config->index.threads = std::thread::hardware_concurrency(); + + LOG_S(INFO) << "start " << g_config->index.threads << " indexers"; + for (int i = 0; i < g_config->index.threads; i++) + SpawnThread(Indexer, new std::pair{m, i}); + + // Start scanning include directories before dispatching project + // files, because that takes a long time. + m->include_complete->Rescan(); + + LOG_S(INFO) << "dispatch initial index requests"; + m->project->Index(m->wfiles, reply.id); + + m->manager->sessions.SetCapacity(g_config->session.maxNum); +} + +void MessageHandler::initialize(JsonReader &reader, ReplyOnce &reply) { + InitializeParam param; + Reflect(reader, param); + if (!param.rootUri) { + reply.Error(ErrorCode::InvalidRequest, "expected rootUri"); + return; } -}; -REGISTER_MESSAGE_HANDLER(InitializeHandler); -} // namespace + Initialize(this, param, reply); +} + +void StandaloneInitialize(MessageHandler &handler, const std::string &root) { + InitializeParam param; + param.rootUri = DocumentUri::FromPath(root); + ReplyOnce reply; + Initialize(&handler, param, reply); +} + +void MessageHandler::shutdown(EmptyParam &, ReplyOnce &reply) { + reply(JsonNull{}); +} + +void MessageHandler::exit(EmptyParam &) { + // FIXME cancel threads + ::exit(0); +} +} // namespace ccls diff --git a/src/messages/shutdown.cc b/src/messages/shutdown.cc deleted file mode 100644 index 5755fb6a0..000000000 --- a/src/messages/shutdown.cc +++ /dev/null @@ -1,25 +0,0 @@ -#include "message_handler.h" -#include "queue_manager.h" - -namespace { -struct Ipc_Shutdown : public RequestMessage { - static const IpcId kIpcId = IpcId::Shutdown; -}; -MAKE_REFLECT_STRUCT(Ipc_Shutdown, id); -REGISTER_IPC_MESSAGE(Ipc_Shutdown); - -struct Out_Shutdown : public lsOutMessage { - lsRequestId id; // defaults to std::monostate (null) - std::monostate result; // null -}; -MAKE_REFLECT_STRUCT(Out_Shutdown, jsonrpc, id, result); - -struct ShutdownHandler : BaseMessageHandler { - void Run(Ipc_Shutdown* request) override { - Out_Shutdown out; - out.id = request->id; - QueueManager::WriteStdout(IpcId::TextDocumentDefinition, out); - } -}; -REGISTER_MESSAGE_HANDLER(ShutdownHandler); -} // namespace diff --git a/src/messages/textDocument_code.cc b/src/messages/textDocument_code.cc new file mode 100644 index 000000000..d9ef62f0e --- /dev/null +++ b/src/messages/textDocument_code.cc @@ -0,0 +1,238 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "query.hh" + +#include + +#include +#include + +#include + +namespace ccls { +namespace { +struct CodeAction { + std::string title; + const char *kind = "quickfix"; + WorkspaceEdit edit; +}; +REFLECT_STRUCT(CodeAction, title, kind, edit); +} +void MessageHandler::textDocument_codeAction(CodeActionParam ¶m, + ReplyOnce &reply) { + WorkingFile *wf = wfiles->GetFile(param.textDocument.uri.GetPath()); + if (!wf) { + reply.NotReady(true); + return; + } + std::vector result; + std::vector diagnostics; + wfiles->WithLock([&]() { diagnostics = wf->diagnostics; }); + for (Diagnostic &diag : diagnostics) + if (diag.fixits_.size() && + !(param.range.Includes(diag.range) || + llvm::any_of(diag.fixits_, [&](const TextEdit &edit) { + return param.range.Includes(edit.range); + }))) { + CodeAction &cmd = result.emplace_back(); + cmd.title = "FixIt: " + diag.message; + auto &edit = cmd.edit.documentChanges.emplace_back(); + edit.textDocument.uri = param.textDocument.uri; + edit.textDocument.version = wf->version; + edit.edits = diag.fixits_; + } + reply(result); +} + +namespace { +struct Cmd_xref { + Usr usr; + Kind kind; + std::string field; +}; +struct Command { + std::string title; + std::string command; + std::vector arguments; +}; +struct CodeLens { + lsRange range; + std::optional command; +}; +REFLECT_STRUCT(Cmd_xref, usr, kind, field); +REFLECT_STRUCT(Command, title, command, arguments); +REFLECT_STRUCT(CodeLens, range, command); + +template +std::string ToString(T &v) { + rapidjson::StringBuffer output; + rapidjson::Writer writer(output); + JsonWriter json_writer(&writer); + Reflect(json_writer, v); + return output.GetString(); +} + +struct CommonCodeLensParams { + std::vector *result; + DB *db; + WorkingFile *wfile; +}; +} // namespace + +void MessageHandler::textDocument_codeLens(TextDocumentParam ¶m, + ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + std::vector result; + auto Add = [&](const char *singular, Cmd_xref show, Range range, int num, + bool force_display = false) { + if (!num && !force_display) + return; + std::optional ls_range = GetLsRange(wf, range); + if (!ls_range) + return; + CodeLens &code_lens = result.emplace_back(); + code_lens.range = *ls_range; + code_lens.command = Command(); + code_lens.command->command = std::string(ccls_xref); + bool plural = num > 1 && singular[strlen(singular) - 1] != 'd'; + code_lens.command->title = + llvm::formatv("{0} {1}{2}", num, singular, plural ? "s" : "").str(); + code_lens.command->arguments.push_back(ToString(show)); + }; + + std::unordered_set seen; + for (auto [sym, refcnt] : file->symbol2refcnt) { + if (refcnt <= 0 || !sym.extent.Valid() || !seen.insert(sym.range).second) + continue; + switch (sym.kind) { + case Kind::Func: { + QueryFunc &func = db->GetFunc(sym); + const QueryFunc::Def *def = func.AnyDef(); + if (!def) + continue; + std::vector base_uses = GetUsesForAllBases(db, func); + std::vector derived_uses = GetUsesForAllDerived(db, func); + Add("ref", {sym.usr, Kind::Func, "uses"}, sym.range, func.uses.size(), + base_uses.empty()); + if (base_uses.size()) + Add("b.ref", {sym.usr, Kind::Func, "bases uses"}, sym.range, + base_uses.size()); + if (derived_uses.size()) + Add("d.ref", {sym.usr, Kind::Func, "derived uses"}, sym.range, + derived_uses.size()); + if (base_uses.empty()) + Add("base", {sym.usr, Kind::Func, "bases"}, sym.range, + def->bases.size()); + Add("derived", {sym.usr, Kind::Func, "derived"}, sym.range, + func.derived.size()); + break; + } + case Kind::Type: { + QueryType &type = db->GetType(sym); + Add("ref", {sym.usr, Kind::Type, "uses"}, sym.range, type.uses.size(), + true); + Add("derived", {sym.usr, Kind::Type, "derived"}, sym.range, + type.derived.size()); + Add("var", {sym.usr, Kind::Type, "instances"}, sym.range, + type.instances.size()); + break; + } + case Kind::Var: { + QueryVar &var = db->GetVar(sym); + const QueryVar::Def *def = var.AnyDef(); + if (!def || (def->is_local() && !g_config->codeLens.localVariables)) + continue; + Add("ref", {sym.usr, Kind::Var, "uses"}, sym.range, var.uses.size(), + def->kind != SymbolKind::Macro); + break; + } + case Kind::File: + case Kind::Invalid: + llvm_unreachable(""); + }; + } + + reply(result); +} + +void MessageHandler::workspace_executeCommand(JsonReader &reader, + ReplyOnce &reply) { + Command param; + Reflect(reader, param); + if (param.arguments.empty()) { + return; + } + rapidjson::Document reader1; + reader1.Parse(param.arguments[0].c_str()); + JsonReader json_reader{&reader1}; + if (param.command == ccls_xref) { + Cmd_xref cmd; + Reflect(json_reader, cmd); + std::vector result; + auto Map = [&](auto &&uses) { + for (auto &use : uses) + if (auto loc = GetLsLocation(db, wfiles, use)) + result.push_back(std::move(*loc)); + }; + switch (cmd.kind) { + case Kind::Func: { + QueryFunc &func = db->Func(cmd.usr); + if (cmd.field == "bases") { + if (auto *def = func.AnyDef()) + Map(GetFuncDeclarations(db, def->bases)); + } else if (cmd.field == "bases uses") { + Map(GetUsesForAllBases(db, func)); + } else if (cmd.field == "derived") { + Map(GetFuncDeclarations(db, func.derived)); + } else if (cmd.field == "derived uses") { + Map(GetUsesForAllDerived(db, func)); + } else if (cmd.field == "uses") { + Map(func.uses); + } + break; + } + case Kind::Type: { + QueryType &type = db->Type(cmd.usr); + if (cmd.field == "derived") { + Map(GetTypeDeclarations(db, type.derived)); + } else if (cmd.field == "instances") { + Map(GetVarDeclarations(db, type.instances, 7)); + } else if (cmd.field == "uses") { + Map(type.uses); + } + break; + } + case Kind::Var: { + QueryVar &var = db->Var(cmd.usr); + if (cmd.field == "uses") + Map(var.uses); + break; + } + default: + break; + } + reply(result); + } +} +} // namespace ccls diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc new file mode 100644 index 000000000..771169904 --- /dev/null +++ b/src/messages/textDocument_completion.cc @@ -0,0 +1,558 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "fuzzy_match.hh" +#include "include_complete.hh" +#include "log.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "sema_manager.hh" +#include "working_files.hh" + +#include +#include + +#if LLVM_VERSION_MAJOR < 8 +#include +#endif + +namespace ccls { +using namespace clang; +using namespace llvm; + +REFLECT_UNDERLYING(InsertTextFormat); +REFLECT_UNDERLYING(CompletionItemKind); + +void Reflect(JsonWriter &vis, CompletionItem &v) { + ReflectMemberStart(vis); + REFLECT_MEMBER(label); + REFLECT_MEMBER(kind); + REFLECT_MEMBER(detail); + if (v.documentation.size()) + REFLECT_MEMBER(documentation); + REFLECT_MEMBER(sortText); + if (v.filterText.size()) + REFLECT_MEMBER(filterText); + REFLECT_MEMBER(insertTextFormat); + REFLECT_MEMBER(textEdit); + if (v.additionalTextEdits.size()) + REFLECT_MEMBER(additionalTextEdits); + ReflectMemberEnd(vis); +} + +namespace { +struct CompletionList { + bool isIncomplete = false; + std::vector items; +}; +REFLECT_STRUCT(CompletionList, isIncomplete, items); + +#if LLVM_VERSION_MAJOR < 8 +void DecorateIncludePaths(const std::smatch &match, + std::vector *items) { + std::string spaces_after_include = " "; + if (match[3].compare("include") == 0 && match[5].length()) + spaces_after_include = match[4].str(); + + std::string prefix = + match[1].str() + '#' + match[2].str() + "include" + spaces_after_include; + std::string suffix = match[7].str(); + + for (CompletionItem &item : *items) { + char quote0, quote1; + if (match[5].compare("<") == 0 || + (match[5].length() == 0 && item.use_angle_brackets_)) + quote0 = '<', quote1 = '>'; + else + quote0 = quote1 = '"'; + + item.textEdit.newText = + prefix + quote0 + item.textEdit.newText + quote1 + suffix; + item.label = prefix + quote0 + item.label + quote1 + suffix; + } +} + +struct ParseIncludeLineResult { + bool ok; + std::string keyword; + std::string quote; + std::string pattern; + std::smatch match; +}; + +ParseIncludeLineResult ParseIncludeLine(const std::string &line) { + static const std::regex pattern("(\\s*)" // [1]: spaces before '#' + "#" // + "(\\s*)" // [2]: spaces after '#' + "([^\\s\"<]*)" // [3]: "include" + "(\\s*)" // [4]: spaces before quote + "([\"<])?" // [5]: the first quote char + "([^\\s\">]*)" // [6]: path of file + "[\">]?" // + "(.*)"); // [7]: suffix after quote char + std::smatch match; + bool ok = std::regex_match(line, match, pattern); + return {ok, match[3], match[5], match[6], match}; +} +#endif + +// Pre-filters completion responses before sending to vscode. This results in a +// significantly snappier completion experience as vscode is easily overloaded +// when given 1000+ completion items. +void FilterCandidates(CompletionList &result, const std::string &complete_text, + Position begin_pos, Position end_pos, + const std::string &buffer_line) { + assert(begin_pos.line == end_pos.line); + auto &items = result.items; + + // People usually does not want to insert snippets or parenthesis when + // changing function or type names, e.g. "str.|()" or "std::|". + bool has_open_paren = false; + for (int c = end_pos.character; c < buffer_line.size(); ++c) { + if (buffer_line[c] == '(' || buffer_line[c] == '<') + has_open_paren = true; + if (!isspace(buffer_line[c])) + break; + } + + auto finalize = [&]() { + int max_num = g_config->completion.maxNum; + if (items.size() > max_num) { + items.resize(max_num); + result.isIncomplete = true; + } + + std::string sort(4, ' '); + for (auto &item : items) { + item.textEdit.range = lsRange{begin_pos, end_pos}; + if (has_open_paren) + item.textEdit.newText = item.filterText; + // https://github.com/Microsoft/language-server-protocol/issues/543 + // Order of textEdit and additionalTextEdits is unspecified. + auto &edits = item.additionalTextEdits; + if (edits.size() && edits[0].range.end == begin_pos) { + Position start = edits[0].range.start, end = edits[0].range.end; + item.textEdit.range.start = start; + item.textEdit.newText = edits[0].newText + item.textEdit.newText; + if (start.line == begin_pos.line) { + item.filterText = + buffer_line.substr(start.character, + end.character - start.character) + + item.filterText; + } + edits.erase(edits.begin()); + } + if (item.filterText == item.label) + item.filterText.clear(); + for (auto i = sort.size(); i && ++sort[i - 1] == 'A';) + sort[--i] = ' '; + item.sortText = sort; + } + }; + + if (!g_config->completion.filterAndSort) { + finalize(); + return; + } + + if (complete_text.size()) { + // Fuzzy match and remove awful candidates. + bool sensitive = g_config->completion.caseSensitivity; + FuzzyMatcher fuzzy(complete_text, sensitive); + for (CompletionItem &item : items) { + const std::string &filter = + item.filterText.size() ? item.filterText : item.label; + item.score_ = ReverseSubseqMatch(complete_text, filter, sensitive) >= 0 + ? fuzzy.Match(filter) + : FuzzyMatcher::kMinScore; + } + items.erase(std::remove_if(items.begin(), items.end(), + [](const CompletionItem &item) { + return item.score_ <= FuzzyMatcher::kMinScore; + }), + items.end()); + } + std::sort(items.begin(), items.end(), + [](const CompletionItem &lhs, const CompletionItem &rhs) { + int t = int(lhs.additionalTextEdits.size() - + rhs.additionalTextEdits.size()); + if (t) + return t < 0; + if (lhs.score_ != rhs.score_) + return lhs.score_ > rhs.score_; + if (lhs.priority_ != rhs.priority_) + return lhs.priority_ < rhs.priority_; + t = lhs.textEdit.newText.compare(rhs.textEdit.newText); + if (t) + return t < 0; + t = lhs.label.compare(rhs.label); + if (t) + return t < 0; + return lhs.filterText < rhs.filterText; + }); + + // Trim result. + finalize(); +} + +CompletionItemKind GetCompletionKind(CXCursorKind cursor_kind) { + switch (cursor_kind) { + case CXCursor_UnexposedDecl: + return CompletionItemKind::Text; + + case CXCursor_StructDecl: + case CXCursor_UnionDecl: + return CompletionItemKind::Struct; + case CXCursor_ClassDecl: + return CompletionItemKind::Class; + case CXCursor_EnumDecl: + return CompletionItemKind::Enum; + case CXCursor_FieldDecl: + return CompletionItemKind::Field; + case CXCursor_EnumConstantDecl: + return CompletionItemKind::EnumMember; + case CXCursor_FunctionDecl: + return CompletionItemKind::Function; + case CXCursor_VarDecl: + case CXCursor_ParmDecl: + return CompletionItemKind::Variable; + case CXCursor_ObjCInterfaceDecl: + return CompletionItemKind::Interface; + + case CXCursor_ObjCInstanceMethodDecl: + case CXCursor_CXXMethod: + case CXCursor_ObjCClassMethodDecl: + return CompletionItemKind::Method; + + case CXCursor_FunctionTemplate: + return CompletionItemKind::Function; + + case CXCursor_Constructor: + case CXCursor_Destructor: + case CXCursor_ConversionFunction: + return CompletionItemKind::Constructor; + + case CXCursor_ObjCIvarDecl: + return CompletionItemKind::Variable; + + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + case CXCursor_UsingDeclaration: + case CXCursor_TypedefDecl: + case CXCursor_TypeAliasDecl: + case CXCursor_TypeAliasTemplateDecl: + case CXCursor_ObjCCategoryDecl: + case CXCursor_ObjCProtocolDecl: + case CXCursor_ObjCImplementationDecl: + case CXCursor_ObjCCategoryImplDecl: + return CompletionItemKind::Class; + + case CXCursor_ObjCPropertyDecl: + return CompletionItemKind::Property; + + case CXCursor_MacroInstantiation: + case CXCursor_MacroDefinition: + return CompletionItemKind::Text; + + case CXCursor_Namespace: + case CXCursor_NamespaceAlias: + case CXCursor_NamespaceRef: + return CompletionItemKind::Module; + + case CXCursor_MemberRef: + case CXCursor_TypeRef: + case CXCursor_ObjCSuperClassRef: + case CXCursor_ObjCProtocolRef: + case CXCursor_ObjCClassRef: + return CompletionItemKind::Reference; + + case CXCursor_NotImplemented: + case CXCursor_OverloadCandidate: + return CompletionItemKind::Text; + + case CXCursor_TemplateTypeParameter: + case CXCursor_TemplateTemplateParameter: + return CompletionItemKind::TypeParameter; + + default: + LOG_S(WARNING) << "Unhandled completion kind " << cursor_kind; + return CompletionItemKind::Text; + } +} + +void BuildItem(const CodeCompletionResult &R, const CodeCompletionString &CCS, + std::vector &out) { + assert(!out.empty()); + auto first = out.size() - 1; + bool ignore = false; + std::string result_type; + + for (const auto &Chunk : CCS) { + CodeCompletionString::ChunkKind Kind = Chunk.Kind; + std::string text; + switch (Kind) { + case CodeCompletionString::CK_TypedText: + text = Chunk.Text; + for (auto i = first; i < out.size(); i++) + out[i].filterText = text; + break; + case CodeCompletionString::CK_Placeholder: + text = Chunk.Text; + for (auto i = first; i < out.size(); i++) + out[i].parameters_.push_back(text); + break; + case CodeCompletionString::CK_Informative: + if (StringRef(Chunk.Text).endswith("::")) + continue; + text = Chunk.Text; + break; + case CodeCompletionString::CK_ResultType: + result_type = Chunk.Text; + continue; + case CodeCompletionString::CK_CurrentParameter: + // This should never be present while collecting completion items. + llvm_unreachable("unexpected CK_CurrentParameter"); + continue; + case CodeCompletionString::CK_Optional: { + // Duplicate last element, the recursive call will complete it. + if (g_config->completion.duplicateOptional) { + out.push_back(out.back()); + BuildItem(R, *Chunk.Optional, out); + } + continue; + } + default: + text = Chunk.Text; + break; + } + + for (auto i = first; i < out.size(); ++i) { + out[i].label += text; + if (ignore || + (!g_config->client.snippetSupport && out[i].parameters_.size())) + continue; + + if (Kind == CodeCompletionString::CK_Placeholder) { + if (R.Kind == CodeCompletionResult::RK_Pattern) { + ignore = true; + continue; + } + out[i].textEdit.newText += + "${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}"; + out[i].insertTextFormat = InsertTextFormat::Snippet; + } else if (Kind != CodeCompletionString::CK_Informative) { + out[i].textEdit.newText += text; + } + } + } + + if (result_type.size()) + for (auto i = first; i < out.size(); ++i) { + // ' : ' for variables, + // ' -> ' (trailing return type-like) for functions + out[i].label += (out[i].label == out[i].filterText ? " : " : " -> "); + out[i].label += result_type; + } +} + +class CompletionConsumer : public CodeCompleteConsumer { + std::shared_ptr Alloc; + CodeCompletionTUInfo CCTUInfo; + +public: + bool from_cache; + std::vector ls_items; + + CompletionConsumer(const CodeCompleteOptions &Opts, bool from_cache) + : CodeCompleteConsumer(Opts, false), + Alloc(std::make_shared()), + CCTUInfo(Alloc), from_cache(from_cache) {} + + void ProcessCodeCompleteResults(Sema &S, CodeCompletionContext Context, + CodeCompletionResult *Results, + unsigned NumResults) override { + if (Context.getKind() == CodeCompletionContext::CCC_Recovery) + return; + ls_items.reserve(NumResults); + for (unsigned i = 0; i != NumResults; i++) { + auto &R = Results[i]; + if (R.Availability == CXAvailability_NotAccessible || + R.Availability == CXAvailability_NotAvailable) + continue; + if (R.Declaration) { + if (R.Declaration->getKind() == Decl::CXXDestructor) + continue; + if (auto *RD = dyn_cast(R.Declaration)) + if (RD->isInjectedClassName()) + continue; + auto NK = R.Declaration->getDeclName().getNameKind(); + if (NK == DeclarationName::CXXOperatorName || + NK == DeclarationName::CXXLiteralOperatorName) + continue; + } + CodeCompletionString *CCS = R.CreateCodeCompletionString( + S, Context, getAllocator(), getCodeCompletionTUInfo(), + includeBriefComments()); + CompletionItem ls_item; + ls_item.kind = GetCompletionKind(R.CursorKind); +#if LLVM_VERSION_MAJOR >= 8 + if (Context.getKind() == CodeCompletionContext::CCC_IncludedFile) + ls_item.kind = CompletionItemKind::File; +#endif + if (const char *brief = CCS->getBriefComment()) + ls_item.documentation = brief; + ls_item.detail = CCS->getParentContextName().str(); + + size_t first_idx = ls_items.size(); + ls_items.push_back(ls_item); + BuildItem(R, *CCS, ls_items); + + for (size_t j = first_idx; j < ls_items.size(); j++) { + if (g_config->client.snippetSupport && + ls_items[j].insertTextFormat == InsertTextFormat::Snippet) + ls_items[j].textEdit.newText += "$0"; + ls_items[j].priority_ = CCS->getPriority(); + if (!g_config->completion.detailedLabel) { + ls_items[j].detail = ls_items[j].label; + ls_items[j].label = ls_items[j].filterText; + } + } +#if LLVM_VERSION_MAJOR >= 7 + for (const FixItHint &FixIt : R.FixIts) { + auto &AST = S.getASTContext(); + TextEdit ls_edit = + ccls::ToTextEdit(AST.getSourceManager(), AST.getLangOpts(), FixIt); + for (size_t j = first_idx; j < ls_items.size(); j++) + ls_items[j].additionalTextEdits.push_back(ls_edit); + } +#endif + } + } + + CodeCompletionAllocator &getAllocator() override { return *Alloc; } + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; +} // namespace + +void MessageHandler::textDocument_completion(CompletionParam ¶m, + ReplyOnce &reply) { + static CompleteConsumerCache> cache; + std::string path = param.textDocument.uri.GetPath(); + WorkingFile *file = wfiles->GetFile(path); + if (!file) { + reply.NotReady(true); + return; + } + + CompletionList result; + + // It shouldn't be possible, but sometimes vscode will send queries out + // of order, ie, we get completion request before buffer content update. + std::string buffer_line; + if (param.position.line >= 0 && + param.position.line < file->buffer_lines.size()) + buffer_line = file->buffer_lines[param.position.line]; + + clang::CodeCompleteOptions CCOpts; + CCOpts.IncludeBriefComments = true; + CCOpts.IncludeCodePatterns = StringRef(buffer_line).ltrim().startswith("#"); +#if LLVM_VERSION_MAJOR >= 7 + CCOpts.IncludeFixIts = true; +#endif + CCOpts.IncludeMacros = true; + + if (param.context.triggerKind == CompletionTriggerKind::TriggerCharacter && + param.context.triggerCharacter) { + bool ok = true; + int col = param.position.character - 2; + switch ((*param.context.triggerCharacter)[0]) { + case '"': + case '/': + case '<': + ok = CCOpts.IncludeCodePatterns; // start with # + break; + case ':': + ok = col >= 0 && buffer_line[col] == ':'; // :: + break; + case '>': + ok = col >= 0 && buffer_line[col] == '-'; // -> + break; + } + if (!ok) { + reply(result); + return; + } + } + + std::string filter; + Position end_pos = param.position; + Position begin_pos = + file->FindStableCompletionSource(param.position, &filter, &end_pos); + +#if LLVM_VERSION_MAJOR < 8 + ParseIncludeLineResult preprocess = ParseIncludeLine(buffer_line); + if (preprocess.ok && preprocess.keyword.compare("include") == 0) { + CompletionList result; + { + std::unique_lock lock( + include_complete->completion_items_mutex, std::defer_lock); + if (include_complete->is_scanning) + lock.lock(); + std::string quote = preprocess.match[5]; + for (auto &item : include_complete->completion_items) + if (quote.empty() || quote == (item.use_angle_brackets_ ? "<" : "\"")) + result.items.push_back(item); + } + begin_pos.character = 0; + end_pos.character = (int)buffer_line.size(); + FilterCandidates(result, preprocess.pattern, begin_pos, end_pos, + buffer_line); + DecorateIncludePaths(preprocess.match, &result.items); + reply(result); + return; + } +#endif + + SemaManager::OnComplete callback = + [filter, path, begin_pos, end_pos, reply, + buffer_line](CodeCompleteConsumer *OptConsumer) { + if (!OptConsumer) + return; + auto *Consumer = static_cast(OptConsumer); + CompletionList result; + result.items = Consumer->ls_items; + + FilterCandidates(result, filter, begin_pos, end_pos, buffer_line); + reply(result); + if (!Consumer->from_cache) { + cache.WithLock([&]() { + cache.path = path; + cache.position = begin_pos; + cache.result = Consumer->ls_items; + }); + } + }; + + if (cache.IsCacheValid(path, begin_pos)) { + CompletionConsumer Consumer(CCOpts, true); + cache.WithLock([&]() { Consumer.ls_items = cache.result; }); + callback(&Consumer); + } else { + manager->comp_tasks.PushBack(std::make_unique( + reply.id, param.textDocument.uri.GetPath(), begin_pos, + std::make_unique(CCOpts, false), CCOpts, callback)); + } +} +} // namespace ccls diff --git a/src/messages/textDocument_definition.cc b/src/messages/textDocument_definition.cc new file mode 100644 index 000000000..62927547c --- /dev/null +++ b/src/messages/textDocument_definition.cc @@ -0,0 +1,218 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "query.hh" + +#include +#include +#include + +namespace ccls { +namespace { +std::vector GetNonDefDeclarationTargets(DB *db, SymbolRef sym) { + switch (sym.kind) { + case Kind::Var: { + std::vector ret = GetNonDefDeclarations(db, sym); + // If there is no declaration, jump to its type. + if (ret.empty()) { + for (auto &def : db->GetVar(sym).def) + if (def.type) { + if (Maybe use = + GetDefinitionSpell(db, SymbolIdx{def.type, Kind::Type})) { + ret.push_back(*use); + break; + } + } + } + return ret; + } + default: + return GetNonDefDeclarations(db, sym); + } +} +} // namespace + +void MessageHandler::textDocument_definition(TextDocumentPositionParam ¶m, + ReplyOnce &reply) { + int file_id; + QueryFile *file = FindFile(param.textDocument.uri.GetPath(), &file_id); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + std::vector result; + Maybe on_def; + Position &ls_pos = param.position; + + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, ls_pos, true)) { + // Special cases which are handled: + // - symbol has declaration but no definition (ie, pure virtual) + // - goto declaration while in definition of recursive type + std::vector uses; + EachEntityDef(db, sym, [&](const auto &def) { + if (def.spell) { + Use spell = *def.spell; + if (spell.file_id == file_id && + spell.range.Contains(ls_pos.line, ls_pos.character)) { + on_def = spell; + uses.clear(); + return false; + } + uses.push_back(spell); + } + return true; + }); + + // |uses| is empty if on a declaration/definition, otherwise it includes + // all declarations/definitions. + if (uses.empty()) { + for (Use use : GetNonDefDeclarationTargets(db, sym)) + if (!(use.file_id == file_id && + use.range.Contains(ls_pos.line, ls_pos.character))) + uses.push_back(use); + // There is no declaration but the cursor is on a definition. + if (uses.empty() && on_def) + uses.push_back(*on_def); + } + auto locs = GetLsLocations(db, wfiles, uses); + result.insert(result.end(), locs.begin(), locs.end()); + } + + if (result.size()) { + std::sort(result.begin(), result.end()); + result.erase(std::unique(result.begin(), result.end()), result.end()); + } else { + Maybe range; + // Check #include + for (const IndexInclude &include : file->def->includes) { + if (include.line == ls_pos.line) { + result.push_back( + Location{DocumentUri::FromPath(include.resolved_path)}); + range = {{0, 0}, {0, 0}}; + break; + } + } + // Find the best match of the identifier at point. + if (!range) { + Position position = param.position; + const std::string &buffer = wf->buffer_content; + std::string_view query = LexIdentifierAroundPos(position, buffer); + std::string_view short_query = query; + { + auto pos = query.rfind(':'); + if (pos != std::string::npos) + short_query = query.substr(pos + 1); + } + + // For symbols whose short/detailed names contain |query| as a + // substring, we use the tuple to find the best match. + std::tuple best_score{INT_MAX, 0, true, 0}; + SymbolIdx best_sym; + best_sym.kind = Kind::Invalid; + auto fn = [&](SymbolIdx sym) { + std::string_view short_name = db->GetSymbolName(sym, false), + name = short_query.size() < query.size() + ? db->GetSymbolName(sym, true) + : short_name; + if (short_name != short_query) + return; + if (Maybe dr = GetDefinitionSpell(db, sym)) { + std::tuple score{ + int(name.size() - short_query.size()), 0, dr->file_id != file_id, + std::abs(dr->range.start.line - position.line)}; + // Update the score with qualified name if the qualified name + // occurs in |name|. + auto pos = name.rfind(query); + if (pos != std::string::npos) { + std::get<0>(score) = int(name.size() - query.size()); + std::get<1>(score) = -int(pos); + } + if (score < best_score) { + best_score = score; + best_sym = sym; + } + } + }; + for (auto &func : db->funcs) + fn({func.usr, Kind::Func}); + for (auto &type : db->types) + fn({type.usr, Kind::Type}); + for (auto &var : db->vars) + if (var.def.size() && !var.def[0].is_local()) + fn({var.usr, Kind::Var}); + + if (best_sym.kind != Kind::Invalid) { + Maybe dr = GetDefinitionSpell(db, best_sym); + assert(dr); + if (auto loc = GetLsLocation(db, wfiles, *dr)) + result.push_back(*loc); + } + } + } + + reply(result); +} + +void MessageHandler::textDocument_typeDefinition( + TextDocumentPositionParam ¶m, ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!file) { + reply.NotReady(file); + return; + } + + std::vector result; + auto Add = [&](const QueryType &type) { + for (const auto &def : type.def) + if (def.spell) { + if (auto ls_loc = GetLsLocation(db, wfiles, *def.spell)) + result.push_back(*ls_loc); + } + if (result.empty()) + for (const DeclRef &dr : type.declarations) + if (auto ls_loc = GetLsLocation(db, wfiles, dr)) + result.push_back(*ls_loc); + }; + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + switch (sym.kind) { + case Kind::Var: { + const QueryVar::Def *def = db->GetVar(sym).AnyDef(); + if (def && def->type) + Add(db->Type(def->type)); + break; + } + case Kind::Type: { + for (auto &def : db->GetType(sym).def) + if (def.alias_of) { + Add(db->Type(def.alias_of)); + break; + } + break; + } + default: + break; + } + } + + std::sort(result.begin(), result.end()); + result.erase(std::unique(result.begin(), result.end()), result.end()); + reply(result); +} +} // namespace ccls diff --git a/src/messages/textDocument_did.cc b/src/messages/textDocument_did.cc new file mode 100644 index 000000000..2dc916290 --- /dev/null +++ b/src/messages/textDocument_did.cc @@ -0,0 +1,69 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "include_complete.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "project.hh" +#include "sema_manager.hh" +#include "working_files.hh" + +namespace ccls { +void MessageHandler::textDocument_didChange(TextDocumentDidChangeParam ¶m) { + std::string path = param.textDocument.uri.GetPath(); + wfiles->OnChange(param); + if (g_config->index.onChange) + pipeline::Index(path, {}, IndexMode::OnChange); + manager->OnView(path); + if (g_config->diagnostics.onChange >= 0) + manager->ScheduleDiag(path, g_config->diagnostics.onChange); +} + +void MessageHandler::textDocument_didClose(TextDocumentParam ¶m) { + std::string path = param.textDocument.uri.GetPath(); + wfiles->OnClose(path); + manager->OnClose(path); +} + +void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam ¶m) { + std::string path = param.textDocument.uri.GetPath(); + WorkingFile *wf = wfiles->OnOpen(param.textDocument); + if (std::optional cached_file_contents = + pipeline::LoadIndexedContent(path)) + wf->SetIndexContent(*cached_file_contents); + + QueryFile *file = FindFile(path); + if (file) { + EmitSkippedRanges(wf, *file); + EmitSemanticHighlight(db, wf, *file); + } + include_complete->AddFile(wf->filename); + + // Submit new index request if it is not a header file or there is no + // pending index request. + std::pair lang = lookupExtension(path); + if ((lang.first != LanguageId::Unknown && !lang.second) || + !pipeline::pending_index_requests) + pipeline::Index(path, {}, IndexMode::Normal); + + manager->OnView(path); +} + +void MessageHandler::textDocument_didSave(TextDocumentParam ¶m) { + const std::string &path = param.textDocument.uri.GetPath(); + pipeline::Index(path, {}, IndexMode::Normal); + manager->OnSave(path); +} +} // namespace ccls diff --git a/src/messages/textDocument_document.cc b/src/messages/textDocument_document.cc new file mode 100644 index 000000000..321c64c0d --- /dev/null +++ b/src/messages/textDocument_document.cc @@ -0,0 +1,291 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "query.hh" + +#include + +MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind); + +namespace ccls { +REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName); + +namespace { +struct DocumentHighlight { + enum Kind { Text = 1, Read = 2, Write = 3 }; + + lsRange range; + int kind = 1; + + // ccls extension + Role role = Role::None; + + bool operator<(const DocumentHighlight &o) const { + return !(range == o.range) ? range < o.range : kind < o.kind; + } +}; +REFLECT_STRUCT(DocumentHighlight, range, kind, role); +} // namespace + +void MessageHandler::textDocument_documentHighlight( + TextDocumentPositionParam ¶m, ReplyOnce &reply) { + int file_id; + QueryFile *file = FindFile(param.textDocument.uri.GetPath(), &file_id); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + std::vector result; + std::vector syms = + FindSymbolsAtLocation(wf, file, param.position, true); + for (auto [sym, refcnt] : file->symbol2refcnt) { + if (refcnt <= 0) + continue; + Usr usr = sym.usr; + Kind kind = sym.kind; + if (std::none_of(syms.begin(), syms.end(), [&](auto &sym1) { + return usr == sym1.usr && kind == sym1.kind; + })) + continue; + if (auto loc = GetLsLocation(db, wfiles, sym, file_id)) { + DocumentHighlight highlight; + highlight.range = loc->range; + if (sym.role & Role::Write) + highlight.kind = DocumentHighlight::Write; + else if (sym.role & Role::Read) + highlight.kind = DocumentHighlight::Read; + else + highlight.kind = DocumentHighlight::Text; + highlight.role = sym.role; + result.push_back(highlight); + } + } + std::sort(result.begin(), result.end()); + reply(result); +} + +namespace { +struct DocumentLink { + lsRange range; + DocumentUri target; +}; +REFLECT_STRUCT(DocumentLink, range, target); +} // namespace + +void MessageHandler::textDocument_documentLink(TextDocumentParam ¶m, + ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + std::vector result; + for (const IndexInclude &include : file->def->includes) + if (std::optional bline = + wf->GetBufferPosFromIndexPos(include.line, nullptr, false)) { + const std::string &line = wf->buffer_lines[*bline]; + auto start = line.find_first_of("\"<"), end = line.find_last_of("\">"); + if (start < end) + result.push_back({lsRange{{*bline, (int)start + 1}, {*bline, (int)end}}, + DocumentUri::FromPath(include.resolved_path)}); + } + reply(result); +} // namespace ccls + +namespace { +struct DocumentSymbolParam : TextDocumentParam { + // false: outline; true: all symbols + bool all = false; + // If >= 0, return Range[] instead of SymbolInformation[] to reduce output. + int startLine = -1; + int endLine = -1; +}; +REFLECT_STRUCT(DocumentSymbolParam, textDocument, all, startLine, endLine); + +struct DocumentSymbol { + std::string name; + std::string detail; + SymbolKind kind; + lsRange range; + lsRange selectionRange; + std::vector> children; +}; +void Reflect(JsonWriter &vis, std::unique_ptr &v); +REFLECT_STRUCT(DocumentSymbol, name, detail, kind, range, selectionRange, + children); +void Reflect(JsonWriter &vis, std::unique_ptr &v) { + Reflect(vis, *v); +} + +template +bool Ignore(const Def *def) { + return false; +} +template <> +bool Ignore(const QueryType::Def *def) { + return !def || def->kind == SymbolKind::TypeParameter; +} +template<> +bool Ignore(const QueryVar::Def *def) { + return !def || def->is_local(); +} + +void Uniquify(std::vector> &cs) { + std::sort(cs.begin(), cs.end(), + [](auto &l, auto &r) { return l->range < r->range; }); + cs.erase(std::unique(cs.begin(), cs.end(), + [](auto &l, auto &r) { return l->range == r->range; }), + cs.end()); + for (auto &c : cs) + Uniquify(c->children); +} +} // namespace + +void MessageHandler::textDocument_documentSymbol(JsonReader &reader, + ReplyOnce &reply) { + DocumentSymbolParam param; + Reflect(reader, param); + + int file_id; + QueryFile *file = FindFile(param.textDocument.uri.GetPath(), &file_id); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + if (param.startLine >= 0) { + std::vector result; + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && (param.all || sym.extent.Valid()) && + param.startLine <= sym.range.start.line && + sym.range.start.line <= param.endLine) + if (auto loc = GetLsLocation(db, wfiles, sym, file_id)) + result.push_back(loc->range); + std::sort(result.begin(), result.end()); + reply(result); + } else if (g_config->client.hierarchicalDocumentSymbolSupport) { + std::unordered_map> sym2ds; + std::vector, DocumentSymbol *>> funcs, + types; + for (auto [sym, refcnt] : file->symbol2refcnt) { + if (refcnt <= 0 || !sym.extent.Valid()) + continue; + auto r = sym2ds.try_emplace(SymbolIdx{sym.usr, sym.kind}); + if (!r.second) + continue; + auto &ds = r.first->second; + ds = std::make_unique(); + if (auto range = GetLsRange(wf, sym.range)) { + ds->selectionRange = *range; + ds->range = ds->selectionRange; + // For a macro expansion, M(name), we may use `M` for extent and `name` + // for spell, do the check as selectionRange must be a subrange of + // range. + if (sym.extent.Valid()) + if (auto range1 = GetLsRange(wf, sym.extent); + range1 && range1->Includes(*range)) + ds->range = *range1; + } + std::vector def_ptrs; + WithEntity(db, sym, [&](const auto &entity) { + auto *def = entity.AnyDef(); + if (!def) + return; + ds->name = def->Name(false); + ds->detail = def->Name(true); + for (auto &def : entity.def) + if (def.file_id == file_id && !Ignore(&def)) { + ds->kind = def.kind; + if (def.spell || def.kind == SymbolKind::Namespace) + def_ptrs.push_back(&def); + } + }); + if (!(param.all || sym.role & Role::Definition || + ds->kind == SymbolKind::Function || + ds->kind == SymbolKind::Method || + ds->kind == SymbolKind::Namespace)) { + ds.reset(); + continue; + } + if (def_ptrs.empty()) + continue; + if (sym.kind == Kind::Func) + funcs.emplace_back(std::move(def_ptrs), ds.get()); + else if (sym.kind == Kind::Type) + types.emplace_back(std::move(def_ptrs), ds.get()); + } + + for (auto &[def_ptrs, ds] : funcs) + for (const void *def_ptr : def_ptrs) + for (Usr usr1 : ((const QueryFunc::Def *)def_ptr)->vars) { + auto it = sym2ds.find(SymbolIdx{usr1, Kind::Var}); + if (it != sym2ds.end() && it->second) + ds->children.push_back(std::move(it->second)); + } + for (auto &[def_ptrs, ds] : types) + for (const void *def_ptr : def_ptrs) { + auto *def = (const QueryType::Def *)def_ptr; + for (Usr usr1 : def->funcs) { + auto it = sym2ds.find(SymbolIdx{usr1, Kind::Func}); + if (it != sym2ds.end() && it->second) + ds->children.push_back(std::move(it->second)); + } + for (Usr usr1 : def->types) { + auto it = sym2ds.find(SymbolIdx{usr1, Kind::Type}); + if (it != sym2ds.end() && it->second) + ds->children.push_back(std::move(it->second)); + } + for (auto [usr1, _] : def->vars) { + auto it = sym2ds.find(SymbolIdx{usr1, Kind::Var}); + if (it != sym2ds.end() && it->second) + ds->children.push_back(std::move(it->second)); + } + } + std::vector> result; + for (auto &[_, ds] : sym2ds) + if (ds) { + Uniquify(ds->children); + result.push_back(std::move(ds)); + } + Uniquify(result); + reply(result); + } else { + std::vector result; + for (auto [sym, refcnt] : file->symbol2refcnt) { + if (refcnt <= 0 || !sym.extent.Valid() || + !(param.all || sym.role & Role::Definition)) + continue; + if (std::optional info = + GetSymbolInfo(db, sym, false)) { + if ((sym.kind == Kind::Type && Ignore(db->GetType(sym).AnyDef())) || + (sym.kind == Kind::Var && Ignore(db->GetVar(sym).AnyDef()))) + continue; + if (auto loc = GetLsLocation(db, wfiles, sym, file_id)) { + info->location = *loc; + result.push_back(*info); + } + } + } + reply(result); + } +} +} // namespace ccls diff --git a/src/messages/textDocument_foldingRange.cc b/src/messages/textDocument_foldingRange.cc new file mode 100644 index 000000000..67fb0c0aa --- /dev/null +++ b/src/messages/textDocument_foldingRange.cc @@ -0,0 +1,55 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "project.hh" +#include "query.hh" +#include "working_files.hh" + +namespace ccls { +namespace { +struct FoldingRange { + int startLine, startCharacter, endLine, endCharacter; + std::string kind = "region"; +}; +REFLECT_STRUCT(FoldingRange, startLine, startCharacter, endLine, endCharacter, + kind); +} // namespace + +void MessageHandler::textDocument_foldingRange(TextDocumentParam ¶m, + ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + std::vector result; + std::optional ls_range; + + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && sym.extent.Valid() && + (sym.kind == Kind::Func || sym.kind == Kind::Type) && + (ls_range = GetLsRange(wf, sym.extent))) { + FoldingRange &fold = result.emplace_back(); + fold.startLine = ls_range->start.line; + fold.startCharacter = ls_range->start.character; + fold.endLine = ls_range->end.line; + fold.endCharacter = ls_range->end.character; + } + reply(result); +} +} // namespace ccls diff --git a/src/messages/textDocument_formatting.cc b/src/messages/textDocument_formatting.cc new file mode 100644 index 000000000..37bd7c46e --- /dev/null +++ b/src/messages/textDocument_formatting.cc @@ -0,0 +1,121 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "working_files.hh" + +#include +#include + +namespace ccls { +using namespace clang; + +namespace { +llvm::Expected +FormatCode(std::string_view code, std::string_view file, tooling::Range Range) { + StringRef Code(code.data(), code.size()), File(file.data(), file.size()); + auto Style = format::getStyle("file", File, "LLVM", Code, nullptr); + if (!Style) + return Style.takeError(); + tooling::Replacements IncludeReplaces = + format::sortIncludes(*Style, Code, {Range}, File); + auto Changed = tooling::applyAllReplacements(Code, IncludeReplaces); + if (!Changed) + return Changed.takeError(); + return IncludeReplaces.merge(format::reformat( + *Style, *Changed, + tooling::calculateRangesAfterReplacements(IncludeReplaces, {Range}), + File)); +} + +std::vector ReplacementsToEdits(std::string_view code, + const tooling::Replacements &Repls) { + std::vector ret; + int i = 0, line = 0, col = 0; + auto move = [&](int p) { + for (; i < p; i++) + if (code[i] == '\n') + line++, col = 0; + else { + if ((uint8_t)code[i] >= 128) { + while (128 <= (uint8_t)code[++i] && (uint8_t)code[i] < 192) + ; + i--; + } + col++; + } + }; + for (const auto &R : Repls) { + move(R.getOffset()); + int l = line, c = col; + move(R.getOffset() + R.getLength()); + ret.push_back({{{l, c}, {line, col}}, R.getReplacementText().str()}); + } + return ret; +} + +void Format(ReplyOnce &reply, WorkingFile *wfile, tooling::Range range) { + std::string_view code = wfile->buffer_content; + auto ReplsOrErr = FormatCode(code, wfile->filename, range); + if (ReplsOrErr) + reply(ReplacementsToEdits(code, *ReplsOrErr)); + else + reply.Error(ErrorCode::UnknownErrorCode, + llvm::toString(ReplsOrErr.takeError())); +} +} // namespace + +void MessageHandler::textDocument_formatting(DocumentFormattingParam ¶m, + ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + Format(reply, wf, {0, (unsigned)wf->buffer_content.size()}); +} + +void MessageHandler::textDocument_onTypeFormatting( + DocumentOnTypeFormattingParam ¶m, ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + std::string_view code = wf->buffer_content; + int pos = GetOffsetForPosition(param.position, code); + auto lbrace = code.find_last_of('{', pos); + if (lbrace == std::string::npos) + lbrace = pos; + Format(reply, wf, {(unsigned)lbrace, unsigned(pos - lbrace)}); +} + +void MessageHandler::textDocument_rangeFormatting( + DocumentRangeFormattingParam ¶m, ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + std::string_view code = wf->buffer_content; + int begin = GetOffsetForPosition(param.range.start, code), + end = GetOffsetForPosition(param.range.end, code); + Format(reply, wf, {(unsigned)begin, unsigned(end - begin)}); +} +} // namespace ccls diff --git a/src/messages/textDocument_hover.cc b/src/messages/textDocument_hover.cc new file mode 100644 index 000000000..7a3e03416 --- /dev/null +++ b/src/messages/textDocument_hover.cc @@ -0,0 +1,123 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "query.hh" + +namespace ccls { +namespace { +struct MarkedString { + std::optional language; + std::string value; +}; +struct Hover { + std::vector contents; + std::optional range; +}; + +void Reflect(JsonWriter &vis, MarkedString &v) { + // If there is a language, emit a `{language:string, value:string}` object. If + // not, emit a string. + if (v.language) { + vis.StartObject(); + REFLECT_MEMBER(language); + REFLECT_MEMBER(value); + vis.EndObject(); + } else { + Reflect(vis, v.value); + } +} +REFLECT_STRUCT(Hover, contents, range); + +const char *LanguageIdentifier(LanguageId lang) { + switch (lang) { + // clang-format off + case LanguageId::C: return "c"; + case LanguageId::Cpp: return "cpp"; + case LanguageId::ObjC: return "objective-c"; + case LanguageId::ObjCpp: return "objective-cpp"; + default: return ""; + // clang-format on + } +} + +// Returns the hover or detailed name for `sym`, if any. +std::pair, std::optional> +GetHover(DB *db, LanguageId lang, SymbolRef sym, int file_id) { + const char *comments = nullptr; + std::optional ls_comments, hover; + WithEntity(db, sym, [&](const auto &entity) { + std::remove_reference_t *def = nullptr; + for (auto &d : entity.def) { + if (d.spell) { + comments = d.comments[0] ? d.comments : nullptr; + def = &d; + if (d.spell->file_id == file_id) + break; + } + } + if (!def && entity.def.size()) { + def = &entity.def[0]; + if (def->comments[0]) + comments = def->comments; + } + if (def) { + MarkedString m; + m.language = LanguageIdentifier(lang); + if (def->hover[0]) { + m.value = def->hover; + hover = m; + } else if (def->detailed_name[0]) { + m.value = def->detailed_name; + hover = m; + } + if (comments) + ls_comments = MarkedString{std::nullopt, comments}; + } + }); + return {hover, ls_comments}; +} +} // namespace + +void MessageHandler::textDocument_hover(TextDocumentPositionParam ¶m, + ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + Hover result; + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + std::optional ls_range = + GetLsRange(wfiles->GetFile(file->def->path), sym.range); + if (!ls_range) + continue; + + auto [hover, comments] = GetHover(db, file->def->language, sym, file->id); + if (comments || hover) { + result.range = *ls_range; + if (comments) + result.contents.push_back(*comments); + if (hover) + result.contents.push_back(*hover); + break; + } + } + + reply(result); +} +} // namespace ccls diff --git a/src/messages/textDocument_references.cc b/src/messages/textDocument_references.cc new file mode 100644 index 000000000..8c784553d --- /dev/null +++ b/src/messages/textDocument_references.cc @@ -0,0 +1,136 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "query.hh" + +#include + +namespace ccls { +namespace { +struct ReferenceParam : public TextDocumentPositionParam { + struct Context { + // Include the declaration of the current symbol. + bool includeDeclaration = false; + } context; + + // ccls extension + // If not empty, restrict to specified folders. + std::vector folders; + // For Type, also return references of base types. + bool base = true; + // Exclude references with any |Role| bits set. + Role excludeRole = Role::None; + // Include references with all |Role| bits set. + Role role = Role::None; +}; +REFLECT_STRUCT(ReferenceParam::Context, includeDeclaration); +REFLECT_STRUCT(ReferenceParam, textDocument, position, context, folders, base, + excludeRole, role); +} // namespace + +void MessageHandler::textDocument_references(JsonReader &reader, + ReplyOnce &reply) { + ReferenceParam param; + Reflect(reader, param); + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + for (auto &folder : param.folders) + EnsureEndsInSlash(folder); + std::vector file_set = db->GetFileSet(param.folders); + std::vector result; + + std::unordered_set seen_uses; + int line = param.position.line; + + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + // Found symbol. Return references. + std::unordered_set seen; + seen.insert(sym.usr); + std::vector stack{sym.usr}; + if (sym.kind != Kind::Func) + param.base = false; + while (stack.size()) { + sym.usr = stack.back(); + stack.pop_back(); + auto fn = [&](Use use, SymbolKind parent_kind) { + if (file_set[use.file_id] && + Role(use.role & param.role) == param.role && + !(use.role & param.excludeRole) && seen_uses.insert(use).second) + if (auto loc = GetLsLocation(db, wfiles, use)) + result.push_back(*loc); + }; + WithEntity(db, sym, [&](const auto &entity) { + SymbolKind parent_kind = SymbolKind::Unknown; + for (auto &def : entity.def) + if (def.spell) { + parent_kind = GetSymbolKind(db, sym); + if (param.base) + for (Usr usr : def.GetBases()) + if (!seen.count(usr)) { + seen.insert(usr); + stack.push_back(usr); + } + break; + } + for (Use use : entity.uses) + fn(use, parent_kind); + if (param.context.includeDeclaration) { + for (auto &def : entity.def) + if (def.spell) + fn(*def.spell, parent_kind); + for (Use use : entity.declarations) + fn(use, parent_kind); + } + }); + } + break; + } + + if (result.empty()) { + // |path| is the #include line. If the cursor is not on such line but line + // = 0, + // use the current filename. + std::string path; + if (line == 0 || line >= (int)wf->buffer_lines.size() - 1) + path = file->def->path; + for (const IndexInclude &include : file->def->includes) + if (include.line == param.position.line) { + path = include.resolved_path; + break; + } + if (path.size()) + for (QueryFile &file1 : db->files) + if (file1.def) + for (const IndexInclude &include : file1.def->includes) + if (include.resolved_path == path) { + // Another file |file1| has the same include line. + Location &loc = result.emplace_back(); + loc.uri = DocumentUri::FromPath(file1.def->path); + loc.range.start.line = loc.range.end.line = include.line; + break; + } + } + + if ((int)result.size() >= g_config->xref.maxNum) + result.resize(g_config->xref.maxNum); + reply(result); +} +} // namespace ccls diff --git a/src/messages/textDocument_rename.cc b/src/messages/textDocument_rename.cc new file mode 100644 index 000000000..05028263a --- /dev/null +++ b/src/messages/textDocument_rename.cc @@ -0,0 +1,74 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "query.hh" + +namespace ccls { +namespace { +WorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym, + const std::string &new_text) { + std::unordered_map path_to_edit; + + EachOccurrence(db, sym, true, [&](Use use) { + std::optional ls_location = GetLsLocation(db, wfiles, use); + if (!ls_location) + return; + + int file_id = use.file_id; + if (path_to_edit.find(file_id) == path_to_edit.end()) { + path_to_edit[file_id] = TextDocumentEdit(); + + QueryFile &file = db->files[file_id]; + if (!file.def) + return; + + const std::string &path = file.def->path; + path_to_edit[file_id].textDocument.uri = DocumentUri::FromPath(path); + + WorkingFile *wf = wfiles->GetFile(path); + if (wf) + path_to_edit[file_id].textDocument.version = wf->version; + } + + TextEdit &edit = path_to_edit[file_id].edits.emplace_back(); + edit.range = ls_location->range; + edit.newText = new_text; + }); + + WorkspaceEdit edit; + for (const auto &changes : path_to_edit) + edit.documentChanges.push_back(changes.second); + return edit; +} +} // namespace + +void MessageHandler::textDocument_rename(RenameParam ¶m, ReplyOnce &reply) { + QueryFile *file = FindFile(param.textDocument.uri.GetPath()); + WorkingFile *wf = file ? wfiles->GetFile(file->def->path) : nullptr; + if (!wf) { + reply.NotReady(file); + return; + } + + WorkspaceEdit result; + for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) { + result = BuildWorkspaceEdit(db, wfiles, sym, param.newName); + break; + } + + reply(result); +} +} // namespace ccls diff --git a/src/messages/textDocument_signatureHelp.cc b/src/messages/textDocument_signatureHelp.cc new file mode 100644 index 000000000..b15def2a9 --- /dev/null +++ b/src/messages/textDocument_signatureHelp.cc @@ -0,0 +1,195 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "message_handler.hh" +#include "pipeline.hh" +#include "sema_manager.hh" + +#include + +namespace ccls { +using namespace clang; + +namespace { +struct ParameterInformation { + std::string label; +}; +struct SignatureInformation { + std::string label; + std::optional documentation; + std::vector parameters; +}; +struct SignatureHelp { + std::vector signatures; + int activeSignature = 0; + int activeParameter = 0; +}; +REFLECT_STRUCT(ParameterInformation, label); +REFLECT_STRUCT(SignatureInformation, label, documentation, parameters); +REFLECT_STRUCT(SignatureHelp, signatures, activeSignature, activeParameter); + +std::string BuildOptional(const CodeCompletionString &CCS, + std::vector &ls_params) { + std::string ret; + for (const auto &Chunk : CCS) { + switch (Chunk.Kind) { + case CodeCompletionString::CK_Optional: + ret += BuildOptional(*Chunk.Optional, ls_params); + break; + case CodeCompletionString::CK_Placeholder: + // A string that acts as a placeholder for, e.g., a function call + // argument. + // Intentional fallthrough here. + case CodeCompletionString::CK_CurrentParameter: { + // A piece of text that describes the parameter that corresponds to + // the code-completion location within a function call, message send, + // macro invocation, etc. + ret += Chunk.Text; + ls_params.push_back({Chunk.Text}); + break; + } + case CodeCompletionString::CK_VerticalSpace: + break; + default: + ret += Chunk.Text; + break; + } + } + return ret; +} + +class SignatureHelpConsumer : public CodeCompleteConsumer { + std::shared_ptr Alloc; + CodeCompletionTUInfo CCTUInfo; +public: + bool from_cache; + SignatureHelp ls_sighelp; + SignatureHelpConsumer(const clang::CodeCompleteOptions &CCOpts, + bool from_cache) + : CodeCompleteConsumer(CCOpts, false), + Alloc(std::make_shared()), + CCTUInfo(Alloc), from_cache(from_cache) {} + void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg, + OverloadCandidate *Candidates, + unsigned NumCandidates +#if LLVM_VERSION_MAJOR >= 8 + , + SourceLocation OpenParLoc +#endif + ) override { + ls_sighelp.activeParameter = (int)CurrentArg; + for (unsigned i = 0; i < NumCandidates; i++) { + OverloadCandidate Cand = Candidates[i]; + // We want to avoid showing instantiated signatures, because they may be + // long in some cases (e.g. when 'T' is substituted with 'std::string', we + // would get 'std::basic_string'). + if (auto *Func = Cand.getFunction()) + if (auto *Pattern = Func->getTemplateInstantiationPattern()) + Cand = OverloadCandidate(Pattern); + + const auto *CCS = + Cand.CreateSignatureString(CurrentArg, S, *Alloc, CCTUInfo, true); + + const char *ret_type = nullptr; + SignatureInformation &ls_sig = ls_sighelp.signatures.emplace_back(); +#if LLVM_VERSION_MAJOR >= 8 + const RawComment *RC = getCompletionComment(S.getASTContext(), Cand.getFunction()); + ls_sig.documentation = RC ? RC->getBriefText(S.getASTContext()) : ""; +#endif + for (const auto &Chunk : *CCS) + switch (Chunk.Kind) { + case CodeCompletionString::CK_ResultType: + ret_type = Chunk.Text; + break; + case CodeCompletionString::CK_Placeholder: + case CodeCompletionString::CK_CurrentParameter: { + ls_sig.label += Chunk.Text; + ls_sig.parameters.push_back({Chunk.Text}); + break; + } + case CodeCompletionString::CK_Optional: + ls_sig.label += BuildOptional(*Chunk.Optional, ls_sig.parameters); + break; + case CodeCompletionString::CK_VerticalSpace: + break; + default: + ls_sig.label += Chunk.Text; + break; + } + if (ret_type) { + ls_sig.label += " -> "; + ls_sig.label += ret_type; + } + } + std::sort( + ls_sighelp.signatures.begin(), ls_sighelp.signatures.end(), + [](const SignatureInformation &l, const SignatureInformation &r) { + if (l.parameters.size() != r.parameters.size()) + return l.parameters.size() < r.parameters.size(); + if (l.label.size() != r.label.size()) + return l.label.size() < r.label.size(); + return l.label < r.label; + }); + } + + CodeCompletionAllocator &getAllocator() override { return *Alloc; } + CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; } +}; +} // namespace + +void MessageHandler::textDocument_signatureHelp( + TextDocumentPositionParam ¶m, ReplyOnce &reply) { + static CompleteConsumerCache cache; + + std::string path = param.textDocument.uri.GetPath(); + Position begin_pos = param.position; + if (WorkingFile *file = wfiles->GetFile(path)) { + std::string completion_text; + Position end_pos = param.position; + begin_pos = file->FindStableCompletionSource(param.position, + &completion_text, &end_pos); + } + + SemaManager::OnComplete callback = + [reply, path, begin_pos](CodeCompleteConsumer *OptConsumer) { + if (!OptConsumer) + return; + auto *Consumer = static_cast(OptConsumer); + reply(Consumer->ls_sighelp); + if (!Consumer->from_cache) { + cache.WithLock([&]() { + cache.path = path; + cache.position = begin_pos; + cache.result = Consumer->ls_sighelp; + }); + } + }; + + CodeCompleteOptions CCOpts; + CCOpts.IncludeGlobals = false; + CCOpts.IncludeMacros = false; + CCOpts.IncludeBriefComments = false; + if (cache.IsCacheValid(path, begin_pos)) { + SignatureHelpConsumer Consumer(CCOpts, true); + cache.WithLock([&]() { Consumer.ls_sighelp = cache.result; }); + callback(&Consumer); + } else { + manager->comp_tasks.PushBack(std::make_unique( + reply.id, param.textDocument.uri.GetPath(), param.position, + std::make_unique(CCOpts, false), CCOpts, + callback)); + } +} +} // namespace ccls diff --git a/src/messages/text_document_code_action.cc b/src/messages/text_document_code_action.cc deleted file mode 100644 index df569e5c3..000000000 --- a/src/messages/text_document_code_action.cc +++ /dev/null @@ -1,597 +0,0 @@ -#include "include_complete.h" -#include "lex_utils.h" -#include "lsp_code_action.h" -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -#include - -#include -#include - -namespace { - -optional FindIncludeLine(const std::vector& lines, - const std::string& full_include_line) { - // - // This returns an include line. For example, - // - // #include // 0 - // #include // 1 - // - // Given #include , this will return '1', which means that the - // #include text should be inserted at the start of line 1. Inserting - // at the start of a line allows insertion at both the top and bottom of the - // document. - // - // If the include line is already in the document this returns nullopt. - // - - optional last_include_line; - optional best_include_line; - - // 1 => include line is gt content (ie, it should go after) - // -1 => include line is lt content (ie, it should go before) - int last_line_compare = 1; - - for (int line = 0; line < (int)lines.size(); ++line) { - std::string text = Trim(lines[line]); - if (!StartsWith(text, "#include")) { - last_line_compare = 1; - continue; - } - - last_include_line = line; - - int current_line_compare = full_include_line.compare(text); - if (current_line_compare == 0) - return nullopt; - - if (last_line_compare == 1 && current_line_compare == -1) - best_include_line = line; - last_line_compare = current_line_compare; - } - - if (best_include_line) - return *best_include_line; - // If |best_include_line| didn't match that means we likely didn't find an - // include which was lt the new one, so put it at the end of the last include - // list. - if (last_include_line) - return *last_include_line + 1; - // No includes, use top of document. - return 0; -} - -optional GetImplementationFile(QueryDatabase* db, - QueryFileId file_id, - QueryFile* file) { - for (SymbolRef sym : file->def->outline) { - switch (sym.kind) { - case SymbolKind::Func: { - if (const auto* def = db->GetFunc(sym).AnyDef()) { - // Note: we ignore the definition if it is in the same file (ie, - // possibly a header). - if (def->extent) { - QueryFileId t = def->extent->file; - if (t != file_id) - return t; - } - } - break; - } - case SymbolKind::Var: { - const QueryVar::Def* def = db->GetVar(sym).AnyDef(); - // Note: we ignore the definition if it is in the same file (ie, - // possibly a header). - if (def && def->extent) { - QueryFileId t = def->extent->file; - if (t != file_id) - return t; - } - break; - } - default: - break; - } - } - - // No associated definition, scan the project for a file in the same - // directory with the same base-name. - NormalizedPath original_path(file->def->path); - std::string target_path = original_path.path; - size_t last = target_path.find_last_of('.'); - if (last != std::string::npos) { - target_path = target_path.substr(0, last); - } - - LOG_S(INFO) << "!! Looking for impl file that starts with " << target_path; - - for (auto& entry : db->usr_to_file) { - const NormalizedPath& path = entry.first; - - // Do not consider header files for implementation files. - // TODO: make file extensions configurable. - if (EndsWith(path.path, ".h") || EndsWith(path.path, ".hpp")) - continue; - - if (StartsWith(path.path, target_path) && path != original_path) { - return entry.second; - } - } - - return nullopt; -} - -void EnsureImplFile(QueryDatabase* db, - QueryFileId file_id, - optional& impl_uri, - optional& impl_file_id) { - if (!impl_uri.has_value()) { - QueryFile& file = db->files[file_id.id]; - assert(file.def); - - impl_file_id = GetImplementationFile(db, file_id, &file); - if (!impl_file_id.has_value()) - impl_file_id = file_id; - - QueryFile& impl_file = db->files[impl_file_id->id]; - if (impl_file.def) - impl_uri = lsDocumentUri::FromPath(impl_file.def->path); - else - impl_uri = lsDocumentUri::FromPath(file.def->path); - } -} - -optional BuildAutoImplementForFunction(QueryDatabase* db, - WorkingFiles* working_files, - WorkingFile* working_file, - int default_line, - QueryFileId decl_file_id, - QueryFileId impl_file_id, - QueryFunc& func) { - const QueryFunc::Def* def = func.AnyDef(); - assert(def); - for (Use decl : func.declarations) { - if (decl.file != decl_file_id) - continue; - - optional ls_decl = GetLsRange(working_file, decl.range); - if (!ls_decl) - continue; - - optional type_name; - optional same_file_insert_end; - if (def->declaring_type) { - QueryType& declaring_type = db->types[def->declaring_type->id]; - if (const auto* def1 = declaring_type.AnyDef()) { - type_name = std::string(def1->ShortName()); - optional ls_type_extent = - GetLsRange(working_file, def1->extent->range); - if (ls_type_extent) { - same_file_insert_end = ls_type_extent->end; - same_file_insert_end->character += 1; // move past semicolon. - } - } - } - - std::string insert_text; - int newlines_after_name = 0; - LexFunctionDeclaration(working_file->buffer_content, ls_decl->start, - type_name, &insert_text, &newlines_after_name); - - if (!same_file_insert_end) { - same_file_insert_end = ls_decl->end; - same_file_insert_end->line += newlines_after_name; - same_file_insert_end->character = 1000; - } - - lsTextEdit edit; - - if (decl_file_id == impl_file_id) { - edit.range.start = *same_file_insert_end; - edit.range.end = *same_file_insert_end; - edit.newText = "\n\n" + insert_text; - } else { - lsPosition best_pos; - best_pos.line = default_line; - int best_dist = INT_MAX; - - QueryFile& file = db->files[impl_file_id.id]; - assert(file.def); - for (SymbolRef sym : file.def->outline) { - switch (sym.kind) { - case SymbolKind::Func: { - QueryFunc& sym_func = db->GetFunc(sym); - const QueryFunc::Def* def1 = sym_func.AnyDef(); - if (!def1 || !def1->extent) - break; - - for (Use func_decl : sym_func.declarations) { - if (func_decl.file == decl_file_id) { - int dist = func_decl.range.start.line - decl.range.start.line; - if (abs(dist) < abs(best_dist)) { - optional def_loc = - GetLsLocation(db, working_files, *def1->extent); - if (!def_loc) - continue; - - best_dist = dist; - - if (dist > 0) - best_pos = def_loc->range.start; - else - best_pos = def_loc->range.end; - } - } - } - - break; - } - case SymbolKind::Var: { - // TODO: handle vars. - break; - } - case SymbolKind::Invalid: - case SymbolKind::File: - case SymbolKind::Type: - LOG_S(WARNING) << "Unexpected SymbolKind " - << static_cast(sym.kind); - break; - } - } - - edit.range.start = best_pos; - edit.range.end = best_pos; - if (best_dist < 0) - edit.newText = "\n\n" + insert_text; - else - edit.newText = insert_text + "\n\n"; - } - - return edit; - } - - return nullopt; -} - -struct Ipc_TextDocumentCodeAction - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentCodeAction; - // Contains additional diagnostic information about the context in which - // a code action is run. - struct lsCodeActionContext { - // An array of diagnostics. - std::vector diagnostics; - }; - // Params for the CodeActionRequest - struct lsCodeActionParams { - // The document in which the command was invoked. - lsTextDocumentIdentifier textDocument; - // The range for which the command was invoked. - lsRange range; - // Context carrying additional information. - lsCodeActionContext context; - }; - lsCodeActionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentCodeAction::lsCodeActionContext, - diagnostics); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentCodeAction::lsCodeActionParams, - textDocument, - range, - context); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentCodeAction, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentCodeAction); - -struct Out_TextDocumentCodeAction - : public lsOutMessage { - using Command = lsCommand; - - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentCodeAction, jsonrpc, id, result); - -struct TextDocumentCodeActionHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentCodeAction* request) override { - // NOTE: This code snippet will generate some FixIts for testing: - // - // struct origin { int x, int y }; - // void foo() { - // point origin = { - // x: 0.0, - // y: 0.0 - // }; - // } - // - - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file, - &file_id)) { - return; - } - - WorkingFile* working_file = working_files->GetFileByFilename( - request->params.textDocument.uri.GetPath()); - if (!working_file) { - // TODO: send error response. - LOG_S(WARNING) - << "[error] textDocument/codeAction could not find working file"; - return; - } - - Out_TextDocumentCodeAction out; - out.id = request->id; - - // TODO: auto-insert namespace? - - int default_line = (int)working_file->buffer_lines.size(); - - // Make sure to call EnsureImplFile before using these. We lazy load - // them because computing the values could involve an entire project - // scan. - optional impl_uri; - optional impl_file_id; - - std::vector syms = - FindSymbolsAtLocation(working_file, file, request->params.range.start); - for (SymbolRef sym : syms) { - switch (sym.kind) { - case SymbolKind::Type: { - QueryType& type = db->GetType(sym); - const QueryType::Def* def = type.AnyDef(); - if (!def) - break; - - int num_edits = 0; - - // Get implementation file. - Out_TextDocumentCodeAction::Command command; - - EachDefinedEntity(db->funcs, def->funcs, [&](QueryFunc& func_def) { - const QueryFunc::Def* def1 = func_def.AnyDef(); - if (def1->extent) - return; - EnsureImplFile(db, file_id, impl_uri /*out*/, impl_file_id /*out*/); - optional edit = BuildAutoImplementForFunction( - db, working_files, working_file, default_line, file_id, - *impl_file_id, func_def); - if (!edit) - return; - - ++num_edits; - - // Merge edits together if they are on the same line. - // TODO: be smarter about newline merging? ie, don't end up - // with foo()\n\n\n\nfoo(), we want foo()\n\nfoo()\n\n - // - if (!command.arguments.edits.empty() && - command.arguments.edits[command.arguments.edits.size() - 1] - .range.end.line == edit->range.start.line) { - command.arguments.edits[command.arguments.edits.size() - 1] - .newText += edit->newText; - } else { - command.arguments.edits.push_back(*edit); - } - }); - if (command.arguments.edits.empty()) - break; - - // If we're inserting at the end of the document, put a newline - // before the insertion. - if (command.arguments.edits[0].range.start.line >= default_line) - command.arguments.edits[0].newText.insert(0, "\n"); - - command.arguments.textDocumentUri = *impl_uri; - command.title = "Auto-Implement " + std::to_string(num_edits) + - " methods on " + std::string(def->ShortName()); - command.command = "cquery._autoImplement"; - out.result.push_back(command); - break; - } - - case SymbolKind::Func: { - QueryFunc& func = db->GetFunc(sym); - const QueryFunc::Def* def = func.AnyDef(); - if (!def || def->extent) - break; - - EnsureImplFile(db, file_id, impl_uri /*out*/, impl_file_id /*out*/); - - // Get implementation file. - Out_TextDocumentCodeAction::Command command; - command.title = "Auto-Implement " + std::string(def->ShortName()); - command.command = "cquery._autoImplement"; - command.arguments.textDocumentUri = *impl_uri; - optional edit = BuildAutoImplementForFunction( - db, working_files, working_file, default_line, file_id, - *impl_file_id, func); - if (!edit) - break; - - // If we're inserting at the end of the document, put a newline - // before the insertion. - if (edit->range.start.line >= default_line) - edit->newText.insert(0, "\n"); - command.arguments.edits.push_back(*edit); - out.result.push_back(command); - break; - } - default: - break; - } - - // Only show one auto-impl section. - if (!out.result.empty()) - break; - } - - std::vector diagnostics; - working_files->DoAction( - [&]() { diagnostics = working_file->diagnostics_; }); - for (lsDiagnostic& diag : diagnostics) { - if (diag.range.start.line != request->params.range.start.line) - continue; - - // For error diagnostics, provide an action to resolve an include. - // TODO: find a way to index diagnostic contents so line numbers - // don't get mismatched when actively editing a file. - std::string_view include_query = - LexIdentifierAroundPos(diag.range.start, working_file->buffer_content); - if (diag.severity == lsDiagnosticSeverity::Error && - !include_query.empty()) { - const size_t kMaxResults = 20; - - std::unordered_set include_absolute_paths; - - // Find include candidate strings. - for (size_t i = 0; i < db->symbols.size(); ++i) { - if (include_absolute_paths.size() > kMaxResults) - break; - if (db->GetSymbolDetailedName(i).find(include_query) == - std::string::npos) - continue; - - Maybe decl_file_id = - GetDeclarationFileForSymbol(db, db->symbols[i]); - if (!decl_file_id) - continue; - - QueryFile& decl_file = db->files[decl_file_id->id]; - if (!decl_file.def) - continue; - - include_absolute_paths.insert(decl_file.def->path); - } - - // Build include strings. - std::unordered_set include_insert_strings; - include_insert_strings.reserve(include_absolute_paths.size()); - - for (const std::string& path : include_absolute_paths) { - optional item = - include_complete->FindCompletionItemForAbsolutePath(path); - if (!item) - continue; - if (item->textEdit) - include_insert_strings.insert(item->textEdit->newText); - else if (!item->insertText.empty()) - include_insert_strings.insert(item->insertText); - else { - // FIXME https://github.com/cquery-project/cquery/issues/463 - LOG_S(WARNING) << "unable to determine insert string for include " - "completion item"; - } - } - - // Build code action. - if (!include_insert_strings.empty()) { - Out_TextDocumentCodeAction::Command command; - - // Build edits. - for (const std::string& include_insert_string : - include_insert_strings) { - lsTextEdit edit; - optional include_line = FindIncludeLine( - working_file->buffer_lines, include_insert_string); - if (!include_line) - continue; - - edit.range.start.line = *include_line; - edit.range.end.line = *include_line; - edit.newText = include_insert_string + "\n"; - command.arguments.edits.push_back(edit); - } - - // Setup metadata and send to client. - if (include_insert_strings.size() == 1) - command.title = "Insert " + *include_insert_strings.begin(); - else - command.title = "Pick one of " + - std::to_string(command.arguments.edits.size()) + - " includes to insert"; - command.command = "cquery._insertInclude"; - command.arguments.textDocumentUri = request->params.textDocument.uri; - out.result.push_back(command); - } - } - - // clang does not provide accurate enough column reporting for - // diagnostics to do good column filtering, so report all - // diagnostics on the line. - if (!diag.fixits_.empty()) { - Out_TextDocumentCodeAction::Command command; - command.title = "FixIt: " + diag.message; - command.command = "cquery._applyFixIt"; - command.arguments.textDocumentUri = request->params.textDocument.uri; - command.arguments.edits = diag.fixits_; - out.result.push_back(command); - } - } - - QueueManager::WriteStdout(IpcId::TextDocumentCodeAction, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentCodeActionHandler); - -TEST_SUITE("FindIncludeLine") { - TEST_CASE("in document") { - std::vector lines = { - "#include ", // 0 - "#include " // 1 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == nullopt); - } - - TEST_CASE("insert before") { - std::vector lines = { - "#include ", // 0 - "#include " // 1 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 0); - } - - TEST_CASE("insert middle") { - std::vector lines = { - "#include ", // 0 - "#include " // 1 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 1); - } - - TEST_CASE("insert after") { - std::vector lines = { - "#include ", // 0 - "#include ", // 1 - "", // 2 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 2); - } - - TEST_CASE("ignore header") { - std::vector lines = { - "// FOOBAR", // 0 - "// FOOBAR", // 1 - "// FOOBAR", // 2 - "// FOOBAR", // 3 - "", // 4 - "#include ", // 5 - "#include ", // 6 - "", // 7 - }; - - REQUIRE(FindIncludeLine(lines, "#include ") == 5); - REQUIRE(FindIncludeLine(lines, "#include ") == 6); - REQUIRE(FindIncludeLine(lines, "#include ") == 7); - } -} -} // namespace diff --git a/src/messages/text_document_code_lens.cc b/src/messages/text_document_code_lens.cc deleted file mode 100644 index 792c84264..000000000 --- a/src/messages/text_document_code_lens.cc +++ /dev/null @@ -1,229 +0,0 @@ -#include "clang_complete.h" -#include "lsp_code_action.h" -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct lsDocumentCodeLensParams { - lsTextDocumentIdentifier textDocument; -}; -MAKE_REFLECT_STRUCT(lsDocumentCodeLensParams, textDocument); - -using TCodeLens = lsCodeLens; -struct Ipc_TextDocumentCodeLens - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentCodeLens; - lsDocumentCodeLensParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentCodeLens, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentCodeLens); - -struct Out_TextDocumentCodeLens - : public lsOutMessage { - lsRequestId id; - std::vector> - result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentCodeLens, jsonrpc, id, result); - -struct CommonCodeLensParams { - std::vector* result; - QueryDatabase* db; - WorkingFiles* working_files; - WorkingFile* working_file; -}; - -Use OffsetStartColumn(Use use, int16_t offset) { - use.range.start.column += offset; - return use; -} - -void AddCodeLens(const char* singular, - const char* plural, - CommonCodeLensParams* common, - Use use, - const std::vector& uses, - bool force_display) { - TCodeLens code_lens; - optional range = GetLsRange(common->working_file, use.range); - if (!range) - return; - if (use.file == QueryFileId()) - return; - code_lens.range = *range; - code_lens.command = lsCommand(); - code_lens.command->command = "cquery.showReferences"; - code_lens.command->arguments.uri = GetLsDocumentUri(common->db, use.file); - code_lens.command->arguments.position = code_lens.range.start; - - // Add unique uses. - std::unordered_set unique_uses; - for (Use use1 : uses) { - optional location = - GetLsLocation(common->db, common->working_files, use1); - if (!location) - continue; - unique_uses.insert(*location); - } - code_lens.command->arguments.locations.assign(unique_uses.begin(), - unique_uses.end()); - - // User visible label - size_t num_usages = unique_uses.size(); - code_lens.command->title = std::to_string(num_usages) + " "; - if (num_usages == 1) - code_lens.command->title += singular; - else - code_lens.command->title += plural; - - if (force_display || unique_uses.size() > 0) - common->result->push_back(code_lens); -} - -struct TextDocumentCodeLensHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentCodeLens* request) override { - Out_TextDocumentCodeLens out; - out.id = request->id; - - lsDocumentUri file_as_uri = request->params.textDocument.uri; - std::string path = file_as_uri.GetPath(); - - clang_complete->NotifyView(path); - - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - CommonCodeLensParams common; - common.result = &out.result; - common.db = db; - common.working_files = working_files; - common.working_file = working_files->GetFileByFilename(file->def->path); - - for (SymbolRef sym : file->def->outline) { - // NOTE: We OffsetColumn so that the code lens always show up in a - // predictable order. Otherwise, the client may randomize it. - Use use(sym.range, sym.id, sym.kind, sym.role, file->def->file); - - switch (sym.kind) { - case SymbolKind::Type: { - QueryType& type = db->GetType(sym); - const QueryType::Def* def = type.AnyDef(); - if (!def || def->kind == lsSymbolKind::Namespace) - continue; - AddCodeLens("ref", "refs", &common, OffsetStartColumn(use, 0), - type.uses, true /*force_display*/); - AddCodeLens("derived", "derived", &common, OffsetStartColumn(use, 1), - GetDeclarations(db, type.derived), false /*force_display*/); - AddCodeLens("var", "vars", &common, OffsetStartColumn(use, 2), - GetDeclarations(db, type.instances), false /*force_display*/); - break; - } - case SymbolKind::Func: { - QueryFunc& func = db->GetFunc(sym); - const QueryFunc::Def* def = func.AnyDef(); - if (!def) - continue; - - int16_t offset = 0; - - // For functions, the outline will report a location that is using the - // extent since that is better for outline. This tries to convert the - // extent location to the spelling location. - auto try_ensure_spelling = [&](Use use) { - Maybe def = GetDefinitionSpell(db, use); - if (!def || def->range.start.line != use.range.start.line) { - return use; - } - return *def; - }; - - std::vector base_callers = GetUsesForAllBases(db, func); - std::vector derived_callers = GetUsesForAllDerived(db, func); - if (base_callers.empty() && derived_callers.empty()) { - Use loc = try_ensure_spelling(use); - AddCodeLens("call", "calls", &common, - OffsetStartColumn(loc, offset++), func.uses, - true /*force_display*/); - } else { - Use loc = try_ensure_spelling(use); - AddCodeLens("direct call", "direct calls", &common, - OffsetStartColumn(loc, offset++), func.uses, - false /*force_display*/); - if (!base_callers.empty()) - AddCodeLens("base call", "base calls", &common, - OffsetStartColumn(loc, offset++), base_callers, - false /*force_display*/); - if (!derived_callers.empty()) - AddCodeLens("derived call", "derived calls", &common, - OffsetStartColumn(loc, offset++), derived_callers, - false /*force_display*/); - } - - AddCodeLens("derived", "derived", &common, - OffsetStartColumn(use, offset++), - GetDeclarations(db, func.derived), false /*force_display*/); - - // "Base" - if (def->bases.size() == 1) { - Maybe base_loc = GetDefinitionSpell( - db, SymbolIdx{def->bases[0], SymbolKind::Func}); - if (base_loc) { - optional ls_base = - GetLsLocation(db, working_files, *base_loc); - if (ls_base) { - optional range = - GetLsRange(common.working_file, sym.range); - if (range) { - TCodeLens code_lens; - code_lens.range = *range; - code_lens.range.start.character += offset++; - code_lens.command = lsCommand(); - code_lens.command->title = "Base"; - code_lens.command->command = "cquery.goto"; - code_lens.command->arguments.uri = ls_base->uri; - code_lens.command->arguments.position = ls_base->range.start; - out.result.push_back(code_lens); - } - } - } - } else { - AddCodeLens("base", "base", &common, OffsetStartColumn(use, 1), - GetDeclarations(db, def->bases), false /*force_display*/); - } - - break; - } - case SymbolKind::Var: { - QueryVar& var = db->GetVar(sym); - const QueryVar::Def* def = var.AnyDef(); - if (!def || (def->is_local() && !config->codeLens.localVariables)) - continue; - - bool force_display = true; - // Do not show 0 refs on macro with no uses, as it is most likely - // a header guard. - if (def->kind == lsSymbolKind::Macro) - force_display = false; - - AddCodeLens("ref", "refs", &common, OffsetStartColumn(use, 0), - var.uses, force_display); - break; - } - case SymbolKind::File: - case SymbolKind::Invalid: { - assert(false && "unexpected"); - break; - } - }; - } - - QueueManager::WriteStdout(IpcId::TextDocumentCodeLens, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentCodeLensHandler); -} // namespace diff --git a/src/messages/text_document_completion.cc b/src/messages/text_document_completion.cc deleted file mode 100644 index ba1a305d6..000000000 --- a/src/messages/text_document_completion.cc +++ /dev/null @@ -1,424 +0,0 @@ -#include "clang_complete.h" -#include "code_complete_cache.h" -#include "include_complete.h" -#include "message_handler.h" -#include "queue_manager.h" -#include "timer.h" -#include "working_files.h" - -#include "lex_utils.h" - -#include - -#include - -namespace { - -// How a completion was triggered -enum class lsCompletionTriggerKind { - // Completion was triggered by typing an identifier (24x7 code - // complete), manual invocation (e.g Ctrl+Space) or via API. - Invoked = 1, - - // Completion was triggered by a trigger character specified by - // the `triggerCharacters` properties of the `CompletionRegistrationOptions`. - TriggerCharacter = 2 -}; -MAKE_REFLECT_TYPE_PROXY(lsCompletionTriggerKind); - -// Contains additional information about the context in which a completion -// request is triggered. -struct lsCompletionContext { - // How the completion was triggered. - lsCompletionTriggerKind triggerKind; - - // The trigger character (a single character) that has trigger code complete. - // Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` - optional triggerCharacter; -}; -MAKE_REFLECT_STRUCT(lsCompletionContext, triggerKind, triggerCharacter); - -struct lsCompletionParams : lsTextDocumentPositionParams { - // The completion context. This is only available it the client specifies to - // send this using - // `ClientCapabilities.textDocument.completion.contextSupport === true` - optional context; -}; -MAKE_REFLECT_STRUCT(lsCompletionParams, textDocument, position, context); - -struct Ipc_TextDocumentComplete - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentCompletion; - lsCompletionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentComplete, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentComplete); - -struct lsTextDocumentCompleteResult { - // This list it not complete. Further typing should result in recomputing - // this list. - bool isIncomplete = false; - // The completion items. - std::vector items; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentCompleteResult, isIncomplete, items); - -struct Out_TextDocumentComplete - : public lsOutMessage { - lsRequestId id; - lsTextDocumentCompleteResult result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentComplete, jsonrpc, id, result); - -bool CompareLsCompletionItem(const lsCompletionItem& lhs, - const lsCompletionItem& rhs) { - if (lhs.found_ != rhs.found_) - return !lhs.found_ < !rhs.found_; - if (lhs.skip_ != rhs.skip_) - return lhs.skip_ < rhs.skip_; - if (lhs.priority_ != rhs.priority_) - return lhs.priority_ < rhs.priority_; - if (lhs.filterText->length() != rhs.filterText->length()) - return lhs.filterText->length() < rhs.filterText->length(); - return *lhs.filterText < *rhs.filterText; -} - -void DecorateIncludePaths(const std::smatch& match, - std::vector* items) { - std::string spaces_after_include = " "; - if (match[3].compare("include") == 0 && match[5].length()) - spaces_after_include = match[4].str(); - - std::string prefix = - match[1].str() + '#' + match[2].str() + "include" + spaces_after_include; - std::string suffix = match[7].str(); - - for (lsCompletionItem& item : *items) { - char quote0, quote1; - if (match[5].compare("<") == 0 || - (match[5].length() == 0 && item.use_angle_brackets_)) - quote0 = '<', quote1 = '>'; - else - quote0 = quote1 = '"'; - - item.textEdit->newText = - prefix + quote0 + item.textEdit->newText + quote1 + suffix; - item.label = prefix + quote0 + item.label + quote1 + suffix; - item.filterText = nullopt; - } -} - -struct ParseIncludeLineResult { - bool ok; - std::string text; // include the "include" part - std::smatch match; -}; - -ParseIncludeLineResult ParseIncludeLine(const std::string& line) { - static const std::regex pattern( - "(\\s*)" // [1]: spaces before '#' - "#" // - "(\\s*)" // [2]: spaces after '#' - "([^\\s\"<]*)" // [3]: "include" - "(\\s*)" // [4]: spaces before quote - "([\"<])?" // [5]: the first quote char - "([^\\s\">]*)" // [6]: path of file - "[\">]?" // - "(.*)"); // [7]: suffix after quote char - std::smatch match; - bool ok = std::regex_match(line, match, pattern); - std::string text = match[3].str() + match[6].str(); - return {ok, text, match}; -} - -template -char* tofixedbase64(T input, char* out) { - const char* digits = - "./0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - int len = (sizeof(T) * 8 - 1) / 6 + 1; - for (int i = len - 1; i >= 0; i--) { - out[i] = digits[input % 64]; - input /= 64; - } - out[len] = '\0'; - return out; -} - -// Pre-filters completion responses before sending to vscode. This results in a -// significantly snappier completion experience as vscode is easily overloaded -// when given 1000+ completion items. -void FilterAndSortCompletionResponse( - Out_TextDocumentComplete* complete_response, - const std::string& complete_text, - bool enable) { - if (!enable) - return; - - ScopedPerfTimer timer("FilterAndSortCompletionResponse"); - -// Used to inject more completions. -#if false - const size_t kNumIterations = 250; - size_t size = complete_response->result.items.size(); - complete_response->result.items.reserve(size * (kNumIterations + 1)); - for (size_t iteration = 0; iteration < kNumIterations; ++iteration) { - for (size_t i = 0; i < size; ++i) { - auto item = complete_response->result.items[i]; - item.label += "#" + std::to_string(iteration); - complete_response->result.items.push_back(item); - } - } -#endif - - auto& items = complete_response->result.items; - - auto finalize = [&]() { - const size_t kMaxResultSize = 100u; - if (items.size() > kMaxResultSize) { - items.resize(kMaxResultSize); - complete_response->result.isIncomplete = true; - } - - // Set sortText. Note that this happens after resizing - we could do it - // before, but then we should also sort by priority. - char buf[16]; - for (size_t i = 0; i < items.size(); ++i) - items[i].sortText = tofixedbase64(i, buf); - }; - - // No complete text; don't run any filtering logic except to trim the items. - if (complete_text.empty()) { - finalize(); - return; - } - - // Make sure all items have |filterText| set, code that follow needs it. - for (auto& item : items) { - if (!item.filterText) - item.filterText = item.label; - } - - // If the text doesn't start with underscore, remove all candidates that - // start with underscore. - if (complete_text[0] != '_') { - auto filter = [](const lsCompletionItem& item) { - return (*item.filterText)[0] == '_'; - }; - items.erase(std::remove_if(items.begin(), items.end(), filter), - items.end()); - } - - // Fuzzy match. Remove any candidates that do not match. - bool found = false; - for (auto& item : items) { - std::tie(item.found_, item.skip_) = - SubsequenceCountSkip(complete_text, *item.filterText); - found = found || item.found_; - } - if (found) { - auto filter = [](const lsCompletionItem& item) { return !item.found_; }; - items.erase(std::remove_if(items.begin(), items.end(), filter), - items.end()); - - // Order all items and set |sortText|. - const size_t kMaxSortSize = 200u; - if (items.size() <= kMaxSortSize) { - std::sort(items.begin(), items.end(), CompareLsCompletionItem); - } else { - // Just place items that found the text before those not. - std::vector items_found, items_notfound; - for (auto& item : items) - (item.found_ ? items_found : items_notfound).push_back(item); - items = items_found; - items.insert(items.end(), items_notfound.begin(), items_notfound.end()); - } - } - - // Trim result. - finalize(); -} - -struct TextDocumentCompletionHandler : MessageHandler { - IpcId GetId() const override { return IpcId::TextDocumentCompletion; } - - void Run(std::unique_ptr message) override { - auto request = std::shared_ptr( - static_cast(message.release())); - - auto write_empty_result = [request]() { - Out_TextDocumentComplete out; - out.id = request->id; - QueueManager::WriteStdout(IpcId::TextDocumentCompletion, out); - }; - - std::string path = request->params.textDocument.uri.GetPath(); - WorkingFile* file = working_files->GetFileByFilename(path); - if (!file) { - write_empty_result(); - return; - } - - // It shouldn't be possible, but sometimes vscode will send queries out - // of order, ie, we get completion request before buffer content update. - std::string buffer_line; - if (request->params.position.line >= 0 && - request->params.position.line < file->buffer_lines.size()) { - buffer_line = file->buffer_lines[request->params.position.line]; - } - - // Check for - and : before completing -> or ::, since vscode does not - // support multi-character trigger characters. - if (request->params.context && - request->params.context->triggerKind == - lsCompletionTriggerKind::TriggerCharacter && - request->params.context->triggerCharacter) { - bool did_fail_check = false; - - std::string character = *request->params.context->triggerCharacter; - int preceding_index = request->params.position.character - 2; - - // If the character is '"', '<' or '/', make sure that the line starts - // with '#'. - if (character == "\"" || character == "<" || character == "/") { - size_t i = 0; - while (i < buffer_line.size() && isspace(buffer_line[i])) - ++i; - if (i >= buffer_line.size() || buffer_line[i] != '#') - did_fail_check = true; - } - // If the character is > or : and we are at the start of the line, do not - // show completion results. - else if ((character == ">" || character == ":") && preceding_index < 0) { - did_fail_check = true; - } - // If the character is > but - does not preced it, or if it is : and : - // does not preced it, do not show completion results. - else if (preceding_index >= 0 && - preceding_index < (int)buffer_line.size()) { - char preceding = buffer_line[preceding_index]; - did_fail_check = (preceding != '-' && character == ">") || - (preceding != ':' && character == ":"); - } - - if (did_fail_check) { - write_empty_result(); - return; - } - } - - bool is_global_completion = false; - std::string existing_completion; - if (file) { - request->params.position = file->FindStableCompletionSource( - request->params.position, &is_global_completion, - &existing_completion); - } - - ParseIncludeLineResult result = ParseIncludeLine(buffer_line); - - if (result.ok) { - Out_TextDocumentComplete out; - out.id = request->id; - - { - std::unique_lock lock( - include_complete->completion_items_mutex, std::defer_lock); - if (include_complete->is_scanning) - lock.lock(); - out.result.items.assign(include_complete->completion_items.begin(), - include_complete->completion_items.end()); - if (lock) - lock.unlock(); - } - - // Needed by |FilterAndSortCompletionResponse|. - for (lsCompletionItem& item : out.result.items) - item.filterText = "include" + item.label; - - FilterAndSortCompletionResponse(&out, result.text, - config->completion.filterAndSort); - DecorateIncludePaths(result.match, &out.result.items); - - for (lsCompletionItem& item : out.result.items) { - item.textEdit->range.start.line = request->params.position.line; - item.textEdit->range.start.character = 0; - item.textEdit->range.end.line = request->params.position.line; - item.textEdit->range.end.character = (int)buffer_line.size(); - } - - QueueManager::WriteStdout(IpcId::TextDocumentCompletion, out); - } else { - ClangCompleteManager::OnComplete callback = std::bind( - [this, is_global_completion, existing_completion, request]( - const std::vector& results, - bool is_cached_result) { - Out_TextDocumentComplete out; - out.id = request->id; - out.result.items = results; - - // Emit completion results. - FilterAndSortCompletionResponse(&out, existing_completion, - config->completion.filterAndSort); - QueueManager::WriteStdout(IpcId::TextDocumentCompletion, out); - - // Cache completion results. - if (!is_cached_result) { - std::string path = request->params.textDocument.uri.GetPath(); - if (is_global_completion) { - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_path_ = path; - global_code_complete_cache->cached_results_ = results; - }); - } else { - non_global_code_complete_cache->WithLock([&]() { - non_global_code_complete_cache->cached_path_ = path; - non_global_code_complete_cache->cached_completion_position_ = - request->params.position; - non_global_code_complete_cache->cached_results_ = results; - }); - } - } - }, - std::placeholders::_1, std::placeholders::_2); - - bool is_cache_match = false; - global_code_complete_cache->WithLock([&]() { - is_cache_match = is_global_completion && - global_code_complete_cache->cached_path_ == path && - !global_code_complete_cache->cached_results_.empty(); - }); - if (is_cache_match) { - ClangCompleteManager::OnComplete freshen_global = - [this](std::vector results, - bool is_cached_result) { - assert(!is_cached_result); - - // note: path is updated in the normal completion handler. - global_code_complete_cache->WithLock([&]() { - global_code_complete_cache->cached_results_ = results; - }); - }; - - global_code_complete_cache->WithLock([&]() { - callback(global_code_complete_cache->cached_results_, - true /*is_cached_result*/); - }); - clang_complete->CodeComplete(request->id, request->params, - freshen_global); - } else if (non_global_code_complete_cache->IsCacheValid( - request->params)) { - non_global_code_complete_cache->WithLock([&]() { - callback(non_global_code_complete_cache->cached_results_, - true /*is_cached_result*/); - }); - } else { - clang_complete->CodeComplete(request->id, request->params, callback); - } - } - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentCompletionHandler); - -} // namespace diff --git a/src/messages/text_document_definition.cc b/src/messages/text_document_definition.cc deleted file mode 100644 index 62f365856..000000000 --- a/src/messages/text_document_definition.cc +++ /dev/null @@ -1,173 +0,0 @@ -#include "lex_utils.h" -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -#include -#include -#include - -namespace { - -struct Ipc_TextDocumentDefinition - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentDefinition; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDefinition, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDefinition); - -struct Out_TextDocumentDefinition - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentDefinition, jsonrpc, id, result); - -std::vector GetNonDefDeclarationTargets(QueryDatabase* db, SymbolRef sym) { - switch (sym.kind) { - case SymbolKind::Var: { - std::vector ret = GetNonDefDeclarations(db, sym); - // If there is no declaration, jump the its type. - if (ret.empty()) { - for (auto& def : db->GetVar(sym).def) - if (def.type) { - if (Maybe use = GetDefinitionSpell( - db, SymbolIdx{*def.type, SymbolKind::Type})) { - ret.push_back(*use); - break; - } - } - } - return ret; - } - default: - return GetNonDefDeclarations(db, sym); - } -} - -struct TextDocumentDefinitionHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDefinition* request) override { - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file, - &file_id)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentDefinition out; - out.id = request->id; - - Maybe on_def; - bool has_symbol = false; - int target_line = request->params.position.line; - int target_column = request->params.position.character; - - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - // Found symbol. Return definition. - has_symbol = true; - - // Special cases which are handled: - // - symbol has declaration but no definition (ie, pure virtual) - // - start at spelling but end at extent for better mouse tooltip - // - goto declaration while in definition of recursive type - std::vector uses; - EachEntityDef(db, sym, [&](const auto& def) { - if (def.spell && def.extent) { - Use spell = *def.spell; - // If on a definition, clear |uses| to find declarations below. - if (spell.file == file_id && - spell.range.Contains(target_line, target_column)) { - on_def = spell; - uses.clear(); - return false; - } - // We use spelling start and extent end because this causes vscode - // to highlight the entire definition when previewing / hoving with - // the mouse. - spell.range.end = def.extent->range.end; - uses.push_back(spell); - } - return true; - }); - - if (uses.empty()) { - // The symbol has no definition or the cursor is on a definition. - uses = GetNonDefDeclarationTargets(db, sym); - // There is no declaration but the cursor is on a definition. - if (uses.empty() && on_def) - uses.push_back(*on_def); - } - AddRange(&out.result, - GetLsLocationExs(db, working_files, uses, config->xref.container, - config->xref.maxNum)); - if (!out.result.empty()) - break; - } - - // No symbols - check for includes. - if (out.result.empty()) { - for (const IndexInclude& include : file->def->includes) { - if (include.line == target_line) { - lsLocationEx result; - result.uri = lsDocumentUri::FromPath(include.resolved_path); - out.result.push_back(result); - has_symbol = true; - break; - } - } - // Find the best match of the identifier at point. - if (!has_symbol) { - lsPosition position = request->params.position; - const std::string& buffer = working_file->buffer_content; - std::string_view query = LexIdentifierAroundPos(position, buffer); - bool has_scope = query.find(':') != std::string::npos; - - // For symbols whose short/detailed names contain |query| as a - // substring, we use the tuple to find the best match. - std::tuple best_score{INT_MAX, 0, true, 0}; - int best_i = -1; - for (int i = 0; i < (int)db->symbols.size(); ++i) { - if (db->symbols[i].kind == SymbolKind::Invalid) - continue; - - std::string_view name = has_scope ? db->GetSymbolDetailedName(i) - : db->GetSymbolShortName(i); - auto pos = name.find(query); - if (pos == std::string::npos) - continue; - Maybe use = GetDefinitionSpell(db, db->symbols[i]); - if (!use) - continue; - - std::tuple score{ - int(name.size() - query.size()), int(pos), use->file != file_id, - std::abs(use->range.start.line - position.line)}; - if (score < best_score) { - best_score = score; - best_i = i; - } - } - if (best_i != -1) { - Maybe use = - GetDefinitionSpell(db, db->symbols[best_i]); - assert(use); - if (auto ls_loc = GetLsLocationEx(db, working_files, *use, - config->xref.container)) - out.result.push_back(*ls_loc); - } - } - } - - QueueManager::WriteStdout(IpcId::TextDocumentDefinition, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDefinitionHandler); -} // namespace diff --git a/src/messages/text_document_did_change.cc b/src/messages/text_document_did_change.cc deleted file mode 100644 index 90e6dc9cc..000000000 --- a/src/messages/text_document_did_change.cc +++ /dev/null @@ -1,27 +0,0 @@ -#include "clang_complete.h" -#include "message_handler.h" -#include "working_files.h" - -namespace { -struct Ipc_TextDocumentDidChange - : public NotificationMessage { - const static IpcId kIpcId = IpcId::TextDocumentDidChange; - lsTextDocumentDidChangeParams params; -}; - -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidChange, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDidChange); - -struct TextDocumentDidChangeHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDidChange* request) override { - std::string path = request->params.textDocument.uri.GetPath(); - working_files->OnChange(request->params); - clang_complete->NotifyEdit(path); - clang_complete->DiagnosticsUpdate( - std::monostate(), - request->params.textDocument.AsTextDocumentIdentifier()); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDidChangeHandler); -} // namespace diff --git a/src/messages/text_document_did_close.cc b/src/messages/text_document_did_close.cc deleted file mode 100644 index d83f0fa66..000000000 --- a/src/messages/text_document_did_close.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include "clang_complete.h" -#include "message_handler.h" -#include "queue_manager.h" -#include "working_files.h" - -namespace { -struct Ipc_TextDocumentDidClose - : public NotificationMessage { - const static IpcId kIpcId = IpcId::TextDocumentDidClose; - struct Params { - lsTextDocumentIdentifier textDocument; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidClose::Params, textDocument); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidClose, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDidClose); - -struct TextDocumentDidCloseHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDidClose* request) override { - std::string path = request->params.textDocument.uri.GetPath(); - - // Clear any diagnostics for the file. - Out_TextDocumentPublishDiagnostics out; - out.params.uri = request->params.textDocument.uri; - QueueManager::WriteStdout(IpcId::TextDocumentPublishDiagnostics, out); - - // Remove internal state. - working_files->OnClose(request->params.textDocument); - clang_complete->NotifyClose(path); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDidCloseHandler); -} // namespace diff --git a/src/messages/text_document_did_open.cc b/src/messages/text_document_did_open.cc deleted file mode 100644 index a16c0fb74..000000000 --- a/src/messages/text_document_did_open.cc +++ /dev/null @@ -1,66 +0,0 @@ -#include "cache_manager.h" -#include "clang_complete.h" -#include "include_complete.h" -#include "message_handler.h" -#include "project.h" -#include "queue_manager.h" -#include "timer.h" -#include "working_files.h" - -namespace { -// Open, view, change, close file -struct Ipc_TextDocumentDidOpen - : public NotificationMessage { - const static IpcId kIpcId = IpcId::TextDocumentDidOpen; - struct Params { - lsTextDocumentItem textDocument; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidOpen::Params, textDocument); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidOpen, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDidOpen); - -struct TextDocumentDidOpenHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDidOpen* request) override { - // NOTE: This function blocks code lens. If it starts taking a long time - // we will need to find a way to unblock the code lens request. - - Timer time; - std::string path = request->params.textDocument.uri.GetPath(); - if (ShouldIgnoreFileForIndexing(path)) - return; - - std::shared_ptr cache_manager = ICacheManager::Make(config); - WorkingFile* working_file = - working_files->OnOpen(request->params.textDocument); - optional cached_file_contents = - cache_manager->LoadCachedFileContents(path); - if (cached_file_contents) - working_file->SetIndexContent(*cached_file_contents); - - QueryFile* file = nullptr; - FindFileOrFail(db, project, nullopt, path, &file); - if (file && file->def) { - EmitInactiveLines(working_file, file->def->inactive_regions); - EmitSemanticHighlighting(db, semantic_cache, working_file, file); - } - - time.ResetAndPrint( - "[querydb] Loading cached index file for DidOpen (blocks " - "CodeLens)"); - - include_complete->AddFile(working_file->filename); - clang_complete->NotifyView(path); - - // Submit new index request. - const Project::Entry& entry = project->FindCompilationEntryForFile(path); - QueueManager::instance()->index_request.PushBack( - Index_Request(entry.filename, entry.args, true /*is_interactive*/, - request->params.textDocument.text, cache_manager), - true /* priority */); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDidOpenHandler); -} // namespace diff --git a/src/messages/text_document_did_save.cc b/src/messages/text_document_did_save.cc deleted file mode 100644 index f76292870..000000000 --- a/src/messages/text_document_did_save.cc +++ /dev/null @@ -1,62 +0,0 @@ -#include "cache_manager.h" -#include "clang_complete.h" -#include "message_handler.h" -#include "project.h" -#include "queue_manager.h" - -#include - -namespace { -struct Ipc_TextDocumentDidSave - : public NotificationMessage { - const static IpcId kIpcId = IpcId::TextDocumentDidSave; - struct Params { - // The document that was saved. - lsTextDocumentIdentifier textDocument; - - // Optional the content when saved. Depends on the includeText value - // when the save notifcation was requested. - // std::string text; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidSave::Params, textDocument); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDidSave, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDidSave); - -struct TextDocumentDidSaveHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDidSave* request) override { - std::string path = request->params.textDocument.uri.GetPath(); - if (ShouldIgnoreFileForIndexing(path)) - return; - - // Send out an index request, and copy the current buffer state so we - // can update the cached index contents when the index is done. - // - // We also do not index if there is already an index request. - // - // TODO: Cancel outgoing index request. Might be tricky to make - // efficient since we have to cancel. - // - we could have an |atomic active_cancellations| variable - // that all of the indexers check before accepting an index. if - // zero we don't slow down fast-path. if non-zero we acquire - // mutex and check to see if we should skip the current request. - // if so, ignore that index response. - // TODO: send as priority request - optional content = ReadContent(path); - if (!content) { - LOG_S(ERROR) << "Unable to read file content after saving " << path; - } else { - Project::Entry entry = project->FindCompilationEntryForFile(path); - QueueManager::instance()->index_request.PushBack( - Index_Request(entry.filename, entry.args, true /*is_interactive*/, - *content, ICacheManager::Make(config)), - true); - } - - clang_complete->NotifySave(path); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDidSaveHandler); -} // namespace diff --git a/src/messages/text_document_document_highlight.cc b/src/messages/text_document_document_highlight.cc deleted file mode 100644 index 1183e1d51..000000000 --- a/src/messages/text_document_document_highlight.cc +++ /dev/null @@ -1,66 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" -#include "symbol.h" - -namespace { -struct Ipc_TextDocumentDocumentHighlight - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentDocumentHighlight; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDocumentHighlight, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDocumentHighlight); - -struct Out_TextDocumentDocumentHighlight - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentDocumentHighlight, jsonrpc, id, result); - -struct TextDocumentDocumentHighlightHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDocumentHighlight* request) override { - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file, - &file_id)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentDocumentHighlight out; - out.id = request->id; - - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - // Found symbol. Return references to highlight. - EachOccurrence(db, sym, true, [&](Use use) { - if (use.file != file_id) - return; - if (optional ls_loc = - GetLsLocation(db, working_files, use)) { - lsDocumentHighlight highlight; - highlight.range = ls_loc->range; - if (use.role & Role::Write) - highlight.kind = lsDocumentHighlightKind::Write; - else if (use.role & Role::Read) - highlight.kind = lsDocumentHighlightKind::Read; - else - highlight.kind = lsDocumentHighlightKind::Text; - highlight.role = use.role; - out.result.push_back(highlight); - } - }); - break; - } - - QueueManager::WriteStdout(IpcId::TextDocumentDocumentHighlight, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDocumentHighlightHandler); -} // namespace diff --git a/src/messages/text_document_document_link.cc b/src/messages/text_document_document_link.cc deleted file mode 100644 index b68bfbcc9..000000000 --- a/src/messages/text_document_document_link.cc +++ /dev/null @@ -1,83 +0,0 @@ -#include "lex_utils.h" -#include "message_handler.h" -#include "queue_manager.h" -#include "working_files.h" - -#include - -namespace { -struct Ipc_TextDocumentDocumentLink - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentDocumentLink; - struct Params { - // The document to provide document links for. - lsTextDocumentIdentifier textDocument; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDocumentLink::Params, textDocument); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDocumentLink, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDocumentLink); - -// A document link is a range in a text document that links to an internal or -// external resource, like another text document or a web site. -struct lsDocumentLink { - // The range this link applies to. - lsRange range; - // The uri this link points to. If missing a resolve request is sent later. - optional target; -}; -MAKE_REFLECT_STRUCT(lsDocumentLink, range, target); - -struct Out_TextDocumentDocumentLink - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentDocumentLink, jsonrpc, id, result); - -struct TextDocumentDocumentLinkHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDocumentLink* request) override { - Out_TextDocumentDocumentLink out; - out.id = request->id; - - if (config->showDocumentLinksOnIncludes) { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = working_files->GetFileByFilename( - request->params.textDocument.uri.GetPath()); - if (!working_file) { - LOG_S(WARNING) << "Unable to find working file " - << request->params.textDocument.uri.GetPath(); - return; - } - for (const IndexInclude& include : file->def->includes) { - optional buffer_line = working_file->GetBufferPosFromIndexPos( - include.line, nullptr, false); - if (!buffer_line) - continue; - - // Subtract 1 from line because querydb stores 1-based lines but - // vscode expects 0-based lines. - optional between_quotes = ExtractQuotedRange( - *buffer_line, working_file->buffer_lines[*buffer_line]); - if (!between_quotes) - continue; - - lsDocumentLink link; - link.target = lsDocumentUri::FromPath(include.resolved_path); - link.range = *between_quotes; - out.result.push_back(link); - } - } - - QueueManager::WriteStdout(IpcId::TextDocumentDocumentLink, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDocumentLinkHandler); -} // namespace diff --git a/src/messages/text_document_document_symbol.cc b/src/messages/text_document_document_symbol.cc deleted file mode 100644 index b6631bd12..000000000 --- a/src/messages/text_document_document_symbol.cc +++ /dev/null @@ -1,68 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { -struct lsDocumentSymbolParams { - lsTextDocumentIdentifier textDocument; -}; -MAKE_REFLECT_STRUCT(lsDocumentSymbolParams, textDocument); - -struct Ipc_TextDocumentDocumentSymbol - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentDocumentSymbol; - lsDocumentSymbolParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentDocumentSymbol, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentDocumentSymbol); - -struct Out_TextDocumentDocumentSymbol - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentDocumentSymbol, jsonrpc, id, result); - -struct TextDocumentDocumentSymbolHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentDocumentSymbol* request) override { - Out_TextDocumentDocumentSymbol out; - out.id = request->id; - - QueryFile* file; - QueryFileId file_id; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file, - &file_id)) { - return; - } - - for (SymbolRef sym : file->def->outline) { - optional info = - GetSymbolInfo(db, working_files, sym, true /*use_short_name*/); - if (!info) - continue; - if (sym.kind == SymbolKind::Var) { - QueryVar& var = db->GetVar(sym); - auto* def = var.AnyDef(); - if (!def || !def->spell) continue; - // Ignore local variables. - if (def->spell->kind == SymbolKind::Func && - def->storage != StorageClass::Static && - def->storage != StorageClass::Extern) - continue; - } - - if (optional location = GetLsLocation( - db, working_files, - Use(sym.range, sym.id, sym.kind, sym.role, file_id))) { - info->location = *location; - out.result.push_back(*info); - } - } - - QueueManager::WriteStdout(IpcId::TextDocumentDocumentSymbol, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentDocumentSymbolHandler); -} // namespace diff --git a/src/messages/text_document_formatting.cc b/src/messages/text_document_formatting.cc deleted file mode 100644 index 08a8d1a2a..000000000 --- a/src/messages/text_document_formatting.cc +++ /dev/null @@ -1,61 +0,0 @@ -#include "clang_format.h" -#include "message_handler.h" -#include "queue_manager.h" -#include "working_files.h" - -#include - -namespace { - -struct Ipc_TextDocumentFormatting - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentFormatting; - struct Params { - lsTextDocumentIdentifier textDocument; - lsFormattingOptions options; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentFormatting::Params, textDocument, options); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentFormatting, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentFormatting); - -struct Out_TextDocumentFormatting - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentFormatting, jsonrpc, id, result); - -struct TextDocumentFormattingHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentFormatting* request) override { - Out_TextDocumentFormatting response; - response.id = request->id; -#if USE_CLANG_CXX - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - response.result = ConvertClangReplacementsIntoTextEdits( - working_file->buffer_content, - ClangFormatDocument(working_file, 0, - working_file->buffer_content.size(), - request->params.options)); -#else - LOG_S(WARNING) << "You must compile cquery with --use-clang-cxx to use " - "textDocument/formatting."; - // TODO: Fallback to execute the clang-format binary? - response.result = {}; -#endif - - QueueManager::WriteStdout(IpcId::TextDocumentFormatting, response); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentFormattingHandler); -} // namespace diff --git a/src/messages/text_document_hover.cc b/src/messages/text_document_hover.cc deleted file mode 100644 index 9af5f077e..000000000 --- a/src/messages/text_document_hover.cc +++ /dev/null @@ -1,118 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { - -std::pair GetCommentsAndHover( - QueryDatabase* db, - SymbolRef sym) { - switch (sym.kind) { - case SymbolKind::Type: { - if (const auto* def = db->GetType(sym).AnyDef()) { - return {def->comments, !def->hover.empty() - ? std::string_view(def->hover) - : std::string_view(def->detailed_name)}; - } - break; - } - case SymbolKind::Func: { - if (const auto* def = db->GetFunc(sym).AnyDef()) { - return {def->comments, !def->hover.empty() - ? std::string_view(def->hover) - : std::string_view(def->detailed_name)}; - } - break; - } - case SymbolKind::Var: { - if (const auto* def = db->GetVar(sym).AnyDef()) { - return {def->comments, !def->hover.empty() - ? std::string_view(def->hover) - : std::string_view(def->detailed_name)}; - } - break; - } - case SymbolKind::File: - case SymbolKind::Invalid: { - assert(false && "unexpected"); - break; - } - } - return {"", ""}; -} - -struct Ipc_TextDocumentHover : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentHover; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentHover, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentHover); - -struct Out_TextDocumentHover : public lsOutMessage { - struct Result { - std::vector contents; - optional range; - }; - - lsRequestId id; - optional result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentHover::Result, contents, range); -void Reflect(Writer& visitor, Out_TextDocumentHover& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(jsonrpc); - REFLECT_MEMBER(id); - if (value.result) - REFLECT_MEMBER(result); - else { - // Empty optional<> is elided by the default serializer, we need to write - // |null| to be compliant with the LSP. - visitor.Key("result"); - visitor.Null(); - } - REFLECT_MEMBER_END(); -} - -struct TextDocumentHoverHandler : BaseMessageHandler { - void Run(Ipc_TextDocumentHover* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentHover out; - out.id = request->id; - - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - // Found symbol. Return hover. - optional ls_range = GetLsRange( - working_files->GetFileByFilename(file->def->path), sym.range); - if (!ls_range) - continue; - - std::pair comments_hover = - GetCommentsAndHover(db, sym); - if (comments_hover.first.size() || comments_hover.second.size()) { - out.result = Out_TextDocumentHover::Result(); - if (comments_hover.first.size()) { - out.result->contents.emplace_back(comments_hover.first); - } - if (comments_hover.second.size()) { - out.result->contents.emplace_back(lsMarkedString1{ - std::string_view(file->def->language), comments_hover.second}); - } - out.result->range = *ls_range; - break; - } - } - - QueueManager::WriteStdout(IpcId::TextDocumentHover, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentHoverHandler); -} // namespace diff --git a/src/messages/text_document_range_formatting.cc b/src/messages/text_document_range_formatting.cc deleted file mode 100644 index 93608905f..000000000 --- a/src/messages/text_document_range_formatting.cc +++ /dev/null @@ -1,69 +0,0 @@ -#include "clang_format.h" -#include "lex_utils.h" -#include "message_handler.h" -#include "queue_manager.h" -#include "working_files.h" - -#include - -namespace { - -struct lsTextDocumentRangeFormattingParams { - lsTextDocumentIdentifier textDocument; - lsRange range; - lsFormattingOptions options; -}; -MAKE_REFLECT_STRUCT(lsTextDocumentRangeFormattingParams, - textDocument, - range, - options); - -struct Ipc_TextDocumentRangeFormatting - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentRangeFormatting; - lsTextDocumentRangeFormattingParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentRangeFormatting, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentRangeFormatting); - -struct Out_TextDocumentRangeFormatting - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentRangeFormatting, jsonrpc, id, result); - -struct TextDocumentRangeFormattingHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentRangeFormatting* request) override { - Out_TextDocumentRangeFormatting response; - response.id = request->id; -#if USE_CLANG_CXX - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - int start = GetOffsetForPosition(request->params.range.start, - working_file->buffer_content), - end = GetOffsetForPosition(request->params.range.end, - working_file->buffer_content); - response.result = ConvertClangReplacementsIntoTextEdits( - working_file->buffer_content, - ClangFormatDocument(working_file, start, end, request->params.options)); -#else - LOG_S(WARNING) << "You must compile cquery with --use-clang-cxx to use " - "textDocument/rangeFormatting."; - // TODO: Fallback to execute the clang-format binary? - response.result = {}; -#endif - - QueueManager::WriteStdout(IpcId::TextDocumentRangeFormatting, response); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentRangeFormattingHandler); -} // namespace diff --git a/src/messages/text_document_references.cc b/src/messages/text_document_references.cc deleted file mode 100644 index f905dcb2c..000000000 --- a/src/messages/text_document_references.cc +++ /dev/null @@ -1,100 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -#include - -namespace { -struct Ipc_TextDocumentReferences - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentReferences; - struct lsReferenceContext { - // Include the declaration of the current symbol. - bool includeDeclaration; - // Include references with these |Role| bits set. - Role role = Role::All; - }; - struct Params { - lsTextDocumentIdentifier textDocument; - lsPosition position; - lsReferenceContext context; - }; - - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentReferences::lsReferenceContext, - includeDeclaration, - role); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentReferences::Params, - textDocument, - position, - context); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentReferences, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentReferences); - -struct Out_TextDocumentReferences - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentReferences, jsonrpc, id, result); - -struct TextDocumentReferencesHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentReferences* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentReferences out; - out.id = request->id; - bool container = config->xref.container; - - for (const SymbolRef& sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - // Found symbol. Return references. - EachOccurrenceWithParent( - db, sym, request->params.context.includeDeclaration, - [&](Use use, lsSymbolKind parent_kind) { - if (use.role & request->params.context.role) - if (optional ls_loc = - GetLsLocationEx(db, working_files, use, container)) { - if (container) - ls_loc->parentKind = parent_kind; - out.result.push_back(*ls_loc); - } - }); - break; - } - - if (out.result.empty()) - for (const IndexInclude& include : file->def->includes) - if (include.line == request->params.position.line) { - // |include| is the line the cursor is on. - for (QueryFile& file1 : db->files) - if (file1.def) - for (const IndexInclude& include1 : file1.def->includes) - if (include1.resolved_path == include.resolved_path) { - // Another file |file1| has the same include line. - lsLocationEx result; - result.uri = lsDocumentUri::FromPath(file1.def->path); - result.range.start.line = result.range.end.line = - include1.line; - out.result.push_back(std::move(result)); - break; - } - break; - } - - if ((int)out.result.size() >= config->xref.maxNum) - out.result.resize(config->xref.maxNum); - QueueManager::WriteStdout(IpcId::TextDocumentReferences, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentReferencesHandler); -} // namespace diff --git a/src/messages/text_document_rename.cc b/src/messages/text_document_rename.cc deleted file mode 100644 index 719eddfe2..000000000 --- a/src/messages/text_document_rename.cc +++ /dev/null @@ -1,107 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { - -lsWorkspaceEdit BuildWorkspaceEdit(QueryDatabase* db, - WorkingFiles* working_files, - SymbolRef sym, - const std::string& new_text) { - std::unordered_map path_to_edit; - - EachOccurrence(db, sym, true, [&](Use use) { - optional ls_location = GetLsLocation(db, working_files, use); - if (!ls_location) - return; - - QueryFileId file_id = use.file; - if (path_to_edit.find(file_id) == path_to_edit.end()) { - path_to_edit[file_id] = lsTextDocumentEdit(); - - QueryFile& file = db->files[file_id.id]; - if (!file.def) - return; - - const std::string& path = file.def->path; - path_to_edit[file_id].textDocument.uri = lsDocumentUri::FromPath(path); - - WorkingFile* working_file = working_files->GetFileByFilename(path); - if (working_file) - path_to_edit[file_id].textDocument.version = working_file->version; - } - - lsTextEdit edit; - edit.range = ls_location->range; - edit.newText = new_text; - - // vscode complains if we submit overlapping text edits. - auto& edits = path_to_edit[file_id].edits; - if (std::find(edits.begin(), edits.end(), edit) == edits.end()) - edits.push_back(edit); - }); - - lsWorkspaceEdit edit; - for (const auto& changes : path_to_edit) - edit.documentChanges.push_back(changes.second); - return edit; -} - -struct Ipc_TextDocumentRename : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentRename; - struct Params { - // The document to format. - lsTextDocumentIdentifier textDocument; - - // The position at which this request was sent. - lsPosition position; - - // The new name of the symbol. If the given name is not valid the - // request must return a [ResponseError](#ResponseError) with an - // appropriate message set. - std::string newName; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentRename::Params, - textDocument, - position, - newName); -MAKE_REFLECT_STRUCT(Ipc_TextDocumentRename, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentRename); - -struct Out_TextDocumentRename : public lsOutMessage { - lsRequestId id; - lsWorkspaceEdit result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentRename, jsonrpc, id, result); - -struct TextDocumentRenameHandler : BaseMessageHandler { - void Run(Ipc_TextDocumentRename* request) override { - QueryFileId file_id; - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file, - &file_id)) { - return; - } - - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentRename out; - out.id = request->id; - - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - // Found symbol. Return references to rename. - out.result = - BuildWorkspaceEdit(db, working_files, sym, request->params.newName); - break; - } - - QueueManager::WriteStdout(IpcId::TextDocumentRename, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentRenameHandler); -} // namespace diff --git a/src/messages/text_document_signature_help.cc b/src/messages/text_document_signature_help.cc deleted file mode 100644 index ec1a9c907..000000000 --- a/src/messages/text_document_signature_help.cc +++ /dev/null @@ -1,172 +0,0 @@ -#include "clang_complete.h" -#include "code_complete_cache.h" -#include "message_handler.h" -#include "queue_manager.h" -#include "timer.h" - -#include - -namespace { -struct Ipc_TextDocumentSignatureHelp - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentSignatureHelp; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentSignatureHelp, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentSignatureHelp); - -// Represents a parameter of a callable-signature. A parameter can -// have a label and a doc-comment. -struct lsParameterInformation { - // The label of this parameter. Will be shown in - // the UI. - std::string label; - - // The human-readable doc-comment of this parameter. Will be shown - // in the UI but can be omitted. - optional documentation; -}; -MAKE_REFLECT_STRUCT(lsParameterInformation, label, documentation); - -// Represents the signature of something callable. A signature -// can have a label, like a function-name, a doc-comment, and -// a set of parameters. -struct lsSignatureInformation { - // The label of this signature. Will be shown in - // the UI. - std::string label; - - // The human-readable doc-comment of this signature. Will be shown - // in the UI but can be omitted. - optional documentation; - - // The parameters of this signature. - std::vector parameters; -}; -MAKE_REFLECT_STRUCT(lsSignatureInformation, label, documentation, parameters); - -// Signature help represents the signature of something -// callable. There can be multiple signature but only one -// active and only one active parameter. -struct lsSignatureHelp { - // One or more signatures. - std::vector signatures; - - // The active signature. If omitted or the value lies outside the - // range of `signatures` the value defaults to zero or is ignored if - // `signatures.length === 0`. Whenever possible implementors should - // make an active decision about the active signature and shouldn't - // rely on a default value. - // In future version of the protocol this property might become - // mandantory to better express this. - optional activeSignature; - - // The active parameter of the active signature. If omitted or the value - // lies outside the range of `signatures[activeSignature].parameters` - // defaults to 0 if the active signature has parameters. If - // the active signature has no parameters it is ignored. - // In future version of the protocol this property might become - // mandantory to better express the active parameter if the - // active signature does have any. - optional activeParameter; -}; -MAKE_REFLECT_STRUCT(lsSignatureHelp, - signatures, - activeSignature, - activeParameter); - -struct Out_TextDocumentSignatureHelp - : public lsOutMessage { - lsRequestId id; - lsSignatureHelp result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentSignatureHelp, jsonrpc, id, result); - -struct TextDocumentSignatureHelpHandler : MessageHandler { - IpcId GetId() const override { return IpcId::TextDocumentSignatureHelp; } - - void Run(std::unique_ptr message) override { - auto request = message->As(); - lsTextDocumentPositionParams& params = request->params; - WorkingFile* file = - working_files->GetFileByFilename(params.textDocument.uri.GetPath()); - std::string search; - int active_param = 0; - if (file) { - lsPosition completion_position; - search = file->FindClosestCallNameInBuffer(params.position, &active_param, - &completion_position); - params.position = completion_position; - } - if (search.empty()) - return; - - ClangCompleteManager::OnComplete callback = std::bind( - [this](BaseIpcMessage* message, std::string search, int active_param, - const std::vector& results, - bool is_cached_result) { - auto msg = message->As(); - - Out_TextDocumentSignatureHelp out; - out.id = msg->id; - - for (auto& result : results) { - if (result.label != search) - continue; - - lsSignatureInformation signature; - signature.label = result.detail; - for (auto& parameter : result.parameters_) { - lsParameterInformation ls_param; - ls_param.label = parameter; - signature.parameters.push_back(ls_param); - } - out.result.signatures.push_back(signature); - } - - // Prefer the signature with least parameter count but still larger - // than active_param. - out.result.activeSignature = 0; - if (out.result.signatures.size()) { - size_t num_parameters = SIZE_MAX; - for (size_t i = 0; i < out.result.signatures.size(); ++i) { - size_t t = out.result.signatures[i].parameters.size(); - if (active_param < t && t < num_parameters) { - out.result.activeSignature = int(i); - num_parameters = t; - } - } - } - - // Set signature to what we parsed from the working file. - out.result.activeParameter = active_param; - - Timer timer; - QueueManager::WriteStdout(IpcId::TextDocumentSignatureHelp, out); - - if (!is_cached_result) { - signature_cache->WithLock([&]() { - signature_cache->cached_path_ = - msg->params.textDocument.uri.GetPath(); - signature_cache->cached_completion_position_ = - msg->params.position; - signature_cache->cached_results_ = results; - }); - } - - delete message; - }, - message.release(), search, active_param, std::placeholders::_1, - std::placeholders::_2); - - if (signature_cache->IsCacheValid(params)) { - signature_cache->WithLock([&]() { - callback(signature_cache->cached_results_, true /*is_cached_result*/); - }); - } else { - clang_complete->CodeComplete(request->id, params, std::move(callback)); - } - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentSignatureHelpHandler); -} // namespace diff --git a/src/messages/text_document_type_definition.cc b/src/messages/text_document_type_definition.cc deleted file mode 100644 index 9745862b1..000000000 --- a/src/messages/text_document_type_definition.cc +++ /dev/null @@ -1,67 +0,0 @@ -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { - -struct Ipc_TextDocumentTypeDefinition - : public RequestMessage { - const static IpcId kIpcId = IpcId::TextDocumentTypeDefinition; - lsTextDocumentPositionParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_TextDocumentTypeDefinition, id, params); -REGISTER_IPC_MESSAGE(Ipc_TextDocumentTypeDefinition); - -struct Out_TextDocumentTypeDefinition - : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_TextDocumentTypeDefinition, jsonrpc, id, result); - -struct TextDocumentTypeDefinitionHandler - : BaseMessageHandler { - void Run(Ipc_TextDocumentTypeDefinition* request) override { - QueryFile* file; - if (!FindFileOrFail(db, project, request->id, - request->params.textDocument.uri.GetPath(), &file, - nullptr)) { - return; - } - WorkingFile* working_file = - working_files->GetFileByFilename(file->def->path); - - Out_TextDocumentTypeDefinition out; - out.id = request->id; - for (SymbolRef sym : - FindSymbolsAtLocation(working_file, file, request->params.position)) { - Id id = sym.id; - switch (sym.kind) { - case SymbolKind::Var: { - const QueryVar::Def* def = db->GetVar(sym).AnyDef(); - if (!def || !def->type) - continue; - id = *def->type; - } - // fallthrough - case SymbolKind::Type: { - QueryType& type = db->types[id.id]; - for (const auto& def : type.def) - if (def.spell) { - if (auto ls_loc = GetLsLocationEx(db, working_files, *def.spell, - config->xref.container)) - out.result.push_back(*ls_loc); - } - break; - } - default: - break; - } - } - - QueueManager::WriteStdout(IpcId::TextDocumentTypeDefinition, out); - } -}; -REGISTER_MESSAGE_HANDLER(TextDocumentTypeDefinitionHandler); - -} // namespace diff --git a/src/messages/workspace.cc b/src/messages/workspace.cc new file mode 100644 index 000000000..5661a5d65 --- /dev/null +++ b/src/messages/workspace.cc @@ -0,0 +1,203 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "sema_manager.hh" +#include "fuzzy_match.hh" +#include "log.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "project.hh" +#include "query.hh" + +#include +#include + +#include +#include +#include +#include + +namespace ccls { +REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName); + +void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) { + for (const std::string &folder : g_config->workspaceFolders) + project->Load(folder); + project->Index(wfiles, RequestId()); + + manager->Clear(); +}; + +void MessageHandler::workspace_didChangeWatchedFiles( + DidChangeWatchedFilesParam ¶m) { + for (auto &event : param.changes) { + std::string path = event.uri.GetPath(); + IndexMode mode = + wfiles->GetFile(path) ? IndexMode::Normal : IndexMode::NonInteractive; + switch (event.type) { + case FileChangeType::Created: + case FileChangeType::Changed: { + pipeline::Index(path, {}, mode); + if (mode == IndexMode::Normal) + manager->OnSave(path); + else + manager->OnClose(path); + break; + } + case FileChangeType::Deleted: + pipeline::Index(path, {}, mode); + manager->OnClose(path); + break; + } + } +} + +void MessageHandler::workspace_didChangeWorkspaceFolders( + DidChangeWorkspaceFoldersParam ¶m) { + for (const WorkspaceFolder &wf : param.event.removed) { + std::string root = wf.uri.GetPath(); + EnsureEndsInSlash(root); + LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root; + auto it = llvm::find(g_config->workspaceFolders, root); + if (it != g_config->workspaceFolders.end()) { + g_config->workspaceFolders.erase(it); + { + // auto &folder = project->root2folder[path]; + // FIXME delete + } + project->root2folder.erase(root); + } + } + for (const WorkspaceFolder &wf : param.event.added) { + std::string root = wf.uri.GetPath(); + EnsureEndsInSlash(root); + LOG_S(INFO) << "add workspace folder " << wf.name << ": " << root; + g_config->workspaceFolders.push_back(root); + project->Load(root); + } + + project->Index(wfiles, RequestId()); + + manager->Clear(); +} + +namespace { +// Lookup |symbol| in |db| and insert the value into |result|. +bool AddSymbol( + DB *db, WorkingFiles *wfiles, const std::vector &file_set, + SymbolIdx sym, bool use_detailed, + std::vector> *result) { + std::optional info = GetSymbolInfo(db, sym, true); + if (!info) + return false; + + Maybe dr; + bool in_folder = false; + WithEntity(db, sym, [&](const auto &entity) { + for (auto &def : entity.def) + if (def.spell) { + dr = def.spell; + if (!in_folder && (in_folder = file_set[def.spell->file_id])) + break; + } + }); + if (!dr) { + auto &decls = GetNonDefDeclarations(db, sym); + for (auto &dr1 : decls) { + dr = dr1; + if (!in_folder && (in_folder = file_set[dr1.file_id])) + break; + } + } + if (!in_folder) + return false; + + std::optional ls_location = GetLsLocation(db, wfiles, *dr); + if (!ls_location) + return false; + info->location = *ls_location; + result->emplace_back(*info, int(use_detailed), sym); + return true; +} +} // namespace + +void MessageHandler::workspace_symbol(WorkspaceSymbolParam ¶m, + ReplyOnce &reply) { + std::vector result; + const std::string &query = param.query; + for (auto &folder : param.folders) + EnsureEndsInSlash(folder); + std::vector file_set = db->GetFileSet(param.folders); + + // {symbol info, matching detailed_name or short_name, index} + std::vector> cands; + bool sensitive = g_config->workspaceSymbol.caseSensitivity; + + // Find subsequence matches. + std::string query_without_space; + query_without_space.reserve(query.size()); + for (char c : query) + if (!isspace(c)) + query_without_space += c; + + auto Add = [&](SymbolIdx sym) { + std::string_view detailed_name = db->GetSymbolName(sym, true); + int pos = ReverseSubseqMatch(query_without_space, detailed_name, sensitive); + return pos >= 0 && + AddSymbol(db, wfiles, file_set, sym, + detailed_name.find(':', pos) != std::string::npos, + &cands) && + cands.size() >= g_config->workspaceSymbol.maxNum; + }; + for (auto &func : db->funcs) + if (Add({func.usr, Kind::Func})) + goto done_add; + for (auto &type : db->types) + if (Add({type.usr, Kind::Type})) + goto done_add; + for (auto &var : db->vars) + if (var.def.size() && !var.def[0].is_local() && Add({var.usr, Kind::Var})) + goto done_add; +done_add: + + if (g_config->workspaceSymbol.sort && query.size() <= FuzzyMatcher::kMaxPat) { + // Sort results with a fuzzy matching algorithm. + int longest = 0; + for (auto &cand : cands) + longest = std::max( + longest, int(db->GetSymbolName(std::get<2>(cand), true).size())); + FuzzyMatcher fuzzy(query, g_config->workspaceSymbol.caseSensitivity); + for (auto &cand : cands) + std::get<1>(cand) = + fuzzy.Match(db->GetSymbolName(std::get<2>(cand), std::get<1>(cand))); + std::sort(cands.begin(), cands.end(), [](const auto &l, const auto &r) { + return std::get<1>(l) > std::get<1>(r); + }); + result.reserve(cands.size()); + for (auto &cand : cands) { + // Discard awful candidates. + if (std::get<1>(cand) <= FuzzyMatcher::kMinScore) + break; + result.push_back(std::get<0>(cand)); + } + } else { + result.reserve(cands.size()); + for (auto &cand : cands) + result.push_back(std::get<0>(cand)); + } + + reply(result); +} +} // namespace ccls diff --git a/src/messages/workspace_did_change_configuration.cc b/src/messages/workspace_did_change_configuration.cc deleted file mode 100644 index 29b5f57d9..000000000 --- a/src/messages/workspace_did_change_configuration.cc +++ /dev/null @@ -1,38 +0,0 @@ -#include "cache_manager.h" -#include "message_handler.h" -#include "project.h" -#include "queue_manager.h" -#include "timer.h" -#include "working_files.h" - -namespace { -struct lsDidChangeConfigurationParams { - bool placeholder; -}; -MAKE_REFLECT_STRUCT(lsDidChangeConfigurationParams, placeholder); - -struct Ipc_WorkspaceDidChangeConfiguration - : public NotificationMessage { - const static IpcId kIpcId = IpcId::WorkspaceDidChangeConfiguration; - lsDidChangeConfigurationParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_WorkspaceDidChangeConfiguration, params); -REGISTER_IPC_MESSAGE(Ipc_WorkspaceDidChangeConfiguration); - -struct WorkspaceDidChangeConfigurationHandler - : BaseMessageHandler { - void Run(Ipc_WorkspaceDidChangeConfiguration* request) override { - Timer time; - project->Load(config, config->projectRoot); - time.ResetAndPrint("[perf] Loaded compilation entries (" + - std::to_string(project->entries.size()) + " files)"); - - time.Reset(); - project->Index(config, QueueManager::instance(), working_files, - std::monostate()); - time.ResetAndPrint( - "[perf] Dispatched workspace/didChangeConfiguration index requests"); - } -}; -REGISTER_MESSAGE_HANDLER(WorkspaceDidChangeConfigurationHandler); -} // namespace diff --git a/src/messages/workspace_did_change_watched_files.cc b/src/messages/workspace_did_change_watched_files.cc deleted file mode 100644 index 18e58d0c1..000000000 --- a/src/messages/workspace_did_change_watched_files.cc +++ /dev/null @@ -1,73 +0,0 @@ -#include "cache_manager.h" -#include "clang_complete.h" -#include "message_handler.h" -#include "project.h" -#include "queue_manager.h" -#include "working_files.h" - -#include - -namespace { -enum class lsFileChangeType { - Created = 1, - Changed = 2, - Deleted = 3, -}; -MAKE_REFLECT_TYPE_PROXY(lsFileChangeType); - -struct lsFileEvent { - lsDocumentUri uri; - lsFileChangeType type; -}; -MAKE_REFLECT_STRUCT(lsFileEvent, uri, type); - -struct lsDidChangeWatchedFilesParams { - std::vector changes; -}; -MAKE_REFLECT_STRUCT(lsDidChangeWatchedFilesParams, changes); - -struct Ipc_WorkspaceDidChangeWatchedFiles - : public NotificationMessage { - const static IpcId kIpcId = IpcId::WorkspaceDidChangeWatchedFiles; - lsDidChangeWatchedFilesParams params; -}; -MAKE_REFLECT_STRUCT(Ipc_WorkspaceDidChangeWatchedFiles, params); -REGISTER_IPC_MESSAGE(Ipc_WorkspaceDidChangeWatchedFiles); - -struct WorkspaceDidChangeWatchedFilesHandler - : BaseMessageHandler { - void Run(Ipc_WorkspaceDidChangeWatchedFiles* request) override { - for (lsFileEvent& event : request->params.changes) { - std::string path = event.uri.GetPath(); - auto it = project->absolute_path_to_entry_index_.find(path); - if (it == project->absolute_path_to_entry_index_.end()) - continue; - const Project::Entry& entry = project->entries[it->second]; - bool is_interactive = - working_files->GetFileByFilename(entry.filename) != nullptr; - switch (event.type) { - case lsFileChangeType::Created: - case lsFileChangeType::Changed: { - optional content = ReadContent(path); - if (!content) - LOG_S(ERROR) << "Unable to read file content after saving " << path; - else { - QueueManager::instance()->index_request.PushBack( - Index_Request(path, entry.args, is_interactive, *content, - ICacheManager::Make(config))); - if (is_interactive) - clang_complete->NotifySave(path); - } - break; - } - case lsFileChangeType::Deleted: - QueueManager::instance()->index_request.PushBack( - Index_Request(path, entry.args, is_interactive, std::string(), - ICacheManager::Make(config))); - break; - } - } - } -}; -REGISTER_MESSAGE_HANDLER(WorkspaceDidChangeWatchedFilesHandler); -} // namespace diff --git a/src/messages/workspace_execute_command.cc b/src/messages/workspace_execute_command.cc deleted file mode 100644 index adb405f45..000000000 --- a/src/messages/workspace_execute_command.cc +++ /dev/null @@ -1,49 +0,0 @@ -#include "lsp_code_action.h" -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -namespace { - -struct Ipc_WorkspaceExecuteCommand - : public RequestMessage { - const static IpcId kIpcId = IpcId::WorkspaceExecuteCommand; - lsCommand params; -}; -MAKE_REFLECT_STRUCT(Ipc_WorkspaceExecuteCommand, id, params); -REGISTER_IPC_MESSAGE(Ipc_WorkspaceExecuteCommand); - -struct Out_WorkspaceExecuteCommand - : public lsOutMessage { - lsRequestId id; - std::variant, CommandArgs> result; -}; -MAKE_REFLECT_STRUCT(Out_WorkspaceExecuteCommand, jsonrpc, id, result); - -void Reflect(Writer& visitor, Out_WorkspaceExecuteCommand& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(jsonrpc); - REFLECT_MEMBER(id); - REFLECT_MEMBER(result); - REFLECT_MEMBER_END(); -} - -struct WorkspaceExecuteCommandHandler - : BaseMessageHandler { - void Run(Ipc_WorkspaceExecuteCommand* request) override { - const auto& params = request->params; - Out_WorkspaceExecuteCommand out; - out.id = request->id; - if (params.command == "cquery._applyFixIt") { - } else if (params.command == "cquery._autoImplement") { - } else if (params.command == "cquery._insertInclude") { - } else if (params.command == "cquery.showReferences") { - out.result = params.arguments.locations; - } - - QueueManager::WriteStdout(IpcId::WorkspaceExecuteCommand, out); - } -}; -REGISTER_MESSAGE_HANDLER(WorkspaceExecuteCommandHandler); - -} // namespace diff --git a/src/messages/workspace_symbol.cc b/src/messages/workspace_symbol.cc deleted file mode 100644 index 0f66e7c17..000000000 --- a/src/messages/workspace_symbol.cc +++ /dev/null @@ -1,159 +0,0 @@ -#include "fuzzy_match.h" -#include "lex_utils.h" -#include "message_handler.h" -#include "query_utils.h" -#include "queue_manager.h" - -#include - -#include -#include -#include -#include - -namespace { - -// Lookup |symbol| in |db| and insert the value into |result|. -bool InsertSymbolIntoResult(QueryDatabase* db, - WorkingFiles* working_files, - SymbolIdx symbol, - std::vector* result) { - optional info = - GetSymbolInfo(db, working_files, symbol, false /*use_short_name*/); - if (!info) - return false; - - Maybe location = GetDefinitionExtent(db, symbol); - Use loc; - if (location) - loc = *location; - else { - auto decls = GetNonDefDeclarations(db, symbol); - if (decls.empty()) - return false; - loc = decls[0]; - } - - optional ls_location = GetLsLocation(db, working_files, loc); - if (!ls_location) - return false; - info->location = *ls_location; - result->push_back(*info); - return true; -} - -struct Ipc_WorkspaceSymbol : public RequestMessage { - const static IpcId kIpcId = IpcId::WorkspaceSymbol; - struct Params { - std::string query; - }; - Params params; -}; -MAKE_REFLECT_STRUCT(Ipc_WorkspaceSymbol::Params, query); -MAKE_REFLECT_STRUCT(Ipc_WorkspaceSymbol, id, params); -REGISTER_IPC_MESSAGE(Ipc_WorkspaceSymbol); - -struct Out_WorkspaceSymbol : public lsOutMessage { - lsRequestId id; - std::vector result; -}; -MAKE_REFLECT_STRUCT(Out_WorkspaceSymbol, jsonrpc, id, result); - -///// Fuzzy matching - -struct WorkspaceSymbolHandler : BaseMessageHandler { - void Run(Ipc_WorkspaceSymbol* request) override { - Out_WorkspaceSymbol out; - out.id = request->id; - - LOG_S(INFO) << "[querydb] Considering " << db->symbols.size() - << " candidates for query " << request->params.query; - - std::string query = request->params.query; - - std::unordered_set inserted_results; - // db->detailed_names indices of each lsSymbolInformation in out.result - std::vector result_indices; - std::vector unsorted_results; - inserted_results.reserve(config->workspaceSymbol.maxNum); - result_indices.reserve(config->workspaceSymbol.maxNum); - - // We use detailed_names without parameters for matching. - - // Find exact substring matches. - for (int i = 0; i < db->symbols.size(); ++i) { - std::string_view detailed_name = db->GetSymbolDetailedName(i); - if (detailed_name.find(query) != std::string::npos) { - // Do not show the same entry twice. - if (!inserted_results.insert(std::string(detailed_name)).second) - continue; - - if (InsertSymbolIntoResult(db, working_files, db->symbols[i], - &unsorted_results)) { - result_indices.push_back(i); - if (unsorted_results.size() >= config->workspaceSymbol.maxNum) - break; - } - } - } - - // Find subsequence matches. - if (unsorted_results.size() < config->workspaceSymbol.maxNum) { - std::string query_without_space; - query_without_space.reserve(query.size()); - for (char c : query) - if (!isspace(c)) - query_without_space += c; - - for (int i = 0; i < (int)db->symbols.size(); ++i) { - std::string_view detailed_name = db->GetSymbolDetailedName(i); - if (SubsequenceMatchIgnoreCase(query_without_space, detailed_name)) { - // Do not show the same entry twice. - if (!inserted_results.insert(std::string(detailed_name)).second) - continue; - - if (InsertSymbolIntoResult(db, working_files, db->symbols[i], - &unsorted_results)) { - result_indices.push_back(i); - if (unsorted_results.size() >= config->workspaceSymbol.maxNum) - break; - } - } - } - } - - if (config->workspaceSymbol.sort) { - // Sort results with a fuzzy matching algorithm. - int longest = 0; - for (int i : result_indices) - longest = std::max(longest, int(db->GetSymbolDetailedName(i).size())); - - std::vector score(longest); // score for each position - std::vector dp( - longest); // dp[i]: maximum value by aligning pattern to str[0..i] - std::vector> permutation(result_indices.size()); - for (int i = 0; i < int(result_indices.size()); i++) { - permutation[i] = { - FuzzyEvaluate(query, db->GetSymbolDetailedName(result_indices[i]), - score, dp), - i}; - } - std::sort(permutation.begin(), permutation.end(), - std::greater>()); - out.result.reserve(result_indices.size()); - for (int i = 0; i < int(result_indices.size()); i++) - out.result.push_back( - std::move(unsorted_results[permutation[i].second])); - } else { - out.result.reserve(unsorted_results.size()); - for (const auto& entry : unsorted_results) - out.result.push_back(std::move(entry)); - } - - LOG_S(INFO) << "[querydb] Found " << out.result.size() - << " results for query " << query; - QueueManager::WriteStdout(IpcId::WorkspaceSymbol, out); - } -}; -REGISTER_MESSAGE_HANDLER(WorkspaceSymbolHandler); -} // namespace diff --git a/src/methods.inc b/src/methods.inc deleted file mode 100644 index 8298c0d6e..000000000 --- a/src/methods.inc +++ /dev/null @@ -1,60 +0,0 @@ -// General -CASE(Initialize, "initialize") -CASE(Shutdown, "shutdown") - -CASE(CodeLensResolve, "codeLens/resolve") -CASE(TextDocumentCodeAction, "textDocument/codeAction") -CASE(TextDocumentCodeLens, "textDocument/codeLens") -CASE(TextDocumentCompletion, "textDocument/completion") -CASE(TextDocumentDefinition, "textDocument/definition") -CASE(TextDocumentDidChange, "textDocument/didChange") -CASE(TextDocumentDidClose, "textDocument/didClose") -CASE(TextDocumentDidOpen, "textDocument/didOpen") -CASE(TextDocumentDidSave, "textDocument/didSave") -CASE(TextDocumentDocumentHighlight, "textDocument/documentHighlight") -CASE(TextDocumentDocumentLink, "textDocument/documentLink") -CASE(TextDocumentDocumentSymbol, "textDocument/documentSymbol") -CASE(TextDocumentFormatting, "textDocument/formatting") -CASE(TextDocumentHover, "textDocument/hover") -CASE(TextDocumentOnTypeFormatting, "textDocument/onTypeFormatting") -CASE(TextDocumentPublishDiagnostics, "textDocument/publishDiagnostics") -CASE(TextDocumentRangeFormatting, "textDocument/rangeFormatting") -CASE(TextDocumentReferences, "textDocument/references") -CASE(TextDocumentRename, "textDocument/rename") -CASE(TextDocumentSignatureHelp, "textDocument/signatureHelp") -CASE(TextDocumentTypeDefinition, "textDocument/typeDefinition") -CASE(WorkspaceDidChangeConfiguration, "workspace/didChangeConfiguration") -CASE(WorkspaceDidChangeWatchedFiles, "workspace/didChangeWatchedFiles") -CASE(WorkspaceExecuteCommand, "workspace/executeCommand") -CASE(WorkspaceSymbol, "workspace/symbol") - -// Notification extensions -CASE(CqueryTextDocumentDidView, "$cquery/textDocumentDidView") -CASE(CqueryPublishInactiveRegions, "$cquery/publishInactiveRegions") -CASE(CqueryPublishSemanticHighlighting, "$cquery/publishSemanticHighlighting") - -// Request extensions -CASE(CqueryFileInfo, "$cquery/fileInfo") -CASE(CqueryFreshenIndex, "$cquery/freshenIndex") - -CASE(CqueryCallHierarchy, "$cquery/callHierarchy") -CASE(CqueryInheritanceHierarchy, "$cquery/inheritanceHierarchy") -CASE(CqueryMemberHierarchy, "$cquery/memberHierarchy") - -// Cross reference extensions. -// Show all variables of a type. -CASE(CqueryVars, "$cquery/vars") -// Show all callers of a function. -CASE(CqueryCallers, "$cquery/callers") -// Show base types/method. -CASE(CqueryBase, "$cquery/base") -// Show all derived types/methods. -CASE(CqueryDerived, "$cquery/derived") -// Show random definition. -CASE(CqueryRandom, "$cquery/random") - -// Messages for testing. -// Index the given file contents. -CASE(CqueryIndexFile, "$cquery/indexFile") -// Wait until all cquery threads are idle. -CASE(CqueryWait, "$cquery/wait") diff --git a/src/nt_string.h b/src/nt_string.h deleted file mode 100644 index 4083535ef..000000000 --- a/src/nt_string.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -// Nullable null-terminated string, which is null if default constructed, -// but non-null if assigned. -// This is used in Query{Func,Type,Var}::def to reduce memory footprint. -class NtString { - using size_type = std::string::size_type; - std::unique_ptr str; - - public: - NtString() = default; - NtString(NtString&& o) = default; - NtString(const NtString& o) { *this = o; } - NtString(std::string_view sv) { *this = sv; } - - const char* c_str() const { return str.get(); } - operator std::string_view() const { - if (c_str()) - return c_str(); - return {}; - } - bool empty() const { return !str || *c_str() == '\0'; } - - void operator=(std::string_view sv) { - str = std::unique_ptr(new char[sv.size() + 1]); - memcpy(str.get(), sv.data(), sv.size()); - str.get()[sv.size()] = '\0'; - } - void operator=(const NtString& o) { - *this = static_cast(o); - } - bool operator==(const NtString& o) const { - return str && o.str ? strcmp(c_str(), o.c_str()) == 0 - : c_str() == o.c_str(); - } -}; diff --git a/src/options.cc b/src/options.cc deleted file mode 100644 index b0962f5fe..000000000 --- a/src/options.cc +++ /dev/null @@ -1,30 +0,0 @@ -#include "options.h" - -#include - -#include - -std::unordered_map ParseOptions(int argc, - char** argv) { - std::unordered_map output; - - for (int i = 1; i < argc; ++i) { - std::string arg = argv[i]; - if (arg[0] == '-') { - auto equal = arg.find('='); - if (equal != std::string::npos) { - output[arg.substr(0, equal)] = arg.substr(equal + 1); - } else if (i + 1 < argc && argv[i + 1][0] != '-') - output[arg] = argv[++i]; - else - output[arg] = ""; - } - } - - return output; -} - -bool HasOption(const std::unordered_map& options, - const std::string& option) { - return options.find(option) != options.end(); -} diff --git a/src/options.h b/src/options.h deleted file mode 100644 index eec6f17ef..000000000 --- a/src/options.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include - -std::unordered_map ParseOptions(int argc, - char** argv); - -bool HasOption(const std::unordered_map& options, - const std::string& option); \ No newline at end of file diff --git a/src/performance.h b/src/performance.h deleted file mode 100644 index 56c348803..000000000 --- a/src/performance.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include "serializer.h" - -#include - -// Contains timing information for the entire pipeline for importing a file -// into the querydb. -struct PerformanceImportFile { - // All units are in microseconds. - - // [indexer] clang parsing the file - uint64_t index_parse = 0; - // [indexer] build the IndexFile object from clang parse - uint64_t index_build = 0; - // [querydb] create IdMap object from IndexFile - uint64_t querydb_id_map = 0; - // [indexer] save the IndexFile to disk - uint64_t index_save_to_disk = 0; - // [indexer] loading previously cached index - uint64_t index_load_cached = 0; - // [indexer] create delta IndexUpdate object - uint64_t index_make_delta = 0; - // [querydb] update WorkingFile indexed file state - // uint64_t querydb_update_working_file = 0; - // [querydb] apply IndexUpdate - // uint64_t querydb_apply_index_update = 0; -}; -MAKE_REFLECT_STRUCT(PerformanceImportFile, - index_parse, - index_build, - querydb_id_map, - index_save_to_disk, - index_load_cached, - index_make_delta); \ No newline at end of file diff --git a/src/pipeline.cc b/src/pipeline.cc new file mode 100644 index 000000000..ab6fd2e16 --- /dev/null +++ b/src/pipeline.cc @@ -0,0 +1,662 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "pipeline.hh" + +#include "config.hh" +#include "include_complete.hh" +#include "log.hh" +#include "lsp.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "platform.hh" +#include "project.hh" +#include "query.hh" +#include "sema_manager.hh" + +#include +#include + +#include +#include +#include +using namespace llvm; + +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +namespace ccls { +namespace { +struct PublishDiagnosticParam { + DocumentUri uri; + std::vector diagnostics; +}; +REFLECT_STRUCT(PublishDiagnosticParam, uri, diagnostics); +} // namespace + +void VFS::Clear() { + std::lock_guard lock(mutex); + state.clear(); +} + +bool VFS::Loaded(const std::string &path) { + std::lock_guard lock(mutex); + return state[path].loaded; +} + +bool VFS::Stamp(const std::string &path, int64_t ts, int step) { + std::lock_guard lock(mutex); + State &st = state[path]; + if (st.timestamp < ts || (st.timestamp == ts && st.step < step)) { + st.timestamp = ts; + st.step = step; + return true; + } else + return false; +} + +struct MessageHandler; +void StandaloneInitialize(MessageHandler &, const std::string &root); + +namespace pipeline { + +std::atomic loaded_ts = ATOMIC_VAR_INIT(0), + pending_index_requests = ATOMIC_VAR_INIT(0); +int64_t tick = 0; + +namespace { + +struct Index_Request { + std::string path; + std::vector args; + IndexMode mode; + RequestId id; + int64_t ts = tick++; +}; + +MultiQueueWaiter *main_waiter; +MultiQueueWaiter *indexer_waiter; +MultiQueueWaiter *stdout_waiter; +ThreadedQueue *on_request; +ThreadedQueue *index_request; +ThreadedQueue *on_indexed; +ThreadedQueue *for_stdout; + +struct InMemoryIndexFile { + std::string content; + IndexFile index; +}; +std::shared_mutex g_index_mutex; +std::unordered_map g_index; + +bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, + const std::vector &args, + const std::optional &from) { + { + std::lock_guard lock(vfs->mutex); + if (prev->mtime < vfs->state[path].timestamp) { + LOG_S(INFO) << "timestamp changed for " << path + << (from ? " (via " + *from + ")" : std::string()); + return true; + } + } + + bool changed = prev->args.size() != args.size(); + for (size_t i = 0; !changed && i < args.size(); i++) + if (strcmp(prev->args[i], args[i])) + changed = true; + if (changed) + LOG_S(INFO) << "args changed for " << path + << (from ? " (via " + *from + ")" : std::string()); + return changed; +}; + +std::string AppendSerializationFormat(const std::string &base) { + switch (g_config->cacheFormat) { + case SerializeFormat::Binary: + return base + ".blob"; + case SerializeFormat::Json: + return base + ".json"; + } +} + +std::string GetCachePath(const std::string &source_file) { + for (auto &root : g_config->workspaceFolders) + if (StringRef(source_file).startswith(root)) { + auto len = root.size(); + return g_config->cacheDirectory + + EscapeFileName(root.substr(0, len - 1)) + '/' + + EscapeFileName(source_file.substr(len)); + } + return g_config->cacheDirectory + '@' + + EscapeFileName(g_config->fallbackFolder.substr( + 0, g_config->fallbackFolder.size() - 1)) + + '/' + EscapeFileName(source_file); +} + +std::unique_ptr RawCacheLoad(const std::string &path) { + if (g_config->cacheDirectory.empty()) { + std::shared_lock lock(g_index_mutex); + auto it = g_index.find(path); + if (it == g_index.end()) + return nullptr; + return std::make_unique(it->second.index); + } + + std::string cache_path = GetCachePath(path); + std::optional file_content = ReadContent(cache_path); + std::optional serialized_indexed_content = + ReadContent(AppendSerializationFormat(cache_path)); + if (!file_content || !serialized_indexed_content) + return nullptr; + + return ccls::Deserialize(g_config->cacheFormat, path, + *serialized_indexed_content, *file_content, + IndexFile::kMajorVersion); +} + +std::mutex &GetFileMutex(const std::string &path) { + const int N_MUTEXES = 256; + static std::mutex mutexes[N_MUTEXES]; + return mutexes[std::hash()(path) % N_MUTEXES]; +} + +bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, + Project *project, VFS *vfs, const GroupMatch &matcher) { + std::optional opt_request = index_request->TryPopFront(); + if (!opt_request) + return false; + auto &request = *opt_request; + bool loud = request.mode != IndexMode::OnChange; + struct RAII { + ~RAII() { pending_index_requests--; } + } raii; + + // Dummy one to trigger refresh semantic highlight. + if (request.path.empty()) { + IndexUpdate dummy; + dummy.refresh = true; + on_indexed->PushBack(std::move(dummy), false); + return false; + } + + if (!matcher.Matches(request.path)) { + LOG_IF_S(INFO, loud) << "skip " << request.path; + return false; + } + + Project::Entry entry = project->FindEntry(request.path, true); + if (request.args.size()) + entry.args = request.args; + std::string path_to_index = entry.filename; + std::unique_ptr prev; + + std::optional write_time = LastWriteTime(path_to_index); + if (!write_time) + return true; + int reparse = vfs->Stamp(path_to_index, *write_time, 0); + if (request.path != path_to_index) { + std::optional mtime1 = LastWriteTime(request.path); + if (!mtime1) + return true; + if (vfs->Stamp(request.path, *mtime1, 0)) + reparse = 2; + } + if (g_config->index.onChange) { + reparse = 2; + std::lock_guard lock(vfs->mutex); + vfs->state[path_to_index].step = 0; + if (request.path != path_to_index) + vfs->state[request.path].step = 0; + } + bool track = g_config->index.trackDependency > 1 || + (g_config->index.trackDependency == 1 && request.ts < loaded_ts); + if (!reparse && !track) + return true; + + if (reparse < 2) + do { + std::unique_lock lock(GetFileMutex(path_to_index)); + prev = RawCacheLoad(path_to_index); + if (!prev || CacheInvalid(vfs, prev.get(), path_to_index, entry.args, + std::nullopt)) + break; + if (track) + for (const auto &dep : prev->dependencies) { + if (auto mtime1 = LastWriteTime(dep.first.val().str())) { + if (dep.second < *mtime1) { + reparse = 2; + break; + } + } else { + reparse = 2; + break; + } + } + if (reparse == 0) + return true; + if (reparse == 2) + break; + + if (vfs->Loaded(path_to_index)) + return true; + LOG_S(INFO) << "load cache for " << path_to_index; + auto dependencies = prev->dependencies; + IndexUpdate update = IndexUpdate::CreateDelta(nullptr, prev.get()); + on_indexed->PushBack(std::move(update), + request.mode != IndexMode::NonInteractive); + { + std::lock_guard lock1(vfs->mutex); + vfs->state[path_to_index].loaded = true; + } + lock.unlock(); + + for (const auto &dep : dependencies) { + std::string path = dep.first.val().str(); + if (!vfs->Stamp(path, dep.second, 1)) + continue; + std::lock_guard lock1(GetFileMutex(path)); + prev = RawCacheLoad(path); + if (!prev) + continue; + { + std::lock_guard lock2(vfs->mutex); + VFS::State &st = vfs->state[path]; + if (st.loaded) + continue; + st.loaded = true; + st.timestamp = prev->mtime; + } + IndexUpdate update = IndexUpdate::CreateDelta(nullptr, prev.get()); + on_indexed->PushBack(std::move(update), + request.mode != IndexMode::NonInteractive); + if (entry.id >= 0) { + std::lock_guard lock2(project->mutex_); + project->root2folder[entry.root].path2entry_index[path] = entry.id; + } + } + return true; + } while (0); + + LOG_IF_S(INFO, loud) << "parse " << path_to_index; + + std::vector> remapped; + if (g_config->index.onChange) { + std::string content = wfiles->GetContent(path_to_index); + if (content.size()) + remapped.emplace_back(path_to_index, content); + } + bool ok; + auto indexes = idx::Index(completion, wfiles, vfs, entry.directory, + path_to_index, entry.args, remapped, ok); + + if (!ok) { + if (request.id.Valid()) { + ResponseError err; + err.code = ErrorCode::InternalError; + err.message = "failed to index " + path_to_index; + pipeline::ReplyError(request.id, err); + } + return true; + } + + for (std::unique_ptr &curr : indexes) { + std::string path = curr->path; + if (!matcher.Matches(path)) { + LOG_IF_S(INFO, loud) << "skip index for " << path; + continue; + } + + LOG_IF_S(INFO, loud) << "store index for " << path << " (delta: " << !!prev + << ")"; + { + std::lock_guard lock(GetFileMutex(path)); + if (vfs->Loaded(path)) + prev = RawCacheLoad(path); + else + prev.reset(); + if (g_config->cacheDirectory.empty()) { + std::lock_guard lock(g_index_mutex); + auto it = g_index.insert_or_assign( + path, InMemoryIndexFile{curr->file_contents, *curr}); + std::string().swap(it.first->second.index.file_contents); + } else { + std::string cache_path = GetCachePath(path); + WriteToFile(cache_path, curr->file_contents); + WriteToFile(AppendSerializationFormat(cache_path), + Serialize(g_config->cacheFormat, *curr)); + } + on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()), + request.mode != IndexMode::NonInteractive); + { + std::lock_guard lock1(vfs->mutex); + vfs->state[path].loaded = true; + } + if (entry.id >= 0) { + std::lock_guard lock(project->mutex_); + auto &folder = project->root2folder[entry.root]; + for (auto &dep : curr->dependencies) + folder.path2entry_index[dep.first.val().str()] = entry.id; + } + } + } + + return true; +} + +} // namespace + +void Init() { + main_waiter = new MultiQueueWaiter; + on_request = new ThreadedQueue(main_waiter); + on_indexed = new ThreadedQueue(main_waiter); + + indexer_waiter = new MultiQueueWaiter; + index_request = new ThreadedQueue(indexer_waiter); + + stdout_waiter = new MultiQueueWaiter; + for_stdout = new ThreadedQueue(stdout_waiter); +} + +void Indexer_Main(SemaManager *manager, VFS *vfs, Project *project, + WorkingFiles *wfiles) { + GroupMatch matcher(g_config->index.whitelist, g_config->index.blacklist); + while (true) + if (!Indexer_Parse(manager, wfiles, project, vfs, matcher)) + indexer_waiter->Wait(index_request); +} + +void Main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) { + if (update->refresh) { + LOG_S(INFO) + << "loaded project. Refresh semantic highlight for all working file."; + std::lock_guard lock(wfiles->mutex); + for (auto &[f, wf] : wfiles->files) { + std::string path = LowerPathIfInsensitive(f); + if (db->name2file_id.find(path) == db->name2file_id.end()) + continue; + QueryFile &file = db->files[db->name2file_id[path]]; + EmitSemanticHighlight(db, wf.get(), file); + } + return; + } + + static Timer timer("apply", "apply index"); + timer.startTimer(); + db->ApplyIndexUpdate(update); + timer.stopTimer(); + + // Update indexed content, skipped ranges, and semantic highlighting. + if (update->files_def_update) { + auto &def_u = *update->files_def_update; + if (WorkingFile *wfile = wfiles->GetFile(def_u.first.path)) { + // FIXME With index.onChange: true, use buffer_content only for + // request.path + wfile->SetIndexContent(g_config->index.onChange ? wfile->buffer_content + : def_u.second); + QueryFile &file = db->files[update->file_id]; + EmitSkippedRanges(wfile, file); + EmitSemanticHighlight(db, wfile, file); + } + } +} + +void LaunchStdin() { + std::thread([]() { + set_thread_name("stdin"); + std::string str; + const std::string_view kContentLength("Content-Length: "); + while (true) { + int len = 0; + str.clear(); + while (true) { + int c = getchar(); + if (c == EOF) + return; + if (c == '\n') { + if (str.empty()) + break; + if (!str.compare(0, kContentLength.size(), kContentLength)) + len = atoi(str.c_str() + kContentLength.size()); + str.clear(); + } else if (c != '\r') { + str += c; + } + } + + str.resize(len); + for (int i = 0; i < len; ++i) { + int c = getchar(); + if (c == EOF) + return; + str[i] = c; + } + + auto message = std::make_unique(len); + std::copy(str.begin(), str.end(), message.get()); + auto document = std::make_unique(); + document->Parse(message.get(), len); + assert(!document->HasParseError()); + + JsonReader reader{document.get()}; + if (!reader.m->HasMember("jsonrpc") || + std::string((*reader.m)["jsonrpc"].GetString()) != "2.0") + return; + RequestId id; + std::string method; + ReflectMember(reader, "id", id); + ReflectMember(reader, "method", method); + on_request->PushBack( + {id, std::move(method), std::move(message), std::move(document)}); + + if (method == "exit") + break; + } + }) + .detach(); +} + +void LaunchStdout() { + std::thread([=]() { + set_thread_name("stdout"); + + while (true) { + std::vector messages = for_stdout->DequeueAll(); + for (auto &s : messages) { + llvm::outs() << "Content-Length: " << s.size() << "\r\n\r\n" << s; + llvm::outs().flush(); + } + stdout_waiter->Wait(for_stdout); + } + }) + .detach(); +} + +void MainLoop() { + Project project; + WorkingFiles wfiles; + VFS vfs; + + SemaManager manager( + &project, &wfiles, + [&](std::string path, std::vector diagnostics) { + PublishDiagnosticParam params; + params.uri = DocumentUri::FromPath(path); + params.diagnostics = diagnostics; + Notify("textDocument/publishDiagnostics", params); + }, + [](RequestId id) { + if (id.Valid()) { + ResponseError err; + err.code = ErrorCode::InternalError; + err.message = "drop older completion request"; + ReplyError(id, err); + } + }); + + IncludeComplete include_complete(&project); + DB db; + + // Setup shared references. + MessageHandler handler; + handler.db = &db; + handler.project = &project; + handler.vfs = &vfs; + handler.wfiles = &wfiles; + handler.manager = &manager; + handler.include_complete = &include_complete; + + bool has_indexed = false; + while (true) { + std::vector messages = on_request->DequeueAll(); + bool did_work = messages.size(); + for (InMessage &message : messages) + handler.Run(message); + + bool indexed = false; + for (int i = 20; i--;) { + std::optional update = on_indexed->TryPopFront(); + if (!update) + break; + did_work = true; + indexed = true; + Main_OnIndexed(&db, &wfiles, &*update); + } + + if (did_work) + has_indexed |= indexed; + else { + if (has_indexed) { + FreeUnusedMemory(); + has_indexed = false; + } + main_waiter->Wait(on_indexed, on_request); + } + } +} + +void Standalone(const std::string &root) { + Project project; + WorkingFiles wfiles; + VFS vfs; + SemaManager manager( + nullptr, nullptr, [&](std::string, std::vector) {}, + [](RequestId id) {}); + IncludeComplete complete(&project); + + MessageHandler handler; + handler.project = &project; + handler.wfiles = &wfiles; + handler.vfs = &vfs; + handler.manager = &manager; + handler.include_complete = &complete; + + StandaloneInitialize(handler, root); + bool tty = sys::Process::StandardOutIsDisplayed(); + + if (tty) { + int entries = 0; + for (auto &[_, folder] : project.root2folder) + entries += folder.entries.size(); + printf("entries: %5d\n", entries); + } + while (1) { + (void)on_indexed->DequeueAll(); + int pending = pending_index_requests; + if (tty) { + printf("\rpending: %5d", pending); + fflush(stdout); + } + if (!pending) + break; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + if (tty) + puts(""); +} + +void Index(const std::string &path, const std::vector &args, + IndexMode mode, RequestId id) { + pending_index_requests++; + index_request->PushBack({path, args, mode, id}, mode != IndexMode::NonInteractive); +} + +std::optional LoadIndexedContent(const std::string &path) { + if (g_config->cacheDirectory.empty()) { + std::shared_lock lock(g_index_mutex); + auto it = g_index.find(path); + if (it == g_index.end()) + return {}; + return it->second.content; + } + return ReadContent(GetCachePath(path)); +} + +void Notify(const char *method, const std::function &fn) { + rapidjson::StringBuffer output; + rapidjson::Writer w(output); + w.StartObject(); + w.Key("jsonrpc"); + w.String("2.0"); + w.Key("method"); + w.String(method); + w.Key("params"); + JsonWriter writer(&w); + fn(writer); + w.EndObject(); + for_stdout->PushBack(output.GetString()); +} + +static void Reply(RequestId id, const char *key, + const std::function &fn) { + rapidjson::StringBuffer output; + rapidjson::Writer w(output); + w.StartObject(); + w.Key("jsonrpc"); + w.String("2.0"); + w.Key("id"); + switch (id.type) { + case RequestId::kNone: + w.Null(); + break; + case RequestId::kInt: + w.Int(id.value); + break; + case RequestId::kString: + auto s = std::to_string(id.value); + w.String(s.c_str(), s.length()); + break; + } + w.Key(key); + JsonWriter writer(&w); + fn(writer); + w.EndObject(); + for_stdout->PushBack(output.GetString()); +} + +void Reply(RequestId id, const std::function &fn) { + Reply(id, "result", fn); +} + +void ReplyError(RequestId id, const std::function &fn) { + Reply(id, "error", fn); +} +} // namespace pipeline +} // namespace ccls diff --git a/src/pipeline.hh b/src/pipeline.hh new file mode 100644 index 000000000..e70d9dd8e --- /dev/null +++ b/src/pipeline.hh @@ -0,0 +1,66 @@ +#pragma once + +#include "lsp.hh" +#include "query.hh" + +#include +#include +#include +#include +#include + +namespace ccls { +struct SemaManager; +struct GroupMatch; +struct Project; +struct WorkingFiles; + +struct VFS { + struct State { + int64_t timestamp; + int step; + bool loaded; + }; + std::unordered_map state; + std::mutex mutex; + + void Clear(); + bool Loaded(const std::string &path); + bool Stamp(const std::string &path, int64_t ts, int step); +}; + +enum class IndexMode { + NonInteractive, + OnChange, + Normal, +}; + +namespace pipeline { +extern std::atomic loaded_ts, pending_index_requests; +extern int64_t tick; +void Init(); +void LaunchStdin(); +void LaunchStdout(); +void Indexer_Main(SemaManager *manager, VFS *vfs, Project *project, + WorkingFiles *wfiles); +void MainLoop(); +void Standalone(const std::string &root); + +void Index(const std::string &path, const std::vector &args, + IndexMode mode, RequestId id = {}); + +std::optional LoadIndexedContent(const std::string& path); + +void Notify(const char *method, const std::function &fn); +template void Notify(const char *method, T &result) { + Notify(method, [&](JsonWriter &w) { Reflect(w, result); }); +} + +void Reply(RequestId id, const std::function &fn); + +void ReplyError(RequestId id, const std::function &fn); +template void ReplyError(RequestId id, T &result) { + ReplyError(id, [&](JsonWriter &w) { Reflect(w, result); }); +} +} // namespace pipeline +} // namespace ccls diff --git a/src/platform.cc b/src/platform.cc deleted file mode 100644 index 84e2ece6f..000000000 --- a/src/platform.cc +++ /dev/null @@ -1,96 +0,0 @@ -#include "platform.h" - -#include -#include - -#include -#include -#include -#include -#include - -namespace { - -// See http://stackoverflow.com/a/236803 -template -void Split(const std::string& s, char delim, Out result) { - std::stringstream ss; - ss.str(s); - std::string item; - while (std::getline(ss, item, delim)) { - if (!item.empty()) - *(result++) = item; - } -} -std::vector Split(const std::string& s, char delim) { - std::vector elems; - Split(s, delim, std::back_inserter(elems)); - return elems; -} - -std::string Join(const std::vector& entries, - char delim, - size_t end) { - std::string result; - bool first = true; - for (size_t i = 0; i < end; ++i) { - if (!first) - result += delim; - first = false; - result += entries[i]; - } - return result; -} - -} // namespace - -PlatformMutex::~PlatformMutex() = default; - -PlatformScopedMutexLock::~PlatformScopedMutexLock() = default; - -PlatformSharedMemory::~PlatformSharedMemory() = default; - -void MakeDirectoryRecursive(std::string path) { - path = NormalizePath(path); - - if (TryMakeDirectory(path)) - return; - - std::string prefix = ""; - if (path[0] == '/') - prefix = "/"; - - std::vector components = Split(path, '/'); - - // Find first parent directory which doesn't exist. - int first_success = -1; - for (size_t j = 0; j < components.size(); ++j) { - size_t i = components.size() - j; - if (TryMakeDirectory(prefix + Join(components, '/', i))) { - first_success = i; - break; - } - } - - if (first_success == -1) { - LOG_S(FATAL) << "Failed to make any parent directory for " << path; - exit(1); - } - - // Make all child directories. - for (size_t i = first_success + 1; i <= components.size(); ++i) { - if (TryMakeDirectory(prefix + Join(components, '/', i)) == false) { - LOG_S(FATAL) << "Failed making directory for " << path - << " even after creating parent directories"; - exit(1); - } - } -} - -TEST_SUITE("Platform") { - TEST_CASE("Split strings") { - std::vector actual = Split("/a/b/c/", '/'); - std::vector expected{"a", "b", "c"}; - REQUIRE(actual == expected); - } -} diff --git a/src/platform.h b/src/platform.h deleted file mode 100644 index cbefb30e3..000000000 --- a/src/platform.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -struct PlatformMutex { - virtual ~PlatformMutex(); -}; -struct PlatformScopedMutexLock { - virtual ~PlatformScopedMutexLock(); -}; -struct PlatformSharedMemory { - virtual ~PlatformSharedMemory(); - void* data; - size_t capacity; - std::string name; -}; - -void PlatformInit(); - -std::string GetExecutablePath(); -std::string GetWorkingDirectory(); -std::string NormalizePath(const std::string& path); -// Creates a directory at |path|. Creates directories recursively if needed. -void MakeDirectoryRecursive(std::string path); -// Tries to create the directory given by |absolute_path|. Returns true if -// successful or if the directory already exists. Returns false otherwise. This -// does not attempt to recursively create directories. -bool TryMakeDirectory(const std::string& absolute_path); - -void SetCurrentThreadName(const std::string& thread_name); - -optional GetLastModificationTime(const std::string& absolute_path); - -void MoveFileTo(const std::string& destination, const std::string& source); -void CopyFileTo(const std::string& destination, const std::string& source); - -bool IsSymLink(const std::string& path); - -// Returns any clang arguments that are specific to the current platform. -std::vector GetPlatformClangArguments(); - -// Free any unused memory and return it to the system. -void FreeUnusedMemory(); - -// If true objective-c index tests will be run. -bool RunObjectiveCIndexTests(); - -// Stop self and wait for SIGCONT. -void TraceMe(); - -std::string GetExternalCommandOutput(const std::vector& command, - std::string_view input); diff --git a/src/platform.hh b/src/platform.hh new file mode 100644 index 000000000..076860f57 --- /dev/null +++ b/src/platform.hh @@ -0,0 +1,33 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include +#include +#include + +std::string NormalizePath(const std::string &path); + +// Free any unused memory and return it to the system. +void FreeUnusedMemory(); + +// Stop self and wait for SIGCONT. +void TraceMe(); + +std::string GetExternalCommandOutput(const std::vector &command, + std::string_view input); + +void SpawnThread(void *(*fn)(void *), void *arg); diff --git a/src/platform_posix.cc b/src/platform_posix.cc index 18a110746..9dd79726c 100644 --- a/src/platform_posix.cc +++ b/src/platform_posix.cc @@ -1,292 +1,79 @@ -#if defined(__unix__) || defined(__APPLE__) -#include "platform.h" +/* Copyright 2017-2018 ccls Authors -#include "utils.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#include "loguru.hpp" + http://www.apache.org/licenses/LICENSE-2.0 -#include -#if defined(__FreeBSD__) -#include -#include -#elif defined(__OpenBSD__) -#include -#endif +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#if defined(__unix__) || defined(__APPLE__) +#include "platform.hh" + +#include "utils.hh" #include -#include #include -#include -#include #include #include #include #include -#include #include -#include -#include // required for stat.h -#include - #include #include - -#include -#include - -#if defined(__FreeBSD__) -#include // MAXPATHLEN -#include // sysctl -#elif defined(__linux__) +#include +#include +#include +#include +#include // required for stat.h +#include +#include +#ifdef __GLIBC__ #include #endif -#include - -namespace { - -// Returns the canonicalized absolute pathname, without expanding symbolic -// links. This is a variant of realpath(2), C++ rewrite of -// https://github.com/freebsd/freebsd/blob/master/lib/libc/stdlib/realpath.c -optional RealPathNotExpandSymlink(std::string path) { - if (path.empty()) { - errno = EINVAL; - return nullopt; - } - if (path[0] == '\0') { - errno = ENOENT; - return nullopt; - } - - // Do not use PATH_MAX because it is tricky on Linux. - // See https://eklitzke.org/path-max-is-tricky - char tmp[1024]; - std::string resolved; - size_t i = 0; - struct stat sb; - if (path[0] == '/') { - resolved = "/"; - i = 1; - } else { - if (!getcwd(tmp, sizeof tmp)) - return nullopt; - resolved = tmp; - } - - while (i < path.size()) { - auto j = path.find('/', i); - if (j == std::string::npos) - j = path.size(); - auto next_token = path.substr(i, j - i); - i = j + 1; - if (resolved.back() != '/') - resolved += '/'; - if (next_token.empty() || next_token == ".") { - // Handle consequential slashes and "." - continue; - } else if (next_token == "..") { - // Strip the last path component except when it is single "/" - if (resolved.size() > 1) - resolved.resize(resolved.rfind('/', resolved.size() - 2) + 1); - continue; - } - // Append the next path component. - // Here we differ from realpath(3), we use stat(2) instead of - // lstat(2) because we do not want to resolve symlinks. - resolved += next_token; - if (stat(resolved.c_str(), &sb) != 0) - return nullopt; - if (!S_ISDIR(sb.st_mode) && j < path.size()) { - errno = ENOTDIR; - return nullopt; - } - } +#include +#include - // Remove trailing slash except when a single "/". - if (resolved.size() > 1 && resolved.back() == '/') - resolved.pop_back(); - return resolved; -} - -} // namespace - -void PlatformInit() {} - -#ifdef __APPLE__ -extern "C" int _NSGetExecutablePath(char* buf, uint32_t* bufsize); -#endif - -// See -// https://stackoverflow.com/questions/143174/how-do-i-get-the-directory-that-a-program-is-running-from -std::string GetExecutablePath() { -#ifdef __APPLE__ - uint32_t size = 0; - _NSGetExecutablePath(nullptr, &size); - char* buffer = new char[size]; - _NSGetExecutablePath(buffer, &size); - char* resolved = realpath(buffer, nullptr); - std::string result(resolved); - delete[] buffer; - free(resolved); - return result; -#elif defined(__FreeBSD__) - static const int name[] = { - CTL_KERN, - KERN_PROC, - KERN_PROC_PATHNAME, - -1, - }; - char path[MAXPATHLEN]; - size_t len = sizeof(path); - path[0] = '\0'; - (void)sysctl(name, 4, path, &len, NULL, 0); - return std::string(path); -#else - char buffer[PATH_MAX] = {0}; - if (-1 == readlink("/proc/self/exe", buffer, PATH_MAX)) - return ""; - return buffer; -#endif -} - -std::string GetWorkingDirectory() { - char result[FILENAME_MAX]; - if (!getcwd(result, sizeof(result))) - return ""; - std::string working_dir = std::string(result, strlen(result)); - EnsureEndsInSlash(working_dir); - return working_dir; -} - -std::string NormalizePath(const std::string& path) { - optional resolved = RealPathNotExpandSymlink(path); - return resolved ? *resolved : path; -} - -bool TryMakeDirectory(const std::string& absolute_path) { - const mode_t kMode = 0777; // UNIX style permissions - if (mkdir(absolute_path.c_str(), kMode) == -1) { - // Success if the directory exists. - return errno == EEXIST; - } - return true; -} - -void SetCurrentThreadName(const std::string& thread_name) { - loguru::set_thread_name(thread_name.c_str()); -#if defined(__APPLE__) - pthread_setname_np(thread_name.c_str()); -#elif defined(__FreeBSD__) || defined(__OpenBSD__) - pthread_set_name_np(pthread_self(), thread_name.c_str()); -#elif defined(__linux__) - pthread_setname_np(pthread_self(), thread_name.c_str()); -#endif -} - -optional GetLastModificationTime(const std::string& absolute_path) { - struct stat buf; - if (stat(absolute_path.c_str(), &buf) != 0) { - switch (errno) { - case ENOENT: - // std::cerr << "GetLastModificationTime: unable to find file " << - // absolute_path << std::endl; - return nullopt; - case EINVAL: - // std::cerr << "GetLastModificationTime: invalid param to _stat for - // file file " << absolute_path << std::endl; - return nullopt; - default: - // std::cerr << "GetLastModificationTime: unhandled for " << - // absolute_path << std::endl; exit(1); - return nullopt; - } - } - - return buf.st_mtime; -} - -void MoveFileTo(const std::string& dest, const std::string& source) { - // TODO/FIXME - do a real move. - CopyFileTo(dest, source); -} - -// See http://stackoverflow.com/q/13198627 -void CopyFileTo(const std::string& dest, const std::string& source) { - int fd_from = open(source.c_str(), O_RDONLY); - if (fd_from < 0) - return; - - int fd_to = open(dest.c_str(), O_WRONLY | O_CREAT, 0666); - if (fd_to < 0) - goto out_error; - - char buf[4096]; - ssize_t nread; - while (nread = read(fd_from, buf, sizeof buf), nread > 0) { - char* out_ptr = buf; - ssize_t nwritten; - - do { - nwritten = write(fd_to, out_ptr, nread); - - if (nwritten >= 0) { - nread -= nwritten; - out_ptr += nwritten; - } else if (errno != EINTR) - goto out_error; - } while (nread > 0); - } - - if (nread == 0) { - if (close(fd_to) < 0) { - fd_to = -1; - goto out_error; - } - close(fd_from); - - return; - } - -out_error: - close(fd_from); - if (fd_to >= 0) - close(fd_to); -} +#include -bool IsSymLink(const std::string& path) { - struct stat buf; - return lstat(path.c_str(), &buf) == 0 && S_ISLNK(buf.st_mode); +std::string NormalizePath(const std::string &path) { + llvm::SmallString<256> P(path); + llvm::sys::path::remove_dots(P, true); + return {P.data(), P.size()}; } void FreeUnusedMemory() { -#if defined(__GLIBC__) - malloc_trim(0); -#endif -} - -bool RunObjectiveCIndexTests() { -#if defined(__APPLE__) - return true; -#else - return false; +#ifdef __GLIBC__ + malloc_trim(4 * 1024 * 1024); #endif } void TraceMe() { // If the environment variable is defined, wait for a debugger. - // In gdb, you need to invoke `signal SIGCONT` if you want cquery to continue + // In gdb, you need to invoke `signal SIGCONT` if you want ccls to continue // after detaching. - if (getenv("CQUERY_TRACEME")) - raise(SIGTSTP); + const char *traceme = getenv("CCLS_TRACEME"); + if (traceme) + raise(traceme[0] == 's' ? SIGSTOP : SIGTSTP); } -std::string GetExternalCommandOutput(const std::vector& command, +std::string GetExternalCommandOutput(const std::vector &command, std::string_view input) { int pin[2], pout[2]; - if (pipe(pin) < 0) + if (pipe(pin) < 0) { + perror("pipe(stdin)"); return ""; + } if (pipe(pout) < 0) { + perror("pipe(stdout)"); close(pin[0]); close(pin[1]); return ""; @@ -299,9 +86,9 @@ std::string GetExternalCommandOutput(const std::vector& command, close(pin[1]); close(pout[0]); close(pout[1]); - auto argv = new char*[command.size() + 1]; + auto argv = new char *[command.size() + 1]; for (size_t i = 0; i < command.size(); i++) - argv[i] = const_cast(command[i].c_str()); + argv[i] = const_cast(command[i].c_str()); argv[command.size()] = nullptr; execvp(argv[0], argv); _Exit(127); @@ -316,8 +103,24 @@ std::string GetExternalCommandOutput(const std::vector& command, ssize_t n; while ((n = read(pin[0], buf, sizeof buf)) > 0) ret.append(buf, n); + close(pin[0]); waitpid(child, NULL, 0); return ret; } +void SpawnThread(void *(*fn)(void *), void *arg) { + pthread_t thd; + pthread_attr_t attr; + struct rlimit rlim; + size_t stack_size = 4 * 1024 * 1024; + if (getrlimit(RLIMIT_STACK, &rlim) == 0 && + rlim.rlim_cur != RLIM_INFINITY) + stack_size = rlim.rlim_cur; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_attr_setstacksize(&attr, stack_size); + pthread_create(&thd, &attr, fn, arg); + pthread_attr_destroy(&attr); +} + #endif diff --git a/src/platform_win.cc b/src/platform_win.cc index 726f964ad..31ba74e93 100644 --- a/src/platform_win.cc +++ b/src/platform_win.cc @@ -1,9 +1,22 @@ -#if defined(_WIN32) -#include "platform.h" +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#include "utils.h" + http://www.apache.org/licenses/LICENSE-2.0 -#include +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#if defined(_WIN32) +#include "platform.hh" + +#include "utils.hh" #include #include @@ -14,133 +27,42 @@ #include #include -#include -#include #include +#include -void PlatformInit() { - // We need to write to stdout in binary mode because in Windows, writing - // \n will implicitly write \r\n. Language server API will ignore a - // \r\r\n split request. - _setmode(_fileno(stdout), O_BINARY); - _setmode(_fileno(stdin), O_BINARY); -} - -// See -// https://stackoverflow.com/questions/143174/how-do-i-get-the-directory-that-a-program-is-running-from -std::string GetExecutablePath() { - char result[MAX_PATH] = {0}; - GetModuleFileName(NULL, result, MAX_PATH); - return NormalizePath(result); -} - -// See http://stackoverflow.com/a/19535628 -std::string GetWorkingDirectory() { - char result[MAX_PATH]; - std::string binary_path(result, GetModuleFileName(NULL, result, MAX_PATH)); - return binary_path.substr(0, binary_path.find_last_of("\\/") + 1); -} - -std::string NormalizePath(const std::string& path) { +std::string NormalizePath(const std::string &path) { DWORD retval = 0; TCHAR buffer[MAX_PATH] = TEXT(""); - TCHAR** lpp_part = {NULL}; + TCHAR **lpp_part = {NULL}; + std::string result; retval = GetFullPathName(path.c_str(), MAX_PATH, buffer, lpp_part); // fail, return original if (retval == 0) - return path; + result = path; + else + result = buffer; - std::string result = buffer; std::replace(result.begin(), result.end(), '\\', '/'); - // std::transform(result.begin(), result.end(), result.begin(), ::tolower); + // Normalize drive letter. + if (result.size() > 1 && result[0] >= 'a' && result[0] <= 'z' && + result[1] == ':') + result[0] = toupper(result[0]); return result; } -bool TryMakeDirectory(const std::string& absolute_path) { - if (_mkdir(absolute_path.c_str()) == -1) { - // Success if the directory exists. - return errno == EEXIST; - } - return true; -} - -// See https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx -const DWORD MS_VC_EXCEPTION = 0x406D1388; -#pragma pack(push, 8) -typedef struct tagTHREADNAME_INFO { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. -} THREADNAME_INFO; -#pragma pack(pop) -void SetCurrentThreadName(const std::string& thread_name) { - loguru::set_thread_name(thread_name.c_str()); - - THREADNAME_INFO info; - info.dwType = 0x1000; - info.szName = thread_name.c_str(); - info.dwThreadID = (DWORD)-1; - info.dwFlags = 0; - - __try { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), - (ULONG_PTR*)&info); -#ifdef _MSC_VER - } __except (EXCEPTION_EXECUTE_HANDLER) { -#else - } catch (...) { -#endif - } -} - -optional GetLastModificationTime(const std::string& absolute_path) { - struct _stat buf; - if (_stat(absolute_path.c_str(), &buf) != 0) { - switch (errno) { - case ENOENT: - // std::cerr << "GetLastModificationTime: unable to find file " << - // absolute_path << std::endl; - return nullopt; - case EINVAL: - // std::cerr << "GetLastModificationTime: invalid param to _stat for - // file file " << absolute_path << std::endl; - return nullopt; - default: - // std::cerr << "GetLastModificationTime: unhandled for " << - // absolute_path << std::endl; exit(1); - return nullopt; - } - } - - return buf.st_mtime; -} - -void MoveFileTo(const std::string& destination, const std::string& source) { - MoveFile(source.c_str(), destination.c_str()); -} - -void CopyFileTo(const std::string& destination, const std::string& source) { - CopyFile(source.c_str(), destination.c_str(), false /*failIfExists*/); -} - -bool IsSymLink(const std::string& path) { - return false; -} - void FreeUnusedMemory() {} -bool RunObjectiveCIndexTests() { - return false; -} - // TODO Wait for debugger to attach void TraceMe() {} -std::string GetExternalCommandOutput(const std::vector& command, +std::string GetExternalCommandOutput(const std::vector &command, std::string_view input) { return ""; } +void SpawnThread(void *(*fn)(void *), void *arg) { + std::thread(fn, arg).detach(); +} + #endif diff --git a/src/port.cc b/src/port.cc deleted file mode 100644 index 7f5d5be26..000000000 --- a/src/port.cc +++ /dev/null @@ -1,10 +0,0 @@ -#include "port.h" - -#include -#include - -void cquery_unreachable_internal(const char* msg, const char* file, int line) { - fprintf(stderr, "unreachable %s:%d %s\n", file, line, msg); - CQUERY_BUILTIN_UNREACHABLE; - abort(); -} diff --git a/src/port.h b/src/port.h deleted file mode 100644 index 6b5a4e3d9..000000000 --- a/src/port.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#ifndef __has_builtin -#define __has_builtin(x) 0 -#endif - -#if defined(__GNUC__) -#define ATTRIBUTE_UNUSED __attribute__((unused)) -#else -#define ATTRIBUTE_UNUSED -#endif - -// TODO GCC -#if __has_builtin(__builtin_unreachable) -#define CQUERY_BUILTIN_UNREACHABLE __builtin_unreachable() -#elif defined(_MSC_VER) -#define CQUERY_BUILTIN_UNREACHABLE __assume(false) -#else -#define CQUERY_BUILTIN_UNREACHABLE -#endif - -void cquery_unreachable_internal(const char* msg, const char* file, int line); -#ifndef NDEBUG -#define CQUERY_UNREACHABLE(msg) \ - cquery_unreachable_internal(msg, __FILE__, __LINE__) -#else -#define CQUERY_UNREACHABLE(msg) -#endif diff --git a/src/position.cc b/src/position.cc index 37c94c6e9..8211ff982 100644 --- a/src/position.cc +++ b/src/position.cc @@ -1,74 +1,49 @@ -#include "position.h" +/* Copyright 2017-2018 ccls Authors -#include +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -Position::Position() : line(-1), column(-1) {} + http://www.apache.org/licenses/LICENSE-2.0 -Position::Position(int16_t line, int16_t column) : line(line), column(column) {} +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ -Position::Position(const char* encoded) { - char* p = const_cast(encoded); - line = int16_t(strtol(p, &p, 10)) - 1; - assert(*p == ':'); - p++; - column = int16_t(strtol(p, &p, 10)) - 1; -} +#include "position.hh" -std::string Position::ToString() { - // Output looks like this: - // - // 1:2 - // - // 1 => line - // 2 => column - - std::string result; - result += std::to_string(line + 1); - result += ':'; - result += std::to_string(column + 1); - return result; -} +#include "serializer.hh" -std::string Position::ToPrettyString(const std::string& filename) { - // Output looks like this: - // - // 1:2:3 - // - // 1 => filename - // 2 => line - // 3 => column - - std::string result; - result += filename; - result += ':'; - result += std::to_string(line + 1); - result += ':'; - result += std::to_string(column + 1); - return result; -} +#include +#include -bool Position::operator==(const Position& that) const { - return line == that.line && column == that.column; -} +#include +#include +#include +#include -bool Position::operator!=(const Position& that) const { - return !(*this == that); +namespace ccls { +Pos Pos::FromString(const std::string &encoded) { + char *p = const_cast(encoded.c_str()); + int16_t line = int16_t(strtol(p, &p, 10)) - 1; + assert(*p == ':'); + p++; + int16_t column = int16_t(strtol(p, &p, 10)) - 1; + return {line, column}; } -bool Position::operator<(const Position& that) const { - if (line != that.line) - return line < that.line; - return column < that.column; +std::string Pos::ToString() { + char buf[99]; + snprintf(buf, sizeof buf, "%d:%d", line + 1, column + 1); + return buf; } -Range::Range() {} - -Range::Range(Position position) : Range(position, position) {} - -Range::Range(Position start, Position end) : start(start), end(end) {} - -Range::Range(const char* encoded) { - char* p = const_cast(encoded); +Range Range::FromString(const std::string &encoded) { + Pos start, end; + char *p = const_cast(encoded.c_str()); start.line = int16_t(strtol(p, &p, 10)) - 1; assert(*p == ':'); p++; @@ -80,101 +55,56 @@ Range::Range(const char* encoded) { assert(*p == ':'); p++; end.column = int16_t(strtol(p, nullptr, 10)) - 1; + return {start, end}; } bool Range::Contains(int line, int column) const { - if (line == start.line && line == end.line) - return column >= start.column && column < end.column; - if (line == start.line) - return column >= start.column; - if (line == end.line) - return column < end.column; - if (line > start.line && line < end.line) - return true; - return false; -} - -Range Range::RemovePrefix(Position position) const { - return {std::min(std::max(position, start), end), end}; + if (line > INT16_MAX) + return false; + Pos p{(int16_t)line, (int16_t)std::min(column, INT16_MAX)}; + return !(p < start) && p < end; } std::string Range::ToString() { - // Output looks like this: - // - // 1:2-3:4 - // - // 1 => start line - // 2 => start column - // 3 => end line - // 4 => end column - - std::string output; - - output += std::to_string(start.line + 1); - output += ':'; - output += std::to_string(start.column + 1); - output += '-'; - output += std::to_string(end.line + 1); - output += ':'; - output += std::to_string(end.column + 1); - - return output; + char buf[99]; + snprintf(buf, sizeof buf, "%d:%d-%d:%d", start.line + 1, start.column + 1, + end.line + 1, end.column + 1); + return buf; } -bool Range::operator==(const Range& that) const { - return start == that.start && end == that.end; +void Reflect(JsonReader &vis, Pos &v) { v = Pos::FromString(vis.GetString()); } +void Reflect(JsonReader &vis, Range &v) { + v = Range::FromString(vis.GetString()); } -bool Range::operator!=(const Range& that) const { - return !(*this == that); +void Reflect(JsonWriter &vis, Pos &v) { + std::string output = v.ToString(); + vis.String(output.c_str(), output.size()); } - -bool Range::operator<(const Range& that) const { - if (start != that.start) - return start < that.start; - return end < that.end; +void Reflect(JsonWriter &vis, Range &v) { + std::string output = v.ToString(); + vis.String(output.c_str(), output.size()); } -// Position -void Reflect(Reader& visitor, Position& value) { - if (visitor.Format() == SerializeFormat::Json) { - std::string s = visitor.GetString(); - value = Position(s.c_str()); - } else { - Reflect(visitor, value.line); - Reflect(visitor, value.column); - } +void Reflect(BinaryReader &visitor, Pos &value) { + Reflect(visitor, value.line); + Reflect(visitor, value.column); } -void Reflect(Writer& visitor, Position& value) { - if (visitor.Format() == SerializeFormat::Json) { - std::string output = value.ToString(); - visitor.String(output.c_str(), output.size()); - } else { - Reflect(visitor, value.line); - Reflect(visitor, value.column); - } +void Reflect(BinaryReader &visitor, Range &value) { + Reflect(visitor, value.start.line); + Reflect(visitor, value.start.column); + Reflect(visitor, value.end.line); + Reflect(visitor, value.end.column); } -// Range -void Reflect(Reader& visitor, Range& value) { - if (visitor.Format() == SerializeFormat::Json) { - std::string s = visitor.GetString(); - value = Range(s.c_str()); - } else { - Reflect(visitor, value.start.line); - Reflect(visitor, value.start.column); - Reflect(visitor, value.end.line); - Reflect(visitor, value.end.column); - } +void Reflect(BinaryWriter &vis, Pos &v) { + Reflect(vis, v.line); + Reflect(vis, v.column); } -void Reflect(Writer& visitor, Range& value) { - if (visitor.Format() == SerializeFormat::Json) { - std::string output = value.ToString(); - visitor.String(output.c_str(), output.size()); - } else { - Reflect(visitor, value.start.line); - Reflect(visitor, value.start.column); - Reflect(visitor, value.end.line); - Reflect(visitor, value.end.column); - } +void Reflect(BinaryWriter &vis, Range &v) { + Reflect(vis, v.start.line); + Reflect(vis, v.start.column); + Reflect(vis, v.end.line); + Reflect(vis, v.end.column); } +} // namespace ccls diff --git a/src/position.h b/src/position.h deleted file mode 100644 index 8d238b555..000000000 --- a/src/position.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -#include "maybe.h" -#include "serializer.h" -#include "utils.h" - -#include -#include - -struct Position { - int16_t line; - int16_t column; - - Position(); - Position(int16_t line, int16_t column); - explicit Position(const char* encoded); - - bool HasValueForMaybe_() const { return line >= 0; } - std::string ToString(); - std::string ToPrettyString(const std::string& filename); - - // Compare two Positions and check if they are equal. Ignores the value of - // |interesting|. - bool operator==(const Position& that) const; - bool operator!=(const Position& that) const; - bool operator<(const Position& that) const; -}; -static_assert( - sizeof(Position) == 4, - "Investigate, Position should be 32-bits for indexer size reasons"); -MAKE_HASHABLE(Position, t.line, t.column); - -struct Range { - Position start; - Position end; - - Range(); - explicit Range(Position position); - Range(Position start, Position end); - explicit Range(const char* encoded); - - bool HasValueForMaybe_() const { return start.HasValueForMaybe_(); } - bool Contains(int line, int column) const; - Range RemovePrefix(Position position) const; - - std::string ToString(); - - bool operator==(const Range& that) const; - bool operator!=(const Range& that) const; - bool operator<(const Range& that) const; -}; -MAKE_HASHABLE(Range, t.start, t.end); - -// Reflection -void Reflect(Reader& visitor, Position& value); -void Reflect(Writer& visitor, Position& value); -void Reflect(Reader& visitor, Range& value); -void Reflect(Writer& visitor, Range& value); diff --git a/src/position.hh b/src/position.hh new file mode 100644 index 000000000..d5bf241b8 --- /dev/null +++ b/src/position.hh @@ -0,0 +1,94 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "utils.hh" + +#include +#include + +namespace ccls { +struct Pos { + int16_t line = -1; + int16_t column = -1; + + static Pos FromString(const std::string &encoded); + + bool Valid() const { return line >= 0; } + std::string ToString(); + + // Compare two Positions and check if they are equal. Ignores the value of + // |interesting|. + bool operator==(const Pos &o) const { + return line == o.line && column == o.column; + } + bool operator<(const Pos &o) const { + if (line != o.line) + return line < o.line; + return column < o.column; + } + bool operator<=(const Pos &o) const { return !(o < *this); } +}; + +struct Range { + Pos start; + Pos end; + + static Range FromString(const std::string &encoded); + + bool Valid() const { return start.Valid(); } + bool Contains(int line, int column) const; + + std::string ToString(); + + bool operator==(const Range &o) const { + return start == o.start && end == o.end; + } + bool operator<(const Range &o) const { + return !(start == o.start) ? start < o.start : end < o.end; + } +}; + +// Reflection +struct JsonReader; +struct JsonWriter; +struct BinaryReader; +struct BinaryWriter; + +void Reflect(JsonReader &visitor, Pos &value); +void Reflect(JsonReader &visitor, Range &value); +void Reflect(JsonWriter &visitor, Pos &value); +void Reflect(JsonWriter &visitor, Range &value); +void Reflect(BinaryReader &visitor, Pos &value); +void Reflect(BinaryReader &visitor, Range &value); +void Reflect(BinaryWriter &visitor, Pos &value); +void Reflect(BinaryWriter &visitor, Range &value); +} // namespace ccls + +namespace std { +template <> struct hash { + std::size_t operator()(ccls::Range x) const { + union { + ccls::Range range; + uint64_t u64; + } u{x}; + static_assert(sizeof(ccls::Range) == 8); + return hash()(u.u64); + } +}; +} // namespace std + +MAKE_HASHABLE(ccls::Pos, t.line, t.column); diff --git a/src/project.cc b/src/project.cc index 35d86aab8..5a4887657 100644 --- a/src/project.cc +++ b/src/project.cc @@ -1,586 +1,474 @@ -#include "project.h" +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "project.hh" + +#include "clang_tu.hh" // llvm::vfs +#include "filesystem.hh" +#include "log.hh" +#include "pipeline.hh" +#include "platform.hh" +#include "utils.hh" +#include "working_files.hh" + +#include +#include +#include +#include +#include +#include +#include +#include -#include "cache_manager.h" -#include "clang_utils.h" -#include "language.h" -#include "match.h" -#include "platform.h" -#include "queue_manager.h" -#include "serializers/json.h" -#include "timer.h" -#include "utils.h" -#include "working_files.h" - -#include -#include #include -#include #if defined(__unix__) || defined(__APPLE__) #include #endif -#include -#include -#include -#include +#include #include #include -struct CompileCommandsEntry { - std::string directory; - std::string file; - std::string command; - std::vector args; -}; -MAKE_REFLECT_STRUCT(CompileCommandsEntry, directory, file, command, args); - -namespace { - -bool g_disable_normalize_path_for_test = false; - -std::string NormalizePathWithTestOptOut(const std::string& path) { - if (g_disable_normalize_path_for_test) { - // Add a & so we can test to verify a path is normalized. - return "&" + path; - } - return NormalizePath(path); +using namespace clang; +using namespace llvm; + +namespace ccls { +std::pair lookupExtension(std::string_view filename) { + using namespace clang::driver; + auto I = types::lookupTypeForExtension( + sys::path::extension({filename.data(), filename.size()}).substr(1)); + bool header = I == types::TY_CHeader || I == types::TY_CXXHeader || + I == types::TY_ObjCXXHeader; + bool objc = types::isObjC(I); + LanguageId ret; + if (types::isCXX(I)) + ret = objc ? LanguageId::ObjCpp : LanguageId::Cpp; + else if (objc) + ret = LanguageId::ObjC; + else if (I == types::TY_C || I == types::TY_CHeader) + ret = LanguageId::C; + else + ret = LanguageId::Unknown; + return {ret, header}; } -bool IsUnixAbsolutePath(const std::string& path) { - return !path.empty() && path[0] == '/'; -} - -bool IsWindowsAbsolutePath(const std::string& path) { - auto is_drive_letter = [](char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - }; - - return path.size() > 3 && path[1] == ':' && - (path[2] == '/' || path[2] == '\\') && is_drive_letter(path[0]); -} +namespace { -enum class ProjectMode { CompileCommandsJson, DotCquery, ExternalCommand }; +enum class ProjectMode { CompileCommandsJson, DotCcls, ExternalCommand }; struct ProjectConfig { std::unordered_set quote_dirs; std::unordered_set angle_dirs; - std::vector extra_flags; - std::string project_dir; - std::string resource_dir; + std::string root; ProjectMode mode = ProjectMode::CompileCommandsJson; }; -// TODO: See -// https://github.com/Valloric/ycmd/blob/master/ycmd/completers/cpp/flags.py. -std::vector kBlacklistMulti = { - "-MF", "-MT", "-MQ", "-o", "--serialize-diagnostics", "-Xclang"}; - -// Blacklisted flags which are always removed from the command line. -std::vector kBlacklist = { - "-c", "-MP", "-MD", "-MMD", "--fcolor-diagnostics", +enum OptionClass { + EqOrJoinOrSep, + EqOrSep, + JoinOrSep, + Separate, }; -// Arguments which are followed by a potentially relative path. We need to make -// all relative paths absolute, otherwise libclang will not resolve them. -std::vector kPathArgs = { - "-I", "-iquote", "-isystem", "--sysroot=", - "-isysroot", "-gcc-toolchain", "-include-pch", "-iframework", - "-F", "-imacros", "-include", "/I"}; - -// Arguments which always require an absolute path, ie, clang -working-directory -// does not work as expected. Argument processing assumes that this is a subset -// of kPathArgs. -std::vector kNormalizePathArgs = {"--sysroot="}; - -// Arguments whose path arguments should be injected into include dir lookup -// for #include completion. -std::vector kQuoteIncludeArgs = {"-iquote"}; -std::vector kAngleIncludeArgs = {"-I", "/I", "-isystem"}; - -bool ShouldAddToQuoteIncludes(const std::string& arg) { - return StartsWithAny(arg, kQuoteIncludeArgs); -} -bool ShouldAddToAngleIncludes(const std::string& arg) { - return StartsWithAny(arg, kAngleIncludeArgs); -} - -LanguageId SourceFileLanguage(const std::string& path) { - if (EndsWith(path, ".c")) - return LanguageId::C; - else if (EndsWith(path, ".cpp") || EndsWith(path, ".cc")) - return LanguageId::Cpp; - else if (EndsWith(path, ".mm")) - return LanguageId::ObjCpp; - else if (EndsWith(path, ".m")) - return LanguageId::ObjC; - return LanguageId::Unknown; -} - -Project::Entry GetCompilationEntryFromCompileCommandEntry( - Config* init_opts, - ProjectConfig* config, - const CompileCommandsEntry& entry) { - auto cleanup_maybe_relative_path = [&](const std::string& path) { - // TODO/FIXME: Normalization will fail for paths that do not exist. Should - // it return an optional? - assert(!path.empty()); - if (entry.directory.empty() || IsUnixAbsolutePath(path) || - IsWindowsAbsolutePath(path)) { - // We still want to normalize, as the path may contain .. characters. - return NormalizePathWithTestOptOut(path); - } - if (EndsWith(entry.directory, "/")) - return NormalizePathWithTestOptOut(entry.directory + path); - return NormalizePathWithTestOptOut(entry.directory + "/" + path); - }; - - Project::Entry result; - result.filename = NormalizePathWithTestOptOut(entry.file); - const std::string base_name = GetBaseName(entry.file); - - // Expand %c %cpp %clang - std::vector args; - const LanguageId lang = SourceFileLanguage(entry.file); - for (const std::string& arg : entry.args) { - if (arg.compare(0, 3, "%c ") == 0) { - if (lang == LanguageId::C) - args.push_back(arg.substr(3)); - } else if (arg.compare(0, 5, "%cpp ") == 0) { - if (lang == LanguageId::Cpp) - args.push_back(arg.substr(5)); - } else if (arg == "%clang") { - args.push_back(lang == LanguageId::Cpp ? "clang++" : "clang"); - } else { - args.push_back(arg); +struct ProjectProcessor { + ProjectConfig *config; + std::unordered_set command_set; + StringSet<> excludeArgs; + ProjectProcessor(ProjectConfig *config) : config(config) { + for (auto &arg : g_config->clang.excludeArgs) + excludeArgs.insert(arg); + } + + void Process(Project::Entry &entry) { + const std::string base_name = sys::path::filename(entry.filename); + + // Expand %c %cpp %clang + std::vector args; + args.reserve(entry.args.size() + g_config->clang.extraArgs.size() + 1); + const LanguageId lang = lookupExtension(entry.filename).first; + for (const char *arg : entry.args) { + StringRef A(arg); + if (A == "%clang") { + args.push_back(lang == LanguageId::Cpp || lang == LanguageId::ObjCpp + ? "clang++" + : "clang"); + } else if (A[0] == '%') { + bool ok = false; + for (;;) { + if (A.consume_front("%c ")) + ok |= lang == LanguageId::C; + else if (A.consume_front("%cpp ")) + ok |= lang == LanguageId::Cpp; + else if (A.consume_front("%objective-c ")) + ok |= lang == LanguageId::ObjC; + else if (A.consume_front("%objective-cpp ")) + ok |= lang == LanguageId::ObjCpp; + else + break; + } + if (ok) + args.push_back(A.data()); + } else if (!excludeArgs.count(A)) { + args.push_back(arg); + } } - } - if (args.empty()) - return result; - - bool clang_cl = strstr(args[0].c_str(), "clang-cl") || - strstr(args[0].c_str(), "cl.exe") || - AnyStartsWith(args, "--driver-mode=cl"); - size_t i = 1; - - // If |compilationDatabaseCommand| is specified, the external command provides - // us the JSON compilation database which should be strict. We should do very - // little processing on |args|. - if (config->mode != ProjectMode::ExternalCommand && !clang_cl) { - // Strip all arguments consisting the compiler command, - // as there may be non-compiler related commands beforehand, - // ie, compiler schedular such as goma. This allows correct parsing for - // command lines like "goma clang -c foo". - std::string::size_type dot; - while (i < args.size() && args[i][0] != '-' && - // Do not skip over main source filename - NormalizePathWithTestOptOut(args[i]) != result.filename && - // There may be other filenames (e.g. more than one source filenames) - // preceding main source filename. We use a heuristic here. `.` may - // occur in both command names and source filenames. If `.` occurs in - // the last 4 bytes of args[i] and not followed by a digit, e.g. - // .c .cpp, We take it as a source filename. Others (like ./a/b/goma - // clang-4.0) are seen as commands. - ((dot = args[i].rfind('.')) == std::string::npos || - dot + 4 < args[i].size() || isdigit(args[i][dot + 1]) || - !args[i].compare(dot + 1, 3, "exe"))) - ++i; - } - // Compiler driver. - result.args.push_back(args[i - 1]); - - // Add -working-directory if not provided. - if (!AnyStartsWith(args, "-working-directory")) - result.args.emplace_back("-working-directory=" + entry.directory); - - bool next_flag_is_path = false; - bool add_next_flag_to_quote_dirs = false; - bool add_next_flag_to_angle_dirs = false; - - // Note that when processing paths, some arguments support multiple forms, ie, - // {"-Ifoo"} or {"-I", "foo"}. Support both styles. - - result.args.reserve(args.size() + config->extra_flags.size()); - for (; i < args.size(); ++i) { - std::string arg = args[i]; - - // If blacklist skip. - if (!next_flag_is_path) { - if (StartsWithAny(arg, kBlacklistMulti)) { - ++i; + if (args.empty()) + return; + for (const std::string &arg : g_config->clang.extraArgs) + args.push_back(Intern(arg)); + + size_t hash = std::hash{}(entry.directory); + bool OPT_o = false; + for (auto &arg : args) { + bool last_o = OPT_o; + OPT_o = false; + if (arg[0] == '-') { + OPT_o = arg[1] == 'o' && arg[2] == '\0'; + if (OPT_o || arg[1] == 'D' || arg[1] == 'W') + continue; + } else if (last_o) { continue; + } else if (sys::path::filename(arg) == base_name) { + LanguageId lang = lookupExtension(arg).first; + if (lang != LanguageId::Unknown) { + hash_combine(hash, (size_t)lang); + continue; + } } - if (StartsWithAny(arg, kBlacklist)) - continue; + hash_combine(hash, std::hash{}(arg)); } + args.push_back(Intern("-working-directory=" + entry.directory)); + entry.args = args; +#if LLVM_VERSION_MAJOR < 8 + args.push_back("-fsyntax-only"); + if (!command_set.insert(hash).second) + return; - // Finish processing path for the previous argument, which was a switch. - // {"-I", "foo"} style. - if (next_flag_is_path) { - std::string normalized_arg = cleanup_maybe_relative_path(arg); - if (add_next_flag_to_quote_dirs) - config->quote_dirs.insert(normalized_arg); - if (add_next_flag_to_angle_dirs) - config->angle_dirs.insert(normalized_arg); - if (clang_cl) - arg = normalized_arg; - - next_flag_is_path = false; - add_next_flag_to_quote_dirs = false; - add_next_flag_to_angle_dirs = false; - } else { - // Check to see if arg is a path and needs to be updated. - for (const std::string& flag_type : kPathArgs) { - // {"-I", "foo"} style. - if (arg == flag_type) { - next_flag_is_path = true; - add_next_flag_to_quote_dirs = ShouldAddToQuoteIncludes(arg); - add_next_flag_to_angle_dirs = ShouldAddToAngleIncludes(arg); - break; - } + // a weird C++ deduction guide heap-use-after-free causes libclang to crash. + IgnoringDiagConsumer DiagC; + IntrusiveRefCntPtr DiagOpts(new DiagnosticOptions()); + DiagnosticsEngine Diags( + IntrusiveRefCntPtr(new DiagnosticIDs()), &*DiagOpts, + &DiagC, false); + + driver::Driver Driver(args[0], llvm::sys::getDefaultTargetTriple(), Diags); + auto TargetAndMode = + driver::ToolChain::getTargetAndModeFromProgramName(args[0]); + if (!TargetAndMode.TargetPrefix.empty()) { + const char *arr[] = {"-target", TargetAndMode.TargetPrefix.c_str()}; + args.insert(args.begin() + 1, std::begin(arr), std::end(arr)); + Driver.setTargetAndMode(TargetAndMode); + } + Driver.setCheckInputsExist(false); - // {"-Ifoo"} style. - if (StartsWith(arg, flag_type)) { - std::string path = arg.substr(flag_type.size()); - assert(!path.empty()); - path = cleanup_maybe_relative_path(path); - if (clang_cl || StartsWithAny(arg, kNormalizePathArgs)) - arg = flag_type + path; - if (ShouldAddToQuoteIncludes(flag_type)) - config->quote_dirs.insert(path); - if (ShouldAddToAngleIncludes(flag_type)) - config->angle_dirs.insert(path); - break; - } + std::unique_ptr C(Driver.BuildCompilation(args)); + const driver::JobList &Jobs = C->getJobs(); + if (Jobs.size() != 1) + return; + const auto &CCArgs = Jobs.begin()->getArguments(); + + auto CI = std::make_unique(); + CompilerInvocation::CreateFromArgs(*CI, CCArgs.data(), + CCArgs.data() + CCArgs.size(), Diags); + CI->getFrontendOpts().DisableFree = false; + CI->getCodeGenOpts().DisableFree = false; + + HeaderSearchOptions &HeaderOpts = CI->getHeaderSearchOpts(); + for (auto &E : HeaderOpts.UserEntries) { + std::string path = + NormalizePath(ResolveIfRelative(entry.directory, E.Path)); + switch (E.Group) { + default: + config->angle_dirs.insert(path); + break; + case frontend::Quoted: + config->quote_dirs.insert(path); + break; + case frontend::Angled: + config->angle_dirs.insert(path); + config->quote_dirs.insert(path); + break; } - - // This is most likely the file path we will be passing to clang. The - // path needs to be absolute, otherwise clang_codeCompleteAt is extremely - // slow. See - // https://github.com/cquery-project/cquery/commit/af63df09d57d765ce12d40007bf56302a0446678. - if (EndsWith(arg, base_name)) - arg = cleanup_maybe_relative_path(arg); - // TODO Exclude .a .o to make link command in compile_commands.json work. - // Also, clang_parseTranslationUnit2FullArgv does not seem to accept - // multiple source filenames. - else if (EndsWith(arg, ".a") || EndsWith(arg, ".o")) - continue; } - - result.args.push_back(arg); - } - - // We don't do any special processing on user-given extra flags. - for (const auto& flag : config->extra_flags) - result.args.push_back(flag); - - // Add -resource-dir so clang can correctly resolve system includes like - // - if (!AnyStartsWith(result.args, "-resource-dir")) - result.args.push_back("-resource-dir=" + config->resource_dir); - - // There could be a clang version mismatch between what the project uses and - // what cquery uses. Make sure we do not emit warnings for mismatched options. - if (!AnyStartsWith(result.args, "-Wno-unknown-warning-option")) - result.args.push_back("-Wno-unknown-warning-option"); - - // Using -fparse-all-comments enables documentation in the indexer and in - // code completion. - if (init_opts->index.comments > 1 && - !AnyStartsWith(result.args, "-fparse-all-comments")) { - result.args.push_back("-fparse-all-comments"); +#endif } +}; - return result; -} - -std::vector ReadCompilerArgumentsFromFile( - const std::string& path) { - std::vector args; - for (std::string line : ReadLinesWithEnding(path)) { - TrimInPlace(line); - if (line.empty() || StartsWith(line, "#")) - continue; - args.push_back(line); +std::vector +ReadCompilerArgumentsFromFile(const std::string &path) { + auto MBOrErr = MemoryBuffer::getFile(path); + if (!MBOrErr) + return {}; + std::vector args; + for (line_iterator I(*MBOrErr.get(), true, '#'), E; I != E; ++I) { + std::string line = *I; + DoPathMapping(line); + args.push_back(Intern(line)); } return args; } -std::vector LoadFromDirectoryListing(Config* init_opts, - ProjectConfig* config) { +std::vector LoadFromDirectoryListing(ProjectConfig *config) { std::vector result; - config->mode = ProjectMode::DotCquery; - LOG_IF_S(WARNING, !FileExists(config->project_dir + "/.cquery") && - config->extra_flags.empty()) - << "cquery has no clang arguments. Considering adding either a " - "compile_commands.json or .cquery file. See the cquery README for " - "more information."; + config->mode = ProjectMode::DotCcls; - std::unordered_map> folder_args; + std::unordered_map> folder_args; std::vector files; + const std::string &root = config->root; - GetFilesInFolder(config->project_dir, true /*recursive*/, + GetFilesInFolder(root, true /*recursive*/, true /*add_folder_to_path*/, - [&folder_args, &files](const std::string& path) { - if (SourceFileLanguage(path) != LanguageId::Unknown) { + [&folder_args, &files](const std::string &path) { + std::pair lang = lookupExtension(path); + if (lang.first != LanguageId::Unknown && !lang.second) { files.push_back(path); - } else if (GetBaseName(path) == ".cquery") { - LOG_S(INFO) << "Using .cquery arguments from " << path; - folder_args.emplace(GetDirName(path), - ReadCompilerArgumentsFromFile(path)); + } else if (sys::path::filename(path) == ".ccls") { + std::vector args = ReadCompilerArgumentsFromFile(path); + folder_args.emplace(sys::path::parent_path(path), args); + std::string l; + for (size_t i = 0; i < args.size(); i++) { + if (i) + l += ' '; + l += args[i]; + } + LOG_S(INFO) << "use " << path << ": " << l; } }); - const auto& project_dir_args = folder_args[config->project_dir]; - LOG_IF_S(INFO, !project_dir_args.empty()) - << "Using .cquery arguments " << StringJoin(project_dir_args); - - auto GetCompilerArgumentForFile = [&config, - &folder_args](const std::string& path) { - for (std::string cur = GetDirName(path);; cur = GetDirName(cur)) { + auto GetCompilerArgumentForFile = [&root, + &folder_args](std::string cur) { + while (!(cur = sys::path::parent_path(cur)).empty()) { auto it = folder_args.find(cur); if (it != folder_args.end()) return it->second; std::string normalized = NormalizePath(cur); // Break if outside of the project root. - if (normalized.size() <= config->project_dir.size() || - normalized.compare(0, config->project_dir.size(), - config->project_dir) != 0) + if (normalized.size() <= root.size() || + normalized.compare(0, root.size(), root) != 0) break; } - return folder_args[config->project_dir]; + return folder_args[root]; }; - for (const std::string& file : files) { - CompileCommandsEntry e; - e.directory = config->project_dir; - e.file = file; + ProjectProcessor proc(config); + for (const std::string &file : files) { + Project::Entry e; + e.root = config->root; + e.directory = config->root; + e.filename = file; e.args = GetCompilerArgumentForFile(file); if (e.args.empty()) - e.args.push_back("%clang"); // Add a Dummy. - e.args.push_back(e.file); - result.push_back( - GetCompilationEntryFromCompileCommandEntry(init_opts, config, e)); + e.args.push_back("%clang"); + e.args.push_back(Intern(e.filename)); + proc.Process(e); + result.push_back(e); } return result; } -std::vector LoadCompilationEntriesFromDirectory( - Config* config, - ProjectConfig* project, - const std::string& opt_compilation_db_dir) { - // If there is a .cquery file always load using directory listing. - if (FileExists(project->project_dir + ".cquery")) - return LoadFromDirectoryListing(config, project); +std::vector +LoadEntriesFromDirectory(ProjectConfig *project, + const std::string &opt_compdb_dir) { + // If there is a .ccls file always load using directory listing. + SmallString<256> Path, CclsPath; + sys::path::append(CclsPath, project->root, ".ccls"); + if (sys::fs::exists(CclsPath)) + return LoadFromDirectoryListing(project); // If |compilationDatabaseCommand| is specified, execute it to get the compdb. std::string comp_db_dir; - if (config->compilationDatabaseCommand.empty()) { + if (g_config->compilationDatabaseCommand.empty()) { project->mode = ProjectMode::CompileCommandsJson; // Try to load compile_commands.json, but fallback to a project listing. - comp_db_dir = opt_compilation_db_dir.empty() ? project->project_dir - : opt_compilation_db_dir; + comp_db_dir = opt_compdb_dir.empty() ? project->root : opt_compdb_dir; + sys::path::append(Path, comp_db_dir, "compile_commands.json"); } else { project->mode = ProjectMode::ExternalCommand; #ifdef _WIN32 // TODO #else - char tmpdir[] = "/tmp/cquery-compdb-XXXXXX"; + char tmpdir[] = "/tmp/ccls-compdb-XXXXXX"; if (!mkdtemp(tmpdir)) return {}; comp_db_dir = tmpdir; + sys::path::append(Path, comp_db_dir, "compile_commands.json"); rapidjson::StringBuffer input; rapidjson::Writer writer(input); JsonWriter json_writer(&writer); - Reflect(json_writer, *config); + Reflect(json_writer, *g_config); std::string contents = GetExternalCommandOutput( - std::vector{config->compilationDatabaseCommand, - project->project_dir}, + std::vector{g_config->compilationDatabaseCommand, + project->root}, input.GetString()); - std::ofstream(comp_db_dir + "/compile_commands.json") << contents; + FILE *fout = fopen(Path.c_str(), "wb"); + fwrite(contents.c_str(), contents.size(), 1, fout); + fclose(fout); #endif } - LOG_S(INFO) << "Trying to load compile_commands.json"; - CXCompilationDatabase_Error cx_db_load_error; - CXCompilationDatabase cx_db = clang_CompilationDatabase_fromDirectory( - comp_db_dir.c_str(), &cx_db_load_error); - if (!config->compilationDatabaseCommand.empty()) { + std::string err_msg; + std::unique_ptr CDB = + tooling::CompilationDatabase::loadFromDirectory(comp_db_dir, err_msg); + if (!g_config->compilationDatabaseCommand.empty()) { #ifdef _WIN32 // TODO #else - unlink((comp_db_dir + "/compile_commands.json").c_str()); + unlink(Path.c_str()); rmdir(comp_db_dir.c_str()); #endif } - - if (cx_db_load_error == CXCompilationDatabase_CanNotLoadDatabase) { - LOG_S(INFO) << "Unable to load compile_commands.json located at \"" - << comp_db_dir << "\"; using directory listing instead."; - return LoadFromDirectoryListing(config, project); + if (!CDB) { + LOG_S(WARNING) << "no .ccls or compile_commands.json . Consider adding one"; + return LoadFromDirectoryListing(project); } - Timer clang_time; - Timer our_time; - clang_time.Pause(); - our_time.Pause(); - - clang_time.Resume(); - CXCompileCommands cx_commands = - clang_CompilationDatabase_getAllCompileCommands(cx_db); - unsigned int num_commands = clang_CompileCommands_getSize(cx_commands); - clang_time.Pause(); + LOG_S(INFO) << "loaded " << Path.c_str(); + StringSet<> Seen; std::vector result; - for (unsigned int i = 0; i < num_commands; i++) { - clang_time.Resume(); - CXCompileCommand cx_command = - clang_CompileCommands_getCommand(cx_commands, i); - - std::string directory = - ToString(clang_CompileCommand_getDirectory(cx_command)); - std::string relative_filename = - ToString(clang_CompileCommand_getFilename(cx_command)); - - unsigned num_args = clang_CompileCommand_getNumArgs(cx_command); - CompileCommandsEntry entry; - entry.args.reserve(num_args); - for (unsigned j = 0; j < num_args; ++j) { - entry.args.push_back( - ToString(clang_CompileCommand_getArg(cx_command, j))); + ProjectProcessor proc(project); + for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) { + static bool once; + Project::Entry entry; + entry.root = project->root; + DoPathMapping(entry.root); + entry.directory = NormalizePath(Cmd.Directory); + DoPathMapping(entry.directory); + entry.filename = + NormalizePath(ResolveIfRelative(entry.directory, Cmd.Filename)); + DoPathMapping(entry.filename); + std::vector args = std::move(Cmd.CommandLine); + entry.args.reserve(args.size()); + for (std::string &arg : args) { + DoPathMapping(arg); + entry.args.push_back(Intern(arg)); } - clang_time.Pause(); // TODO: don't call ToString in this block. - // LOG_S(INFO) << "Got args " << StringJoin(entry.args); - our_time.Resume(); - entry.directory = directory; - std::string absolute_filename; - if (IsUnixAbsolutePath(relative_filename) || - IsWindowsAbsolutePath(relative_filename)) - absolute_filename = relative_filename; - else - absolute_filename = directory + "/" + relative_filename; - entry.file = NormalizePathWithTestOptOut(absolute_filename); + // Work around relative --sysroot= as it isn't affected by + // -working-directory=. chdir is thread hostile but this function runs + // before indexers do actual work and it works when there is only one + // workspace folder. + if (!once) { + once = true; + llvm::vfs::getRealFileSystem()->setCurrentWorkingDirectory( + entry.directory); + } + proc.Process(entry); - result.push_back( - GetCompilationEntryFromCompileCommandEntry(config, project, entry)); - our_time.Pause(); + if (Seen.insert(entry.filename).second) + result.push_back(entry); } - - clang_time.Resume(); - clang_CompileCommands_dispose(cx_commands); - clang_CompilationDatabase_dispose(cx_db); - clang_time.Pause(); - - clang_time.ResetAndPrint("compile_commands.json clang time"); - our_time.ResetAndPrint("compile_commands.json our time"); return result; } // Computes a score based on how well |a| and |b| match. This is used for // argument guessing. -int ComputeGuessScore(const std::string& a, const std::string& b) { - const int kMatchPrefixWeight = 100; - const int kMismatchDirectoryWeight = 100; - const int kMatchPostfixWeight = 1; - - int score = 0; - size_t i = 0; - - // Increase score based on matching prefix. - for (i = 0; i < a.size() && i < b.size(); ++i) { - if (a[i] != b[i]) - break; - score += kMatchPrefixWeight; - } - - // Reduce score based on mismatched directory distance. - for (size_t j = i; j < a.size(); ++j) { - if (a[j] == '/') - score -= kMismatchDirectoryWeight; - } - for (size_t j = i; j < b.size(); ++j) { - if (b[j] == '/') - score -= kMismatchDirectoryWeight; - } - - // Increase score based on common ending. Don't increase as much as matching - // prefix or directory distance. - for (size_t offset = 1; offset <= a.size() && offset <= b.size(); ++offset) { - if (a[a.size() - offset] != b[b.size() - offset]) - break; - score += kMatchPostfixWeight; - } - +int ComputeGuessScore(std::string_view a, std::string_view b) { + // Increase score based on common prefix and suffix. Prefixes are prioritized. + if (a.size() > b.size()) + std::swap(a, b); + size_t i = std::mismatch(a.begin(), a.end(), b.begin()).first - a.begin(); + size_t j = std::mismatch(a.rbegin(), a.rend(), b.rbegin()).first - a.rbegin(); + int score = 10 * i + j; + if (i + j < b.size()) + score -= 100 * (std::count(a.begin() + i, a.end() - j, '/') + + std::count(b.begin() + i, b.end() - j, '/')); return score; } -} // namespace +} // namespace -void Project::Load(Config* config, const std::string& root_directory) { - // Load data. +void Project::Load(const std::string &root) { + assert(root.back() == '/'); ProjectConfig project; - project.extra_flags = config->extraClangArguments; - project.project_dir = root_directory; - project.resource_dir = config->resourceDirectory; - entries = LoadCompilationEntriesFromDirectory( - config, &project, config->compilationDatabaseDirectory); - - // Cleanup / postprocess include directories. - quote_include_directories.assign(project.quote_dirs.begin(), - project.quote_dirs.end()); - angle_include_directories.assign(project.angle_dirs.begin(), - project.angle_dirs.end()); - for (std::string& path : quote_include_directories) { + project.root = root; + Folder &folder = root2folder[root]; + + folder.entries = LoadEntriesFromDirectory( + &project, g_config->compilationDatabaseDirectory); + folder.quote_search_list.assign(project.quote_dirs.begin(), + project.quote_dirs.end()); + folder.angle_search_list.assign(project.angle_dirs.begin(), + project.angle_dirs.end()); + for (std::string &path : folder.angle_search_list) { EnsureEndsInSlash(path); - LOG_S(INFO) << "quote_include_dir: " << path; + LOG_S(INFO) << "angle search: " << path; } - for (std::string& path : angle_include_directories) { + for (std::string &path : folder.quote_search_list) { EnsureEndsInSlash(path); - LOG_S(INFO) << "angle_include_dir: " << path; + LOG_S(INFO) << "quote search: " << path; } // Setup project entries. - absolute_path_to_entry_index_.resize(entries.size()); - for (int i = 0; i < entries.size(); ++i) - absolute_path_to_entry_index_[entries[i].filename] = i; + std::lock_guard lock(mutex_); + folder.path2entry_index.reserve(folder.entries.size()); + for (size_t i = 0; i < folder.entries.size(); ++i) { + folder.entries[i].id = i; + folder.path2entry_index[folder.entries[i].filename] = i; + } } -Project::Entry Project::FindCompilationEntryForFile( - const std::string& filename) { - auto it = absolute_path_to_entry_index_.find(filename); - if (it != absolute_path_to_entry_index_.end()) - return entries[it->second]; - - // We couldn't find the file. Try to infer it. - // TODO: Cache inferred file in a separate array (using a lock or similar) - Entry* best_entry = nullptr; - int best_score = std::numeric_limits::min(); - for (Entry& entry : entries) { - int score = ComputeGuessScore(filename, entry.filename); - if (score > best_score) { - best_score = score; - best_entry = &entry; +Project::Entry Project::FindEntry(const std::string &path, + bool can_be_inferred) { + std::lock_guard lock(mutex_); + for (auto &[root, folder] : root2folder) { + auto it = folder.path2entry_index.find(path); + if (it != folder.path2entry_index.end()) { + Project::Entry &entry = folder.entries[it->second]; + if (can_be_inferred || entry.filename == path) + return entry; } } Project::Entry result; + const Entry *best_entry = nullptr; + int best_score = INT_MIN; + for (auto &[root, folder] : root2folder) { + for (const Entry &entry : folder.entries) { + int score = ComputeGuessScore(path, entry.filename); + if (score > best_score) { + best_score = score; + best_entry = &entry; + } + } + if (StringRef(path).startswith(root)) + result.root = root; + } + if (result.root.empty()) + result.root = g_config->fallbackFolder; + result.is_inferred = true; - result.filename = filename; + result.filename = path; if (!best_entry) { result.args.push_back("%clang"); - result.args.push_back(filename); + result.args.push_back(Intern(path)); } else { result.args = best_entry->args; // |best_entry| probably has its own path in the arguments. We need to remap // that path to the new filename. - std::string best_entry_base_name = GetBaseName(best_entry->filename); - for (std::string& arg : result.args) { - if (arg == best_entry->filename || - GetBaseName(arg) == best_entry_base_name) { - arg = filename; + std::string best_entry_base_name = + sys::path::filename(best_entry->filename); + for (const char *&arg : result.args) { + try { + if (arg == best_entry->filename || + sys::path::filename(arg) == best_entry_base_name) + arg = Intern(path); + } catch (...) { } } } @@ -588,1001 +476,34 @@ Project::Entry Project::FindCompilationEntryForFile( return result; } -void Project::ForAllFilteredFiles( - Config* config, - std::function action) { - GroupMatch matcher(config->index.whitelist, config->index.blacklist); - for (int i = 0; i < entries.size(); ++i) { - const Project::Entry& entry = entries[i]; - std::string failure_reason; - if (matcher.IsMatch(entry.filename, &failure_reason)) - action(i, entries[i]); - else { - if (config->index.logSkippedPaths) { - LOG_S(INFO) << "[" << i + 1 << "/" << entries.size() << "]: Failed " - << failure_reason << "; skipping " << entry.filename; - } - } - } -} - -void Project::Index(Config* config, - QueueManager* queue, - WorkingFiles* wfiles, - lsRequestId id) { - ForAllFilteredFiles(config, [&](int i, const Project::Entry& entry) { - optional content = ReadContent(entry.filename); - if (!content) { - LOG_S(ERROR) << "When loading project, canont read file " - << entry.filename; - return; - } - bool is_interactive = wfiles->GetFileByFilename(entry.filename) != nullptr; - queue->index_request.PushBack( - Index_Request(entry.filename, entry.args, is_interactive, *content, - ICacheManager::Make(config), id)); - }); -} - -TEST_SUITE("Project") { - void CheckFlags(const std::string& directory, const std::string& file, - std::vector raw, - std::vector expected) { - g_disable_normalize_path_for_test = true; - - Config config; - ProjectConfig project; - project.project_dir = "/w/c/s/"; - project.resource_dir = "/w/resource_dir/"; - - CompileCommandsEntry entry; - entry.directory = directory; - entry.args = raw; - entry.file = file; - Project::Entry result = - GetCompilationEntryFromCompileCommandEntry(&config, &project, entry); - - if (result.args != expected) { - std::cout << "Raw: " << StringJoin(raw) << std::endl; - std::cout << "Expected: " << StringJoin(expected) << std::endl; - std::cout << "Actual: " << StringJoin(result.args) << std::endl; - } - for (int i = 0; i < std::min(result.args.size(), expected.size()); ++i) { - if (result.args[i] != expected[i]) { - std::cout << std::endl; - std::cout << "mismatch at " << i << std::endl; - std::cout << " expected: " << expected[i] << std::endl; - std::cout << " actual: " << result.args[i] << std::endl; +void Project::Index(WorkingFiles *wfiles, RequestId id) { + auto &gi = g_config->index; + GroupMatch match(gi.whitelist, gi.blacklist), + match_i(gi.initialWhitelist, gi.initialBlacklist); + { + std::lock_guard lock(mutex_); + for (auto &[root, folder] : root2folder) { + int i = 0; + for (const Project::Entry &entry : folder.entries) { + std::string reason; + if (match.Matches(entry.filename, &reason) && + match_i.Matches(entry.filename, &reason)) { + bool interactive = wfiles->GetFile(entry.filename) != nullptr; + pipeline::Index( + entry.filename, entry.args, + interactive ? IndexMode::Normal : IndexMode::NonInteractive, id); + } else { + LOG_V(1) << "[" << i << "/" << folder.entries.size() << "]: " << reason + << "; skip " << entry.filename; + } + i++; } } - REQUIRE(result.args == expected); - } - - void CheckFlags(std::vector raw, - std::vector expected) { - CheckFlags("/dir/", "file.cc", raw, expected); - } - - TEST_CASE("strip meta-compiler invocations") { - CheckFlags( - /* raw */ {"clang", "-lstdc++", "myfile.cc"}, - /* expected */ - {"clang", "-working-directory=/dir/", "-lstdc++", "&/dir/myfile.cc", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - - CheckFlags( - /* raw */ {"clang.exe"}, - /* expected */ - {"clang.exe", "-working-directory=/dir/", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - - CheckFlags( - /* raw */ {"goma", "clang"}, - /* expected */ - {"clang", "-working-directory=/dir/", "-resource-dir=/w/resource_dir/", - "-Wno-unknown-warning-option", "-fparse-all-comments"}); - - CheckFlags( - /* raw */ {"goma", "clang", "--foo"}, - /* expected */ - {"clang", "-working-directory=/dir/", "--foo", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - } - - TEST_CASE("Windows path normalization") { - CheckFlags("E:/workdir", "E:/workdir/bar.cc", /* raw */ {"clang", "bar.cc"}, - /* expected */ - {"clang", "-working-directory=E:/workdir", "&E:/workdir/bar.cc", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - - CheckFlags("E:/workdir", "E:/workdir/bar.cc", - /* raw */ {"clang", "E:/workdir/bar.cc"}, - /* expected */ - {"clang", "-working-directory=E:/workdir", "&E:/workdir/bar.cc", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - - CheckFlags("E:/workdir", "E:/workdir/bar.cc", - /* raw */ {"clang-cl.exe", "/I./test", "E:/workdir/bar.cc"}, - /* expected */ - {"clang-cl.exe", "-working-directory=E:/workdir", - "/I&E:/workdir/./test", "&E:/workdir/bar.cc", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - - CheckFlags("E:/workdir", "E:/workdir/bar.cc", - /* raw */ - {"cl.exe", "/I../third_party/test/include", "E:/workdir/bar.cc"}, - /* expected */ - {"cl.exe", "-working-directory=E:/workdir", - "/I&E:/workdir/../third_party/test/include", - "&E:/workdir/bar.cc", "-resource-dir=/w/resource_dir/", - "-Wno-unknown-warning-option", "-fparse-all-comments"}); - } - - TEST_CASE("Path in args") { - CheckFlags("/home/user", "/home/user/foo/bar.c", - /* raw */ {"cc", "-O0", "foo/bar.c"}, - /* expected */ - {"cc", "-working-directory=/home/user", "-O0", - "&/home/user/foo/bar.c", "-resource-dir=/w/resource_dir/", - "-Wno-unknown-warning-option", "-fparse-all-comments"}); - } - - TEST_CASE("Implied binary") { - CheckFlags("/home/user", "/home/user/foo/bar.cc", - /* raw */ {"clang", "-DDONT_IGNORE_ME"}, - /* expected */ - {"clang", "-working-directory=/home/user", "-DDONT_IGNORE_ME", - "-resource-dir=/w/resource_dir/", "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - } - - // Checks flag parsing for a random chromium file in comparison to what - // YouCompleteMe fetches. - TEST_CASE("ycm") { - CheckFlags( - "/w/c/s/out/Release", "../../ash/login/lock_screen_sanity_unittest.cc", - - /* raw */ - { - "/work/goma/gomacc", - "../../third_party/llvm-build/Release+Asserts/bin/clang++", - "-MMD", - "-MF", - "obj/ash/ash_unittests/lock_screen_sanity_unittest.o.d", - "-DV8_DEPRECATION_WARNINGS", - "-DDCHECK_ALWAYS_ON=1", - "-DUSE_UDEV", - "-DUSE_AURA=1", - "-DUSE_NSS_CERTS=1", - "-DUSE_OZONE=1", - "-DFULL_SAFE_BROWSING", - "-DSAFE_BROWSING_CSD", - "-DSAFE_BROWSING_DB_LOCAL", - "-DCHROMIUM_BUILD", - "-DFIELDTRIAL_TESTING_ENABLED", - "-D_FILE_OFFSET_BITS=64", - "-D_LARGEFILE_SOURCE", - "-D_LARGEFILE64_SOURCE", - "-DCR_CLANG_REVISION=\"313786-1\"", - "-D__STDC_CONSTANT_MACROS", - "-D__STDC_FORMAT_MACROS", - "-DCOMPONENT_BUILD", - "-DOS_CHROMEOS", - "-DNDEBUG", - "-DNVALGRIND", - "-DDYNAMIC_ANNOTATIONS_ENABLED=0", - "-DGL_GLEXT_PROTOTYPES", - "-DUSE_GLX", - "-DUSE_EGL", - "-DANGLE_ENABLE_RELEASE_ASSERTS", - "-DTOOLKIT_VIEWS=1", - "-DGTEST_API_=", - "-DGTEST_HAS_POSIX_RE=0", - "-DGTEST_LANG_CXX11=1", - "-DUNIT_TEST", - "-DUSING_V8_SHARED", - "-DU_USING_ICU_NAMESPACE=0", - "-DU_ENABLE_DYLOAD=0", - "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE", - "-DUCHAR_TYPE=uint16_t", - "-DGOOGLE_PROTOBUF_NO_RTTI", - "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER", - "-DHAVE_PTHREAD", - "-DPROTOBUF_USE_DLLS", - "-DBORINGSSL_SHARED_LIBRARY", - "-DSK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS", - "-DSK_HAS_PNG_LIBRARY", - "-DSK_HAS_WEBP_LIBRARY", - "-DSK_HAS_JPEG_LIBRARY", - "-DSKIA_DLL", - "-DGR_GL_IGNORE_ES3_MSAA=0", - "-DSK_SUPPORT_GPU=1", - "-DMESA_EGL_NO_X11_HEADERS", - "-I../..", - "-Igen", - "-I../../third_party/libwebp/src", - "-I../../third_party/khronos", - "-I../../gpu", - "-I../../third_party/googletest/src/googletest/include", - "-I../../third_party/WebKit", - "-Igen/third_party/WebKit", - "-I../../v8/include", - "-Igen/v8/include", - "-I../../third_party/icu/source/common", - "-I../../third_party/icu/source/i18n", - "-I../../third_party/protobuf/src", - "-Igen/protoc_out", - "-I../../third_party/protobuf/src", - "-I../../third_party/boringssl/src/include", - "-I../../build/linux/debian_jessie_amd64-sysroot/usr/include/nss", - "-I../../build/linux/debian_jessie_amd64-sysroot/usr/include/nspr", - "-I../../skia/config", - "-I../../skia/ext", - "-I../../third_party/skia/include/c", - "-I../../third_party/skia/include/config", - "-I../../third_party/skia/include/core", - "-I../../third_party/skia/include/effects", - "-I../../third_party/skia/include/encode", - "-I../../third_party/skia/include/gpu", - "-I../../third_party/skia/include/images", - "-I../../third_party/skia/include/lazy", - "-I../../third_party/skia/include/pathops", - "-I../../third_party/skia/include/pdf", - "-I../../third_party/skia/include/pipe", - "-I../../third_party/skia/include/ports", - "-I../../third_party/skia/include/utils", - "-I../../third_party/skia/third_party/vulkan", - "-I../../third_party/skia/include/codec", - "-I../../third_party/skia/src/gpu", - "-I../../third_party/skia/src/sksl", - "-I../../third_party/ced/src", - "-I../../third_party/mesa/src/include", - "-I../../third_party/libwebm/source", - "-Igen", - "-I../../build/linux/debian_jessie_amd64-sysroot/usr/include/" - "dbus-1.0", - "-I../../build/linux/debian_jessie_amd64-sysroot/usr/lib/" - "x86_64-linux-gnu/dbus-1.0/include", - "-I../../third_party/googletest/custom", - "-I../../third_party/googletest/src/googlemock/include", - "-fno-strict-aliasing", - "-Wno-builtin-macro-redefined", - "-D__DATE__=", - "-D__TIME__=", - "-D__TIMESTAMP__=", - "-funwind-tables", - "-fPIC", - "-pipe", - "-B../../third_party/binutils/Linux_x64/Release/bin", - "-pthread", - "-fcolor-diagnostics", - "-no-canonical-prefixes", - "-m64", - "-march=x86-64", - "-Wall", - "-Werror", - "-Wextra", - "-Wno-missing-field-initializers", - "-Wno-unused-parameter", - "-Wno-c++11-narrowing", - "-Wno-covered-switch-default", - "-Wno-unneeded-internal-declaration", - "-Wno-inconsistent-missing-override", - "-Wno-undefined-var-template", - "-Wno-nonportable-include-path", - "-Wno-address-of-packed-member", - "-Wno-unused-lambda-capture", - "-Wno-user-defined-warnings", - "-Wno-enum-compare-switch", - "-Wno-tautological-unsigned-zero-compare", - "-Wno-null-pointer-arithmetic", - "-Wno-tautological-unsigned-enum-zero-compare", - "-O2", - "-fno-ident", - "-fdata-sections", - "-ffunction-sections", - "-fno-omit-frame-pointer", - "-g0", - "-fvisibility=hidden", - "-Xclang", - "-load", - "-Xclang", - "../../third_party/llvm-build/Release+Asserts/lib/" - "libFindBadConstructs.so", - "-Xclang", - "-add-plugin", - "-Xclang", - "find-bad-constructs", - "-Xclang", - "-plugin-arg-find-bad-constructs", - "-Xclang", - "check-auto-raw-pointer", - "-Xclang", - "-plugin-arg-find-bad-constructs", - "-Xclang", - "check-ipc", - "-Wheader-hygiene", - "-Wstring-conversion", - "-Wtautological-overlap-compare", - "-Wno-header-guard", - "-std=gnu++14", - "-fno-rtti", - "-nostdinc++", - "-isystem../../buildtools/third_party/libc++/trunk/include", - "-isystem../../buildtools/third_party/libc++abi/trunk/include", - "--sysroot=../../build/linux/debian_jessie_amd64-sysroot", - "-fno-exceptions", - "-fvisibility-inlines-hidden", - "-c", - "../../ash/login/ui/lock_screen_sanity_unittest.cc", - "-o", - "obj/ash/ash_unittests/lock_screen_sanity_unittest.o", - }, - - /* expected */ - {"../../third_party/llvm-build/Release+Asserts/bin/clang++", - "-working-directory=/w/c/s/out/Release", - "-DV8_DEPRECATION_WARNINGS", - "-DDCHECK_ALWAYS_ON=1", - "-DUSE_UDEV", - "-DUSE_AURA=1", - "-DUSE_NSS_CERTS=1", - "-DUSE_OZONE=1", - "-DFULL_SAFE_BROWSING", - "-DSAFE_BROWSING_CSD", - "-DSAFE_BROWSING_DB_LOCAL", - "-DCHROMIUM_BUILD", - "-DFIELDTRIAL_TESTING_ENABLED", - "-D_FILE_OFFSET_BITS=64", - "-D_LARGEFILE_SOURCE", - "-D_LARGEFILE64_SOURCE", - "-DCR_CLANG_REVISION=\"313786-1\"", - "-D__STDC_CONSTANT_MACROS", - "-D__STDC_FORMAT_MACROS", - "-DCOMPONENT_BUILD", - "-DOS_CHROMEOS", - "-DNDEBUG", - "-DNVALGRIND", - "-DDYNAMIC_ANNOTATIONS_ENABLED=0", - "-DGL_GLEXT_PROTOTYPES", - "-DUSE_GLX", - "-DUSE_EGL", - "-DANGLE_ENABLE_RELEASE_ASSERTS", - "-DTOOLKIT_VIEWS=1", - "-DGTEST_API_=", - "-DGTEST_HAS_POSIX_RE=0", - "-DGTEST_LANG_CXX11=1", - "-DUNIT_TEST", - "-DUSING_V8_SHARED", - "-DU_USING_ICU_NAMESPACE=0", - "-DU_ENABLE_DYLOAD=0", - "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE", - "-DUCHAR_TYPE=uint16_t", - "-DGOOGLE_PROTOBUF_NO_RTTI", - "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER", - "-DHAVE_PTHREAD", - "-DPROTOBUF_USE_DLLS", - "-DBORINGSSL_SHARED_LIBRARY", - "-DSK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS", - "-DSK_HAS_PNG_LIBRARY", - "-DSK_HAS_WEBP_LIBRARY", - "-DSK_HAS_JPEG_LIBRARY", - "-DSKIA_DLL", - "-DGR_GL_IGNORE_ES3_MSAA=0", - "-DSK_SUPPORT_GPU=1", - "-DMESA_EGL_NO_X11_HEADERS", - "-I../..", - "-Igen", - "-I../../third_party/libwebp/src", - "-I../../third_party/khronos", - "-I../../gpu", - "-I../../third_party/googletest/src/googletest/" - "include", - "-I../../third_party/WebKit", - "-Igen/third_party/WebKit", - "-I../../v8/include", - "-Igen/v8/include", - "-I../../third_party/icu/source/common", - "-I../../third_party/icu/source/i18n", - "-I../../third_party/protobuf/src", - "-Igen/protoc_out", - "-I../../third_party/protobuf/src", - "-I../../third_party/boringssl/src/include", - "-I../../build/linux/debian_jessie_amd64-sysroot/" - "usr/include/nss", - "-I../../build/linux/debian_jessie_amd64-sysroot/" - "usr/include/nspr", - "-I../../skia/config", - "-I../../skia/ext", - "-I../../third_party/skia/include/c", - "-I../../third_party/skia/include/config", - "-I../../third_party/skia/include/core", - "-I../../third_party/skia/include/effects", - "-I../../third_party/skia/include/encode", - "-I../../third_party/skia/include/gpu", - "-I../../third_party/skia/include/images", - "-I../../third_party/skia/include/lazy", - "-I../../third_party/skia/include/pathops", - "-I../../third_party/skia/include/pdf", - "-I../../third_party/skia/include/pipe", - "-I../../third_party/skia/include/ports", - "-I../../third_party/skia/include/utils", - "-I../../third_party/skia/third_party/vulkan", - "-I../../third_party/skia/include/codec", - "-I../../third_party/skia/src/gpu", - "-I../../third_party/skia/src/sksl", - "-I../../third_party/ced/src", - "-I../../third_party/mesa/src/include", - "-I../../third_party/libwebm/source", - "-Igen", - "-I../../build/linux/debian_jessie_amd64-sysroot/" - "usr/include/dbus-1.0", - "-I../../build/linux/debian_jessie_amd64-sysroot/" - "usr/lib/x86_64-linux-gnu/dbus-1.0/include", - "-I../../third_party/googletest/custom", - "-I../../third_party/googletest/src/googlemock/" - "include", - "-fno-strict-aliasing", - "-Wno-builtin-macro-redefined", - "-D__DATE__=", - "-D__TIME__=", - "-D__TIMESTAMP__=", - "-funwind-tables", - "-fPIC", - "-pipe", - "-B../../third_party/binutils/Linux_x64/Release/bin", - "-pthread", - "-fcolor-diagnostics", - "-no-canonical-prefixes", - "-m64", - "-march=x86-64", - "-Wall", - "-Werror", - "-Wextra", - "-Wno-missing-field-initializers", - "-Wno-unused-parameter", - "-Wno-c++11-narrowing", - "-Wno-covered-switch-default", - "-Wno-unneeded-internal-declaration", - "-Wno-inconsistent-missing-override", - "-Wno-undefined-var-template", - "-Wno-nonportable-include-path", - "-Wno-address-of-packed-member", - "-Wno-unused-lambda-capture", - "-Wno-user-defined-warnings", - "-Wno-enum-compare-switch", - "-Wno-tautological-unsigned-zero-compare", - "-Wno-null-pointer-arithmetic", - "-Wno-tautological-unsigned-enum-zero-compare", - "-O2", - "-fno-ident", - "-fdata-sections", - "-ffunction-sections", - "-fno-omit-frame-pointer", - "-g0", - "-fvisibility=hidden", - "-Wheader-hygiene", - "-Wstring-conversion", - "-Wtautological-overlap-compare", - "-Wno-header-guard", - "-std=gnu++14", - "-fno-rtti", - "-nostdinc++", - "-isystem../../buildtools/third_party/libc++/" - "trunk/" - "include", - "-isystem../../buildtools/third_party/libc++abi/" - "trunk/" - "include", - "--sysroot=&/w/c/s/out/Release/../../build/linux/" - "debian_jessie_amd64-sysroot", - "-fno-exceptions", - "-fvisibility-inlines-hidden", - "&/w/c/s/out/Release/../../ash/login/ui/" - "lock_screen_sanity_unittest.cc", - "-resource-dir=/w/resource_dir/", - "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - } - - // Checks flag parsing for an example chromium file. - TEST_CASE("chromium") { - CheckFlags( - "/w/c/s/out/Release", "../../apps/app_lifetime_monitor.cc", - /* raw */ - {"/work/goma/gomacc", - "../../third_party/llvm-build/Release+Asserts/bin/clang++", - "-MMD", - "-MF", - "obj/apps/apps/app_lifetime_monitor.o.d", - "-DV8_DEPRECATION_WARNINGS", - "-DDCHECK_ALWAYS_ON=1", - "-DUSE_UDEV", - "-DUSE_ASH=1", - "-DUSE_AURA=1", - "-DUSE_NSS_CERTS=1", - "-DUSE_OZONE=1", - "-DDISABLE_NACL", - "-DFULL_SAFE_BROWSING", - "-DSAFE_BROWSING_CSD", - "-DSAFE_BROWSING_DB_LOCAL", - "-DCHROMIUM_BUILD", - "-DFIELDTRIAL_TESTING_ENABLED", - "-DCR_CLANG_REVISION=\"310694-1\"", - "-D_FILE_OFFSET_BITS=64", - "-D_LARGEFILE_SOURCE", - "-D_LARGEFILE64_SOURCE", - "-D__STDC_CONSTANT_MACROS", - "-D__STDC_FORMAT_MACROS", - "-DCOMPONENT_BUILD", - "-DOS_CHROMEOS", - "-DNDEBUG", - "-DNVALGRIND", - "-DDYNAMIC_ANNOTATIONS_ENABLED=0", - "-DGL_GLEXT_PROTOTYPES", - "-DUSE_GLX", - "-DUSE_EGL", - "-DANGLE_ENABLE_RELEASE_ASSERTS", - "-DTOOLKIT_VIEWS=1", - "-DV8_USE_EXTERNAL_STARTUP_DATA", - "-DU_USING_ICU_NAMESPACE=0", - "-DU_ENABLE_DYLOAD=0", - "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE", - "-DUCHAR_TYPE=uint16_t", - "-DGOOGLE_PROTOBUF_NO_RTTI", - "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER", - "-DHAVE_PTHREAD", - "-DPROTOBUF_USE_DLLS", - "-DSK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS", - "-DSK_HAS_PNG_LIBRARY", - "-DSK_HAS_WEBP_LIBRARY", - "-DSK_HAS_JPEG_LIBRARY", - "-DSKIA_DLL", - "-DGR_GL_IGNORE_ES3_MSAA=0", - "-DSK_SUPPORT_GPU=1", - "-DMESA_EGL_NO_X11_HEADERS", - "-DBORINGSSL_SHARED_LIBRARY", - "-DUSING_V8_SHARED", - "-I../..", - "-Igen", - "-I../../third_party/libwebp/src", - "-I../../third_party/khronos", - "-I../../gpu", - "-I../../third_party/ced/src", - "-I../../third_party/icu/source/common", - "-I../../third_party/icu/source/i18n", - "-I../../third_party/protobuf/src", - "-I../../skia/config", - "-I../../skia/ext", - "-I../../third_party/skia/include/c", - "-I../../third_party/skia/include/config", - "-I../../third_party/skia/include/core", - "-I../../third_party/skia/include/effects", - "-I../../third_party/skia/include/encode", - "-I../../third_party/skia/include/gpu", - "-I../../third_party/skia/include/images", - "-I../../third_party/skia/include/lazy", - "-I../../third_party/skia/include/pathops", - "-I../../third_party/skia/include/pdf", - "-I../../third_party/skia/include/pipe", - "-I../../third_party/skia/include/ports", - "-I../../third_party/skia/include/utils", - "-I../../third_party/skia/third_party/vulkan", - "-I../../third_party/skia/src/gpu", - "-I../../third_party/skia/src/sksl", - "-I../../third_party/mesa/src/include", - "-I../../third_party/libwebm/source", - "-I../../third_party/protobuf/src", - "-Igen/protoc_out", - "-I../../third_party/boringssl/src/include", - "-I../../build/linux/debian_jessie_amd64-sysroot/usr/include/nss", - "-I../../build/linux/debian_jessie_amd64-sysroot/usr/include/nspr", - "-Igen", - "-I../../third_party/WebKit", - "-Igen/third_party/WebKit", - "-I../../v8/include", - "-Igen/v8/include", - "-Igen", - "-I../../third_party/flatbuffers/src/include", - "-Igen", - "-fno-strict-aliasing", - "-Wno-builtin-macro-redefined", - "-D__DATE__=", - "-D__TIME__=", - "-D__TIMESTAMP__=", - "-funwind-tables", - "-fPIC", - "-pipe", - "-B../../third_party/binutils/Linux_x64/Release/bin", - "-pthread", - "-fcolor-diagnostics", - "-m64", - "-march=x86-64", - "-Wall", - "-Werror", - "-Wextra", - "-Wno-missing-field-initializers", - "-Wno-unused-parameter", - "-Wno-c++11-narrowing", - "-Wno-covered-switch-default", - "-Wno-unneeded-internal-declaration", - "-Wno-inconsistent-missing-override", - "-Wno-undefined-var-template", - "-Wno-nonportable-include-path", - "-Wno-address-of-packed-member", - "-Wno-unused-lambda-capture", - "-Wno-user-defined-warnings", - "-Wno-enum-compare-switch", - "-O2", - "-fno-ident", - "-fdata-sections", - "-ffunction-sections", - "-fno-omit-frame-pointer", - "-g0", - "-fvisibility=hidden", - "-Xclang", - "-load", - "-Xclang", - "../../third_party/llvm-build/Release+Asserts/lib/" - "libFindBadConstructs.so", - "-Xclang", - "-add-plugin", - "-Xclang", - "find-bad-constructs", - "-Xclang", - "-plugin-arg-find-bad-constructs", - "-Xclang", - "check-auto-raw-pointer", - "-Xclang", - "-plugin-arg-find-bad-constructs", - "-Xclang", - "check-ipc", - "-Wheader-hygiene", - "-Wstring-conversion", - "-Wtautological-overlap-compare", - "-Wexit-time-destructors", - "-Wno-header-guard", - "-Wno-exit-time-destructors", - "-std=gnu++14", - "-fno-rtti", - "-nostdinc++", - "-isystem../../buildtools/third_party/libc++/trunk/include", - "-isystem../../buildtools/third_party/libc++abi/trunk/include", - "--sysroot=../../build/linux/debian_jessie_amd64-sysroot", - "-fno-exceptions", - "-fvisibility-inlines-hidden", - "../../apps/app_lifetime_monitor.cc"}, - - /* expected */ - {"../../third_party/llvm-build/Release+Asserts/bin/clang++", - "-working-directory=/w/c/s/out/Release", - "-DV8_DEPRECATION_WARNINGS", - "-DDCHECK_ALWAYS_ON=1", - "-DUSE_UDEV", - "-DUSE_ASH=1", - "-DUSE_AURA=1", - "-DUSE_NSS_CERTS=1", - "-DUSE_OZONE=1", - "-DDISABLE_NACL", - "-DFULL_SAFE_BROWSING", - "-DSAFE_BROWSING_CSD", - "-DSAFE_BROWSING_DB_LOCAL", - "-DCHROMIUM_BUILD", - "-DFIELDTRIAL_TESTING_ENABLED", - "-DCR_CLANG_REVISION=\"310694-1\"", - "-D_FILE_OFFSET_BITS=64", - "-D_LARGEFILE_SOURCE", - "-D_LARGEFILE64_SOURCE", - "-D__STDC_CONSTANT_MACROS", - "-D__STDC_FORMAT_MACROS", - "-DCOMPONENT_BUILD", - "-DOS_CHROMEOS", - "-DNDEBUG", - "-DNVALGRIND", - "-DDYNAMIC_ANNOTATIONS_ENABLED=0", - "-DGL_GLEXT_PROTOTYPES", - "-DUSE_GLX", - "-DUSE_EGL", - "-DANGLE_ENABLE_RELEASE_ASSERTS", - "-DTOOLKIT_VIEWS=1", - "-DV8_USE_EXTERNAL_STARTUP_DATA", - "-DU_USING_ICU_NAMESPACE=0", - "-DU_ENABLE_DYLOAD=0", - "-DICU_UTIL_DATA_IMPL=ICU_UTIL_DATA_FILE", - "-DUCHAR_TYPE=uint16_t", - "-DGOOGLE_PROTOBUF_NO_RTTI", - "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER", - "-DHAVE_PTHREAD", - "-DPROTOBUF_USE_DLLS", - "-DSK_IGNORE_LINEONLY_AA_CONVEX_PATH_OPTS", - "-DSK_HAS_PNG_LIBRARY", - "-DSK_HAS_WEBP_LIBRARY", - "-DSK_HAS_JPEG_LIBRARY", - "-DSKIA_DLL", - "-DGR_GL_IGNORE_ES3_MSAA=0", - "-DSK_SUPPORT_GPU=1", - "-DMESA_EGL_NO_X11_HEADERS", - "-DBORINGSSL_SHARED_LIBRARY", - "-DUSING_V8_SHARED", - "-I../..", - "-Igen", - "-I../../third_party/libwebp/src", - "-I../../third_party/khronos", - "-I../../gpu", - "-I../../third_party/ced/src", - "-I../../third_party/icu/source/common", - "-I../../third_party/icu/source/i18n", - "-I../../third_party/protobuf/src", - "-I../../skia/config", - "-I../../skia/ext", - "-I../../third_party/skia/include/c", - "-I../../third_party/skia/include/config", - "-I../../third_party/skia/include/core", - "-I../../third_party/skia/include/effects", - "-I../../third_party/skia/include/encode", - "-I../../third_party/skia/include/gpu", - "-I../../third_party/skia/include/images", - "-I../../third_party/skia/include/lazy", - "-I../../third_party/skia/include/pathops", - "-I../../third_party/skia/include/pdf", - "-I../../third_party/skia/include/pipe", - "-I../../third_party/skia/include/ports", - "-I../../third_party/skia/include/utils", - "-I../../third_party/skia/third_party/vulkan", - "-I../../third_party/skia/src/gpu", - "-I../../third_party/skia/src/sksl", - "-I../../third_party/mesa/src/include", - "-I../../third_party/libwebm/source", - "-I../../third_party/protobuf/src", - "-Igen/protoc_out", - "-I../../third_party/boringssl/src/include", - "-I../../build/linux/debian_jessie_amd64-sysroot/" - "usr/include/nss", - "-I../../build/linux/debian_jessie_amd64-sysroot/" - "usr/include/nspr", - "-Igen", - "-I../../third_party/WebKit", - "-Igen/third_party/WebKit", - "-I../../v8/include", - "-Igen/v8/include", - "-Igen", - "-I../../third_party/flatbuffers/src/include", - "-Igen", - "-fno-strict-aliasing", - "-Wno-builtin-macro-redefined", - "-D__DATE__=", - "-D__TIME__=", - "-D__TIMESTAMP__=", - "-funwind-tables", - "-fPIC", - "-pipe", - "-B../../third_party/binutils/Linux_x64/Release/bin", - "-pthread", - "-fcolor-diagnostics", - "-m64", - "-march=x86-64", - "-Wall", - "-Werror", - "-Wextra", - "-Wno-missing-field-initializers", - "-Wno-unused-parameter", - "-Wno-c++11-narrowing", - "-Wno-covered-switch-default", - "-Wno-unneeded-internal-declaration", - "-Wno-inconsistent-missing-override", - "-Wno-undefined-var-template", - "-Wno-nonportable-include-path", - "-Wno-address-of-packed-member", - "-Wno-unused-lambda-capture", - "-Wno-user-defined-warnings", - "-Wno-enum-compare-switch", - "-O2", - "-fno-ident", - "-fdata-sections", - "-ffunction-sections", - "-fno-omit-frame-pointer", - "-g0", - "-fvisibility=hidden", - "-Wheader-hygiene", - "-Wstring-conversion", - "-Wtautological-overlap-compare", - "-Wexit-time-destructors", - "-Wno-header-guard", - "-Wno-exit-time-destructors", - "-std=gnu++14", - "-fno-rtti", - "-nostdinc++", - "-isystem../../buildtools/third_party/libc++/" - "trunk/" - "include", - "-isystem../../buildtools/third_party/libc++abi/" - "trunk/" - "include", - "--sysroot=&/w/c/s/out/Release/../../build/linux/" - "debian_jessie_amd64-sysroot", - "-fno-exceptions", - "-fvisibility-inlines-hidden", - "&/w/c/s/out/Release/../../apps/app_lifetime_monitor.cc", - "-resource-dir=/w/resource_dir/", - "-Wno-unknown-warning-option", - "-fparse-all-comments"}); - } - - TEST_CASE("Directory extraction") { - Config init_opts; - ProjectConfig config; - config.project_dir = "/w/c/s/"; - - CompileCommandsEntry entry; - entry.directory = "/base"; - entry.args = {"clang", - "-I/a_absolute1", - "--foobar", - "-I", - "/a_absolute2", - "--foobar", - "-Ia_relative1", - "--foobar", - "-I", - "a_relative2", - "--foobar", - "-iquote/q_absolute1", - "--foobar", - "-iquote", - "/q_absolute2", - "--foobar", - "-iquoteq_relative1", - "--foobar", - "-iquote", - "q_relative2", - "--foobar", - "foo.cc"}; - entry.file = "foo.cc"; - Project::Entry result = - GetCompilationEntryFromCompileCommandEntry(&init_opts, &config, entry); - - std::unordered_set angle_expected{ - "&/a_absolute1", "&/a_absolute2", "&/base/a_relative1", - "&/base/a_relative2"}; - std::unordered_set quote_expected{ - "&/q_absolute1", "&/q_absolute2", "&/base/q_relative1", - "&/base/q_relative2"}; - REQUIRE(config.angle_dirs == angle_expected); - REQUIRE(config.quote_dirs == quote_expected); - } - - TEST_CASE("Entry inference") { - Project p; - { - Project::Entry e; - e.args = {"arg1"}; - e.filename = "/a/b/c/d/bar.cc"; - p.entries.push_back(e); - } - { - Project::Entry e; - e.args = {"arg2"}; - e.filename = "/a/b/c/baz.cc"; - p.entries.push_back(e); - } - - // Guess at same directory level, when there are parent directories. - { - optional entry = - p.FindCompilationEntryForFile("/a/b/c/d/new.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg1"}); - } - - // Guess at same directory level, when there are child directories. - { - optional entry = - p.FindCompilationEntryForFile("/a/b/c/new.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg2"}); - } - - // Guess at new directory (use the closest parent directory). - { - optional entry = - p.FindCompilationEntryForFile("/a/b/c/new/new.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg2"}); - } } - TEST_CASE("Entry inference remaps file names") { - Project p; - { - Project::Entry e; - e.args = {"a", "b", "aaaa.cc", "d"}; - e.filename = "absolute/aaaa.cc"; - p.entries.push_back(e); - } - - { - optional entry = p.FindCompilationEntryForFile("ee.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"a", "b", "ee.cc", "d"}); - } - } - - TEST_CASE("IsWindowsAbsolutePath works correctly") { - REQUIRE(IsWindowsAbsolutePath("C:/Users/projects/")); - REQUIRE(IsWindowsAbsolutePath("C:/Users/projects")); - REQUIRE(IsWindowsAbsolutePath("C:/Users/projects")); - REQUIRE(IsWindowsAbsolutePath("C:\\Users\\projects")); - REQUIRE(IsWindowsAbsolutePath("C:\\\\Users\\\\projects")); - REQUIRE(IsWindowsAbsolutePath("c:\\\\Users\\\\projects")); - REQUIRE(IsWindowsAbsolutePath("A:\\\\Users\\\\projects")); - - REQUIRE(!IsWindowsAbsolutePath("C:/")); - REQUIRE(!IsWindowsAbsolutePath("../abc/test")); - REQUIRE(!IsWindowsAbsolutePath("5:/test")); - REQUIRE(!IsWindowsAbsolutePath("cquery/project/file.cc")); - REQUIRE(!IsWindowsAbsolutePath("")); - REQUIRE(!IsWindowsAbsolutePath("/etc/linux/path")); - } - - TEST_CASE("Entry inference prefers same file endings") { - Project p; - { - Project::Entry e; - e.args = {"arg1"}; - e.filename = "common/simple_browsertest.cc"; - p.entries.push_back(e); - } - { - Project::Entry e; - e.args = {"arg2"}; - e.filename = "common/simple_unittest.cc"; - p.entries.push_back(e); - } - { - Project::Entry e; - e.args = {"arg3"}; - e.filename = "common/a/simple_unittest.cc"; - p.entries.push_back(e); - } - - // Prefer files with the same ending. - { - optional entry = - p.FindCompilationEntryForFile("my_browsertest.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg1"}); - } - { - optional entry = - p.FindCompilationEntryForFile("my_unittest.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg2"}); - } - { - optional entry = - p.FindCompilationEntryForFile("common/my_browsertest.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg1"}); - } - { - optional entry = - p.FindCompilationEntryForFile("common/my_unittest.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg2"}); - } - - // Prefer the same directory over matching file-ending. - { - optional entry = - p.FindCompilationEntryForFile("common/a/foo.cc"); - REQUIRE(entry.has_value()); - REQUIRE(entry->args == std::vector{"arg3"}); - } - } + pipeline::loaded_ts = pipeline::tick; + // Dummy request to indicate that project is loaded and + // trigger refreshing semantic highlight for all working files. + pipeline::Index("", {}, IndexMode::NonInteractive); } +} // namespace ccls diff --git a/src/project.h b/src/project.h deleted file mode 100644 index 17e41c591..000000000 --- a/src/project.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "config.h" - -#include -#include -#include - -#include -#include -#include -#include - -// FIXME ipc.h -using lsRequestId = std::variant; -class QueueManager; -struct WorkingFiles; - -struct Project { - struct Entry { - std::string filename; - std::vector args; - // If true, this entry is inferred and was not read from disk. - bool is_inferred = false; - }; - - // Include directories for "" headers - std::vector quote_include_directories; - // Include directories for <> headers - std::vector angle_include_directories; - - std::vector entries; - spp::sparse_hash_map absolute_path_to_entry_index_; - - // Loads a project for the given |directory|. - // - // If |config->compilationDatabaseDirectory| is not empty, look for .cquery or - // compile_commands.json in it, otherwise they are retrieved in - // |root_directory|. - // For .cquery, recursive directory listing is used and files with known - // suffixes are indexed. .cquery files can exist in subdirectories and they will affect - // flags in their subtrees (relative paths are relative to the project root, - // not subdirectories). - // For compile_commands.json, its entries are indexed. - void Load(Config* config, const std::string& root_directory); - - // Lookup the CompilationEntry for |filename|. If no entry was found this - // will infer one based on existing project structure. - Entry FindCompilationEntryForFile(const std::string& filename); - - // Run |action| on every file in the project. - void ForAllFilteredFiles( - Config* config, - std::function action); - - void Index(Config* config, - QueueManager* queue, - WorkingFiles* wfiles, - lsRequestId id); -}; diff --git a/src/project.hh b/src/project.hh new file mode 100644 index 000000000..a95951225 --- /dev/null +++ b/src/project.hh @@ -0,0 +1,80 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "config.hh" +#include "lsp.hh" + +#include +#include +#include +#include +#include + +namespace ccls { +struct WorkingFiles; + +std::pair lookupExtension(std::string_view filename); + +struct Project { + struct Entry { + std::string root; + std::string directory; + std::string filename; + std::vector args; + // If true, this entry is inferred and was not read from disk. + bool is_inferred = false; + int id = -1; + }; + + struct Folder { + std::string name; + // Include directories for <> headers + std::vector angle_search_list; + // Include directories for "" headers + std::vector quote_search_list; + std::vector entries; + std::unordered_map path2entry_index; + }; + + std::mutex mutex_; + std::unordered_map root2folder; + + // Loads a project for the given |directory|. + // + // If |config->compilationDatabaseDirectory| is not empty, look for .ccls or + // compile_commands.json in it, otherwise they are retrieved in + // |root_directory|. + // For .ccls, recursive directory listing is used and files with known + // suffixes are indexed. .ccls files can exist in subdirectories and they + // will affect flags in their subtrees (relative paths are relative to the + // project root, not subdirectories). For compile_commands.json, its entries + // are indexed. + void Load(const std::string &root_directory); + + // Lookup the CompilationEntry for |filename|. If no entry was found this + // will infer one based on existing project structure. + Entry FindEntry(const std::string &path, bool can_be_inferred); + + // If the client has overridden the flags, or specified them for a file + // that is not in the compilation_database.json make sure those changes + // are permanent. + void SetArgsForFile(const std::vector &args, + const std::string &path); + + void Index(WorkingFiles *wfiles, RequestId id); +}; +} // namespace ccls diff --git a/src/query.cc b/src/query.cc index bb0d897bb..81dbb561b 100644 --- a/src/query.cc +++ b/src/query.cc @@ -1,1240 +1,837 @@ -#include "query.h" +/* Copyright 2017-2018 ccls Authors -#include "indexer.h" -#include "serializer.h" -#include "serializers/json.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#include -#include -#include + http://www.apache.org/licenses/LICENSE-2.0 -#include -#include +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "query.hh" + +#include "indexer.hh" +#include "pipeline.hh" +#include "serializer.hh" + +#include + +#include +#include +#include #include +#include #include #include #include -// TODO: Make all copy constructors explicit. - +namespace ccls { namespace { - -template -void VerifyUnique(const std::vector& values0) { -// FIXME: Run on a big code-base for a while and verify no assertions are -// triggered. -#if false - auto values = values0; - std::sort(values.begin(), values.end()); - assert(std::unique(values.begin(), values.end()) == values.end()); -#endif +void AssignFileId(const Lid2file_id &lid2file_id, int file_id, Use &use) { + if (use.file_id == -1) + use.file_id = file_id; + else + use.file_id = lid2file_id.find(use.file_id)->second; } template -void RemoveRange(std::vector* dest, const std::vector& to_remove) { - std::unordered_set to_remove_set(to_remove.begin(), to_remove.end()); - dest->erase( - std::remove_if(dest->begin(), dest->end(), - [&](const T& t) { return to_remove_set.count(t) > 0; }), - dest->end()); +void AddRange(std::vector &into, const std::vector &from) { + into.insert(into.end(), from.begin(), from.end()); } -optional ToQuery(const IdMap& id_map, - const IndexType::Def& type) { - if (type.detailed_name.empty()) - return nullopt; - - QueryType::Def result; - result.detailed_name = type.detailed_name; - result.short_name_offset = type.short_name_offset; - result.short_name_size = type.short_name_size; - result.kind = type.kind; - if (!type.hover.empty()) - result.hover = type.hover; - if (!type.comments.empty()) - result.comments = type.comments; - result.file = id_map.primary_file; - result.spell = id_map.ToQuery(type.spell); - result.extent = id_map.ToQuery(type.extent); - result.alias_of = id_map.ToQuery(type.alias_of); - result.bases = id_map.ToQuery(type.bases); - result.types = id_map.ToQuery(type.types); - result.funcs = id_map.ToQuery(type.funcs); - result.vars = id_map.ToQuery(type.vars); - return result; -} - -optional ToQuery(const IdMap& id_map, - const IndexFunc::Def& func) { - if (func.detailed_name.empty()) - return nullopt; - - QueryFunc::Def result; - result.detailed_name = func.detailed_name; - result.short_name_offset = func.short_name_offset; - result.short_name_size = func.short_name_size; - result.kind = func.kind; - result.storage = func.storage; - if (!func.hover.empty()) - result.hover = func.hover; - if (!func.comments.empty()) - result.comments = func.comments; - result.file = id_map.primary_file; - result.spell = id_map.ToQuery(func.spell); - result.extent = id_map.ToQuery(func.extent); - result.declaring_type = id_map.ToQuery(func.declaring_type); - result.bases = id_map.ToQuery(func.bases); - result.vars = id_map.ToQuery(func.vars); - result.callees = id_map.ToQuery(func.callees); - return result; +template +void RemoveRange(std::vector &from, const std::vector &to_remove) { + if (to_remove.size()) { + std::unordered_set to_remove_set(to_remove.begin(), to_remove.end()); + from.erase( + std::remove_if(from.begin(), from.end(), + [&](const T &t) { return to_remove_set.count(t) > 0; }), + from.end()); + } } -optional ToQuery(const IdMap& id_map, const IndexVar::Def& var) { - if (var.detailed_name.empty()) - return nullopt; - - QueryVar::Def result; - result.detailed_name = var.detailed_name; - result.short_name_offset = var.short_name_offset; - result.short_name_size = var.short_name_size; - if (!var.hover.empty()) - result.hover = var.hover; - if (!var.comments.empty()) - result.comments = var.comments; - result.file = id_map.primary_file; - result.spell = id_map.ToQuery(var.spell); - result.extent = id_map.ToQuery(var.extent); - result.type = id_map.ToQuery(var.type); - result.kind = var.kind; - result.storage = var.storage; - return result; +QueryFile::DefUpdate BuildFileDefUpdate(IndexFile &&indexed) { + QueryFile::Def def; + def.path = std::move(indexed.path); + def.args = std::move(indexed.args); + def.includes = std::move(indexed.includes); + def.skipped_ranges = std::move(indexed.skipped_ranges); + def.dependencies.reserve(indexed.dependencies.size()); + for (auto &dep : indexed.dependencies) + def.dependencies.push_back(dep.first.val().data()); // llvm 8 -> data() + def.language = indexed.language; + return {std::move(def), std::move(indexed.file_contents)}; } -// Adds the mergeable updates in |source| to |dest|. If a mergeable update for -// the destination type already exists, it will be combined. This makes merging -// updates take longer but reduces import time on the querydb thread. -template -void AddMergeableRange(std::vector>* dest, - std::vector>&& source) { - // TODO: Consider caching the lookup table. It can probably save even more - // time at the cost of some additional memory. - - // Build lookup table. - spp::sparse_hash_map id_to_index; - id_to_index.resize(dest->size()); - for (size_t i = 0; i < dest->size(); ++i) - id_to_index[(*dest)[i].id] = i; - - // Add entries. Try to add them to an existing entry. - for (auto& entry : source) { - auto it = id_to_index.find(entry.id); - if (it != id_to_index.end()) { - AddRange(&(*dest)[it->second].to_add, std::move(entry.to_add)); - AddRange(&(*dest)[it->second].to_remove, std::move(entry.to_remove)); - } else { - dest->push_back(std::move(entry)); +// Returns true if an element with the same file is found. +template +bool TryReplaceDef(llvm::SmallVectorImpl &def_list, Q &&def) { + for (auto &def1 : def_list) + if (def1.file_id == def.file_id) { + def1 = std::move(def); + return true; } - } + return false; } -// Compares |previous| and |current|, adding all elements that are in |previous| -// but not |current| to |removed|, and all elements that are in |current| but -// not |previous| to |added|. -// -// Returns true iff |removed| or |added| are non-empty. -template -bool ComputeDifferenceForUpdate(std::vector&& previous, - std::vector&& current, - std::vector* removed, - std::vector* added) { - // We need to sort to use std::set_difference. - std::sort(previous.begin(), previous.end()); - std::sort(current.begin(), current.end()); - - auto it0 = previous.begin(), it1 = current.begin(); - while (it0 != previous.end() && it1 != current.end()) { - // Elements in |previous| that are not in |current|. - if (*it0 < *it1) - removed->push_back(std::move(*it0++)); - // Elements in |current| that are not in |previous|. - else if (*it1 < *it0) - added->push_back(std::move(*it1++)); - else - ++it0, ++it1; +} // namespace + +IndexUpdate IndexUpdate::CreateDelta(IndexFile *previous, IndexFile *current) { + IndexUpdate r; + static IndexFile empty(llvm::sys::fs::UniqueID(0, 0), current->path, + ""); + if (previous) + r.prev_lid2path = std::move(previous->lid2path); + else + previous = ∅ + r.lid2path = std::move(current->lid2path); + + r.funcs_hint = current->usr2func.size() - previous->usr2func.size(); + for (auto &it : previous->usr2func) { + auto &func = it.second; + if (func.def.detailed_name[0]) + r.funcs_removed.emplace_back(func.usr, func.def); + r.funcs_declarations[func.usr].first = std::move(func.declarations); + r.funcs_uses[func.usr].first = std::move(func.uses); + r.funcs_derived[func.usr].first = std::move(func.derived); + r.funcs_data_flow_into_return[func.usr].first = std::move(func.data_flow_into_return); + } + for (auto &it : current->usr2func) { + auto &func = it.second; + if (func.def.detailed_name[0]) + r.funcs_def_update.emplace_back(it.first, func.def); + r.funcs_declarations[func.usr].second = std::move(func.declarations); + r.funcs_uses[func.usr].second = std::move(func.uses); + r.funcs_derived[func.usr].second = std::move(func.derived); + r.funcs_data_flow_into_return[func.usr].second = std::move(func.data_flow_into_return); } - while (it0 != previous.end()) - removed->push_back(std::move(*it0++)); - while (it1 != current.end()) - added->push_back(std::move(*it1++)); - return !removed->empty() || !added->empty(); -} + r.types_hint = current->usr2type.size() - previous->usr2type.size(); + for (auto &it : previous->usr2type) { + auto &type = it.second; + if (type.def.detailed_name[0]) + r.types_removed.emplace_back(type.usr, type.def); + r.types_declarations[type.usr].first = std::move(type.declarations); + r.types_uses[type.usr].first = std::move(type.uses); + r.types_derived[type.usr].first = std::move(type.derived); + r.types_instances[type.usr].first = std::move(type.instances); + }; + for (auto &it : current->usr2type) { + auto &type = it.second; + if (type.def.detailed_name[0]) + r.types_def_update.emplace_back(it.first, type.def); + r.types_declarations[type.usr].second = std::move(type.declarations); + r.types_uses[type.usr].second = std::move(type.uses); + r.types_derived[type.usr].second = std::move(type.derived); + r.types_instances[type.usr].second = std::move(type.instances); + }; -template -void CompareGroups(std::vector& previous_data, - std::vector& current_data, - std::function on_removed, - std::function on_added, - std::function on_found) { - std::sort(previous_data.begin(), previous_data.end()); - std::sort(current_data.begin(), current_data.end()); - - auto prev_it = previous_data.begin(); - auto curr_it = current_data.begin(); - while (prev_it != previous_data.end() && curr_it != current_data.end()) { - // same id - if (prev_it->usr == curr_it->usr) { - on_found(&*prev_it, &*curr_it); - ++prev_it; - ++curr_it; - } + r.vars_hint = current->usr2var.size() - previous->usr2var.size(); + for (auto &it : previous->usr2var) { + auto &var = it.second; + if (var.def.detailed_name[0]) + r.vars_removed.emplace_back(var.usr, var.def); + r.vars_declarations[var.usr].first = std::move(var.declarations); + r.vars_uses[var.usr].first = std::move(var.uses); + r.vars_data_flow_into[var.usr].first = std::move(var.data_flow_into); + } + for (auto &it : current->usr2var) { + auto &var = it.second; + if (var.def.detailed_name[0]) + r.vars_def_update.emplace_back(it.first, var.def); + r.vars_declarations[var.usr].second = std::move(var.declarations); + r.vars_uses[var.usr].second = std::move(var.uses); + r.vars_data_flow_into[var.usr].second = std::move(var.data_flow_into); + } - // prev_id is smaller - prev_it has data curr_it does not have. - else if (prev_it->usr < curr_it->usr) { - on_removed(&*prev_it); - ++prev_it; + r.files_def_update = BuildFileDefUpdate(std::move(*current)); + return r; +} + +void DB::clear() { + files.clear(); + name2file_id.clear(); + func_usr.clear(); + type_usr.clear(); + var_usr.clear(); + funcs.clear(); + types.clear(); + vars.clear(); +} + +template +void DB::RemoveUsrs(Kind kind, int file_id, + const std::vector> &to_remove) { + switch (kind) { + case Kind::Func: { + for (auto &[usr, _] : to_remove) { + // FIXME + if (!HasFunc(usr)) + continue; + QueryFunc &func = Func(usr); + auto it = llvm::find_if(func.def, [=](const QueryFunc::Def &def) { + return def.file_id == file_id; + }); + if (it != func.def.end()) + func.def.erase(it); } - - // prev_id is bigger - curr_it has data prev_it does not have. - else { - on_added(&*curr_it); - ++curr_it; + break; + } + case Kind::Type: { + for (auto &[usr, _] : to_remove) { + // FIXME + if (!HasType(usr)) + continue; + QueryType &type = Type(usr); + auto it = llvm::find_if(type.def, [=](const QueryType::Def &def) { + return def.file_id == file_id; + }); + if (it != type.def.end()) + type.def.erase(it); } + break; } - - // if prev_it still has data, that means it is not in curr_it and was removed. - while (prev_it != previous_data.end()) { - on_removed(&*prev_it); - ++prev_it; + case Kind::Var: { + for (auto &[usr, _] : to_remove) { + // FIXME + if (!HasVar(usr)) + continue; + QueryVar &var = Var(usr); + auto it = llvm::find_if(var.def, [=](const QueryVar::Def &def) { + return def.file_id == file_id; + }); + if (it != var.def.end()) + var.def.erase(it); + } + break; } - - // if curr_it still has data, that means it is not in prev_it and was added. - while (curr_it != current_data.end()) { - on_added(&*curr_it); - ++curr_it; + default: + break; } } -QueryFile::DefUpdate BuildFileDefUpdate(const IdMap& id_map, - const IndexFile& indexed) { - QueryFile::Def def; - def.file = id_map.primary_file; - def.path = indexed.path; - def.args = indexed.args; - def.includes = indexed.includes; - def.inactive_regions = indexed.skipped_by_preprocessor; - def.dependencies = indexed.dependencies; - - // Convert enum to markdown compatible strings - def.language = [&indexed]() { - switch (indexed.language) { - case LanguageId::C: - return "c"; - case LanguageId::Cpp: - return "cpp"; - case LanguageId::ObjC: - return "objective-c"; - case LanguageId::ObjCpp: - return "objective-cpp"; - default: - return ""; +void DB::ApplyIndexUpdate(IndexUpdate *u) { +#define REMOVE_ADD(C, F) \ + for (auto &it : u->C##s_##F) { \ + auto R = C##_usr.try_emplace({it.first}, C##_usr.size()); \ + if (R.second) \ + C##s.emplace_back().usr = it.first; \ + auto &entity = C##s[R.first->second]; \ + RemoveRange(entity.F, it.second.first); \ + AddRange(entity.F, it.second.second); \ + } + + std::unordered_map prev_lid2file_id, lid2file_id; + for (auto &[lid, path] : u->prev_lid2path) + prev_lid2file_id[lid] = GetFileId(path); + for (auto &[lid, path] : u->lid2path) { + int file_id = GetFileId(path); + lid2file_id[lid] = file_id; + if (!files[file_id].def) { + files[file_id].def = QueryFile::Def(); + files[file_id].def->path = path; } - }(); + } - auto add_all_symbols = [&](Use use, Id id, SymbolKind kind) { - def.all_symbols.push_back(SymbolRef(use.range, id, kind, use.role)); + // References (Use &use) in this function are important to update file_id. + auto Ref = [&](std::unordered_map &lid2fid, Usr usr, Kind kind, + Use &use, int delta) { + use.file_id = + use.file_id == -1 ? u->file_id : lid2fid.find(use.file_id)->second; + if (usr == 0) return; + + ExtentRef sym{{use.range, usr, kind, use.role}}; + int &v = files[use.file_id].symbol2refcnt[sym]; + v += delta; + assert(v >= 0); + if (!v) + files[use.file_id].symbol2refcnt.erase(sym); }; - auto add_outline = [&](Use use, Id id, SymbolKind kind) { - def.outline.push_back(SymbolRef(use.range, id, kind, use.role)); + auto RefDecl = [&](std::unordered_map &lid2fid, Usr usr, Kind kind, + DeclRef &dr, int delta) { + dr.file_id = + dr.file_id == -1 ? u->file_id : lid2fid.find(dr.file_id)->second; + ExtentRef sym{{dr.range, usr, kind, dr.role}, dr.extent}; + int &v = files[dr.file_id].symbol2refcnt[sym]; + v += delta; + assert(v >= 0); + if (!v) + files[dr.file_id].symbol2refcnt.erase(sym); }; - for (const IndexType& type : indexed.types) { - QueryTypeId id = id_map.ToQuery(type.id); - if (type.def.spell) - add_all_symbols(*type.def.spell, id, SymbolKind::Type); - if (type.def.extent) - add_outline(*type.def.extent, id, SymbolKind::Type); - for (Use decl : type.declarations) { - add_all_symbols(decl, id, SymbolKind::Type); - // Constructor positions have references to the class, - // which we do not want to show in textDocument/documentSymbol - if (!(decl.role & Role::Reference)) - add_outline(decl, id, SymbolKind::Type); - } - for (Use use : type.uses) - add_all_symbols(use, id, SymbolKind::Type); + auto UpdateUses = + [&](Usr usr, Kind kind, + llvm::DenseMap &entity_usr, + auto &entities, auto &p, bool hint_implicit) { + auto R = entity_usr.try_emplace(usr, entity_usr.size()); + if (R.second) + entities.emplace_back().usr = usr; + auto &entity = entities[R.first->second]; + for (Use &use : p.first) { + if (hint_implicit && use.role & Role::Implicit) { + // Make ranges of implicit function calls larger (spanning one more + // column to the left/right). This is hacky but useful. e.g. + // textDocument/definition on the space/semicolon in `A a;` or ` + // 42;` will take you to the constructor. + if (use.range.start.column > 0) + use.range.start.column--; + use.range.end.column++; + } + Ref(prev_lid2file_id, usr, kind, use, -1); + } + RemoveRange(entity.uses, p.first); + for (Use &use : p.second) { + if (hint_implicit && use.role & Role::Implicit) { + if (use.range.start.column > 0) + use.range.start.column--; + use.range.end.column++; + } + Ref(lid2file_id, usr, kind, use, 1); + } + AddRange(entity.uses, p.second); + }; + + if (u->files_removed) + files[name2file_id[LowerPathIfInsensitive(*u->files_removed)]].def = + std::nullopt; + u->file_id = + u->files_def_update ? Update(std::move(*u->files_def_update)) : -1; + + const double grow = 1.3; + size_t t; + + if ((t = funcs.size() + u->funcs_hint) > funcs.capacity()) { + t = size_t(t * grow); + funcs.reserve(t); + func_usr.reserve(t); + } + for (auto &[usr, def] : u->funcs_removed) + if (def.spell) + RefDecl(prev_lid2file_id, usr, Kind::Func, *def.spell, -1); + RemoveUsrs(Kind::Func, u->file_id, u->funcs_removed); + Update(lid2file_id, u->file_id, std::move(u->funcs_def_update)); + for (auto &[usr, del_add]: u->funcs_declarations) { + for (DeclRef &dr : del_add.first) + RefDecl(prev_lid2file_id, usr, Kind::Func, dr, -1); + for (DeclRef &dr : del_add.second) + RefDecl(lid2file_id, usr, Kind::Func, dr, 1); } - for (const IndexFunc& func : indexed.funcs) { - QueryFuncId id = id_map.ToQuery(func.id); - if (func.def.spell) - add_all_symbols(*func.def.spell, id, SymbolKind::Func); - if (func.def.extent) - add_outline(*func.def.extent, id, SymbolKind::Func); - for (const IndexFunc::Declaration& decl : func.declarations) { - add_all_symbols(decl.spell, id, SymbolKind::Func); - add_outline(decl.spell, id, SymbolKind::Func); + REMOVE_ADD(func, declarations); + REMOVE_ADD(func, derived); + for (auto &[usr, p] : u->funcs_uses) + UpdateUses(usr, Kind::Func, func_usr, funcs, p, true); + for (auto &[usr, p] : u->funcs_data_flow_into_return) { + auto R = func_usr.try_emplace(usr, func_usr.size()); + if (R.second) + funcs.emplace_back().usr = usr; + auto &func = funcs[R.first->second]; + for (DataFlow &src : p.first) { + Ref(prev_lid2file_id, src.from, src.use.role == Role::Read ? Kind::Var : Kind::Func, src.use, -1); } - for (Use use : func.uses) { - // Make ranges of implicit function calls larger (spanning one more column - // to the left/right). This is hacky but useful. e.g. - // textDocument/definition on the space/semicolon in `A a;` or `return - // 42;` will take you to the constructor. - if (use.role & Role::Implicit) { - if (use.range.start.column > 0) - use.range.start.column--; - use.range.end.column++; - } - add_all_symbols(use, id, SymbolKind::Func); + RemoveRange(func.data_flow_into_return, p.first); + for (DataFlow &src : p.second) { + Ref(lid2file_id, src.from, src.use.role == Role::Read ? Kind::Var : Kind::Func, src.use, 1); } + AddRange(func.data_flow_into_return, p.second); } - for (const IndexVar& var : indexed.vars) { - QueryVarId id = id_map.ToQuery(var.id); - if (var.def.spell) - add_all_symbols(*var.def.spell, id, SymbolKind::Var); - if (var.def.extent) - add_outline(*var.def.extent, id, SymbolKind::Var); - for (Use decl : var.declarations) { - add_all_symbols(decl, id, SymbolKind::Var); - add_outline(decl, id, SymbolKind::Var); + + if ((t = types.size() + u->types_hint) > types.capacity()) { + t = size_t(t * grow); + types.reserve(t); + type_usr.reserve(t); + } + for (auto &[usr, def] : u->types_removed) + if (def.spell) + RefDecl(prev_lid2file_id, usr, Kind::Type, *def.spell, -1); + RemoveUsrs(Kind::Type, u->file_id, u->types_removed); + Update(lid2file_id, u->file_id, std::move(u->types_def_update)); + for (auto &[usr, del_add]: u->types_declarations) { + for (DeclRef &dr : del_add.first) + RefDecl(prev_lid2file_id, usr, Kind::Type, dr, -1); + for (DeclRef &dr : del_add.second) + RefDecl(lid2file_id, usr, Kind::Type, dr, 1); + } + REMOVE_ADD(type, declarations); + REMOVE_ADD(type, derived); + REMOVE_ADD(type, instances); + for (auto &[usr, p] : u->types_uses) + UpdateUses(usr, Kind::Type, type_usr, types, p, false); + + if ((t = vars.size() + u->vars_hint) > vars.capacity()) { + t = size_t(t * grow); + vars.reserve(t); + var_usr.reserve(t); + } + for (auto &[usr, def] : u->vars_removed) + if (def.spell) + RefDecl(prev_lid2file_id, usr, Kind::Var, *def.spell, -1); + RemoveUsrs(Kind::Var, u->file_id, u->vars_removed); + Update(lid2file_id, u->file_id, std::move(u->vars_def_update)); + for (auto &[usr, del_add]: u->vars_declarations) { + for (DeclRef &dr : del_add.first) + RefDecl(prev_lid2file_id, usr, Kind::Var, dr, -1); + for (DeclRef &dr : del_add.second) + RefDecl(lid2file_id, usr, Kind::Var, dr, 1); + } + REMOVE_ADD(var, declarations); + for (auto &[usr, p] : u->vars_uses) + UpdateUses(usr, Kind::Var, var_usr, vars, p, false); + for (auto &[usr, p] : u->vars_data_flow_into) { + auto R = var_usr.try_emplace(usr, var_usr.size()); + if (R.second) + vars.emplace_back().usr = usr; + auto &var = vars[R.first->second]; + for (DataFlow &src : p.first) { + Ref(prev_lid2file_id, src.from, src.use.role == Role::Read ? Kind::Var : Kind::Func, src.use, -1); + } + RemoveRange(var.data_flow_into, p.first); + for (DataFlow &src : p.second) { + Ref(lid2file_id, src.from, src.use.role == Role::Read ? Kind::Var : Kind::Func, src.use, 1); } - for (Use use : var.uses) - add_all_symbols(use, id, SymbolKind::Var); + AddRange(var.data_flow_into, p.second); } - std::sort(def.outline.begin(), def.outline.end(), - [](const SymbolRef& a, const SymbolRef& b) { - return a.range.start < b.range.start; - }); - std::sort(def.all_symbols.begin(), def.all_symbols.end(), - [](const SymbolRef& a, const SymbolRef& b) { - return a.range.start < b.range.start; - }); - - return QueryFile::DefUpdate(def, indexed.file_contents); -} - -Maybe GetQueryFileIdFromPath(QueryDatabase* query_db, - const std::string& path, - bool create_if_missing) { - NormalizedPath normalized_path(path); - auto it = query_db->usr_to_file.find(normalized_path); - if (it != query_db->usr_to_file.end()) - return QueryFileId(it->second.id); - if (!create_if_missing) - return {}; - - RawId idx = query_db->files.size(); - query_db->usr_to_file[normalized_path] = QueryFileId(idx); - query_db->files.push_back(QueryFile(path)); - return QueryFileId(idx); -} - -Maybe GetQueryTypeIdFromUsr(QueryDatabase* query_db, - Usr usr, - bool create_if_missing) { - auto it = query_db->usr_to_type.find(usr); - if (it != query_db->usr_to_type.end()) - return QueryTypeId(it->second.id); - if (!create_if_missing) - return {}; - - RawId idx = query_db->types.size(); - query_db->usr_to_type[usr] = QueryTypeId(idx); - query_db->types.push_back(QueryType(usr)); - return QueryTypeId(idx); -} - -Maybe GetQueryFuncIdFromUsr(QueryDatabase* query_db, - Usr usr, - bool create_if_missing) { - auto it = query_db->usr_to_func.find(usr); - if (it != query_db->usr_to_func.end()) - return QueryFuncId(it->second.id); - if (!create_if_missing) - return {}; - - RawId idx = query_db->funcs.size(); - query_db->usr_to_func[usr] = QueryFuncId(idx); - query_db->funcs.push_back(QueryFunc(usr)); - return QueryFuncId(idx); +#undef REMOVE_ADD } -Maybe GetQueryVarIdFromUsr(QueryDatabase* query_db, - Usr usr, - bool create_if_missing) { - auto it = query_db->usr_to_var.find(usr); - if (it != query_db->usr_to_var.end()) - return QueryVarId(it->second.id); - if (!create_if_missing) - return {}; - - RawId idx = query_db->vars.size(); - query_db->usr_to_var[usr] = QueryVarId(idx); - query_db->vars.push_back(QueryVar(usr)); - return QueryVarId(idx); -} - -// Returns true if an element with the same file is found. -template -bool TryReplaceDef(std::forward_list& def_list, Q&& def) { - for (auto& def1 : def_list) - if (def1.file == def.file) { - if (!def1.spell || def.spell) - def1 = std::move(def); - return true; +int DB::GetFileId(const std::string &path) { + auto it = name2file_id.try_emplace(LowerPathIfInsensitive(path)); + if (it.second) { + int id = files.size(); + it.first->second = files.emplace_back().id = id; + } + return it.first->second; +} + +int DB::Update(QueryFile::DefUpdate &&u) { + int file_id = GetFileId(u.first.path); + files[file_id].def = u.first; + return file_id; +} + +void DB::Update(const Lid2file_id &lid2file_id, int file_id, + std::vector> &&us) { + for (auto &u : us) { + auto &def = u.second; + assert(def.detailed_name[0]); + u.second.file_id = file_id; + if (def.spell) { + AssignFileId(lid2file_id, file_id, *def.spell); + files[def.spell->file_id].symbol2refcnt[{ + {def.spell->range, u.first, Kind::Func, def.spell->role}, + def.spell->extent}]++; } - return false; -} - -} // namespace -Maybe QueryDatabase::GetQueryFileIdFromPath( - const std::string& path) { - return ::GetQueryFileIdFromPath(this, path, false); + auto R = func_usr.try_emplace({u.first}, func_usr.size()); + if (R.second) + funcs.emplace_back(); + QueryFunc &existing = funcs[R.first->second]; + existing.usr = u.first; + if (!TryReplaceDef(existing.def, std::move(def))) + existing.def.push_back(std::move(def)); + } } -Maybe QueryDatabase::GetQueryTypeIdFromUsr(Usr usr) { - return ::GetQueryTypeIdFromUsr(this, usr, false); +void DB::Update(const Lid2file_id &lid2file_id, int file_id, + std::vector> &&us) { + for (auto &u : us) { + auto &def = u.second; + assert(def.detailed_name[0]); + u.second.file_id = file_id; + if (def.spell) { + AssignFileId(lid2file_id, file_id, *def.spell); + files[def.spell->file_id].symbol2refcnt[{ + {def.spell->range, u.first, Kind::Type, def.spell->role}, + def.spell->extent}]++; + } + auto R = type_usr.try_emplace({u.first}, type_usr.size()); + if (R.second) + types.emplace_back(); + QueryType &existing = types[R.first->second]; + existing.usr = u.first; + if (!TryReplaceDef(existing.def, std::move(def))) + existing.def.push_back(std::move(def)); + } } -Maybe QueryDatabase::GetQueryFuncIdFromUsr(Usr usr) { - return ::GetQueryFuncIdFromUsr(this, usr, false); +void DB::Update(const Lid2file_id &lid2file_id, int file_id, + std::vector> &&us) { + for (auto &u : us) { + auto &def = u.second; + assert(def.detailed_name[0]); + u.second.file_id = file_id; + if (def.spell) { + AssignFileId(lid2file_id, file_id, *def.spell); + files[def.spell->file_id].symbol2refcnt[{ + {def.spell->range, u.first, Kind::Var, def.spell->role}, + def.spell->extent}]++; + } + auto R = var_usr.try_emplace({u.first}, var_usr.size()); + if (R.second) + vars.emplace_back(); + QueryVar &existing = vars[R.first->second]; + existing.usr = u.first; + if (!TryReplaceDef(existing.def, std::move(def))) + existing.def.push_back(std::move(def)); + } } -Maybe QueryDatabase::GetQueryVarIdFromUsr(Usr usr) { - return ::GetQueryVarIdFromUsr(this, usr, false); +std::string_view DB::GetSymbolName(SymbolIdx sym, bool qualified) { + Usr usr = sym.usr; + switch (sym.kind) { + default: + break; + case Kind::File: + if (files[usr].def) + return files[usr].def->path; + break; + case Kind::Func: + if (const auto *def = Func(usr).AnyDef()) + return def->Name(qualified); + break; + case Kind::Type: + if (const auto *def = Type(usr).AnyDef()) + return def->Name(qualified); + break; + case Kind::Var: + if (const auto *def = Var(usr).AnyDef()) + return def->Name(qualified); + break; + } + return ""; } -IdMap::IdMap(QueryDatabase* query_db, const IdCache& local_ids) - : local_ids(local_ids) { - // LOG_S(INFO) << "Creating IdMap for " << local_ids.primary_file; - primary_file = - *GetQueryFileIdFromPath(query_db, local_ids.primary_file, true); - - cached_type_ids_.resize(local_ids.type_id_to_usr.size()); - for (const auto& entry : local_ids.type_id_to_usr) - cached_type_ids_[entry.first] = - *GetQueryTypeIdFromUsr(query_db, entry.second, true); - - cached_func_ids_.resize(local_ids.func_id_to_usr.size()); - for (const auto& entry : local_ids.func_id_to_usr) - cached_func_ids_[entry.first] = - *GetQueryFuncIdFromUsr(query_db, entry.second, true); - - cached_var_ids_.resize(local_ids.var_id_to_usr.size()); - for (const auto& entry : local_ids.var_id_to_usr) - cached_var_ids_[entry.first] = - *GetQueryVarIdFromUsr(query_db, entry.second, true); +std::vector DB::GetFileSet(const std::vector &folders) { + if (folders.empty()) + return std::vector(files.size(), 1); + std::vector file_set(files.size()); + for (QueryFile &file : files) + if (file.def) { + bool ok = false; + for (auto &folder : folders) + if (llvm::StringRef(file.def->path).startswith(folder)) { + ok = true; + break; + } + if (ok) + file_set[file.id] = 1; + } + return file_set; } -QueryTypeId IdMap::ToQuery(IndexTypeId id) const { - assert(cached_type_ids_.find(id) != cached_type_ids_.end()); - return QueryTypeId(cached_type_ids_.find(id)->second); -} -QueryFuncId IdMap::ToQuery(IndexFuncId id) const { - assert(cached_func_ids_.find(id) != cached_func_ids_.end()); - return QueryFuncId(cached_func_ids_.find(id)->second); -} -QueryVarId IdMap::ToQuery(IndexVarId id) const { - assert(cached_var_ids_.find(id) != cached_var_ids_.end()); - return QueryVarId(cached_var_ids_.find(id)->second); +namespace { +// Computes roughly how long |range| is. +int ComputeRangeSize(const Range &range) { + if (range.start.line != range.end.line) + return INT_MAX; + return range.end.column - range.start.column; } -Use IdMap::ToQuery(Reference ref) const { - Use ret(ref.range, ref.id, ref.kind, ref.role, primary_file); - switch (ref.kind) { - case SymbolKind::Invalid: - break; - case SymbolKind::File: - ret.id = primary_file; - break; - case SymbolKind::Func: - ret.id = ToQuery(IndexFuncId(ref.id)); - break; - case SymbolKind::Type: - ret.id = ToQuery(IndexTypeId(ref.id)); - break; - case SymbolKind::Var: - ret.id = ToQuery(IndexVarId(ref.id)); - break; +template +std::vector +GetDeclarations(llvm::DenseMap &entity_usr, + std::vector &entities, const std::vector &usrs) { + std::vector ret; + ret.reserve(usrs.size()); + for (Usr usr : usrs) { + Q &entity = entities[entity_usr[{usr}]]; + bool has_def = false; + for (auto &def : entity.def) + if (def.spell) { + ret.push_back(*def.spell); + has_def = true; + break; + } + if (!has_def && entity.declarations.size()) + ret.push_back(entity.declarations[0]); } return ret; } -SymbolRef IdMap::ToQuery(SymbolRef ref) const { - ref.Reference::operator=(ToQuery(static_cast(ref))); - return ref; -} -Use IdMap::ToQuery(Use use) const { - return ToQuery(static_cast(use)); } -Use IdMap::ToQuery(IndexFunc::Declaration decl) const { - return ToQuery(static_cast(decl.spell)); +Maybe GetDefinitionSpell(DB *db, SymbolIdx sym) { + Maybe ret; + EachEntityDef(db, sym, [&](const auto &def) { return !(ret = def.spell); }); + return ret; } -// ---------------------- -// INDEX THREAD FUNCTIONS -// ---------------------- - -// static -IndexUpdate IndexUpdate::CreateDelta(const IdMap* previous_id_map, - const IdMap* current_id_map, - IndexFile* previous, - IndexFile* current) { - // This function runs on an indexer thread. - - if (!previous_id_map) { - assert(!previous); - IndexFile empty(current->path, ""); - return IndexUpdate(*current_id_map, *current_id_map, empty, *current); +std::vector GetFuncDeclarations(DB *db, const std::vector &usrs) { + return GetDeclarations(db->func_usr, db->funcs, usrs); +} +std::vector GetTypeDeclarations(DB *db, const std::vector &usrs) { + return GetDeclarations(db->type_usr, db->types, usrs); +} +std::vector GetVarDeclarations(DB *db, const std::vector &usrs, + unsigned kind) { + std::vector ret; + ret.reserve(usrs.size()); + for (Usr usr : usrs) { + QueryVar &var = db->Var(usr); + bool has_def = false; + for (auto &def : var.def) + if (def.spell) { + has_def = true; + // See messages/ccls_vars.cc + if (def.kind == SymbolKind::Field) { + if (!(kind & 1)) + break; + } else if (def.kind == SymbolKind::Variable) { + if (!(kind & 2)) + break; + } else if (def.kind == SymbolKind::Parameter) { + if (!(kind & 4)) + break; + } + ret.push_back(*def.spell); + break; + } + if (!has_def && var.declarations.size()) + ret.push_back(var.declarations[0]); } - return IndexUpdate(*previous_id_map, *current_id_map, *previous, *current); + return ret; } -IndexUpdate::IndexUpdate(const IdMap& previous_id_map, - const IdMap& current_id_map, - IndexFile& previous_file, - IndexFile& current_file) { -// This function runs on an indexer thread. - -// |query_name| is the name of the variable on the query type. -// |index_name| is the name of the variable on the index type. -// |type| is the type of the variable. -#define PROCESS_UPDATE_DIFF(type_id, query_name, index_name, type) \ - { \ - /* Check for changes. */ \ - std::vector removed, added; \ - auto query_previous = previous_id_map.ToQuery(previous->index_name); \ - auto query_current = current_id_map.ToQuery(current->index_name); \ - bool did_add = ComputeDifferenceForUpdate(std::move(query_previous), \ - std::move(query_current), \ - &removed, &added); \ - if (did_add) { \ - query_name.push_back(MergeableUpdate( \ - current_id_map.ToQuery(current->id), std::move(added), \ - std::move(removed))); \ - } \ +std::vector &GetNonDefDeclarations(DB *db, SymbolIdx sym) { + static std::vector empty; + switch (sym.kind) { + case Kind::Func: + return db->GetFunc(sym).declarations; + case Kind::Type: + return db->GetType(sym).declarations; + case Kind::Var: + return db->GetVar(sym).declarations; + default: + break; } - // File - files_def_update.push_back(BuildFileDefUpdate(current_id_map, current_file)); - - // **NOTE** We only remove entries if they were defined in the previous index. - // For example, if a type is included from another file it will be defined - // simply so we can attribute the usage/reference to it. If the reference goes - // away we don't want to remove the type/func/var usage. - - // Types - CompareGroups( - previous_file.types, current_file.types, - /*onRemoved:*/ - [this, &previous_id_map](IndexType* type) { - if (type->def.spell) - types_removed.push_back(type->usr); - if (!type->declarations.empty()) - types_declarations.push_back(QueryType::DeclarationsUpdate( - previous_id_map.ToQuery(type->id), {}, - previous_id_map.ToQuery(type->declarations))); - if (!type->derived.empty()) - types_derived.push_back( - QueryType::DerivedUpdate(previous_id_map.ToQuery(type->id), {}, - previous_id_map.ToQuery(type->derived))); - if (!type->instances.empty()) - types_instances.push_back(QueryType::InstancesUpdate( - previous_id_map.ToQuery(type->id), {}, - previous_id_map.ToQuery(type->instances))); - if (!type->uses.empty()) - types_uses.push_back( - QueryType::UsesUpdate(previous_id_map.ToQuery(type->id), {}, - previous_id_map.ToQuery(type->uses))); - }, - /*onAdded:*/ - [this, ¤t_id_map](IndexType* type) { - optional def_update = - ToQuery(current_id_map, type->def); - if (def_update) - types_def_update.push_back( - QueryType::DefUpdate(type->usr, std::move(*def_update))); - if (!type->declarations.empty()) - types_declarations.push_back(QueryType::DeclarationsUpdate( - current_id_map.ToQuery(type->id), - current_id_map.ToQuery(type->declarations))); - if (!type->derived.empty()) - types_derived.push_back( - QueryType::DerivedUpdate(current_id_map.ToQuery(type->id), - current_id_map.ToQuery(type->derived))); - if (!type->instances.empty()) - types_instances.push_back(QueryType::InstancesUpdate( - current_id_map.ToQuery(type->id), - current_id_map.ToQuery(type->instances))); - if (!type->uses.empty()) - types_uses.push_back( - QueryType::UsesUpdate(current_id_map.ToQuery(type->id), - current_id_map.ToQuery(type->uses))); - }, - /*onFound:*/ - [this, &previous_id_map, ¤t_id_map](IndexType* previous, - IndexType* current) { - optional previous_remapped_def = - ToQuery(previous_id_map, previous->def); - optional current_remapped_def = - ToQuery(current_id_map, current->def); - if (current_remapped_def && - previous_remapped_def != current_remapped_def && - !current_remapped_def->detailed_name.empty()) { - types_def_update.push_back(QueryType::DefUpdate( - current->usr, std::move(*current_remapped_def))); - } - - PROCESS_UPDATE_DIFF(QueryTypeId, types_declarations, declarations, Use); - PROCESS_UPDATE_DIFF(QueryTypeId, types_derived, derived, QueryTypeId); - PROCESS_UPDATE_DIFF(QueryTypeId, types_instances, instances, - QueryVarId); - PROCESS_UPDATE_DIFF(QueryTypeId, types_uses, uses, Use); - }); - - // Functions - CompareGroups( - previous_file.funcs, current_file.funcs, - /*onRemoved:*/ - [this, &previous_id_map](IndexFunc* func) { - if (func->def.spell) - funcs_removed.emplace_back(func->usr, previous_id_map.primary_file); - if (!func->declarations.empty()) - funcs_declarations.push_back(QueryFunc::DeclarationsUpdate( - previous_id_map.ToQuery(func->id), {}, - previous_id_map.ToQuery(func->declarations))); - if (!func->derived.empty()) - funcs_derived.push_back( - QueryFunc::DerivedUpdate(previous_id_map.ToQuery(func->id), {}, - previous_id_map.ToQuery(func->derived))); - if (!func->uses.empty()) - funcs_uses.push_back( - QueryFunc::UsesUpdate(previous_id_map.ToQuery(func->id), {}, - previous_id_map.ToQuery(func->uses))); - }, - /*onAdded:*/ - [this, ¤t_id_map](IndexFunc* func) { - optional def_update = - ToQuery(current_id_map, func->def); - if (def_update) - funcs_def_update.push_back( - QueryFunc::DefUpdate(func->usr, std::move(*def_update))); - if (!func->declarations.empty()) - funcs_declarations.push_back(QueryFunc::DeclarationsUpdate( - current_id_map.ToQuery(func->id), - current_id_map.ToQuery(func->declarations))); - if (!func->derived.empty()) - funcs_derived.push_back( - QueryFunc::DerivedUpdate(current_id_map.ToQuery(func->id), - current_id_map.ToQuery(func->derived))); - if (!func->uses.empty()) - funcs_uses.push_back( - QueryFunc::UsesUpdate(current_id_map.ToQuery(func->id), - current_id_map.ToQuery(func->uses))); - }, - /*onFound:*/ - [this, &previous_id_map, ¤t_id_map](IndexFunc* previous, - IndexFunc* current) { - optional previous_remapped_def = - ToQuery(previous_id_map, previous->def); - optional current_remapped_def = - ToQuery(current_id_map, current->def); - if (current_remapped_def && - previous_remapped_def != current_remapped_def && - !current_remapped_def->detailed_name.empty()) { - funcs_def_update.push_back(QueryFunc::DefUpdate( - current->usr, std::move(*current_remapped_def))); + return empty; +} + +std::vector GetUsesForAllBases(DB *db, QueryFunc &root) { + std::vector ret; + std::vector stack{&root}; + std::unordered_set seen; + seen.insert(root.usr); + while (!stack.empty()) { + QueryFunc &func = *stack.back(); + stack.pop_back(); + if (auto *def = func.AnyDef()) { + EachDefinedFunc(db, def->bases, [&](QueryFunc &func1) { + if (!seen.count(func1.usr)) { + seen.insert(func1.usr); + stack.push_back(&func1); + ret.insert(ret.end(), func1.uses.begin(), func1.uses.end()); } - - PROCESS_UPDATE_DIFF(QueryFuncId, funcs_declarations, declarations, Use); - PROCESS_UPDATE_DIFF(QueryFuncId, funcs_derived, derived, QueryFuncId); - PROCESS_UPDATE_DIFF(QueryFuncId, funcs_uses, uses, Use); - }); - - // Variables - CompareGroups( - previous_file.vars, current_file.vars, - /*onRemoved:*/ - [this, &previous_id_map](IndexVar* var) { - if (var->def.spell) - vars_removed.emplace_back(var->usr, previous_id_map.primary_file); - if (!var->declarations.empty()) - vars_declarations.push_back(QueryVar::DeclarationsUpdate( - previous_id_map.ToQuery(var->id), {}, - previous_id_map.ToQuery(var->declarations))); - if (!var->uses.empty()) - vars_uses.push_back( - QueryVar::UsesUpdate(previous_id_map.ToQuery(var->id), {}, - previous_id_map.ToQuery(var->uses))); - }, - /*onAdded:*/ - [this, ¤t_id_map](IndexVar* var) { - optional def_update = ToQuery(current_id_map, var->def); - if (def_update) - vars_def_update.push_back( - QueryVar::DefUpdate(var->usr, std::move(*def_update))); - if (!var->declarations.empty()) - vars_declarations.push_back(QueryVar::DeclarationsUpdate( - current_id_map.ToQuery(var->id), - current_id_map.ToQuery(var->declarations))); - if (!var->uses.empty()) - vars_uses.push_back( - QueryVar::UsesUpdate(current_id_map.ToQuery(var->id), - current_id_map.ToQuery(var->uses))); - }, - /*onFound:*/ - [this, &previous_id_map, ¤t_id_map](IndexVar* previous, - IndexVar* current) { - optional previous_remapped_def = - ToQuery(previous_id_map, previous->def); - optional current_remapped_def = - ToQuery(current_id_map, current->def); - if (current_remapped_def && - previous_remapped_def != current_remapped_def && - !current_remapped_def->detailed_name.empty()) - vars_def_update.push_back(QueryVar::DefUpdate( - current->usr, std::move(*current_remapped_def))); - - PROCESS_UPDATE_DIFF(QueryVarId, vars_declarations, declarations, Use); - PROCESS_UPDATE_DIFF(QueryVarId, vars_uses, uses, Use); }); - -#undef PROCESS_UPDATE_DIFF -} - -// This function runs on an indexer thread. -void IndexUpdate::Merge(IndexUpdate&& update) { -#define INDEX_UPDATE_APPEND(name) AddRange(&name, std::move(update.name)); -#define INDEX_UPDATE_MERGE(name) \ - AddMergeableRange(&name, std::move(update.name)); - - INDEX_UPDATE_APPEND(files_removed); - INDEX_UPDATE_APPEND(files_def_update); - - INDEX_UPDATE_APPEND(types_removed); - INDEX_UPDATE_APPEND(types_def_update); - INDEX_UPDATE_MERGE(types_derived); - INDEX_UPDATE_MERGE(types_instances); - INDEX_UPDATE_MERGE(types_uses); - - INDEX_UPDATE_APPEND(funcs_removed); - INDEX_UPDATE_APPEND(funcs_def_update); - INDEX_UPDATE_MERGE(funcs_declarations); - INDEX_UPDATE_MERGE(funcs_derived); - INDEX_UPDATE_MERGE(funcs_uses); - - INDEX_UPDATE_APPEND(vars_removed); - INDEX_UPDATE_APPEND(vars_def_update); - INDEX_UPDATE_MERGE(vars_declarations); - INDEX_UPDATE_MERGE(vars_uses); - -#undef INDEX_UPDATE_APPEND -#undef INDEX_UPDATE_MERGE -} - -std::string IndexUpdate::ToString() { - rapidjson::StringBuffer output; - rapidjson::Writer writer(output); - JsonWriter json_writer(&writer); - IndexUpdate& update = *this; - Reflect(json_writer, update); - return output.GetString(); -} - -NormalizedPath::NormalizedPath(const std::string& path) - : path(LowerPathIfCaseInsensitive(path)) {} - -bool NormalizedPath::operator==(const NormalizedPath& rhs) const { - return path == rhs.path; -} - -bool NormalizedPath::operator!=(const NormalizedPath& rhs) const { - return path != rhs.path; -} - -// ------------------------ -// QUERYDB THREAD FUNCTIONS -// ------------------------ - -void QueryDatabase::RemoveUsrs(SymbolKind usr_kind, - const std::vector& to_remove) { - // This function runs on the querydb thread. - - // When we remove an element, we just erase the state from the storage. We do - // not update array indices because that would take a huge amount of time for - // a very large index. - // - // There means that there is some memory growth that will never be reclaimed, - // but it should be pretty minimal and is solved by simply restarting the - // indexer and loading from cache, which is a fast operation. - // - // TODO: Add "cquery: Reload Index" command which unloads all querydb state - // and fully reloads from cache. This will address the memory leak above. - - switch (usr_kind) { - case SymbolKind::Type: { - for (const Usr& usr : to_remove) { - QueryType& type = types[usr_to_type[usr].id]; - if (type.symbol_idx) - symbols[type.symbol_idx->id].kind = SymbolKind::Invalid; - type.def.clear(); - } - break; } - default: - break; } -} -void QueryDatabase::RemoveUsrs( - SymbolKind usr_kind, - const std::vector>& to_remove) { - switch (usr_kind) { - case SymbolKind::Func: { - for (const auto& usr_file : to_remove) { - QueryFunc& func = funcs[usr_to_func[usr_file.usr].id]; - func.def.remove_if([&](const QueryFunc::Def& def) { - return def.file == usr_file.value; - }); - } - break; - } - case SymbolKind::Var: { - for (const auto& usr_file : to_remove) { - QueryVar& var = vars[usr_to_var[usr_file.usr].id]; - var.def.remove_if([&](const QueryVar::Def& def) { - return def.file == usr_file.value; - }); - } - break; - } - default: - assert(false); - break; - } + return ret; } -void QueryDatabase::ApplyIndexUpdate(IndexUpdate* update) { -// This function runs on the querydb thread. - -// Example types: -// storage_name => std::vector> -// merge_update => QueryType::DerivedUpdate => -// MergeableUpdate def => QueryType -// def->def_var_name => std::vector -#define HANDLE_MERGEABLE(update_var_name, def_var_name, storage_name) \ - for (auto merge_update : update->update_var_name) { \ - auto& def = storage_name[merge_update.id.id]; \ - AddRange(&def.def_var_name, merge_update.to_add); \ - RemoveRange(&def.def_var_name, merge_update.to_remove); \ - VerifyUnique(def.def_var_name); \ +std::vector GetUsesForAllDerived(DB *db, QueryFunc &root) { + std::vector ret; + std::vector stack{&root}; + std::unordered_set seen; + seen.insert(root.usr); + while (!stack.empty()) { + QueryFunc &func = *stack.back(); + stack.pop_back(); + EachDefinedFunc(db, func.derived, [&](QueryFunc &func1) { + if (!seen.count(func1.usr)) { + seen.insert(func1.usr); + stack.push_back(&func1); + ret.insert(ret.end(), func1.uses.begin(), func1.uses.end()); + } + }); } - for (const std::string& filename : update->files_removed) - files[usr_to_file[NormalizedPath(filename)].id].def = nullopt; - ImportOrUpdate(update->files_def_update); - - RemoveUsrs(SymbolKind::Type, update->types_removed); - ImportOrUpdate(std::move(update->types_def_update)); - HANDLE_MERGEABLE(types_declarations, declarations, types); - HANDLE_MERGEABLE(types_derived, derived, types); - HANDLE_MERGEABLE(types_instances, instances, types); - HANDLE_MERGEABLE(types_uses, uses, types); - - RemoveUsrs(SymbolKind::Func, update->funcs_removed); - ImportOrUpdate(std::move(update->funcs_def_update)); - HANDLE_MERGEABLE(funcs_declarations, declarations, funcs); - HANDLE_MERGEABLE(funcs_derived, derived, funcs); - HANDLE_MERGEABLE(funcs_uses, uses, funcs); - - RemoveUsrs(SymbolKind::Var, update->vars_removed); - ImportOrUpdate(std::move(update->vars_def_update)); - HANDLE_MERGEABLE(vars_declarations, declarations, vars); - HANDLE_MERGEABLE(vars_uses, uses, vars); - -#undef HANDLE_MERGEABLE + return ret; } -void QueryDatabase::ImportOrUpdate( - const std::vector& updates) { - // This function runs on the querydb thread. +std::optional GetLsRange(WorkingFile *wfile, + const Range &location) { + if (!wfile || wfile->index_lines.empty()) + return lsRange{Position{location.start.line, location.start.column}, + Position{location.end.line, location.end.column}}; - for (auto& def : updates) { - auto it = usr_to_file.find(NormalizedPath(def.value.path)); - assert(it != usr_to_file.end()); + int start_column = location.start.column, end_column = location.end.column; + std::optional start = wfile->GetBufferPosFromIndexPos( + location.start.line, &start_column, false); + std::optional end = wfile->GetBufferPosFromIndexPos( + location.end.line, &end_column, true); + if (!start || !end) + return std::nullopt; - QueryFile& existing = files[it->second.id]; - - existing.def = def.value; - UpdateSymbols(&existing.symbol_idx, SymbolKind::File, it->second); + // If remapping end fails (end can never be < start), just guess that the + // final location didn't move. This only screws up the highlighted code + // region if we guess wrong, so not a big deal. + // + // Remapping fails often in C++ since there are a lot of "};" at the end of + // class/struct definitions. + if (*end < *start) + *end = *start + (location.end.line - location.start.line); + if (*start == *end && start_column > end_column) + end_column = start_column; + + return lsRange{Position{*start, start_column}, Position{*end, end_column}}; +} + +DocumentUri GetLsDocumentUri(DB *db, int file_id, std::string *path) { + QueryFile &file = db->files[file_id]; + if (file.def) { + *path = file.def->path; + return DocumentUri::FromPath(*path); + } else { + *path = ""; + return DocumentUri::FromPath(""); } } -void QueryDatabase::ImportOrUpdate( - std::vector&& updates) { - // This function runs on the querydb thread. - - for (auto& def : updates) { - assert(!def.value.detailed_name.empty()); - - auto it = usr_to_type.find(def.usr); - assert(it != usr_to_type.end()); - - assert(it->second.id >= 0 && it->second.id < types.size()); - QueryType& existing = types[it->second.id]; - if (!TryReplaceDef(existing.def, std::move(def.value))) { - existing.def.push_front(std::move(def.value)); - UpdateSymbols(&existing.symbol_idx, SymbolKind::Type, it->second); - } +DocumentUri GetLsDocumentUri(DB *db, int file_id) { + QueryFile &file = db->files[file_id]; + if (file.def) { + return DocumentUri::FromPath(file.def->path); + } else { + return DocumentUri::FromPath(""); } } -void QueryDatabase::ImportOrUpdate( - std::vector&& updates) { - // This function runs on the querydb thread. - - for (auto& def : updates) { - assert(!def.value.detailed_name.empty()); - - auto it = usr_to_func.find(def.usr); - assert(it != usr_to_func.end()); - - assert(it->second.id >= 0 && it->second.id < funcs.size()); - QueryFunc& existing = funcs[it->second.id]; - if (!TryReplaceDef(existing.def, std::move(def.value))) { - existing.def.push_front(std::move(def.value)); - UpdateSymbols(&existing.symbol_idx, SymbolKind::Func, it->second); - } - } +std::optional GetLsLocation(DB *db, WorkingFiles *wfiles, Use use) { + std::string path; + DocumentUri uri = GetLsDocumentUri(db, use.file_id, &path); + std::optional range = GetLsRange(wfiles->GetFile(path), use.range); + if (!range) + return std::nullopt; + return Location{uri, *range}; } -void QueryDatabase::ImportOrUpdate(std::vector&& updates) { - // This function runs on the querydb thread. - - for (auto& def : updates) { - assert(!def.value.detailed_name.empty()); - - auto it = usr_to_var.find(def.usr); - assert(it != usr_to_var.end()); - - assert(it->second.id >= 0 && it->second.id < vars.size()); - QueryVar& existing = vars[it->second.id]; - if (!TryReplaceDef(existing.def, std::move(def.value))) { - existing.def.push_front(std::move(def.value)); - if (!existing.def.front().is_local()) - UpdateSymbols(&existing.symbol_idx, SymbolKind::Var, it->second); - } - } +std::optional GetLsLocation(DB *db, WorkingFiles *wfiles, + SymbolRef sym, int file_id) { + return GetLsLocation(db, wfiles, Use{{sym.range, sym.role}, file_id}); } -void QueryDatabase::UpdateSymbols(Maybe>* symbol_idx, - SymbolKind kind, - Id idx) { - if (!symbol_idx->HasValue()) { - *symbol_idx = Id(symbols.size()); - symbols.push_back(SymbolIdx{idx, kind}); - } +std::vector GetLsLocations(DB *db, WorkingFiles *wfiles, + const std::vector &uses) { + std::vector ret; + for (Use use : uses) + if (auto loc = GetLsLocation(db, wfiles, use)) + ret.push_back(*loc); + std::sort(ret.begin(), ret.end()); + ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); + if (ret.size() > g_config->xref.maxNum) + ret.resize(g_config->xref.maxNum); + return ret; } -// For Func, the returned name does not include parameters. -std::string_view QueryDatabase::GetSymbolDetailedName(RawId symbol_idx) const { - RawId idx = symbols[symbol_idx].id.id; - switch (symbols[symbol_idx].kind) { - default: - break; - case SymbolKind::File: - if (files[idx].def) - return files[idx].def->path; - break; - case SymbolKind::Func: - if (const auto* def = funcs[idx].AnyDef()) - return def->DetailedName(false); - break; - case SymbolKind::Type: - if (const auto* def = types[idx].AnyDef()) - return def->detailed_name; - break; - case SymbolKind::Var: - if (const auto* def = vars[idx].AnyDef()) - return def->detailed_name; - break; +SymbolKind GetSymbolKind(DB *db, SymbolIdx sym) { + SymbolKind ret; + if (sym.kind == Kind::File) + ret = SymbolKind::File; + else { + ret = SymbolKind::Unknown; + WithEntity(db, sym, [&](const auto &entity) { + for (auto &def : entity.def) { + ret = def.kind; + break; + } + }); } - return ""; + return ret; } -std::string_view QueryDatabase::GetSymbolShortName(RawId symbol_idx) const { - RawId idx = symbols[symbol_idx].id.id; - switch (symbols[symbol_idx].kind) { - default: - break; - case SymbolKind::File: - if (files[idx].def) - return files[idx].def->path; - break; - case SymbolKind::Func: - if (const auto* def = funcs[idx].AnyDef()) - return def->ShortName(); +std::optional GetSymbolInfo(DB *db, SymbolIdx sym, + bool detailed) { + switch (sym.kind) { + case Kind::Invalid: + break; + case Kind::File: { + QueryFile &file = db->GetFile(sym); + if (!file.def) break; - case SymbolKind::Type: - if (const auto* def = types[idx].AnyDef()) - return def->ShortName(); - break; - case SymbolKind::Var: - if (const auto* def = vars[idx].AnyDef()) - return def->ShortName(); - break; - } - return ""; -} -TEST_SUITE("query") { - IndexUpdate GetDelta(IndexFile previous, IndexFile current) { - QueryDatabase db; - IdMap previous_map(&db, previous.id_cache); - IdMap current_map(&db, current.id_cache); - return IndexUpdate::CreateDelta(&previous_map, ¤t_map, &previous, - ¤t); + SymbolInformation info; + info.name = file.def->path; + info.kind = SymbolKind::File; + return info; } - - TEST_CASE("remove defs") { - IndexFile previous("foo.cc", ""); - IndexFile current("foo.cc", ""); - - previous.Resolve(previous.ToTypeId(HashUsr("usr1")))->def.spell = - Use(Range(Position(1, 0)), {}, {}, {}, {}); - previous.Resolve(previous.ToFuncId(HashUsr("usr2")))->def.spell = - Use(Range(Position(2, 0)), {}, {}, {}, {}); - previous.Resolve(previous.ToVarId(HashUsr("usr3")))->def.spell = - Use(Range(Position(3, 0)), {}, {}, {}, {}); - - IndexUpdate update = GetDelta(previous, current); - - REQUIRE(update.types_removed == std::vector{HashUsr("usr1")}); - REQUIRE(update.funcs_removed.size() == 1); - REQUIRE(update.funcs_removed[0].usr == HashUsr("usr2")); - REQUIRE(update.vars_removed.size() == 1); - REQUIRE(update.vars_removed[0].usr == HashUsr("usr3")); + default: { + SymbolInformation info; + EachEntityDef(db, sym, [&](const auto &def) { + if (detailed) + info.name = def.detailed_name; + else + info.name = def.Name(true); + info.kind = def.kind; + return false; + }); + return info; } - - TEST_CASE("do not remove ref-only defs") { - IndexFile previous("foo.cc", ""); - IndexFile current("foo.cc", ""); - - previous.Resolve(previous.ToTypeId(HashUsr("usr1"))) - ->uses.push_back(Use{Range(Position(1, 0)), {}, {}, {}, {}}); - previous.Resolve(previous.ToFuncId(HashUsr("usr2"))) - ->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); - previous.Resolve(previous.ToVarId(HashUsr("usr3"))) - ->uses.push_back(Use(Range(Position(3, 0)), {}, {}, {}, {})); - - IndexUpdate update = GetDelta(previous, current); - - REQUIRE(update.types_removed == std::vector{}); - REQUIRE(update.funcs_removed.empty()); - REQUIRE(update.vars_removed.empty()); } - TEST_CASE("func callers") { - IndexFile previous("foo.cc", ""); - IndexFile current("foo.cc", ""); - - IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr"))); - IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr"))); - - pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {})); - cf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); - - IndexUpdate update = GetDelta(previous, current); - - REQUIRE(update.funcs_removed.empty()); - REQUIRE(update.funcs_uses.size() == 1); - REQUIRE(update.funcs_uses[0].id == QueryFuncId(0)); - REQUIRE(update.funcs_uses[0].to_remove.size() == 1); - REQUIRE(update.funcs_uses[0].to_remove[0].range == Range(Position(1, 0))); - REQUIRE(update.funcs_uses[0].to_add.size() == 1); - REQUIRE(update.funcs_uses[0].to_add[0].range == Range(Position(2, 0))); - } - - TEST_CASE("type usages") { - IndexFile previous("foo.cc", ""); - IndexFile current("foo.cc", ""); - - IndexType* pt = previous.Resolve(previous.ToTypeId(HashUsr("usr"))); - IndexType* ct = current.Resolve(current.ToTypeId(HashUsr("usr"))); - - pt->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {})); - ct->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); - - IndexUpdate update = GetDelta(previous, current); - - REQUIRE(update.types_removed == std::vector{}); - REQUIRE(update.types_def_update.empty()); - REQUIRE(update.types_uses.size() == 1); - REQUIRE(update.types_uses[0].to_remove.size() == 1); - REQUIRE(update.types_uses[0].to_remove[0].range == Range(Position(1, 0))); - REQUIRE(update.types_uses[0].to_add.size() == 1); - REQUIRE(update.types_uses[0].to_add[0].range == Range(Position(2, 0))); - } - - TEST_CASE("apply delta") { - IndexFile previous("foo.cc", ""); - IndexFile current("foo.cc", ""); - - IndexFunc* pf = previous.Resolve(previous.ToFuncId(HashUsr("usr"))); - IndexFunc* cf = current.Resolve(current.ToFuncId(HashUsr("usr"))); - pf->uses.push_back(Use(Range(Position(1, 0)), {}, {}, {}, {})); - pf->uses.push_back(Use(Range(Position(2, 0)), {}, {}, {}, {})); - cf->uses.push_back(Use(Range(Position(4, 0)), {}, {}, {}, {})); - cf->uses.push_back(Use(Range(Position(5, 0)), {}, {}, {}, {})); - - QueryDatabase db; - IdMap previous_map(&db, previous.id_cache); - IdMap current_map(&db, current.id_cache); - REQUIRE(db.funcs.size() == 1); - - IndexUpdate import_update = - IndexUpdate::CreateDelta(nullptr, &previous_map, nullptr, &previous); - IndexUpdate delta_update = IndexUpdate::CreateDelta( - &previous_map, ¤t_map, &previous, ¤t); - - db.ApplyIndexUpdate(&import_update); - REQUIRE(db.funcs[0].uses.size() == 2); - REQUIRE(db.funcs[0].uses[0].range == Range(Position(1, 0))); - REQUIRE(db.funcs[0].uses[1].range == Range(Position(2, 0))); - - db.ApplyIndexUpdate(&delta_update); - REQUIRE(db.funcs[0].uses.size() == 2); - REQUIRE(db.funcs[0].uses[0].range == Range(Position(4, 0))); - REQUIRE(db.funcs[0].uses[1].range == Range(Position(5, 0))); - } - - TEST_CASE("Remove variable with usage") { - auto load_index_from_json = [](const char* json) { - return Deserialize(SerializeFormat::Json, "foo.cc", json, "", - nullopt); - }; - - auto previous = load_index_from_json(R"RAW( -{ - "types": [ - { - "id": 0, - "usr": 17, - "detailed_name": "", - "short_name_offset": 0, - "short_name_size": 0, - "kind": 0, - "hover": "", - "comments": "", - "parents": [], - "derived": [], - "types": [], - "funcs": [], - "vars": [], - "instances": [ - 0 - ], - "uses": [] - } - ], - "funcs": [ - { - "id": 0, - "usr": 4259594751088586730, - "detailed_name": "void foo()", - "short_name_offset": 5, - "short_name_size": 3, - "kind": 12, - "storage": 1, - "hover": "", - "comments": "", - "declarations": [], - "spell": "1:6-1:9|-1|1|2", - "extent": "1:1-4:2|-1|1|0", - "base": [], - "derived": [], - "locals": [], - "uses": [], - "callees": [] - } - ], - "vars": [ - { - "id": 0, - "usr": 16837348799350457167, - "detailed_name": "int a", - "short_name_offset": 4, - "short_name_size": 1, - "hover": "", - "comments": "", - "declarations": [], - "spell": "2:7-2:8|0|3|2", - "extent": "2:3-2:8|0|3|2", - "type": 0, - "uses": [ - "3:3-3:4|0|3|4" - ], - "kind": 13, - "storage": 1 - } - ] + return std::nullopt; } - )RAW"); - - auto current = load_index_from_json(R"RAW( -{ - "types": [], - "funcs": [ - { - "id": 0, - "usr": 4259594751088586730, - "detailed_name": "void foo()", - "short_name_offset": 5, - "short_name_size": 3, - "kind": 12, - "storage": 1, - "hover": "", - "comments": "", - "declarations": [], - "spell": "1:6-1:9|-1|1|2", - "extent": "1:1-5:2|-1|1|0", - "base": [], - "derived": [], - "locals": [], - "uses": [], - "callees": [] - } - ], - "vars": [] -} - )RAW"); - - // Validate previous/current were parsed. - REQUIRE(previous->vars.size() == 1); - REQUIRE(current->vars.size() == 0); - QueryDatabase db; - - // Apply initial file. - { - IdMap previous_map(&db, previous->id_cache); - IndexUpdate import_update = IndexUpdate::CreateDelta( - nullptr, &previous_map, nullptr, previous.get()); - db.ApplyIndexUpdate(&import_update); +std::vector FindSymbolsAtLocation(WorkingFile *wfile, + QueryFile *file, Position &ls_pos, + bool smallest) { + std::vector symbols; + // If multiVersion > 0, index may not exist and thus index_lines is empty. + if (wfile && wfile->index_lines.size()) { + if (auto line = wfile->GetIndexPosFromBufferPos( + ls_pos.line, &ls_pos.character, false)) { + ls_pos.line = *line; + } else { + ls_pos.line = -1; + return {}; } + } - REQUIRE(db.vars.size() == 1); - REQUIRE(db.vars[0].uses.size() == 1); + for (auto [sym, refcnt] : file->symbol2refcnt) + if (refcnt > 0 && sym.range.Contains(ls_pos.line, ls_pos.character)) + symbols.push_back(sym); - // Apply change. - { - IdMap previous_map(&db, previous->id_cache); - IdMap current_map(&db, current->id_cache); - IndexUpdate delta_update = IndexUpdate::CreateDelta( - &previous_map, ¤t_map, previous.get(), current.get()); - db.ApplyIndexUpdate(&delta_update); - } - REQUIRE(db.vars.size() == 1); - REQUIRE(db.vars[0].uses.size() == 0); + // Order shorter ranges first, since they are more detailed/precise. This is + // important for macros which generate code so that we can resolving the + // macro argument takes priority over the entire macro body. + // + // Order Kind::Var before Kind::Type. Macro calls are treated as Var + // currently. If a macro expands to tokens led by a Kind::Type, the macro and + // the Type have the same range. We want to find the macro definition instead + // of the Type definition. + // + // Then order functions before other types, which makes goto definition work + // better on constructors. + std::sort( + symbols.begin(), symbols.end(), + [](const SymbolRef &a, const SymbolRef &b) { + int t = ComputeRangeSize(a.range) - ComputeRangeSize(b.range); + if (t) + return t < 0; + // MacroExpansion + if ((t = (a.role & Role::Dynamic) - (b.role & Role::Dynamic))) + return t > 0; + if ((t = (a.role & Role::Definition) - (b.role & Role::Definition))) + return t > 0; + // operator> orders Var/Func before Type. + t = static_cast(a.kind) - static_cast(b.kind); + if (t) + return t > 0; + return a.usr < b.usr; + }); + if (symbols.size() && smallest) { + SymbolRef sym = symbols[0]; + for (size_t i = 1; i < symbols.size(); i++) + if (!(sym.range == symbols[i].range && sym.kind == symbols[i].kind)) { + symbols.resize(i); + break; + } } + + return symbols; } +} // namespace ccls diff --git a/src/query.h b/src/query.h deleted file mode 100644 index 008d52a51..000000000 --- a/src/query.h +++ /dev/null @@ -1,369 +0,0 @@ -#pragma once - -#include "indexer.h" -#include "serializer.h" - -#include - -#include -#include - -struct QueryFile; -struct QueryType; -struct QueryFunc; -struct QueryVar; -struct QueryDatabase; - -using QueryFileId = Id; -using QueryTypeId = Id; -using QueryFuncId = Id; -using QueryVarId = Id; - -struct IdMap; - -// There are two sources of reindex updates: the (single) definition of a -// symbol has changed, or one of many users of the symbol has changed. -// -// For simplicitly, if the single definition has changed, we update all of the -// associated single-owner definition data. See |Update*DefId|. -// -// If one of the many symbol users submits an update, we store the update such -// that it can be merged with other updates before actually being applied to -// the main database. See |MergeableUpdate|. - -template -struct MergeableUpdate { - // The type/func/var which is getting new usages. - TId id; - // Entries to add and remove. - std::vector to_add; - std::vector to_remove; - - MergeableUpdate(TId id, std::vector&& to_add) - : id(id), to_add(std::move(to_add)) {} - MergeableUpdate(TId id, - std::vector&& to_add, - std::vector&& to_remove) - : id(id), to_add(std::move(to_add)), to_remove(std::move(to_remove)) {} -}; -template -void Reflect(TVisitor& visitor, MergeableUpdate& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(id); - REFLECT_MEMBER(to_add); - REFLECT_MEMBER(to_remove); - REFLECT_MEMBER_END(); -} - -template -struct WithUsr { - Usr usr; - T value; - - WithUsr(Usr usr, const T& value) : usr(usr), value(value) {} - WithUsr(Usr usr, T&& value) : usr(usr), value(std::move(value)) {} -}; -template -void Reflect(TVisitor& visitor, WithUsr& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(usr); - REFLECT_MEMBER(value); - REFLECT_MEMBER_END(); -} - -template -struct WithFileContent { - T value; - std::string file_content; - - WithFileContent(const T& value, const std::string& file_content) - : value(value), file_content(file_content) {} -}; -template -void Reflect(TVisitor& visitor, WithFileContent& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER(value); - REFLECT_MEMBER(file_content); - REFLECT_MEMBER_END(); -} - -struct QueryFamily { - using FileId = Id; - using FuncId = Id; - using TypeId = Id; - using VarId = Id; - using Range = Reference; -}; - -struct QueryFile { - struct Def { - Id file; - std::string path; - std::vector args; - // Language identifier - std::string language; - // Includes in the file. - std::vector includes; - // Outline of the file (ie, for code lens). - std::vector outline; - // Every symbol found in the file (ie, for goto definition) - std::vector all_symbols; - // Parts of the file which are disabled. - std::vector inactive_regions; - // Used by |$cquery/freshenIndex|. - std::vector dependencies; - }; - - using DefUpdate = WithFileContent; - - optional def; - Maybe> symbol_idx; - - explicit QueryFile(const std::string& path) { - def = Def(); - def->path = path; - } -}; -MAKE_REFLECT_STRUCT(QueryFile::Def, - file, - path, - args, - language, - outline, - all_symbols, - inactive_regions, - dependencies); - -template -struct QueryEntity { - using Def = QDef; - using DefUpdate = WithUsr; - using DeclarationsUpdate = MergeableUpdate, Use>; - using UsesUpdate = MergeableUpdate, Use>; - Def* AnyDef() { - Def* ret = nullptr; - for (auto& i : static_cast(this)->def) { - ret = &i; - if (i.spell) - break; - } - return ret; - } - const Def* AnyDef() const { return const_cast(this)->AnyDef(); } -}; - -struct QueryType : QueryEntity> { - using DerivedUpdate = MergeableUpdate; - using InstancesUpdate = MergeableUpdate; - - Usr usr; - Maybe> symbol_idx; - std::forward_list def; - std::vector declarations; - std::vector derived; - std::vector instances; - std::vector uses; - - explicit QueryType(const Usr& usr) : usr(usr) {} -}; - -struct QueryFunc : QueryEntity> { - using DerivedUpdate = MergeableUpdate; - - Usr usr; - Maybe> symbol_idx; - std::forward_list def; - std::vector declarations; - std::vector derived; - std::vector uses; - - explicit QueryFunc(const Usr& usr) : usr(usr) {} -}; - -struct QueryVar : QueryEntity> { - Usr usr; - Maybe> symbol_idx; - std::forward_list def; - std::vector declarations; - std::vector uses; - - explicit QueryVar(const Usr& usr) : usr(usr) {} -}; - -struct IndexUpdate { - // Creates a new IndexUpdate based on the delta from previous to current. If - // no delta computation should be done just pass null for previous. - static IndexUpdate CreateDelta(const IdMap* previous_id_map, - const IdMap* current_id_map, - IndexFile* previous, - IndexFile* current); - - // Merge |update| into this update; this can reduce overhead / index update - // work can be parallelized. - void Merge(IndexUpdate&& update); - - // Dump the update to a string. - std::string ToString(); - - // File updates. - std::vector files_removed; - std::vector files_def_update; - - // Type updates. - std::vector types_removed; - std::vector types_def_update; - std::vector types_declarations; - std::vector types_derived; - std::vector types_instances; - std::vector types_uses; - - // Function updates. - std::vector> funcs_removed; - std::vector funcs_def_update; - std::vector funcs_declarations; - std::vector funcs_derived; - std::vector funcs_uses; - - // Variable updates. - std::vector> vars_removed; - std::vector vars_def_update; - std::vector vars_declarations; - std::vector vars_uses; - - private: - // Creates an index update assuming that |previous| is already - // in the index, so only the delta between |previous| and |current| - // will be applied. - IndexUpdate(const IdMap& previous_id_map, - const IdMap& current_id_map, - IndexFile& previous, - IndexFile& current); -}; -// NOTICE: We're not reflecting on files_removed or files_def_update, it is too -// much output when logging -MAKE_REFLECT_STRUCT(IndexUpdate, - types_removed, - types_def_update, - types_derived, - types_instances, - types_uses, - funcs_removed, - funcs_def_update, - funcs_declarations, - funcs_derived, - funcs_uses, - vars_removed, - vars_def_update, - vars_declarations, - vars_uses); - -struct NormalizedPath { - explicit NormalizedPath(const std::string& path); - bool operator==(const NormalizedPath& rhs) const; - bool operator!=(const NormalizedPath& rhs) const; - - std::string path; -}; -MAKE_HASHABLE(NormalizedPath, t.path); - -// The query database is heavily optimized for fast queries. It is stored -// in-memory. -struct QueryDatabase { - // All File/Func/Type/Var symbols. - std::vector symbols; - - // Raw data storage. Accessible via SymbolIdx instances. - std::vector files; - std::vector types; - std::vector funcs; - std::vector vars; - - // Lookup symbol based on a usr. - spp::sparse_hash_map usr_to_file; - spp::sparse_hash_map usr_to_type; - spp::sparse_hash_map usr_to_func; - spp::sparse_hash_map usr_to_var; - - // Marks the given Usrs as invalid. - void RemoveUsrs(SymbolKind usr_kind, const std::vector& to_remove); - void RemoveUsrs(SymbolKind usr_kind, - const std::vector>& to_remove); - // Insert the contents of |update| into |db|. - void ApplyIndexUpdate(IndexUpdate* update); - void ImportOrUpdate(const std::vector& updates); - void ImportOrUpdate(std::vector&& updates); - void ImportOrUpdate(std::vector&& updates); - void ImportOrUpdate(std::vector&& updates); - void UpdateSymbols(Maybe>* symbol_idx, - SymbolKind kind, - Id idx); - std::string_view GetSymbolDetailedName(RawId symbol_idx) const; - std::string_view GetSymbolShortName(RawId symbol_idx) const; - - // Query the indexing structure to look up symbol id for given Usr. - Maybe GetQueryFileIdFromPath(const std::string& path); - Maybe GetQueryTypeIdFromUsr(Usr usr); - Maybe GetQueryFuncIdFromUsr(Usr usr); - Maybe GetQueryVarIdFromUsr(Usr usr); - - QueryFile& GetFile(SymbolIdx ref) { return files[ref.id.id]; } - QueryFunc& GetFunc(SymbolIdx ref) { return funcs[ref.id.id]; } - QueryType& GetType(SymbolIdx ref) { return types[ref.id.id]; } - QueryVar& GetVar(SymbolIdx ref) { return vars[ref.id.id]; } -}; - -template -struct IndexToQuery; - -// clang-format off -template <> struct IndexToQuery { using type = QueryFileId; }; -template <> struct IndexToQuery { using type = QueryFuncId; }; -template <> struct IndexToQuery { using type = QueryTypeId; }; -template <> struct IndexToQuery { using type = QueryVarId; }; -template <> struct IndexToQuery { using type = Use; }; -template <> struct IndexToQuery { using type = SymbolRef; }; -template <> struct IndexToQuery { using type = Use; }; -template struct IndexToQuery> { - using type = optional::type>; -}; -template struct IndexToQuery> { - using type = std::vector::type>; -}; -// clang-format on - -struct IdMap { - const IdCache& local_ids; - QueryFileId primary_file; - - IdMap(QueryDatabase* query_db, const IdCache& local_ids); - - // FIXME Too verbose - // clang-format off - QueryTypeId ToQuery(IndexTypeId id) const; - QueryFuncId ToQuery(IndexFuncId id) const; - QueryVarId ToQuery(IndexVarId id) const; - SymbolRef ToQuery(SymbolRef ref) const; - Use ToQuery(Reference ref) const; - Use ToQuery(Use ref) const; - Use ToQuery(IndexFunc::Declaration decl) const; - template - Maybe::type> ToQuery(Maybe id) const { - if (!id) - return nullopt; - return ToQuery(*id); - } - template - std::vector::type> ToQuery(const std::vector& a) const { - std::vector::type> ret; - ret.reserve(a.size()); - for (auto& x : a) - ret.push_back(ToQuery(x)); - return ret; - } - // clang-format on - - private: - spp::sparse_hash_map cached_type_ids_; - spp::sparse_hash_map cached_func_ids_; - spp::sparse_hash_map cached_var_ids_; -}; diff --git a/src/query.hh b/src/query.hh new file mode 100644 index 000000000..a1d4af7b2 --- /dev/null +++ b/src/query.hh @@ -0,0 +1,283 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "indexer.hh" +#include "serializer.hh" +#include "working_files.hh" + +#include +#include + +namespace llvm { +template <> struct DenseMapInfo { + static inline ccls::ExtentRef getEmptyKey() { return {}; } + static inline ccls::ExtentRef getTombstoneKey() { + return {{ccls::Range(), ccls::Usr(-1)}}; + } + static unsigned getHashValue(ccls::ExtentRef sym) { + return std::hash()(sym); + } + static bool isEqual(ccls::ExtentRef l, ccls::ExtentRef r) { return l == r; } +}; +} // namespace llvm + +namespace ccls { +struct QueryFile { + struct Def { + std::string path; + std::vector args; + LanguageId language; + // Includes in the file. + std::vector includes; + // Parts of the file which are disabled. + std::vector skipped_ranges; + // Used by |$ccls/reload|. + std::vector dependencies; + }; + + using DefUpdate = std::pair; + + int id = -1; + std::optional def; + // `extent` is valid => declaration; invalid => regular reference + llvm::DenseMap symbol2refcnt; +}; + +template struct QueryEntity { + using Def = QDef; + Def *AnyDef() { + Def *ret = nullptr; + for (auto &i : static_cast(this)->def) { + ret = &i; + if (i.spell) + break; + } + return ret; + } + const Def *AnyDef() const { + return const_cast(this)->AnyDef(); + } +}; + +template +using Update = + std::unordered_map, std::vector>>; + +struct QueryFunc : QueryEntity { + Usr usr; + llvm::SmallVector def; + std::vector declarations; + std::vector derived; + std::vector uses; + std::vector data_flow_into_return; +}; + +struct QueryType : QueryEntity { + Usr usr; + llvm::SmallVector def; + std::vector declarations; + std::vector derived; + std::vector instances; + std::vector uses; +}; + +struct QueryVar : QueryEntity { + Usr usr; + llvm::SmallVector def; + std::vector declarations; + std::vector uses; + std::vector data_flow_into; +}; + +struct IndexUpdate { + // Creates a new IndexUpdate based on the delta from previous to current. If + // no delta computation should be done just pass null for previous. + static IndexUpdate CreateDelta(IndexFile *previous, IndexFile *current); + + int file_id; + + // Dummy one to refresh all semantic highlight. + bool refresh = false; + + decltype(IndexFile::lid2path) prev_lid2path; + decltype(IndexFile::lid2path) lid2path; + + // File updates. + std::optional files_removed; + std::optional files_def_update; + + // Function updates. + int funcs_hint; + std::vector> funcs_removed; + std::vector> funcs_def_update; + Update funcs_declarations; + Update funcs_uses; + Update funcs_derived; + Update funcs_data_flow_into_return; + + // Type updates. + int types_hint; + std::vector> types_removed; + std::vector> types_def_update; + Update types_declarations; + Update types_uses; + Update types_derived; + Update types_instances; + + // Variable updates. + int vars_hint; + std::vector> vars_removed; + std::vector> vars_def_update; + Update vars_declarations; + Update vars_uses; + Update vars_data_flow_into; +}; + +struct DenseMapInfoForUsr { + static inline Usr getEmptyKey() { return 0; } + static inline Usr getTombstoneKey() { return ~0ULL; } + static unsigned getHashValue(Usr w) { return w; } + static bool isEqual(Usr l, Usr r) { return l == r; } +}; + +using Lid2file_id = std::unordered_map; + +// The query database is heavily optimized for fast queries. It is stored +// in-memory. +struct DB { + std::vector files; + llvm::StringMap name2file_id; + llvm::DenseMap func_usr, type_usr, var_usr; + std::vector funcs; + std::vector types; + std::vector vars; + + void clear(); + + template + void RemoveUsrs(Kind kind, int file_id, + const std::vector> &to_remove); + // Insert the contents of |update| into |db|. + void ApplyIndexUpdate(IndexUpdate *update); + int GetFileId(const std::string &path); + int Update(QueryFile::DefUpdate &&u); + void Update(const Lid2file_id &, int file_id, + std::vector> &&us); + void Update(const Lid2file_id &, int file_id, + std::vector> &&us); + void Update(const Lid2file_id &, int file_id, + std::vector> &&us); + std::string_view GetSymbolName(SymbolIdx sym, bool qualified); + std::vector GetFileSet(const std::vector &folders); + + bool HasFunc(Usr usr) const { return func_usr.count(usr); } + bool HasType(Usr usr) const { return type_usr.count(usr); } + bool HasVar(Usr usr) const { return var_usr.count(usr); } + + QueryFunc &Func(Usr usr) { return funcs[func_usr[usr]]; } + QueryType &Type(Usr usr) { return types[type_usr[usr]]; } + QueryVar &Var(Usr usr) { return vars[var_usr[usr]]; } + + QueryFile &GetFile(SymbolIdx ref) { return files[ref.usr]; } + QueryFunc &GetFunc(SymbolIdx ref) { return Func(ref.usr); } + QueryType &GetType(SymbolIdx ref) { return Type(ref.usr); } + QueryVar &GetVar(SymbolIdx ref) { return Var(ref.usr); } +}; + +Maybe GetDefinitionSpell(DB *db, SymbolIdx sym); + +// Get defining declaration (if exists) or an arbitrary declaration (otherwise) +// for each id. +std::vector GetFuncDeclarations(DB *, const std::vector &); +std::vector GetTypeDeclarations(DB *, const std::vector &); +std::vector GetVarDeclarations(DB *, const std::vector &, unsigned); + +// Get non-defining declarations. +std::vector &GetNonDefDeclarations(DB *db, SymbolIdx sym); + +std::vector GetUsesForAllBases(DB *db, QueryFunc &root); +std::vector GetUsesForAllDerived(DB *db, QueryFunc &root); +std::optional GetLsRange(WorkingFile *working_file, + const Range &location); +DocumentUri GetLsDocumentUri(DB *db, int file_id, std::string *path); +DocumentUri GetLsDocumentUri(DB *db, int file_id); + +std::optional GetLsLocation(DB *db, WorkingFiles *wfiles, Use use); +std::optional GetLsLocation(DB *db, WorkingFiles *wfiles, + SymbolRef sym, int file_id); +std::vector GetLsLocations(DB *db, WorkingFiles *wfiles, + const std::vector &uses); +// Returns a symbol. The symbol will *NOT* have a location assigned. +std::optional GetSymbolInfo(DB *db, SymbolIdx sym, + bool detailed); + +std::vector FindSymbolsAtLocation(WorkingFile *working_file, + QueryFile *file, + Position &ls_pos, + bool smallest = false); + +template void WithEntity(DB *db, SymbolIdx sym, Fn &&fn) { + switch (sym.kind) { + case Kind::Invalid: + case Kind::File: + break; + case Kind::Func: + fn(db->GetFunc(sym)); + break; + case Kind::Type: + fn(db->GetType(sym)); + break; + case Kind::Var: + fn(db->GetVar(sym)); + break; + } +} + +template void EachEntityDef(DB *db, SymbolIdx sym, Fn &&fn) { + WithEntity(db, sym, [&](const auto &entity) { + for (auto &def : entity.def) + if (!fn(def)) + break; + }); +} + +template +void EachOccurrence(DB *db, SymbolIdx sym, bool include_decl, Fn &&fn) { + WithEntity(db, sym, [&](const auto &entity) { + for (Use use : entity.uses) + fn(use); + if (include_decl) { + for (auto &def : entity.def) + if (def.spell) + fn(*def.spell); + for (Use use : entity.declarations) + fn(use); + } + }); +} + +SymbolKind GetSymbolKind(DB *db, SymbolIdx sym); + +template +void EachDefinedFunc(DB *db, const std::vector &usrs, Fn &&fn) { + for (Usr usr : usrs) { + auto &obj = db->Func(usr); + if (!obj.def.empty()) + fn(obj); + } +} +} // namespace ccls diff --git a/src/query_utils.cc b/src/query_utils.cc deleted file mode 100644 index 7377ad176..000000000 --- a/src/query_utils.cc +++ /dev/null @@ -1,368 +0,0 @@ -#include "query_utils.h" - -#include "queue_manager.h" - -#include - -#include -#include - -namespace { - -// Computes roughly how long |range| is. -int ComputeRangeSize(const Range& range) { - if (range.start.line != range.end.line) - return INT_MAX; - return range.end.column - range.start.column; -} - -template -std::vector GetDeclarations(std::vector& entities, - const std::vector>& ids) { - std::vector ret; - ret.reserve(ids.size()); - for (auto id : ids) { - Q& entity = entities[id.id]; - bool has_def = false; - for (auto& def : entity.def) - if (def.spell) { - ret.push_back(*def.spell); - has_def = true; - break; - } - if (!has_def && entity.declarations.size()) - ret.push_back(entity.declarations[0]); - } - return ret; -} - -} // namespace - -Maybe GetDefinitionSpell(QueryDatabase* db, SymbolIdx sym) { - Maybe ret; - EachEntityDef(db, sym, [&](const auto& def) { return !(ret = def.spell); }); - return ret; -} - -Maybe GetDefinitionExtent(QueryDatabase* db, SymbolIdx sym) { - // Used to jump to file. - if (sym.kind == SymbolKind::File) - return Use(Range(Position(0, 0), Position(0, 0)), sym.id, sym.kind, - Role::None, QueryFileId(sym.id)); - Maybe ret; - EachEntityDef(db, sym, [&](const auto& def) { return !(ret = def.extent); }); - return ret; -} - -Maybe GetDeclarationFileForSymbol(QueryDatabase* db, - SymbolIdx sym) { - switch (sym.kind) { - case SymbolKind::File: - return QueryFileId(sym.id); - case SymbolKind::Func: { - QueryFunc& func = db->GetFunc(sym); - if (!func.declarations.empty()) - return func.declarations[0].file; - if (const auto* def = func.AnyDef()) - return def->file; - break; - } - case SymbolKind::Type: { - if (const auto* def = db->GetType(sym).AnyDef()) - return def->file; - break; - } - case SymbolKind::Var: { - if (const auto* def = db->GetVar(sym).AnyDef()) - return def->file; - break; - } - case SymbolKind::Invalid: { - assert(false && "unexpected"); - break; - } - } - return nullopt; -} - -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids) { - return GetDeclarations(db->funcs, ids); -} - -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids) { - return GetDeclarations(db->types, ids); -} - -std::vector GetDeclarations(QueryDatabase* db, - const std::vector& ids) { - return GetDeclarations(db->vars, ids); -} - -std::vector GetNonDefDeclarations(QueryDatabase* db, - SymbolIdx sym) { - switch (sym.kind) { - case SymbolKind::Func: - return db->GetFunc(sym).declarations; - case SymbolKind::Type: - return db->GetType(sym).declarations; - case SymbolKind::Var: - return db->GetVar(sym).declarations; - default: - return {}; - } -} - -std::vector GetUsesForAllBases(QueryDatabase* db, QueryFunc& root) { - std::vector ret; - std::vector stack{&root}; - std::unordered_set seen; - seen.insert(root.usr); - while (!stack.empty()) { - QueryFunc& func = *stack.back(); - stack.pop_back(); - if (auto* def = func.AnyDef()) { - EachDefinedEntity(db->funcs, def->bases, [&](QueryFunc& func1) { - if (!seen.count(func1.usr)) { - seen.insert(func1.usr); - stack.push_back(&func1); - AddRange(&ret, func1.uses); - } - }); - } - } - - return ret; -} - -std::vector GetUsesForAllDerived(QueryDatabase* db, QueryFunc& root) { - std::vector ret; - std::vector stack{&root}; - std::unordered_set seen; - seen.insert(root.usr); - while (!stack.empty()) { - QueryFunc& func = *stack.back(); - stack.pop_back(); - EachDefinedEntity(db->funcs, func.derived, [&](QueryFunc& func1) { - if (!seen.count(func1.usr)) { - seen.insert(func1.usr); - stack.push_back(&func1); - AddRange(&ret, func1.uses); - } - }); - } - - return ret; -} - -optional GetLsPosition(WorkingFile* working_file, - const Position& position) { - if (!working_file) - return lsPosition(position.line, position.column); - - int column = position.column; - if (optional start = - working_file->GetBufferPosFromIndexPos(position.line, &column, false)) - return lsPosition(*start, column); - return nullopt; -} - -optional GetLsRange(WorkingFile* working_file, const Range& location) { - if (!working_file) { - return lsRange(lsPosition(location.start.line, location.start.column), - lsPosition(location.end.line, location.end.column)); - } - - int start_column = location.start.column, end_column = location.end.column; - optional start = working_file->GetBufferPosFromIndexPos( - location.start.line, &start_column, false); - optional end = working_file->GetBufferPosFromIndexPos(location.end.line, - &end_column, true); - if (!start || !end) - return nullopt; - - // If remapping end fails (end can never be < start), just guess that the - // final location didn't move. This only screws up the highlighted code - // region if we guess wrong, so not a big deal. - // - // Remapping fails often in C++ since there are a lot of "};" at the end of - // class/struct definitions. - if (*end < *start) - *end = *start + (location.end.line - location.start.line); - if (*start == *end && start_column > end_column) - end_column = start_column; - - return lsRange(lsPosition(*start, start_column), - lsPosition(*end, end_column)); -} - -lsDocumentUri GetLsDocumentUri(QueryDatabase* db, - QueryFileId file_id, - std::string* path) { - QueryFile& file = db->files[file_id.id]; - if (file.def) { - *path = file.def->path; - return lsDocumentUri::FromPath(*path); - } else { - *path = ""; - return lsDocumentUri::FromPath(""); - } -} - -lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id) { - QueryFile& file = db->files[file_id.id]; - if (file.def) { - return lsDocumentUri::FromPath(file.def->path); - } else { - return lsDocumentUri::FromPath(""); - } -} - -optional GetLsLocation(QueryDatabase* db, - WorkingFiles* working_files, - Use use) { - std::string path; - lsDocumentUri uri = GetLsDocumentUri(db, use.file, &path); - optional range = - GetLsRange(working_files->GetFileByFilename(path), use.range); - if (!range) - return nullopt; - return lsLocation(uri, *range); -} - -optional GetLsLocationEx(QueryDatabase* db, - WorkingFiles* working_files, - Use use, - bool container) { - optional ls_loc = GetLsLocation(db, working_files, use); - if (!ls_loc) - return nullopt; - lsLocationEx ret; - ret.lsLocation::operator=(*ls_loc); - if (container) { - ret.role = uint16_t(use.role); - EachEntityDef(db, use, [&](const auto& def) { - ret.containerName = std::string_view(def.detailed_name); - return false; - }); - } - return ret; -} - -std::vector GetLsLocationExs(QueryDatabase* db, - WorkingFiles* working_files, - const std::vector& uses, - bool container, - int limit) { - std::vector ret; - for (Use use : uses) - if (auto loc = GetLsLocationEx(db, working_files, use, container)) - ret.push_back(*loc); - std::sort(ret.begin(), ret.end()); - ret.erase(std::unique(ret.begin(), ret.end()), ret.end()); - if (ret.size() > limit) - ret.resize(limit); - return ret; -} - -lsSymbolKind GetSymbolKind(QueryDatabase* db, SymbolIdx sym) { - lsSymbolKind ret; - if (sym.kind == SymbolKind::File) - ret = lsSymbolKind::File; - else { - ret = lsSymbolKind::Unknown; - WithEntity(db, sym, [&](const auto& entity) { - for (auto& def : entity.def) { - ret = def.kind; - break; - } - }); - } - return ret; -} - -// Returns a symbol. The symbol will have *NOT* have a location assigned. -optional GetSymbolInfo(QueryDatabase* db, - WorkingFiles* working_files, - SymbolIdx sym, - bool use_short_name) { - switch (sym.kind) { - case SymbolKind::Invalid: - break; - case SymbolKind::File: { - QueryFile& file = db->GetFile(sym); - if (!file.def) - break; - - lsSymbolInformation info; - info.name = file.def->path; - info.kind = lsSymbolKind::File; - return info; - } - default: { - lsSymbolInformation info; - EachEntityDef(db, sym, [&](const auto& def) { - if (use_short_name) - info.name = def.ShortName(); - else - info.name = def.detailed_name; - info.kind = def.kind; - info.containerName = def.detailed_name; - return false; - }); - return info; - } - } - - return nullopt; -} - -std::vector FindSymbolsAtLocation(WorkingFile* working_file, - QueryFile* file, - lsPosition position) { - std::vector symbols; - symbols.reserve(1); - - int target_line = position.line; - int target_column = position.character; - if (working_file) { - optional index_line = working_file->GetIndexPosFromBufferPos( - target_line, &target_column, false); - if (index_line) - target_line = *index_line; - } - - for (const SymbolRef& sym : file->def->all_symbols) { - if (sym.range.Contains(target_line, target_column)) - symbols.push_back(sym); - } - - // Order shorter ranges first, since they are more detailed/precise. This is - // important for macros which generate code so that we can resolving the - // macro argument takes priority over the entire macro body. - // - // Order SymbolKind::Var before SymbolKind::Type. Macro calls are treated as - // Var currently. If a macro expands to tokens led by a SymbolKind::Type, the - // macro and the Type have the same range. We want to find the macro - // definition instead of the Type definition. - // - // Then order functions before other types, which makes goto definition work - // better on constructors. - std::sort(symbols.begin(), symbols.end(), - [](const SymbolRef& a, const SymbolRef& b) { - int t = ComputeRangeSize(a.range) - ComputeRangeSize(b.range); - if (t) - return t < 0; - t = (a.role & Role::Definition) - (b.role & Role::Definition); - if (t) - return t > 0; - // operator> orders Var/Func before Type. - t = static_cast(a.kind) - static_cast(b.kind); - if (t) - return t > 0; - return a.id < b.id; - }); - - return symbols; -} diff --git a/src/query_utils.h b/src/query_utils.h deleted file mode 100644 index f9a10a6ad..000000000 --- a/src/query_utils.h +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once - -#include "query.h" -#include "working_files.h" - -#include - -Maybe GetDefinitionSpell(QueryDatabase* db, SymbolIdx sym); -Maybe GetDefinitionExtent(QueryDatabase* db, SymbolIdx sym); -Maybe GetDeclarationFileForSymbol(QueryDatabase* db, - SymbolIdx sym); - -// Get defining declaration (if exists) or an arbitrary declaration (otherwise) for each id. -std::vector GetDeclarations(QueryDatabase* db, const std::vector& ids); -std::vector GetDeclarations(QueryDatabase* db, const std::vector& ids); -std::vector GetDeclarations(QueryDatabase* db, const std::vector& ids); - -// Get non-defining declarations. -std::vector GetNonDefDeclarations(QueryDatabase* db, SymbolIdx sym); - -std::vector GetUsesForAllBases(QueryDatabase* db, QueryFunc& root); -std::vector GetUsesForAllDerived(QueryDatabase* db, QueryFunc& root); -optional GetLsPosition(WorkingFile* working_file, - const Position& position); -optional GetLsRange(WorkingFile* working_file, const Range& location); -lsDocumentUri GetLsDocumentUri(QueryDatabase* db, - QueryFileId file_id, - std::string* path); -lsDocumentUri GetLsDocumentUri(QueryDatabase* db, QueryFileId file_id); - -optional GetLsLocation(QueryDatabase* db, - WorkingFiles* working_files, - Use use); -optional GetLsLocationEx(QueryDatabase* db, - WorkingFiles* working_files, - Use use, - bool container); -std::vector GetLsLocationExs(QueryDatabase* db, - WorkingFiles* working_files, - const std::vector& refs, - bool container, - int limit); -// Returns a symbol. The symbol will have *NOT* have a location assigned. -optional GetSymbolInfo(QueryDatabase* db, - WorkingFiles* working_files, - SymbolIdx sym, - bool use_short_name); - -std::vector FindSymbolsAtLocation(WorkingFile* working_file, - QueryFile* file, - lsPosition position); - -template -void WithEntity(QueryDatabase* db, SymbolIdx sym, Fn&& fn) { - switch (sym.kind) { - case SymbolKind::Invalid: - case SymbolKind::File: - break; - case SymbolKind::Func: - fn(db->GetFunc(sym)); - break; - case SymbolKind::Type: - fn(db->GetType(sym)); - break; - case SymbolKind::Var: - fn(db->GetVar(sym)); - break; - } -} - -template -void EachEntityDef(QueryDatabase* db, SymbolIdx sym, Fn&& fn) { - WithEntity(db, sym, [&](const auto& entity) { - for (auto& def : entity.def) - if (!fn(def)) - break; - }); -} - -template -void EachOccurrence(QueryDatabase* db, SymbolIdx sym, bool include_decl, Fn&& fn) { - WithEntity(db, sym, [&](const auto& entity) { - for (Use use : entity.uses) - fn(use); - if (include_decl) { - for (auto& def : entity.def) - if (def.spell) - fn(*def.spell); - for (Use use : entity.declarations) - fn(use); - } - }); -} - -lsSymbolKind GetSymbolKind(QueryDatabase* db, SymbolIdx sym); - -template -void EachOccurrenceWithParent(QueryDatabase* db, - SymbolIdx sym, - bool include_decl, - Fn&& fn) { - WithEntity(db, sym, [&](const auto& entity) { - lsSymbolKind parent_kind = lsSymbolKind::Unknown; - for (auto& def : entity.def) - if (def.spell) { - parent_kind = GetSymbolKind(db, sym); - break; - } - for (Use use : entity.uses) - fn(use, parent_kind); - if (include_decl) { - for (auto& def : entity.def) - if (def.spell) - fn(*def.spell, parent_kind); - for (Use use : entity.declarations) - fn(use, parent_kind); - } - }); -} - -template -void EachDefinedEntity(std::vector& collection, - const std::vector>& ids, - Fn&& fn) { - for (Id x : ids) { - Q& obj = collection[x.id]; - if (!obj.def.empty()) - fn(obj); - } -} diff --git a/src/queue_manager.cc b/src/queue_manager.cc deleted file mode 100644 index c3a24cfdd..000000000 --- a/src/queue_manager.cc +++ /dev/null @@ -1,92 +0,0 @@ -#include "queue_manager.h" - -#include "cache_manager.h" -#include "lsp.h" -#include "query.h" - -#include - -Index_Request::Index_Request( - const std::string& path, - const std::vector& args, - bool is_interactive, - const std::string& contents, - const std::shared_ptr& cache_manager, - lsRequestId id) - : path(path), - args(args), - is_interactive(is_interactive), - contents(contents), - cache_manager(cache_manager), - id(id) {} - -Index_DoIdMap::Index_DoIdMap( - std::unique_ptr current, - const std::shared_ptr& cache_manager, - PerformanceImportFile perf, - bool is_interactive, - bool write_to_disk) - : current(std::move(current)), - cache_manager(cache_manager), - perf(perf), - is_interactive(is_interactive), - write_to_disk(write_to_disk) { - assert(this->current); -} - -Index_OnIdMapped::File::File(std::unique_ptr file, - std::unique_ptr ids) - : file(std::move(file)), ids(std::move(ids)) {} - -Index_OnIdMapped::Index_OnIdMapped( - const std::shared_ptr& cache_manager, - PerformanceImportFile perf, - bool is_interactive, - bool write_to_disk) - : cache_manager(cache_manager), - perf(perf), - is_interactive(is_interactive), - write_to_disk(write_to_disk) {} - -Index_OnIndexed::Index_OnIndexed(IndexUpdate&& update, - PerformanceImportFile perf) - : update(std::move(update)), perf(perf) {} - -std::unique_ptr QueueManager::instance_; - -// static -void QueueManager::Init(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter) { - instance_ = std::unique_ptr( - new QueueManager(querydb_waiter, indexer_waiter, stdout_waiter)); -} - -// static -void QueueManager::WriteStdout(IpcId id, lsBaseOutMessage& response) { - std::ostringstream sstream; - response.Write(sstream); - - Stdout_Request out; - out.content = sstream.str(); - out.id = id; - instance()->for_stdout.PushBack(std::move(out)); -} - -QueueManager::QueueManager(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter) - : for_stdout(stdout_waiter), - for_querydb(querydb_waiter), - do_id_map(querydb_waiter), - index_request(indexer_waiter), - load_previous_index(indexer_waiter), - on_id_mapped(indexer_waiter), - // TODO on_indexed is shared by "querydb" and "indexer" - on_indexed(querydb_waiter, indexer_waiter) {} - -bool QueueManager::HasWork() { - return !index_request.IsEmpty() || !do_id_map.IsEmpty() || - !load_previous_index.IsEmpty() || !on_id_mapped.IsEmpty() || - !on_indexed.IsEmpty(); -} diff --git a/src/queue_manager.h b/src/queue_manager.h deleted file mode 100644 index 46312d541..000000000 --- a/src/queue_manager.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include "ipc.h" -#include "performance.h" -#include "query.h" -#include "threaded_queue.h" - -#include - -struct ICacheManager; -struct lsBaseOutMessage; - -struct Stdout_Request { - IpcId id; - std::string content; -}; - -struct Index_Request { - std::string path; - // TODO: make |args| a string that is parsed lazily. - std::vector args; - bool is_interactive; - std::string contents; // Preloaded contents. - std::shared_ptr cache_manager; - lsRequestId id; - - Index_Request(const std::string& path, - const std::vector& args, - bool is_interactive, - const std::string& contents, - const std::shared_ptr& cache_manager, - lsRequestId id = {}); -}; - -struct Index_DoIdMap { - std::unique_ptr current; - std::unique_ptr previous; - std::shared_ptr cache_manager; - - PerformanceImportFile perf; - bool is_interactive = false; - bool write_to_disk = false; - bool load_previous = false; - - Index_DoIdMap(std::unique_ptr current, - const std::shared_ptr& cache_manager, - PerformanceImportFile perf, - bool is_interactive, - bool write_to_disk); -}; - -struct Index_OnIdMapped { - struct File { - std::unique_ptr file; - std::unique_ptr ids; - - File(std::unique_ptr file, std::unique_ptr ids); - }; - - std::unique_ptr previous; - std::unique_ptr current; - std::shared_ptr cache_manager; - - PerformanceImportFile perf; - bool is_interactive; - bool write_to_disk; - - Index_OnIdMapped(const std::shared_ptr& cache_manager, - PerformanceImportFile perf, - bool is_interactive, - bool write_to_disk); -}; - -struct Index_OnIndexed { - IndexUpdate update; - PerformanceImportFile perf; - - Index_OnIndexed(IndexUpdate&& update, PerformanceImportFile perf); -}; - -class QueueManager { - static std::unique_ptr instance_; - -public: - static QueueManager* instance() { return instance_.get(); } - static void Init(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter); - static void WriteStdout(IpcId id, lsBaseOutMessage& response); - - bool HasWork(); - - // Messages received by "stdout" thread. - ThreadedQueue for_stdout; - - // Runs on querydb thread. - ThreadedQueue> for_querydb; - ThreadedQueue do_id_map; - - // Runs on indexer threads. - ThreadedQueue index_request; - ThreadedQueue load_previous_index; - ThreadedQueue on_id_mapped; - - // Shared by querydb and indexer. - // TODO split on_indexed - ThreadedQueue on_indexed; - - private: - explicit QueueManager(MultiQueueWaiter* querydb_waiter, - MultiQueueWaiter* indexer_waiter, - MultiQueueWaiter* stdout_waiter); -}; diff --git a/src/recorder.cc b/src/recorder.cc deleted file mode 100644 index 94a46ed00..000000000 --- a/src/recorder.cc +++ /dev/null @@ -1,56 +0,0 @@ -#include "recorder.h" - -#include - -#include -#include - -namespace { -std::ofstream* g_file_in = nullptr; -std::ofstream* g_file_out = nullptr; -} // namespace - -void EnableRecording(std::string path) { - if (path.empty()) - path = "cquery"; - - // We can only call |EnableRecording| once. - assert(!g_file_in && !g_file_out); - - // Open the files. - g_file_in = new std::ofstream( - path + ".in", std::ios::out | std::ios::trunc | std::ios::binary); - g_file_out = new std::ofstream( - path + ".out", std::ios::out | std::ios::trunc | std::ios::binary); - - // Make sure we can write to the files. - bool bad = false; - if (!g_file_in->good()) { - LOG_S(ERROR) << "record: cannot write to " << path << ".in"; - bad = true; - } - if (!g_file_out->good()) { - LOG_S(ERROR) << "record: cannot write to " << path << ".out"; - bad = true; - } - if (bad) { - delete g_file_in; - delete g_file_out; - g_file_in = nullptr; - g_file_out = nullptr; - } -} - -void RecordInput(std::string_view content) { - if (!g_file_in) - return; - (*g_file_in) << "Content-Length: " << content.size() << "\r\n\r\n" << content; - (*g_file_in).flush(); -} - -void RecordOutput(std::string_view content) { - if (!g_file_out) - return; - (*g_file_out) << content; - (*g_file_out).flush(); -} \ No newline at end of file diff --git a/src/recorder.h b/src/recorder.h deleted file mode 100644 index 772841d3a..000000000 --- a/src/recorder.h +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include - -#include - -void EnableRecording(std::string path); -void RecordInput(std::string_view content); -void RecordOutput(std::string_view content); \ No newline at end of file diff --git a/src/sema_manager.cc b/src/sema_manager.cc new file mode 100644 index 000000000..c6be9c531 --- /dev/null +++ b/src/sema_manager.cc @@ -0,0 +1,707 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "sema_manager.hh" + +#include "clang_tu.hh" +#include "filesystem.hh" +#include "log.hh" +#include "platform.hh" + +#include +#include +#include +#include +#include +#include +#include +using namespace clang; +using namespace llvm; + +#include +#include +#include +#include +namespace chrono = std::chrono; + +#if LLVM_VERSION_MAJOR < 8 +namespace clang::vfs { +struct ProxyFileSystem : FileSystem { + explicit ProxyFileSystem(IntrusiveRefCntPtr FS) + : FS(std::move(FS)) {} + llvm::ErrorOr status(const Twine &Path) override { + return FS->status(Path); + } + llvm::ErrorOr> + openFileForRead(const Twine &Path) override { + return FS->openFileForRead(Path); + } + directory_iterator dir_begin(const Twine &Dir, std::error_code &EC) override { + return FS->dir_begin(Dir, EC); + } + llvm::ErrorOr getCurrentWorkingDirectory() const override { + return FS->getCurrentWorkingDirectory(); + } + std::error_code setCurrentWorkingDirectory(const Twine &Path) override { + return FS->setCurrentWorkingDirectory(Path); + } +#if LLVM_VERSION_MAJOR == 7 + std::error_code getRealPath(const Twine &Path, + SmallVectorImpl &Output) const override { + return FS->getRealPath(Path, Output); + } +#endif + FileSystem &getUnderlyingFS() { return *FS; } + IntrusiveRefCntPtr FS; +}; +} +#endif + +namespace ccls { + +TextEdit ToTextEdit(const clang::SourceManager &SM, const clang::LangOptions &L, + const clang::FixItHint &FixIt) { + TextEdit edit; + edit.newText = FixIt.CodeToInsert; + auto r = FromCharSourceRange(SM, L, FixIt.RemoveRange); + edit.range = + lsRange{{r.start.line, r.start.column}, {r.end.line, r.end.column}}; + return edit; +} + +using IncludeStructure = std::vector>; + +struct PreambleStatCache { + llvm::StringMap> Cache; + + void Update(Twine Path, ErrorOr S) { + Cache.try_emplace(Path.str(), std::move(S)); + } + + IntrusiveRefCntPtr + Producer(IntrusiveRefCntPtr FS) { + struct VFS : llvm::vfs::ProxyFileSystem { + PreambleStatCache &Cache; + + VFS(IntrusiveRefCntPtr FS, + PreambleStatCache &Cache) + : ProxyFileSystem(std::move(FS)), Cache(Cache) {} + llvm::ErrorOr> + openFileForRead(const Twine &Path) override { + auto File = getUnderlyingFS().openFileForRead(Path); + if (!File || !*File) + return File; + Cache.Update(Path, File->get()->status()); + return File; + } + llvm::ErrorOr status(const Twine &Path) override { + auto S = getUnderlyingFS().status(Path); + Cache.Update(Path, S); + return S; + } + }; + return new VFS(std::move(FS), *this); + } + + IntrusiveRefCntPtr + Consumer(IntrusiveRefCntPtr FS) { + struct VFS : llvm::vfs::ProxyFileSystem { + const PreambleStatCache &Cache; + VFS(IntrusiveRefCntPtr FS, + const PreambleStatCache &Cache) + : ProxyFileSystem(std::move(FS)), Cache(Cache) {} + llvm::ErrorOr status(const Twine &Path) override { + auto I = Cache.Cache.find(Path.str()); + if (I != Cache.Cache.end()) + return I->getValue(); + return getUnderlyingFS().status(Path); + } + }; + return new VFS(std::move(FS), *this); + } +}; + +struct PreambleData { + PreambleData(clang::PrecompiledPreamble P, IncludeStructure includes, + std::vector diags, + std::unique_ptr stat_cache) + : Preamble(std::move(P)), includes(std::move(includes)), + diags(std::move(diags)), stat_cache(std::move(stat_cache)) {} + clang::PrecompiledPreamble Preamble; + IncludeStructure includes; + std::vector diags; + std::unique_ptr stat_cache; +}; + +namespace { +bool LocationInRange(SourceLocation L, CharSourceRange R, + const SourceManager &M) { + assert(R.isCharRange()); + if (!R.isValid() || M.getFileID(R.getBegin()) != M.getFileID(R.getEnd()) || + M.getFileID(R.getBegin()) != M.getFileID(L)) + return false; + return L != R.getEnd() && M.isPointWithin(L, R.getBegin(), R.getEnd()); +} + +CharSourceRange DiagnosticRange(const clang::Diagnostic &D, const LangOptions &L) { + auto &M = D.getSourceManager(); + auto Loc = M.getFileLoc(D.getLocation()); + // Accept the first range that contains the location. + for (const auto &CR : D.getRanges()) { + auto R = Lexer::makeFileCharRange(CR, M, L); + if (LocationInRange(Loc, R, M)) + return R; + } + // The range may be given as a fixit hint instead. + for (const auto &F : D.getFixItHints()) { + auto R = Lexer::makeFileCharRange(F.RemoveRange, M, L); + if (LocationInRange(Loc, R, M)) + return R; + } + // If no suitable range is found, just use the token at the location. + auto R = Lexer::makeFileCharRange(CharSourceRange::getTokenRange(Loc), M, L); + if (!R.isValid()) // Fall back to location only, let the editor deal with it. + R = CharSourceRange::getCharRange(Loc); + return R; +} + +class StoreInclude : public PPCallbacks { + const SourceManager &SM; + IncludeStructure &out; + DenseSet Seen; + +public: + StoreInclude(const SourceManager &SM, IncludeStructure &out) + : SM(SM), out(out) {} + void InclusionDirective(SourceLocation HashLoc, const Token &IncludeTok, + StringRef FileName, bool IsAngled, + CharSourceRange FilenameRange, const FileEntry *File, + StringRef SearchPath, StringRef RelativePath, + const clang::Module *Imported +#if LLVM_VERSION_MAJOR >= 7 + , SrcMgr::CharacteristicKind FileKind +#endif + ) override { + (void)SM; + if (File && Seen.insert(File).second) + out.emplace_back(PathFromFileEntry(*File), File->getModificationTime()); + } +}; + +class CclsPreambleCallbacks : public PreambleCallbacks { +public: + void BeforeExecute(CompilerInstance &CI) override { + SM = &CI.getSourceManager(); + } + std::unique_ptr createPPCallbacks() override { + return std::make_unique(*SM, includes); + } + SourceManager *SM = nullptr; + IncludeStructure includes; +}; + +class StoreDiags : public DiagnosticConsumer { + const LangOptions *LangOpts; + std::optional last; + std::vector output; + std::string path; + std::unordered_map FID2concerned; + void Flush() { + if (!last) + return; + bool mentions = last->concerned || last->edits.size(); + if (!mentions) + for (auto &N : last->notes) + if (N.concerned) + mentions = true; + if (mentions) + output.push_back(std::move(*last)); + last.reset(); + } +public: + StoreDiags(std::string path) : path(path) {} + std::vector Take() { + return std::move(output); + } + bool IsConcerned(const SourceManager &SM, SourceLocation L) { + FileID FID = SM.getFileID(L); + auto it = FID2concerned.try_emplace(FID.getHashValue()); + if (it.second) { + const FileEntry *FE = SM.getFileEntryForID(FID); + it.first->second = FE && PathFromFileEntry(*FE) == path; + } + return it.first->second; + } + void BeginSourceFile(const LangOptions &Opts, const Preprocessor *) override { + LangOpts = &Opts; + } + void EndSourceFile() override { + Flush(); + } + void HandleDiagnostic(DiagnosticsEngine::Level Level, + const clang::Diagnostic &Info) override { + DiagnosticConsumer::HandleDiagnostic(Level, Info); + SourceLocation L = Info.getLocation(); + if (!L.isValid()) return; + const SourceManager &SM = Info.getSourceManager(); + StringRef Filename = SM.getFilename(Info.getLocation()); + bool concerned = SM.isInMainFile(L); + auto fillDiagBase = [&](DiagBase &d) { + llvm::SmallString<64> Message; + Info.FormatDiagnostic(Message); + d.range = + FromCharSourceRange(SM, *LangOpts, DiagnosticRange(Info, *LangOpts)); + d.message = Message.str(); + d.concerned = concerned; + d.file = Filename; + d.level = Level; + d.category = DiagnosticIDs::getCategoryNumberForDiag(Info.getID()); + }; + + auto addFix = [&](bool SyntheticMessage) -> bool { + if (!concerned) + return false; + for (const FixItHint &FixIt : Info.getFixItHints()) { + if (!IsConcerned(SM, FixIt.RemoveRange.getBegin())) + return false; + last->edits.push_back(ToTextEdit(SM, *LangOpts, FixIt)); + } + return true; + }; + + if (Level == DiagnosticsEngine::Note || Level == DiagnosticsEngine::Remark) { + if (Info.getFixItHints().size()) { + addFix(false); + } else { + Note &n = last->notes.emplace_back(); + fillDiagBase(n); + } + } else { + Flush(); + last = Diag(); + fillDiagBase(*last); + if (!Info.getFixItHints().empty()) + addFix(true); + } + } +}; + +std::unique_ptr BuildCompilerInstance( + Session &session, std::unique_ptr CI, + IntrusiveRefCntPtr FS, DiagnosticConsumer &DC, + const PreambleData *preamble, const std::string &path, + std::unique_ptr &Buf) { + if (preamble) { +#if LLVM_VERSION_MAJOR >= 7 + preamble->Preamble.OverridePreamble(*CI, FS, Buf.get()); +#else + preamble->Preamble.AddImplicitPreamble(*CI, FS, Buf.get()); +#endif + } else { + CI->getPreprocessorOpts().addRemappedFile(path, Buf.get()); + } + + auto Clang = std::make_unique(session.PCH); + Clang->setInvocation(std::move(CI)); + Clang->setVirtualFileSystem(FS); + Clang->createDiagnostics(&DC, false); + Clang->setTarget(TargetInfo::CreateTargetInfo( + Clang->getDiagnostics(), Clang->getInvocation().TargetOpts)); + if (!Clang->hasTarget()) + return nullptr; + // Construct SourceManager with UserFilesAreVolatile: true because otherwise + // RequiresNullTerminator: true may cause out-of-bounds read when a file is + // mmap'ed but is saved concurrently. + Clang->createFileManager(); + Clang->setSourceManager(new SourceManager(Clang->getDiagnostics(), + Clang->getFileManager(), true)); + return Clang; +} + +bool Parse(CompilerInstance &Clang) { + SyntaxOnlyAction Action; + if (!Action.BeginSourceFile(Clang, Clang.getFrontendOpts().Inputs[0])) + return false; + if (!Action.Execute()) + return false; + Action.EndSourceFile(); + return true; +} + +void BuildPreamble(Session &session, CompilerInvocation &CI, + IntrusiveRefCntPtr FS, + const SemaManager::PreambleTask &task, + std::unique_ptr stat_cache) { + std::shared_ptr OldP = session.GetPreamble(); + std::string content = session.wfiles->GetContent(task.path); + std::unique_ptr Buf = + llvm::MemoryBuffer::getMemBuffer(content); + auto Bounds = ComputePreambleBounds(*CI.getLangOpts(), Buf.get(), 0); + if (!task.from_diag && OldP && + OldP->Preamble.CanReuse(CI, Buf.get(), Bounds, FS.get())) + return; + // -Werror makes warnings issued as errors, which stops parsing + // prematurely because of -ferror-limit=. This also works around the issue + // of -Werror + -Wunused-parameter in interaction with SkipFunctionBodies. + auto &Ws = CI.getDiagnosticOpts().Warnings; + Ws.erase(std::remove(Ws.begin(), Ws.end(), "error"), Ws.end()); + CI.getDiagnosticOpts().IgnoreWarnings = false; + CI.getFrontendOpts().SkipFunctionBodies = true; + CI.getLangOpts()->CommentOpts.ParseAllComments = g_config->index.comments > 1; + + StoreDiags DC(task.path); + IntrusiveRefCntPtr DE = + CompilerInstance::createDiagnostics(&CI.getDiagnosticOpts(), &DC, false); + if (OldP) { + std::lock_guard lock(session.wfiles->mutex); + for (auto &include : OldP->includes) + if (WorkingFile *wf = session.wfiles->GetFileUnlocked(include.first)) + CI.getPreprocessorOpts().addRemappedFile( + include.first, + llvm::MemoryBuffer::getMemBufferCopy(wf->buffer_content).release()); + } + + CclsPreambleCallbacks PC; + if (auto NewPreamble = PrecompiledPreamble::Build( + CI, Buf.get(), Bounds, *DE, FS, session.PCH, true, PC)) { + assert(!CI.getPreprocessorOpts().RetainRemappedFileBuffers); + if (OldP) { + auto &old_includes = OldP->includes; + auto it = old_includes.begin(); + std::sort(PC.includes.begin(), PC.includes.end()); + for (auto &include : PC.includes) + if (include.second == 0) { + while (it != old_includes.end() && it->first < include.first) + ++it; + if (it == old_includes.end()) + break; + include.second = it->second; + } + } + + std::lock_guard lock(session.mutex); + session.preamble = std::make_shared( + std::move(*NewPreamble), std::move(PC.includes), DC.Take(), + std::move(stat_cache)); + } +} + +void *PreambleMain(void *manager_) { + auto *manager = static_cast(manager_); + set_thread_name("preamble"); + while (true) { + SemaManager::PreambleTask task = manager->preamble_tasks.Dequeue(); + + bool created = false; + std::shared_ptr session = + manager->EnsureSession(task.path, &created); + + auto stat_cache = std::make_unique(); + IntrusiveRefCntPtr FS = + stat_cache->Producer(session->FS); + if (std::unique_ptr CI = + BuildCompilerInvocation(session->file.args, FS)) + BuildPreamble(*session, *CI, FS, task, std::move(stat_cache)); + + if (task.from_diag) { + manager->ScheduleDiag(task.path, 0); + } else { + int debounce = + created ? g_config->diagnostics.onOpen : g_config->diagnostics.onSave; + if (debounce >= 0) + manager->ScheduleDiag(task.path, debounce); + } + } + return nullptr; +} + +void *CompletionMain(void *manager_) { + auto *manager = static_cast(manager_); + set_thread_name("comp"); + while (true) { + std::unique_ptr task = manager->comp_tasks.Dequeue(); + + // Drop older requests if we're not buffering. + while (g_config->completion.dropOldRequests && + !manager->comp_tasks.IsEmpty()) { + manager->on_dropped_(task->id); + task->Consumer.reset(); + task->on_complete(nullptr); + task = manager->comp_tasks.Dequeue(); + } + + std::shared_ptr session = manager->EnsureSession(task->path); + std::shared_ptr preamble = session->GetPreamble(); + IntrusiveRefCntPtr FS = + preamble ? preamble->stat_cache->Consumer(session->FS) : session->FS; + std::unique_ptr CI = + BuildCompilerInvocation(session->file.args, FS); + if (!CI) + continue; + auto &FOpts = CI->getFrontendOpts(); + FOpts.CodeCompleteOpts = task->CCOpts; + FOpts.CodeCompletionAt.FileName = task->path; + FOpts.CodeCompletionAt.Line = task->position.line + 1; + FOpts.CodeCompletionAt.Column = task->position.character + 1; + FOpts.SkipFunctionBodies = true; + CI->getLangOpts()->CommentOpts.ParseAllComments = true; + + DiagnosticConsumer DC; + std::string content = manager->wfiles->GetContent(task->path); + auto Buf = llvm::MemoryBuffer::getMemBuffer(content); + bool in_preamble = + GetOffsetForPosition({task->position.line, task->position.character}, + content) < + ComputePreambleBounds(*CI->getLangOpts(), Buf.get(), 0).Size; + if (in_preamble) + preamble.reset(); + auto Clang = BuildCompilerInstance(*session, std::move(CI), FS, DC, + preamble.get(), task->path, Buf); + if (!Clang) + continue; + + Clang->getPreprocessorOpts().SingleFileParseMode = in_preamble; + Clang->setCodeCompletionConsumer(task->Consumer.release()); + if (!Parse(*Clang)) + continue; + Buf.release(); + + task->on_complete(&Clang->getCodeCompletionConsumer()); + } + return nullptr; +} + +llvm::StringRef diagLeveltoString(DiagnosticsEngine::Level Lvl) { + switch (Lvl) { + case DiagnosticsEngine::Ignored: + return "ignored"; + case DiagnosticsEngine::Note: + return "note"; + case DiagnosticsEngine::Remark: + return "remark"; + case DiagnosticsEngine::Warning: + return "warning"; + case DiagnosticsEngine::Error: + return "error"; + case DiagnosticsEngine::Fatal: + return "fatal error"; + } +} + +void printDiag(llvm::raw_string_ostream &OS, const DiagBase &d) { + if (d.concerned) + OS << llvm::sys::path::filename(d.file); + else + OS << d.file; + auto pos = d.range.start; + OS << ":" << (pos.line + 1) << ":" << (pos.column + 1) << ":" + << (d.concerned ? " " : "\n"); + OS << diagLeveltoString(d.level) << ": " << d.message; +} + +void *DiagnosticMain(void *manager_) { + auto *manager = static_cast(manager_); + set_thread_name("diag"); + while (true) { + SemaManager::DiagTask task = manager->diag_tasks.Dequeue(); + int64_t wait = task.wait_until - + chrono::duration_cast( + chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + if (wait > 0) + std::this_thread::sleep_for( + chrono::duration(std::min(wait, task.debounce))); + + std::shared_ptr session = manager->EnsureSession(task.path); + std::shared_ptr preamble = session->GetPreamble(); + IntrusiveRefCntPtr FS = + preamble ? preamble->stat_cache->Consumer(session->FS) : session->FS; + if (preamble) { + bool rebuild = false; + { + std::lock_guard lock(manager->wfiles->mutex); + for (auto &include : preamble->includes) + if (WorkingFile *wf = manager->wfiles->GetFileUnlocked(include.first); + wf && include.second < wf->timestamp) { + include.second = wf->timestamp; + rebuild = true; + } + } + if (rebuild) { + manager->preamble_tasks.PushBack({task.path, true}, true); + continue; + } + } + + std::unique_ptr CI = + BuildCompilerInvocation(session->file.args, FS); + if (!CI) + continue; + // If main file is a header, add -Wno-unused-function + if (lookupExtension(session->file.filename).second) + CI->getDiagnosticOpts().Warnings.push_back("no-unused-function"); + CI->getDiagnosticOpts().IgnoreWarnings = false; + CI->getLangOpts()->SpellChecking = g_config->diagnostics.spellChecking; + StoreDiags DC(task.path); + std::string content = manager->wfiles->GetContent(task.path); + auto Buf = llvm::MemoryBuffer::getMemBuffer(content); + auto Clang = BuildCompilerInstance(*session, std::move(CI), FS, DC, + preamble.get(), task.path, Buf); + if (!Clang) + continue; + if (!Parse(*Clang)) + continue; + Buf.release(); + + auto Fill = [](const DiagBase &d, Diagnostic &ret) { + ret.range = lsRange{{d.range.start.line, d.range.start.column}, + {d.range.end.line, d.range.end.column}}; + switch (d.level) { + case DiagnosticsEngine::Ignored: + // llvm_unreachable + case DiagnosticsEngine::Remark: + ret.severity = 4; + break; + case DiagnosticsEngine::Note: + ret.severity = 3; + break; + case DiagnosticsEngine::Warning: + ret.severity = 2; + break; + case DiagnosticsEngine::Error: + case DiagnosticsEngine::Fatal: + ret.severity = 1; + break; + } + ret.code = d.category; + return ret; + }; + + std::vector diags = DC.Take(); + if (std::shared_ptr preamble = session->GetPreamble()) + diags.insert(diags.end(), preamble->diags.begin(), preamble->diags.end()); + std::vector ls_diags; + for (auto &d : diags) { + if (!d.concerned) + continue; + std::string buf; + llvm::raw_string_ostream OS(buf); + Diagnostic &ls_diag = ls_diags.emplace_back(); + Fill(d, ls_diag); + ls_diag.fixits_ = d.edits; + OS << d.message; + for (auto &n : d.notes) { + OS << "\n\n"; + printDiag(OS, n); + } + OS.flush(); + ls_diag.message = std::move(buf); + for (auto &n : d.notes) { + if (!n.concerned) + continue; + Diagnostic &ls_diag1 = ls_diags.emplace_back(); + Fill(n, ls_diag1); + OS << n.message << "\n\n"; + printDiag(OS, d); + OS.flush(); + ls_diag1.message = std::move(buf); + } + } + + { + std::lock_guard lock(manager->wfiles->mutex); + if (WorkingFile *wf = manager->wfiles->GetFileUnlocked(task.path)) + wf->diagnostics = ls_diags; + } + manager->on_diagnostic_(task.path, ls_diags); + } + return nullptr; +} + +} // namespace + +std::shared_ptr Session::GetPreamble() { + std::lock_guard lock(mutex); + return preamble; +} + +SemaManager::SemaManager(Project *project, WorkingFiles *wfiles, + OnDiagnostic on_diagnostic, OnDropped on_dropped) + : project_(project), wfiles(wfiles), on_diagnostic_(on_diagnostic), + on_dropped_(on_dropped), PCH(std::make_shared()) { + SpawnThread(ccls::PreambleMain, this); + SpawnThread(ccls::CompletionMain, this); + SpawnThread(ccls::DiagnosticMain, this); +} + +void SemaManager::ScheduleDiag(const std::string &path, int debounce) { + static GroupMatch match(g_config->diagnostics.whitelist, + g_config->diagnostics.blacklist); + if (!match.Matches(path)) + return; + int64_t now = chrono::duration_cast( + chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + bool flag = false; + { + std::lock_guard lock(diag_mutex); + int64_t &next = next_diag[path]; + auto &d = g_config->diagnostics; + if (next <= now || + now - next > std::max(d.onChange, std::max(d.onChange, d.onSave))) { + next = now + debounce; + flag = true; + } + } + if (flag) + diag_tasks.PushBack({path, now + debounce, debounce}, false); +} + +void SemaManager::OnView(const std::string &path) { + std::lock_guard lock(mutex); + if (!sessions.Get(path)) + preamble_tasks.PushBack(PreambleTask{path}, true); +} + +void SemaManager::OnSave(const std::string &path) { + preamble_tasks.PushBack(PreambleTask{path}, true); +} + +void SemaManager::OnClose(const std::string &path) { + std::lock_guard lock(mutex); + sessions.Take(path); +} + +std::shared_ptr +SemaManager::EnsureSession(const std::string &path, bool *created) { + std::lock_guard lock(mutex); + std::shared_ptr session = sessions.Get(path); + if (!session) { + session = std::make_shared( + project_->FindEntry(path, false), wfiles, PCH); + LOG_S(INFO) << "create session for " << path; + sessions.Insert(path, session); + if (created) + *created = true; + } + return session; +} + +void SemaManager::Clear() { + LOG_S(INFO) << "clear all sessions"; + std::lock_guard lock(mutex); + sessions.Clear(); +} +} // namespace ccls diff --git a/src/sema_manager.hh b/src/sema_manager.hh new file mode 100644 index 000000000..632b2eaa0 --- /dev/null +++ b/src/sema_manager.hh @@ -0,0 +1,191 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "clang_tu.hh" +#include "lsp.hh" +#include "project.hh" +#include "threaded_queue.hh" +#include "working_files.hh" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace ccls { +struct PreambleData; + +struct DiagBase { + Range range; + std::string message; + std::string file; + clang::DiagnosticsEngine::Level level = clang::DiagnosticsEngine::Note; + unsigned category; + bool concerned = false; +}; +struct Note : DiagBase {}; +struct Diag : DiagBase { + std::vector notes; + std::vector edits; +}; + +TextEdit ToTextEdit(const clang::SourceManager &SM, + const clang::LangOptions &L, + const clang::FixItHint &FixIt); + +template struct LruCache { + std::shared_ptr Get(const K &key) { + for (auto it = items.begin(); it != items.end(); ++it) + if (it->first == key) { + auto x = std::move(*it); + std::move_backward(items.begin(), it, it + 1); + items[0] = std::move(x); + return items[0].second; + } + return nullptr; + } + std::shared_ptr Take(const K &key) { + for (auto it = items.begin(); it != items.end(); ++it) + if (it->first == key) { + auto x = std::move(it->second); + items.erase(it); + return x; + } + return nullptr; + } + void Insert(const K &key, std::shared_ptr value) { + if ((int)items.size() >= capacity) + items.pop_back(); + items.emplace(items.begin(), key, std::move(value)); + } + void Clear() { items.clear(); } + void SetCapacity(int cap) { capacity = cap; } + +private: + std::vector>> items; + int capacity = 1; +}; + +struct Session { + std::mutex mutex; + std::shared_ptr preamble; + + Project::Entry file; + WorkingFiles *wfiles; + bool inferred = false; + + // TODO share + llvm::IntrusiveRefCntPtr FS = + llvm::vfs::getRealFileSystem(); + std::shared_ptr PCH; + + Session(const Project::Entry &file, WorkingFiles *wfiles, + std::shared_ptr PCH) + : file(file), wfiles(wfiles), PCH(PCH) {} + + std::shared_ptr GetPreamble(); +}; + +struct SemaManager { + using OnDiagnostic = std::function diagnostics)>; + // If OptConsumer is nullptr, the request has been cancelled. + using OnComplete = + std::function; + using OnDropped = std::function; + + struct PreambleTask { + std::string path; + bool from_diag = false; + }; + struct CompTask { + CompTask(const RequestId &id, const std::string &path, + const Position &position, + std::unique_ptr Consumer, + clang::CodeCompleteOptions CCOpts, const OnComplete &on_complete) + : id(id), path(path), position(position), Consumer(std::move(Consumer)), + CCOpts(CCOpts), on_complete(on_complete) {} + + RequestId id; + std::string path; + Position position; + std::unique_ptr Consumer; + clang::CodeCompleteOptions CCOpts; + OnComplete on_complete; + }; + struct DiagTask { + std::string path; + int64_t wait_until; + int64_t debounce; + }; + + SemaManager(Project *project, WorkingFiles *wfiles, + OnDiagnostic on_diagnostic, OnDropped on_dropped); + + void ScheduleDiag(const std::string &path, int debounce); + void OnView(const std::string &path); + void OnSave(const std::string &path); + void OnClose(const std::string &path); + std::shared_ptr EnsureSession(const std::string &path, + bool *created = nullptr); + void Clear(void); + + // Global state. + Project *project_; + WorkingFiles *wfiles; + OnDiagnostic on_diagnostic_; + OnDropped on_dropped_; + + std::mutex mutex; + LruCache sessions; + + std::mutex diag_mutex; + std::unordered_map next_diag; + + ThreadedQueue> comp_tasks; + ThreadedQueue diag_tasks; + ThreadedQueue preamble_tasks; + + std::shared_ptr PCH; +}; + +// Cached completion information, so we can give fast completion results when +// the user erases a character. vscode will resend the completion request if +// that happens. +template +struct CompleteConsumerCache { + std::mutex mutex; + std::string path; + Position position; + T result; + + template void WithLock(Fn &&fn) { + std::lock_guard lock(mutex); + fn(); + } + bool IsCacheValid(const std::string path, Position position) { + std::lock_guard lock(mutex); + return this->path == path && this->position == position; + } +}; +} // namespace ccls diff --git a/src/semantic_highlight_symbol_cache.cc b/src/semantic_highlight_symbol_cache.cc deleted file mode 100644 index 5992b5d4c..000000000 --- a/src/semantic_highlight_symbol_cache.cc +++ /dev/null @@ -1,72 +0,0 @@ -#include "semantic_highlight_symbol_cache.h" - -SemanticHighlightSymbolCache::Entry::Entry( - SemanticHighlightSymbolCache* all_caches, - const std::string& path) - : all_caches_(all_caches), path(path) {} - -optional SemanticHighlightSymbolCache::Entry::TryGetStableId( - SymbolKind kind, - const std::string& detailed_name) { - TNameToId* map = GetMapForSymbol_(kind); - auto it = map->find(detailed_name); - if (it != map->end()) - return it->second; - - return nullopt; -} - -int SemanticHighlightSymbolCache::Entry::GetStableId( - SymbolKind kind, - const std::string& detailed_name) { - optional id = TryGetStableId(kind, detailed_name); - if (id) - return *id; - - // Create a new id. First try to find a key in another map. - all_caches_->cache_.IterateValues([&](const std::shared_ptr& entry) { - optional other_id = entry->TryGetStableId(kind, detailed_name); - if (other_id) { - id = other_id; - return false; - } - return true; - }); - - // Create a new id. - TNameToId* map = GetMapForSymbol_(kind); - if (!id) - id = all_caches_->next_stable_id_++; - return (*map)[detailed_name] = *id; -} - -SemanticHighlightSymbolCache::Entry::TNameToId* -SemanticHighlightSymbolCache::Entry::GetMapForSymbol_(SymbolKind kind) { - switch (kind) { - case SymbolKind::Type: - return &detailed_type_name_to_stable_id; - case SymbolKind::Func: - return &detailed_func_name_to_stable_id; - case SymbolKind::Var: - return &detailed_var_name_to_stable_id; - case SymbolKind::File: - case SymbolKind::Invalid: - break; - } - assert(false); - return nullptr; -} - -SemanticHighlightSymbolCache::SemanticHighlightSymbolCache() - : cache_(kCacheSize) {} - -void SemanticHighlightSymbolCache::Init(Config* config) { - match_ = std::make_unique(config->highlight.whitelist, - config->highlight.blacklist); -} - -std::shared_ptr -SemanticHighlightSymbolCache::GetCacheForFile(const std::string& path) { - return cache_.Get( - path, [&, this]() { return std::make_shared(this, path); }); -} diff --git a/src/semantic_highlight_symbol_cache.h b/src/semantic_highlight_symbol_cache.h deleted file mode 100644 index f540ecb90..000000000 --- a/src/semantic_highlight_symbol_cache.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include "lru_cache.h" -#include "match.h" -#include "query.h" - -#include - -#include -#include - -// Caches symbols for a single file for semantic highlighting to provide -// relatively stable ids. Only supports xxx files at a time. -struct SemanticHighlightSymbolCache { - struct Entry { - SemanticHighlightSymbolCache* all_caches_ = nullptr; - - // The path this cache belongs to. - std::string path; - // Detailed symbol name to stable id. - using TNameToId = std::unordered_map; - TNameToId detailed_type_name_to_stable_id; - TNameToId detailed_func_name_to_stable_id; - TNameToId detailed_var_name_to_stable_id; - - Entry(SemanticHighlightSymbolCache* all_caches, const std::string& path); - - optional TryGetStableId(SymbolKind kind, - const std::string& detailed_name); - int GetStableId(SymbolKind kind, const std::string& detailed_name); - - TNameToId* GetMapForSymbol_(SymbolKind kind); - }; - - constexpr static int kCacheSize = 10; - LruCache cache_; - uint32_t next_stable_id_ = 0; - std::unique_ptr match_; - - SemanticHighlightSymbolCache(); - void Init(Config*); - std::shared_ptr GetCacheForFile(const std::string& path); -}; diff --git a/src/serializer.cc b/src/serializer.cc index 02f8fd3c9..d9cd0b736 100644 --- a/src/serializer.cc +++ b/src/serializer.cc @@ -1,456 +1,549 @@ -#include "serializer.h" +/* Copyright 2017-2018 ccls Authors -#include "serializers/json.h" -#include "serializers/msgpack.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#include "indexer.h" + http://www.apache.org/licenses/LICENSE-2.0 -#include -#include +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ -#include - -bool gTestOutputMode = false; - -//// Elementary types - -void Reflect(Reader& visitor, uint8_t& value) { - if (!visitor.IsInt()) - throw std::invalid_argument("uint8_t"); - value = (uint8_t)visitor.GetInt(); -} -void Reflect(Writer& visitor, uint8_t& value) { - visitor.Int(value); -} - -void Reflect(Reader& visitor, short& value) { - if (!visitor.IsInt()) - throw std::invalid_argument("short"); - value = (short)visitor.GetInt(); -} -void Reflect(Writer& visitor, short& value) { - visitor.Int(value); -} - -void Reflect(Reader& visitor, unsigned short& value) { - if (!visitor.IsInt()) - throw std::invalid_argument("unsigned short"); - value = (unsigned short)visitor.GetInt(); -} -void Reflect(Writer& visitor, unsigned short& value) { - visitor.Int(value); -} +#include "serializer.hh" -void Reflect(Reader& visitor, int& value) { - if (!visitor.IsInt()) - throw std::invalid_argument("int"); - value = visitor.GetInt(); -} -void Reflect(Writer& visitor, int& value) { - visitor.Int(value); -} +#include "filesystem.hh" +#include "indexer.hh" +#include "log.hh" +#include "message_handler.hh" -void Reflect(Reader& visitor, unsigned& value) { - if (!visitor.IsUint64()) - throw std::invalid_argument("unsigned"); - value = visitor.GetUint32(); -} -void Reflect(Writer& visitor, unsigned& value) { - visitor.Uint32(value); -} - -void Reflect(Reader& visitor, long& value) { - if (!visitor.IsInt64()) - throw std::invalid_argument("long"); - value = long(visitor.GetInt64()); -} -void Reflect(Writer& visitor, long& value) { - visitor.Int64(value); -} - -void Reflect(Reader& visitor, unsigned long& value) { - if (!visitor.IsUint64()) - throw std::invalid_argument("unsigned long"); - value = (unsigned long)visitor.GetUint64(); -} -void Reflect(Writer& visitor, unsigned long& value) { - visitor.Uint64(value); -} +#include +#include -void Reflect(Reader& visitor, long long& value) { - if (!visitor.IsInt64()) - throw std::invalid_argument("long long"); - value = visitor.GetInt64(); -} -void Reflect(Writer& visitor, long long& value) { - visitor.Int64(value); -} +#include +#include -void Reflect(Reader& visitor, unsigned long long& value) { - if (!visitor.IsUint64()) - throw std::invalid_argument("unsigned long long"); - value = visitor.GetUint64(); -} -void Reflect(Writer& visitor, unsigned long long& value) { - visitor.Uint64(value); -} +#include +#include -void Reflect(Reader& visitor, double& value) { - if (!visitor.IsDouble()) - throw std::invalid_argument("double"); - value = visitor.GetDouble(); -} -void Reflect(Writer& visitor, double& value) { - visitor.Double(value); -} +using namespace llvm; -void Reflect(Reader& visitor, bool& value) { - if (!visitor.IsBool()) - throw std::invalid_argument("bool"); - value = visitor.GetBool(); -} -void Reflect(Writer& visitor, bool& value) { - visitor.Bool(value); -} +bool gTestOutputMode = false; -void Reflect(Reader& visitor, std::string& value) { - if (!visitor.IsString()) - throw std::invalid_argument("std::string"); - value = visitor.GetString(); +namespace ccls { + +void JsonReader::IterArray(std::function fn) { + if (!m->IsArray()) + throw std::invalid_argument("array"); + // Use "0" to indicate any element for now. + path_.push_back("0"); + for (auto &entry : m->GetArray()) { + auto saved = m; + m = &entry; + fn(); + m = saved; + } + path_.pop_back(); +} +void JsonReader::Member(const char *name, std::function fn) { + path_.push_back(name); + auto it = m->FindMember(name); + if (it != m->MemberEnd()) { + auto saved = m; + m = &it->value; + fn(); + m = saved; + } + path_.pop_back(); +} +bool JsonReader::IsNull() { return m->IsNull(); } +std::string JsonReader::GetString() { return m->GetString(); } +std::string JsonReader::GetPath() const { + std::string ret; + for (auto &t : path_) { + ret += '/'; + ret += t; + } + ret.pop_back(); + return ret; +} + +void JsonWriter::StartArray() { m->StartArray(); } +void JsonWriter::EndArray() { m->EndArray(); } +void JsonWriter::StartObject() { m->StartObject(); } +void JsonWriter::EndObject() { m->EndObject(); } +void JsonWriter::Key(const char *name) { m->Key(name); } +void JsonWriter::Null() { m->Null(); } +void JsonWriter::Int(int v) { m->Int(v); } +void JsonWriter::String(const char *s) { m->String(s); } +void JsonWriter::String(const char *s, size_t len) { m->String(s, len); } + +// clang-format off +void Reflect(JsonReader &vis, bool &v ) { if (!vis.m->IsBool()) throw std::invalid_argument("bool"); v = vis.m->GetBool(); } +void Reflect(JsonReader &vis, unsigned char &v ) { if (!vis.m->IsInt()) throw std::invalid_argument("uint8_t"); v = (uint8_t)vis.m->GetInt(); } +void Reflect(JsonReader &vis, short &v ) { if (!vis.m->IsInt()) throw std::invalid_argument("short"); v = (short)vis.m->GetInt(); } +void Reflect(JsonReader &vis, unsigned short &v ) { if (!vis.m->IsInt()) throw std::invalid_argument("unsigned short"); v = (unsigned short)vis.m->GetInt(); } +void Reflect(JsonReader &vis, int &v ) { if (!vis.m->IsInt()) throw std::invalid_argument("int"); v = vis.m->GetInt(); } +void Reflect(JsonReader &vis, unsigned &v ) { if (!vis.m->IsUint64()) throw std::invalid_argument("unsigned"); v = (unsigned)vis.m->GetUint64(); } +void Reflect(JsonReader &vis, long &v ) { if (!vis.m->IsInt64()) throw std::invalid_argument("long"); v = (long)vis.m->GetInt64(); } +void Reflect(JsonReader &vis, unsigned long &v ) { if (!vis.m->IsUint64()) throw std::invalid_argument("unsigned long"); v = (unsigned long)vis.m->GetUint64(); } +void Reflect(JsonReader &vis, long long &v ) { if (!vis.m->IsInt64()) throw std::invalid_argument("long long"); v = vis.m->GetInt64(); } +void Reflect(JsonReader &vis, unsigned long long &v) { if (!vis.m->IsUint64()) throw std::invalid_argument("unsigned long long"); v = vis.m->GetUint64(); } +void Reflect(JsonReader &vis, double &v ) { if (!vis.m->IsDouble()) throw std::invalid_argument("double"); v = vis.m->GetDouble(); } +void Reflect(JsonReader &vis, const char *&v ) { if (!vis.m->IsString()) throw std::invalid_argument("std::string"); v = Intern(vis.GetString()); } +void Reflect(JsonReader &vis, std::string &v ) { if (!vis.m->IsString()) throw std::invalid_argument("std::string"); v = vis.GetString(); } + +void Reflect(JsonWriter &vis, bool &v ) { vis.m->Bool(v); } +void Reflect(JsonWriter &vis, unsigned char &v ) { vis.m->Int(v); } +void Reflect(JsonWriter &vis, short &v ) { vis.m->Int(v); } +void Reflect(JsonWriter &vis, unsigned short &v ) { vis.m->Int(v); } +void Reflect(JsonWriter &vis, int &v ) { vis.m->Int(v); } +void Reflect(JsonWriter &vis, unsigned &v ) { vis.m->Uint64(v); } +void Reflect(JsonWriter &vis, long &v ) { vis.m->Int64(v); } +void Reflect(JsonWriter &vis, unsigned long &v ) { vis.m->Uint64(v); } +void Reflect(JsonWriter &vis, long long &v ) { vis.m->Int64(v); } +void Reflect(JsonWriter &vis, unsigned long long &v) { vis.m->Uint64(v); } +void Reflect(JsonWriter &vis, double &v ) { vis.m->Double(v); } +void Reflect(JsonWriter &vis, const char *&v ) { vis.String(v); } +void Reflect(JsonWriter &vis, std::string &v ) { vis.String(v.c_str(), v.size()); } + +void Reflect(BinaryReader &vis, bool &v ) { v = vis.Get(); } +void Reflect(BinaryReader &vis, unsigned char &v ) { v = vis.Get(); } +void Reflect(BinaryReader &vis, short &v ) { v = (short)vis.VarInt(); } +void Reflect(BinaryReader &vis, unsigned short &v ) { v = (unsigned short)vis.VarUInt(); } +void Reflect(BinaryReader &vis, int &v ) { v = (int)vis.VarInt(); } +void Reflect(BinaryReader &vis, unsigned &v ) { v = (unsigned)vis.VarUInt(); } +void Reflect(BinaryReader &vis, long &v ) { v = (long)vis.VarInt(); } +void Reflect(BinaryReader &vis, unsigned long &v ) { v = (unsigned long)vis.VarUInt(); } +void Reflect(BinaryReader &vis, long long &v ) { v = vis.VarInt(); } +void Reflect(BinaryReader &vis, unsigned long long &v) { v = vis.VarUInt(); } +void Reflect(BinaryReader &vis, double &v ) { v = vis.Get(); } +void Reflect(BinaryReader &vis, const char *&v ) { v = Intern(vis.GetString()); } +void Reflect(BinaryReader &vis, std::string &v ) { v = vis.GetString(); } + +void Reflect(BinaryWriter &vis, bool &v ) { vis.Pack(v); } +void Reflect(BinaryWriter &vis, unsigned char &v ) { vis.Pack(v); } +void Reflect(BinaryWriter &vis, short &v ) { vis.VarInt(v); } +void Reflect(BinaryWriter &vis, unsigned short &v ) { vis.VarUInt(v); } +void Reflect(BinaryWriter &vis, int &v ) { vis.VarInt(v); } +void Reflect(BinaryWriter &vis, unsigned &v ) { vis.VarUInt(v); } +void Reflect(BinaryWriter &vis, long &v ) { vis.VarInt(v); } +void Reflect(BinaryWriter &vis, unsigned long &v ) { vis.VarUInt(v); } +void Reflect(BinaryWriter &vis, long long &v ) { vis.VarInt(v); } +void Reflect(BinaryWriter &vis, unsigned long long &v) { vis.VarUInt(v); } +void Reflect(BinaryWriter &vis, double &v ) { vis.Pack(v); } +void Reflect(BinaryWriter &vis, const char *&v ) { vis.String(v); } +void Reflect(BinaryWriter &vis, std::string &v ) { vis.String(v.c_str(), v.size()); } +// clang-format on + +void Reflect(JsonWriter &vis, std::string_view &data) { + if (data.empty()) + vis.String(""); + else + vis.String(&data[0], (rapidjson::SizeType)data.size()); +} + +void Reflect(JsonReader &vis, JsonNull &v) {} +void Reflect(JsonWriter &vis, JsonNull &v) { vis.m->Null(); } + +template +void Reflect(JsonReader &vis, std::unordered_map &v) { + vis.IterArray([&]() { + V val; + Reflect(vis, val); + v[val.usr] = std::move(val); + }); +} +template +void Reflect(JsonWriter &vis, std::unordered_map &v) { + // Determinism + std::vector> xs(v.begin(), v.end()); + std::sort(xs.begin(), xs.end(), + [](const auto &a, const auto &b) { return a.first < b.first; }); + vis.StartArray(); + for (auto &it : xs) + Reflect(vis, it.second); + vis.EndArray(); +} +template +void Reflect(BinaryReader &vis, std::unordered_map &v) { + for (auto n = vis.VarUInt(); n; n--) { + V val; + Reflect(vis, val); + v[val.usr] = std::move(val); + } } -void Reflect(Writer& visitor, std::string& value) { - visitor.String(value.c_str(), (rapidjson::SizeType)value.size()); +template +void Reflect(BinaryWriter &vis, std::unordered_map &v) { + vis.VarUInt(v.size()); + for (auto &it : v) + Reflect(vis, it.second); } -void Reflect(Reader&, std::string_view&) { - assert(0); +// Used by IndexFile::dependencies. +void Reflect(JsonReader &vis, DenseMap &v) { + std::string name; + for (auto it = vis.m->MemberBegin(); it != vis.m->MemberEnd(); ++it) + v[InternH(it->name.GetString())] = it->value.GetInt64(); } -void Reflect(Writer& visitor, std::string_view& data) { - if (data.empty()) - visitor.String(""); - else - visitor.String(&data[0], (rapidjson::SizeType)data.size()); +void Reflect(JsonWriter &vis, DenseMap &v) { + vis.StartObject(); + for (auto &it : v) { + vis.m->Key(it.first.val().data()); // llvm 8 -> data() + vis.m->Int64(it.second); + } + vis.EndObject(); } - -void Reflect(Reader& visitor, NtString& value) { - if (!visitor.IsString()) - throw std::invalid_argument("std::string"); - value = visitor.GetString(); +void Reflect(BinaryReader &vis, DenseMap &v) { + std::string name; + for (auto n = vis.VarUInt(); n; n--) { + Reflect(vis, name); + Reflect(vis, v[InternH(name)]); + } } -void Reflect(Writer& visitor, NtString& value) { - const char* s = value.c_str(); - visitor.String(s ? s : ""); +void Reflect(BinaryWriter &vis, DenseMap &v) { + std::string key; + vis.VarUInt(v.size()); + for (auto &it : v) { + key = it.first.val().str(); + Reflect(vis, key); + Reflect(vis, it.second); + } } -// TODO: Move this to indexer.cc -void Reflect(Reader& visitor, IndexInclude& value) { - REFLECT_MEMBER_START(); +template void Reflect(Vis &vis, IndexInclude &v) { + ReflectMemberStart(vis); REFLECT_MEMBER(line); REFLECT_MEMBER(resolved_path); - REFLECT_MEMBER_END(); + ReflectMemberEnd(vis); } -void Reflect(Writer& visitor, IndexInclude& value) { - REFLECT_MEMBER_START(); +void Reflect(JsonWriter &vis, IndexInclude &v) { + ReflectMemberStart(vis); REFLECT_MEMBER(line); if (gTestOutputMode) { - std::string basename = GetBaseName(value.resolved_path); - if (!StartsWith(value.resolved_path, "&")) + std::string basename = llvm::sys::path::filename(v.resolved_path); + if (v.resolved_path[0] != '&') basename = "&" + basename; REFLECT_MEMBER2("resolved_path", basename); } else { REFLECT_MEMBER(resolved_path); } - REFLECT_MEMBER_END(); + ReflectMemberEnd(vis); } template -void ReflectHoverAndComments(Reader& visitor, Def& def) { - ReflectMember(visitor, "hover", def.hover); - ReflectMember(visitor, "comments", def.comments); +void ReflectHoverAndComments(JsonReader &vis, Def &def) { + ReflectMember(vis, "hover", def.hover); + ReflectMember(vis, "comments", def.comments); } - template -void ReflectHoverAndComments(Writer& visitor, Def& def) { +void ReflectHoverAndComments(JsonWriter &vis, Def &def) { // Don't emit empty hover and comments in JSON test mode. - if (!gTestOutputMode || !def.hover.empty()) - ReflectMember(visitor, "hover", def.hover); - if (!gTestOutputMode || !def.comments.empty()) - ReflectMember(visitor, "comments", def.comments); + if (!gTestOutputMode || def.hover[0]) + ReflectMember(vis, "hover", def.hover); + if (!gTestOutputMode || def.comments[0]) + ReflectMember(vis, "comments", def.comments); } - template -void ReflectShortName(Reader& visitor, Def& def) { +void ReflectHoverAndComments(BinaryReader &vis, Def &def) { + Reflect(vis, def.hover); + Reflect(vis, def.comments); +} +template +void ReflectHoverAndComments(BinaryWriter &vis, Def &def) { + Reflect(vis, def.hover); + Reflect(vis, def.comments); +} + +template void ReflectShortName(JsonReader &vis, Def &def) { if (gTestOutputMode) { std::string short_name; - ReflectMember(visitor, "short_name", short_name); - def.short_name_offset = def.detailed_name.find(short_name); + ReflectMember(vis, "short_name", short_name); + def.short_name_offset = + std::string_view(def.detailed_name).find(short_name); assert(def.short_name_offset != std::string::npos); def.short_name_size = short_name.size(); } else { - ReflectMember(visitor, "short_name_offset", def.short_name_offset); - ReflectMember(visitor, "short_name_size", def.short_name_size); + ReflectMember(vis, "short_name_offset", def.short_name_offset); + ReflectMember(vis, "short_name_size", def.short_name_size); } } - -template -void ReflectShortName(Writer& visitor, Def& def) { +template void ReflectShortName(JsonWriter &vis, Def &def) { if (gTestOutputMode) { - std::string short_name( - def.detailed_name.substr(def.short_name_offset, def.short_name_size)); - ReflectMember(visitor, "short_name", short_name); + std::string_view short_name(def.detailed_name + def.short_name_offset, + def.short_name_size); + ReflectMember(vis, "short_name", short_name); } else { - ReflectMember(visitor, "short_name_offset", def.short_name_offset); - ReflectMember(visitor, "short_name_size", def.short_name_size); + ReflectMember(vis, "short_name_offset", def.short_name_offset); + ReflectMember(vis, "short_name_size", def.short_name_size); } } - -template -void Reflect(TVisitor& visitor, IndexType& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER2("id", value.id); - REFLECT_MEMBER2("usr", value.usr); - REFLECT_MEMBER2("detailed_name", value.def.detailed_name); - ReflectShortName(visitor, value.def); - REFLECT_MEMBER2("kind", value.def.kind); - ReflectHoverAndComments(visitor, value.def); - REFLECT_MEMBER2("declarations", value.declarations); - REFLECT_MEMBER2("spell", value.def.spell); - REFLECT_MEMBER2("extent", value.def.extent); - REFLECT_MEMBER2("alias_of", value.def.alias_of); - REFLECT_MEMBER2("bases", value.def.bases); - REFLECT_MEMBER2("derived", value.derived); - REFLECT_MEMBER2("types", value.def.types); - REFLECT_MEMBER2("funcs", value.def.funcs); - REFLECT_MEMBER2("vars", value.def.vars); - REFLECT_MEMBER2("instances", value.instances); - REFLECT_MEMBER2("uses", value.uses); - REFLECT_MEMBER_END(); -} - -template -void Reflect(TVisitor& visitor, IndexFunc& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER2("id", value.id); - REFLECT_MEMBER2("usr", value.usr); - REFLECT_MEMBER2("detailed_name", value.def.detailed_name); - ReflectShortName(visitor, value.def); - REFLECT_MEMBER2("kind", value.def.kind); - REFLECT_MEMBER2("storage", value.def.storage); - ReflectHoverAndComments(visitor, value.def); - REFLECT_MEMBER2("declarations", value.declarations); - REFLECT_MEMBER2("spell", value.def.spell); - REFLECT_MEMBER2("extent", value.def.extent); - REFLECT_MEMBER2("declaring_type", value.def.declaring_type); - REFLECT_MEMBER2("bases", value.def.bases); - REFLECT_MEMBER2("derived", value.derived); - REFLECT_MEMBER2("vars", value.def.vars); - REFLECT_MEMBER2("uses", value.uses); - REFLECT_MEMBER2("callees", value.def.callees); - REFLECT_MEMBER_END(); -} - -template -void Reflect(TVisitor& visitor, IndexVar& value) { - REFLECT_MEMBER_START(); - REFLECT_MEMBER2("id", value.id); - REFLECT_MEMBER2("usr", value.usr); - REFLECT_MEMBER2("detailed_name", value.def.detailed_name); - ReflectShortName(visitor, value.def); - ReflectHoverAndComments(visitor, value.def); - REFLECT_MEMBER2("declarations", value.declarations); - REFLECT_MEMBER2("spell", value.def.spell); - REFLECT_MEMBER2("extent", value.def.extent); - REFLECT_MEMBER2("type", value.def.type); - REFLECT_MEMBER2("uses", value.uses); - REFLECT_MEMBER2("kind", value.def.kind); - REFLECT_MEMBER2("storage", value.def.storage); - REFLECT_MEMBER_END(); -} +template void ReflectShortName(BinaryReader &vis, Def &def) { + Reflect(vis, def.short_name_offset); + Reflect(vis, def.short_name_size); +} +template void ReflectShortName(BinaryWriter &vis, Def &def) { + Reflect(vis, def.short_name_offset); + Reflect(vis, def.short_name_size); +} + +template void Reflect1(TVisitor &vis, IndexFunc &v) { + ReflectMemberStart(vis); + REFLECT_MEMBER2("usr", v.usr); + REFLECT_MEMBER2("detailed_name", v.def.detailed_name); + REFLECT_MEMBER2("qual_name_offset", v.def.qual_name_offset); + ReflectShortName(vis, v.def); + REFLECT_MEMBER2("spell", v.def.spell); + ReflectHoverAndComments(vis, v.def); + REFLECT_MEMBER2("bases", v.def.bases); + REFLECT_MEMBER2("vars", v.def.vars); + REFLECT_MEMBER2("callees", v.def.callees); + REFLECT_MEMBER2("kind", v.def.kind); + REFLECT_MEMBER2("parent_kind", v.def.parent_kind); + REFLECT_MEMBER2("storage", v.def.storage); + + REFLECT_MEMBER2("declarations", v.declarations); + REFLECT_MEMBER2("derived", v.derived); + REFLECT_MEMBER2("uses", v.uses); + REFLECT_MEMBER2("data_flow_into_return", v.data_flow_into_return); + ReflectMemberEnd(vis); +} +void Reflect(JsonReader &vis, IndexFunc &v) { Reflect1(vis, v); } +void Reflect(JsonWriter &vis, IndexFunc &v) { Reflect1(vis, v); } +void Reflect(BinaryReader &vis, IndexFunc &v) { Reflect1(vis, v); } +void Reflect(BinaryWriter &vis, IndexFunc &v) { Reflect1(vis, v); } + +template void Reflect1(TVisitor &vis, IndexType &v) { + ReflectMemberStart(vis); + REFLECT_MEMBER2("usr", v.usr); + REFLECT_MEMBER2("detailed_name", v.def.detailed_name); + REFLECT_MEMBER2("qual_name_offset", v.def.qual_name_offset); + ReflectShortName(vis, v.def); + ReflectHoverAndComments(vis, v.def); + REFLECT_MEMBER2("spell", v.def.spell); + REFLECT_MEMBER2("bases", v.def.bases); + REFLECT_MEMBER2("funcs", v.def.funcs); + REFLECT_MEMBER2("types", v.def.types); + REFLECT_MEMBER2("vars", v.def.vars); + REFLECT_MEMBER2("alias_of", v.def.alias_of); + REFLECT_MEMBER2("kind", v.def.kind); + REFLECT_MEMBER2("parent_kind", v.def.parent_kind); + + REFLECT_MEMBER2("declarations", v.declarations); + REFLECT_MEMBER2("derived", v.derived); + REFLECT_MEMBER2("instances", v.instances); + REFLECT_MEMBER2("uses", v.uses); + ReflectMemberEnd(vis); +} +void Reflect(JsonReader &vis, IndexType &v) { Reflect1(vis, v); } +void Reflect(JsonWriter &vis, IndexType &v) { Reflect1(vis, v); } +void Reflect(BinaryReader &vis, IndexType &v) { Reflect1(vis, v); } +void Reflect(BinaryWriter &vis, IndexType &v) { Reflect1(vis, v); } + +template void Reflect1(TVisitor &vis, IndexVar &v) { + ReflectMemberStart(vis); + REFLECT_MEMBER2("usr", v.usr); + REFLECT_MEMBER2("detailed_name", v.def.detailed_name); + REFLECT_MEMBER2("qual_name_offset", v.def.qual_name_offset); + ReflectShortName(vis, v.def); + ReflectHoverAndComments(vis, v.def); + REFLECT_MEMBER2("spell", v.def.spell); + REFLECT_MEMBER2("type", v.def.type); + REFLECT_MEMBER2("kind", v.def.kind); + REFLECT_MEMBER2("parent_kind", v.def.parent_kind); + REFLECT_MEMBER2("storage", v.def.storage); + + REFLECT_MEMBER2("declarations", v.declarations); + REFLECT_MEMBER2("uses", v.uses); + REFLECT_MEMBER2("data_flow_into", v.data_flow_into); + ReflectMemberEnd(vis); +} +void Reflect(JsonReader &vis, IndexVar &v) { Reflect1(vis, v); } +void Reflect(JsonWriter &vis, IndexVar &v) { Reflect1(vis, v); } +void Reflect(BinaryReader &vis, IndexVar &v) { Reflect1(vis, v); } +void Reflect(BinaryWriter &vis, IndexVar &v) { Reflect1(vis, v); } // IndexFile -bool ReflectMemberStart(Writer& visitor, IndexFile& value) { - // FIXME - auto it = value.id_cache.usr_to_type_id.find(HashUsr("")); - if (it != value.id_cache.usr_to_type_id.end()) { - value.Resolve(it->second)->def.detailed_name = ""; - assert(value.Resolve(it->second)->uses.size() == 0); - } - - DefaultReflectMemberStart(visitor); - return true; -} -template -void Reflect(TVisitor& visitor, IndexFile& value) { - REFLECT_MEMBER_START(); +template void Reflect1(TVisitor &vis, IndexFile &v) { + ReflectMemberStart(vis); if (!gTestOutputMode) { - REFLECT_MEMBER(last_modification_time); + REFLECT_MEMBER(mtime); REFLECT_MEMBER(language); + REFLECT_MEMBER(lid2path); REFLECT_MEMBER(import_file); REFLECT_MEMBER(args); + REFLECT_MEMBER(dependencies); } REFLECT_MEMBER(includes); - if (!gTestOutputMode) - REFLECT_MEMBER(dependencies); - REFLECT_MEMBER(skipped_by_preprocessor); - REFLECT_MEMBER(types); - REFLECT_MEMBER(funcs); - REFLECT_MEMBER(vars); - REFLECT_MEMBER_END(); -} - -void Reflect(Reader& visitor, std::monostate&) { - visitor.GetNull(); + REFLECT_MEMBER(skipped_ranges); + REFLECT_MEMBER(usr2func); + REFLECT_MEMBER(usr2type); + REFLECT_MEMBER(usr2var); + ReflectMemberEnd(vis); +} +void ReflectFile(JsonReader &vis, IndexFile &v) { Reflect1(vis, v); } +void ReflectFile(JsonWriter &vis, IndexFile &v) { Reflect1(vis, v); } +void ReflectFile(BinaryReader &vis, IndexFile &v) { Reflect1(vis, v); } +void ReflectFile(BinaryWriter &vis, IndexFile &v) { Reflect1(vis, v); } + +void Reflect(JsonReader &vis, SerializeFormat &v) { + v = vis.GetString()[0] == 'j' ? SerializeFormat::Json + : SerializeFormat::Binary; +} + +void Reflect(JsonWriter &vis, SerializeFormat &v) { + switch (v) { + case SerializeFormat::Binary: + vis.String("binary"); + break; + case SerializeFormat::Json: + vis.String("json"); + break; + } } -void Reflect(Writer& visitor, std::monostate&) { - visitor.Null(); +static BumpPtrAllocator Alloc; +static DenseSet Strings; +static std::mutex AllocMutex; + +CachedHashStringRef InternH(StringRef S) { + if (S.empty()) + S = ""; + CachedHashString HS(S); + std::lock_guard lock(AllocMutex); + auto R = Strings.insert(HS); + if (R.second) { + char *P = Alloc.Allocate(S.size() + 1); + memcpy(P, S.data(), S.size()); + P[S.size()] = '\0'; + *R.first = CachedHashStringRef(StringRef(P, S.size()), HS.hash()); + } + return *R.first; } -void Reflect(Reader& visitor, SerializeFormat& value) { - std::string fmt = visitor.GetString(); - value = fmt[0] == 'm' ? SerializeFormat::MessagePack : SerializeFormat::Json; +const char *Intern(StringRef S) { + return InternH(S).val().data(); } -void Reflect(Writer& visitor, SerializeFormat& value) { - switch (value) { - case SerializeFormat::Json: - visitor.String("json"); - break; - case SerializeFormat::MessagePack: - visitor.String("msgpack"); - break; - } -} - -std::string Serialize(SerializeFormat format, IndexFile& file) { +std::string Serialize(SerializeFormat format, IndexFile &file) { switch (format) { - case SerializeFormat::Json: { - rapidjson::StringBuffer output; - rapidjson::PrettyWriter writer(output); - writer.SetFormatOptions( - rapidjson::PrettyFormatOptions::kFormatSingleLineArray); - writer.SetIndent(' ', 2); - JsonWriter json_writer(&writer); - if (!gTestOutputMode) { - std::string version = std::to_string(IndexFile::kMajorVersion); - for (char c : version) - output.Put(c); - output.Put('\n'); - } - Reflect(json_writer, file); - return output.GetString(); - } - case SerializeFormat::MessagePack: { - msgpack::sbuffer buf; - msgpack::packer pk(&buf); - MessagePackWriter msgpack_writer(&pk); - uint64_t magic = IndexFile::kMajorVersion; - int version = IndexFile::kMinorVersion; - Reflect(msgpack_writer, magic); - Reflect(msgpack_writer, version); - Reflect(msgpack_writer, file); - return std::string(buf.data(), buf.size()); + case SerializeFormat::Binary: { + BinaryWriter writer; + int major = IndexFile::kMajorVersion; + int minor = IndexFile::kMinorVersion; + Reflect(writer, major); + Reflect(writer, minor); + ReflectFile(writer, file); + return writer.Take(); + } + case SerializeFormat::Json: { + rapidjson::StringBuffer output; + rapidjson::PrettyWriter writer(output); + writer.SetFormatOptions( + rapidjson::PrettyFormatOptions::kFormatSingleLineArray); + writer.SetIndent(' ', 2); + JsonWriter json_writer(&writer); + if (!gTestOutputMode) { + std::string version = std::to_string(IndexFile::kMajorVersion); + for (char c : version) + output.Put(c); + output.Put('\n'); } + ReflectFile(json_writer, file); + return output.GetString(); + } } return ""; } -std::unique_ptr Deserialize( - SerializeFormat format, - const std::string& path, - const std::string& serialized_index_content, - const std::string& file_content, - optional expected_version) { +std::unique_ptr +Deserialize(SerializeFormat format, const std::string &path, + const std::string &serialized_index_content, + const std::string &file_content, + std::optional expected_version) { if (serialized_index_content.empty()) return nullptr; std::unique_ptr file; switch (format) { - case SerializeFormat::Json: { - rapidjson::Document reader; - if (gTestOutputMode || !expected_version) { - reader.Parse(serialized_index_content.c_str()); - } else { - const char* p = strchr(serialized_index_content.c_str(), '\n'); - if (!p) - return nullptr; - if (atoi(serialized_index_content.c_str()) != *expected_version) - return nullptr; - reader.Parse(p + 1); - } - if (reader.HasParseError()) + case SerializeFormat::Binary: { + try { + int major, minor; + if (serialized_index_content.size() < 8) + throw std::invalid_argument("Invalid"); + BinaryReader reader(serialized_index_content); + Reflect(reader, major); + Reflect(reader, minor); + if (major != IndexFile::kMajorVersion || + minor != IndexFile::kMinorVersion) + throw std::invalid_argument("Invalid version"); + file = std::make_unique(sys::fs::UniqueID(0, 0), path, + file_content); + ReflectFile(reader, *file); + } catch (std::invalid_argument &e) { + LOG_S(INFO) << "failed to deserialize '" << path << "': " << e.what(); + return nullptr; + } + break; + } + case SerializeFormat::Json: { + rapidjson::Document reader; + if (gTestOutputMode || !expected_version) { + reader.Parse(serialized_index_content.c_str()); + } else { + const char *p = strchr(serialized_index_content.c_str(), '\n'); + if (!p) return nullptr; - - file = std::make_unique(path, file_content); - JsonReader json_reader{&reader}; - try { - Reflect(json_reader, *file); - } catch (std::invalid_argument& e) { - LOG_S(INFO) << "'" << path << "': failed to deserialize " - << json_reader.GetPath() << "." << e.what(); + if (atoi(serialized_index_content.c_str()) != *expected_version) return nullptr; - } - break; + reader.Parse(p + 1); } - - case SerializeFormat::MessagePack: { - try { - int major, minor; - if (serialized_index_content.size() < 8) - throw std::invalid_argument("Invalid"); - msgpack::unpacker upk; - upk.reserve_buffer(serialized_index_content.size()); - memcpy(upk.buffer(), serialized_index_content.data(), - serialized_index_content.size()); - upk.buffer_consumed(serialized_index_content.size()); - file = std::make_unique(path, file_content); - MessagePackReader reader(&upk); - Reflect(reader, major); - Reflect(reader, minor); - if (major != IndexFile::kMajorVersion || - minor != IndexFile::kMinorVersion) - throw std::invalid_argument("Invalid version"); - Reflect(reader, *file); - } catch (std::invalid_argument& e) { - LOG_S(INFO) << "Failed to deserialize msgpack '" << path - << "': " << e.what(); - return nullptr; - } - break; + if (reader.HasParseError()) + return nullptr; + + file = std::make_unique(sys::fs::UniqueID(0, 0), path, + file_content); + JsonReader json_reader{&reader}; + try { + ReflectFile(json_reader, *file); + } catch (std::invalid_argument &e) { + LOG_S(INFO) << "'" << path << "': failed to deserialize " + << json_reader.GetPath() << "." << e.what(); + return nullptr; } + break; + } } // Restore non-serialized state. file->path = path; - file->id_cache.primary_file = file->path; - for (const auto& type : file->types) { - file->id_cache.type_id_to_usr[type.id] = type.usr; - file->id_cache.usr_to_type_id[type.usr] = type.id; - } - for (const auto& func : file->funcs) { - file->id_cache.func_id_to_usr[func.id] = func.usr; - file->id_cache.usr_to_func_id[func.usr] = func.id; - } - for (const auto& var : file->vars) { - file->id_cache.var_id_to_usr[var.id] = var.usr; - file->id_cache.usr_to_var_id[var.usr] = var.id; + if (g_config->clang.pathMappings.size()) { + DoPathMapping(file->import_file); + std::vector args; + for (const char *arg : file->args) { + std::string s(arg); + DoPathMapping(s); + args.push_back(Intern(s)); + } + file->args = std::move(args); + for (auto &[_, path] : file->lid2path) + DoPathMapping(path); + for (auto &include : file->includes) { + std::string p(include.resolved_path); + DoPathMapping(p); + include.resolved_path = Intern(p); + } + decltype(file->dependencies) dependencies; + for (auto &it : file->dependencies) { + std::string path = it.first.val().str(); + DoPathMapping(path); + dependencies[InternH(path)] = it.second; + } + file->dependencies = std::move(dependencies); } - return file; } - -void SetTestOutputMode() { - gTestOutputMode = true; -} - -TEST_SUITE("Serializer utils") { - TEST_CASE("GetBaseName") { - REQUIRE(GetBaseName("foo.cc") == "foo.cc"); - REQUIRE(GetBaseName("foo/foo.cc") == "foo.cc"); - REQUIRE(GetBaseName("/foo.cc") == "foo.cc"); - REQUIRE(GetBaseName("///foo.cc") == "foo.cc"); - REQUIRE(GetBaseName("bar/") == "bar/"); - REQUIRE(GetBaseName("foobar/bar/") == - "foobar/bar/"); // TODO: Should be bar, but good enough. - } -} +} // namespace ccls diff --git a/src/serializer.h b/src/serializer.h deleted file mode 100644 index dbee42438..000000000 --- a/src/serializer.h +++ /dev/null @@ -1,368 +0,0 @@ -#pragma once - -#include "maybe.h" -#include "nt_string.h" -#include "port.h" - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -enum class SerializeFormat { Json, MessagePack }; - -class Reader { - public: - virtual ~Reader() {} - virtual SerializeFormat Format() const = 0; - - virtual bool IsBool() = 0; - virtual bool IsNull() = 0; - virtual bool IsArray() = 0; - virtual bool IsInt() = 0; - virtual bool IsInt64() = 0; - virtual bool IsUint64() = 0; - virtual bool IsDouble() = 0; - virtual bool IsString() = 0; - - virtual void GetNull() = 0; - virtual bool GetBool() = 0; - virtual int GetInt() = 0; - virtual uint32_t GetUint32() = 0; - virtual int64_t GetInt64() = 0; - virtual uint64_t GetUint64() = 0; - virtual double GetDouble() = 0; - virtual std::string GetString() = 0; - - virtual bool HasMember(const char* x) = 0; - virtual std::unique_ptr operator[](const char* x) = 0; - - virtual void IterArray(std::function fn) = 0; - virtual void DoMember(const char* name, std::function fn) = 0; -}; - -class Writer { - public: - virtual ~Writer() {} - virtual SerializeFormat Format() const = 0; - - virtual void Null() = 0; - virtual void Bool(bool x) = 0; - virtual void Int(int x) = 0; - virtual void Uint32(uint32_t x) = 0; - virtual void Int64(int64_t x) = 0; - virtual void Uint64(uint64_t x) = 0; - virtual void Double(double x) = 0; - virtual void String(const char* x) = 0; - virtual void String(const char* x, size_t len) = 0; - virtual void StartArray(size_t) = 0; - virtual void EndArray() = 0; - virtual void StartObject() = 0; - virtual void EndObject() = 0; - virtual void Key(const char* name) = 0; -}; - -struct IndexFile; - -#define REFLECT_MEMBER_START() ReflectMemberStart(visitor, value) -#define REFLECT_MEMBER_END() ReflectMemberEnd(visitor, value); -#define REFLECT_MEMBER_END1(value) ReflectMemberEnd(visitor, value); -#define REFLECT_MEMBER(name) ReflectMember(visitor, #name, value.name) -#define REFLECT_MEMBER2(name, value) ReflectMember(visitor, name, value) - -#define MAKE_REFLECT_TYPE_PROXY(type_name) \ - MAKE_REFLECT_TYPE_PROXY2(type_name, std::underlying_type::type) -#define MAKE_REFLECT_TYPE_PROXY2(type, as_type) \ - ATTRIBUTE_UNUSED inline void Reflect(Reader& visitor, type& value) { \ - as_type value0; \ - ::Reflect(visitor, value0); \ - value = static_cast(value0); \ - } \ - ATTRIBUTE_UNUSED inline void Reflect(Writer& visitor, type& value) { \ - auto value0 = static_cast(value); \ - ::Reflect(visitor, value0); \ - } - -#define _MAPPABLE_REFLECT_MEMBER(name) REFLECT_MEMBER(name); - -#define MAKE_REFLECT_EMPTY_STRUCT(type, ...) \ - template \ - void Reflect(TVisitor& visitor, type& value) { \ - REFLECT_MEMBER_START(); \ - REFLECT_MEMBER_END(); \ - } - -#define MAKE_REFLECT_STRUCT(type, ...) \ - template \ - void Reflect(TVisitor& visitor, type& value) { \ - REFLECT_MEMBER_START(); \ - MACRO_MAP(_MAPPABLE_REFLECT_MEMBER, __VA_ARGS__) \ - REFLECT_MEMBER_END(); \ - } - -// clang-format off -// Config has many fields, we need to support at least its number of fields. -#define NUM_VA_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,N,...) N -#define NUM_VA_ARGS(...) NUM_VA_ARGS_IMPL(__VA_ARGS__,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1) -// clang-format on - -#define _MAPPABLE_REFLECT_ARRAY(name) Reflect(visitor, value.name); - -// Reflects the struct so it is serialized as an array instead of an object. -// This currently only supports writers. -#define MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY(type, ...) \ - inline void Reflect(Writer& visitor, type& value) { \ - visitor.StartArray(NUM_VA_ARGS(__VA_ARGS__)); \ - MACRO_MAP(_MAPPABLE_REFLECT_ARRAY, __VA_ARGS__) \ - visitor.EndArray(); \ - } - -//// Elementary types - -void Reflect(Reader& visitor, uint8_t& value); -void Reflect(Writer& visitor, uint8_t& value); - -void Reflect(Reader& visitor, short& value); -void Reflect(Writer& visitor, short& value); - -void Reflect(Reader& visitor, unsigned short& value); -void Reflect(Writer& visitor, unsigned short& value); - -void Reflect(Reader& visitor, int& value); -void Reflect(Writer& visitor, int& value); - -void Reflect(Reader& visitor, unsigned& value); -void Reflect(Writer& visitor, unsigned& value); - -void Reflect(Reader& visitor, long& value); -void Reflect(Writer& visitor, long& value); - -void Reflect(Reader& visitor, unsigned long& value); -void Reflect(Writer& visitor, unsigned long& value); - -void Reflect(Reader& visitor, long long& value); -void Reflect(Writer& visitor, long long& value); - -void Reflect(Reader& visitor, unsigned long long& value); -void Reflect(Writer& visitor, unsigned long long& value); - -void Reflect(Reader& visitor, double& value); -void Reflect(Writer& visitor, double& value); - -void Reflect(Reader& visitor, bool& value); -void Reflect(Writer& visitor, bool& value); - -void Reflect(Reader& visitor, std::string& value); -void Reflect(Writer& visitor, std::string& value); - -void Reflect(Reader& visitor, std::string_view& view); -void Reflect(Writer& visitor, std::string_view& view); - -void Reflect(Reader& visitor, NtString& value); -void Reflect(Writer& visitor, NtString& value); - -// std::monostate is used to represent JSON null -void Reflect(Reader& visitor, std::monostate&); -void Reflect(Writer& visitor, std::monostate&); - -void Reflect(Reader& visitor, SerializeFormat& value); -void Reflect(Writer& visitor, SerializeFormat& value); - -//// Type constructors - -// ReflectMember optional is used to represent TypeScript optional properties -// (in `key: value` context). -// Reflect optional is used for a different purpose, whether an object is -// nullable (possibly in `value` context). For the nullable semantics, -// std::variant is recommended. -template -void Reflect(Reader& visitor, optional& value) { - if (visitor.IsNull()) { - visitor.GetNull(); - return; - } - T real_value; - Reflect(visitor, real_value); - value = std::move(real_value); -} -template -void Reflect(Writer& visitor, optional& value) { - if (value) - Reflect(visitor, *value); - else - visitor.Null(); -} - -// The same as std::optional -template -void Reflect(Reader& visitor, Maybe& value) { - if (visitor.IsNull()) { - visitor.GetNull(); - return; - } - T real_value; - Reflect(visitor, real_value); - value = std::move(real_value); -} -template -void Reflect(Writer& visitor, Maybe& value) { - if (value) - Reflect(visitor, *value); - else - visitor.Null(); -} - -template -void ReflectMember(Writer& visitor, const char* name, optional& value) { - // For TypeScript optional property key?: value in the spec, - // We omit both key and value if value is std::nullopt (null) for JsonWriter - // to reduce output. But keep it for other serialization formats. - if (value || visitor.Format() != SerializeFormat::Json) { - visitor.Key(name); - Reflect(visitor, value); - } -} - -// The same as std::optional -template -void ReflectMember(Writer& visitor, const char* name, Maybe& value) { - if (value.HasValue() || visitor.Format() != SerializeFormat::Json) { - visitor.Key(name); - Reflect(visitor, value); - } -} - -// Backport C++17 std::disjunction -namespace { -template -struct disjunction - : std::conditional>::type {}; -template -struct disjunction : B0 {}; -} // namespace - -// Helper struct to reflect std::variant -template -struct ReflectVariant { - // If T appears in Ts..., we should set the value of std::variant to - // what we get from Reader. - template - typename std::enable_if...>::value, - void>::type - ReflectTag(Reader& visitor, std::variant& value) { - T a; - Reflect(visitor, a); - value = std::move(a); - } - // This SFINAE overload is used to prevent compile error. value = a; is not - // allowed if T does not appear in Ts... - template - typename std::enable_if...>::value, - void>::type - ReflectTag(Reader&, std::variant&) {} - - void operator()(Reader& visitor, std::variant& value) { - // Based on tag dispatch, call different ReflectTag helper. - if (visitor.IsNull()) - ReflectTag(visitor, value); - // It is possible that IsInt64() && IsInt(). We don't call ReflectTag - // if int is not in Ts... - else if (disjunction...>::value && visitor.IsInt()) - ReflectTag(visitor, value); - else if (visitor.IsInt64()) - ReflectTag(visitor, value); - else if (visitor.IsString()) - ReflectTag(visitor, value); - else - assert(0); - } - - // Check which type the variant contains and call corresponding Reflect. - void operator()(Writer& visitor, std::variant& value) { - if (value.index() == N - 1) - Reflect(visitor, std::get(value)); - else - ReflectVariant()(visitor, value); - } -}; - -// Writer reflection on std::variant recurses. This is induction basis. -template -struct ReflectVariant<0, Ts...> { - void operator()(Writer& visitor, std::variant& value) {} -}; - -// std::variant -template -void Reflect(TVisitor& visitor, std::variant& value) { - ReflectVariant()(visitor, value); -} - -// std::vector -template -void Reflect(Reader& visitor, std::vector& values) { - visitor.IterArray([&](Reader& entry) { - T entry_value; - Reflect(entry, entry_value); - values.push_back(std::move(entry_value)); - }); -} -template -void Reflect(Writer& visitor, std::vector& values) { - visitor.StartArray(values.size()); - for (auto& value : values) - Reflect(visitor, value); - visitor.EndArray(); -} - -// ReflectMember - -inline void DefaultReflectMemberStart(Writer& visitor) { - visitor.StartObject(); -} -inline void DefaultReflectMemberStart(Reader& visitor) {} - -template -bool ReflectMemberStart(Reader& visitor, T& value) { - return false; -} -template -bool ReflectMemberStart(Writer& visitor, T& value) { - visitor.StartObject(); - return true; -} - -template -void ReflectMemberEnd(Reader& visitor, T& value) {} -template -void ReflectMemberEnd(Writer& visitor, T& value) { - visitor.EndObject(); -} - -template -void ReflectMember(Reader& visitor, const char* name, T& value) { - visitor.DoMember(name, [&](Reader& child) { Reflect(child, value); }); -} -template -void ReflectMember(Writer& visitor, const char* name, T& value) { - visitor.Key(name); - Reflect(visitor, value); -} - -// API - -std::string Serialize(SerializeFormat format, IndexFile& file); -std::unique_ptr Deserialize( - SerializeFormat format, - const std::string& path, - const std::string& serialized_index_content, - const std::string& file_content, - optional expected_version); - -void SetTestOutputMode(); diff --git a/src/serializer.hh b/src/serializer.hh new file mode 100644 index 000000000..de5c24ac2 --- /dev/null +++ b/src/serializer.hh @@ -0,0 +1,412 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "utils.hh" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace llvm { +class CachedHashStringRef; +class StringRef; +} + +namespace ccls { +enum class SerializeFormat { Binary, Json }; + +struct JsonNull {}; + +struct JsonReader { + rapidjson::Value *m; + std::vector path_; + + JsonReader(rapidjson::Value *m) : m(m) {} + void StartObject() {} + void EndObject() {} + void IterArray(std::function fn); + void Member(const char *name, std::function fn); + bool IsNull(); + std::string GetString(); + std::string GetPath() const; +}; + +struct JsonWriter { + using W = + rapidjson::Writer, + rapidjson::UTF8, rapidjson::CrtAllocator, 0>; + + W *m; + + JsonWriter(W *m) : m(m) {} + void StartArray(); + void EndArray(); + void StartObject(); + void EndObject(); + void Key(const char *name); + void Null(); + void Int(int v); + void String(const char *s); + void String(const char *s, size_t len); +}; + +struct BinaryReader { + const char *p_; + + BinaryReader(std::string_view buf) : p_(buf.data()) {} + template T Get() { + T ret; + memcpy(&ret, p_, sizeof(T)); + p_ += sizeof(T); + return ret; + } + uint64_t VarUInt() { + auto x = *reinterpret_cast(p_++); + if (x < 253) + return x; + if (x == 253) + return Get(); + if (x == 254) + return Get(); + return Get(); + } + int64_t VarInt() { + uint64_t x = VarUInt(); + return int64_t(x >> 1 ^ -(x & 1)); + } + const char *GetString() { + const char *ret = p_; + while (*p_) + p_++; + p_++; + return ret; + } +}; + +struct BinaryWriter { + std::string buf_; + + template void Pack(T x) { + auto i = buf_.size(); + buf_.resize(i + sizeof(x)); + memcpy(buf_.data() + i, &x, sizeof(x)); + } + + void VarUInt(uint64_t n) { + if (n < 253) + Pack(n); + else if (n < 65536) { + Pack(253); + Pack(n); + } else if (n < 4294967296) { + Pack(254); + Pack(n); + } else { + Pack(255); + Pack(n); + } + } + void VarInt(int64_t n) { VarUInt(uint64_t(n) << 1 ^ n >> 63); } + std::string Take() { return std::move(buf_); } + + void String(const char *x) { String(x, strlen(x)); } + void String(const char *x, size_t len) { + auto i = buf_.size(); + buf_.resize(i + len + 1); + memcpy(buf_.data() + i, x, len); + } +}; + +struct IndexFile; + +#define REFLECT_MEMBER(name) ReflectMember(vis, #name, v.name) +#define REFLECT_MEMBER2(name, v) ReflectMember(vis, name, v) + +#define REFLECT_UNDERLYING(T) \ + LLVM_ATTRIBUTE_UNUSED inline void Reflect(JsonReader &vis, T &v) { \ + std::underlying_type_t v0; \ + ::ccls::Reflect(vis, v0); \ + v = static_cast(v0); \ + } \ + LLVM_ATTRIBUTE_UNUSED inline void Reflect(JsonWriter &vis, T &v) { \ + auto v0 = static_cast>(v); \ + ::ccls::Reflect(vis, v0); \ + } + +#define REFLECT_UNDERLYING_B(T) \ + REFLECT_UNDERLYING(T) \ + LLVM_ATTRIBUTE_UNUSED inline void Reflect(BinaryReader &vis, T &v) { \ + std::underlying_type_t v0; \ + ::ccls::Reflect(vis, v0); \ + v = static_cast(v0); \ + } \ + LLVM_ATTRIBUTE_UNUSED inline void Reflect(BinaryWriter &vis, T &v) { \ + auto v0 = static_cast>(v); \ + ::ccls::Reflect(vis, v0); \ + } + +#define _MAPPABLE_REFLECT_MEMBER(name) REFLECT_MEMBER(name); + +#define REFLECT_STRUCT(type, ...) \ + template void Reflect(Vis &vis, type &v) { \ + ReflectMemberStart(vis); \ + MACRO_MAP(_MAPPABLE_REFLECT_MEMBER, __VA_ARGS__) \ + ReflectMemberEnd(vis); \ + } + +#define _MAPPABLE_REFLECT_ARRAY(name) Reflect(vis, v.name); + +void Reflect(JsonReader &vis, bool &v); +void Reflect(JsonReader &vis, unsigned char &v); +void Reflect(JsonReader &vis, short &v); +void Reflect(JsonReader &vis, unsigned short &v); +void Reflect(JsonReader &vis, int &v); +void Reflect(JsonReader &vis, unsigned &v); +void Reflect(JsonReader &vis, long &v); +void Reflect(JsonReader &vis, unsigned long &v); +void Reflect(JsonReader &vis, long long &v); +void Reflect(JsonReader &vis, unsigned long long &v); +void Reflect(JsonReader &vis, double &v); +void Reflect(JsonReader &vis, const char *&v); +void Reflect(JsonReader &vis, std::string &v); + +void Reflect(JsonWriter &vis, bool &v); +void Reflect(JsonWriter &vis, unsigned char &v); +void Reflect(JsonWriter &vis, short &v); +void Reflect(JsonWriter &vis, unsigned short &v); +void Reflect(JsonWriter &vis, int &v); +void Reflect(JsonWriter &vis, unsigned &v); +void Reflect(JsonWriter &vis, long &v); +void Reflect(JsonWriter &vis, unsigned long &v); +void Reflect(JsonWriter &vis, long long &v); +void Reflect(JsonWriter &vis, unsigned long long &v); +void Reflect(JsonWriter &vis, double &v); +void Reflect(JsonWriter &vis, const char *&v); +void Reflect(JsonWriter &vis, std::string &v); + +void Reflect(BinaryReader &vis, bool &v); +void Reflect(BinaryReader &vis, unsigned char &v); +void Reflect(BinaryReader &vis, short &v); +void Reflect(BinaryReader &vis, unsigned short &v); +void Reflect(BinaryReader &vis, int &v); +void Reflect(BinaryReader &vis, unsigned &v); +void Reflect(BinaryReader &vis, long &v); +void Reflect(BinaryReader &vis, unsigned long &v); +void Reflect(BinaryReader &vis, long long &v); +void Reflect(BinaryReader &vis, unsigned long long &v); +void Reflect(BinaryReader &vis, double &v); +void Reflect(BinaryReader &vis, const char *&v); +void Reflect(BinaryReader &vis, std::string &v); + +void Reflect(BinaryWriter &vis, bool &v); +void Reflect(BinaryWriter &vis, unsigned char &v); +void Reflect(BinaryWriter &vis, short &v); +void Reflect(BinaryWriter &vis, unsigned short &v); +void Reflect(BinaryWriter &vis, int &v); +void Reflect(BinaryWriter &vis, unsigned &v); +void Reflect(BinaryWriter &vis, long &v); +void Reflect(BinaryWriter &vis, unsigned long &v); +void Reflect(BinaryWriter &vis, long long &v); +void Reflect(BinaryWriter &vis, unsigned long long &v); +void Reflect(BinaryWriter &vis, double &v); +void Reflect(BinaryWriter &vis, const char *&v); +void Reflect(BinaryWriter &vis, std::string &v); + +void Reflect(JsonReader &vis, JsonNull &v); +void Reflect(JsonWriter &vis, JsonNull &v); + +void Reflect(JsonReader &vis, SerializeFormat &v); +void Reflect(JsonWriter &vis, SerializeFormat &v); + +void Reflect(JsonWriter &vis, std::string_view &v); + +//// Type constructors + +// ReflectMember std::optional is used to represent TypeScript optional +// properties (in `key: value` context). Reflect std::optional is used for a +// different purpose, whether an object is nullable (possibly in `value` +// context). +template void Reflect(JsonReader &vis, std::optional &v) { + if (!vis.IsNull()) { + v.emplace(); + Reflect(vis, *v); + } +} +template void Reflect(JsonWriter &vis, std::optional &v) { + if (v) + Reflect(vis, *v); + else + vis.Null(); +} +template void Reflect(BinaryReader &vis, std::optional &v) { + if (*vis.p_++) { + v.emplace(); + Reflect(vis, *v); + } +} +template void Reflect(BinaryWriter &vis, std::optional &v) { + if (v) { + vis.Pack(1); + Reflect(vis, *v); + } else { + vis.Pack(0); + } +} + +// The same as std::optional +template void Reflect(JsonReader &vis, Maybe &v) { + if (!vis.IsNull()) + Reflect(vis, *v); +} +template void Reflect(JsonWriter &vis, Maybe &v) { + if (v) + Reflect(vis, *v); + else + vis.Null(); +} +template void Reflect(BinaryReader &vis, Maybe &v) { + if (*vis.p_++) + Reflect(vis, *v); +} +template void Reflect(BinaryWriter &vis, Maybe &v) { + if (v) { + vis.Pack(1); + Reflect(vis, *v); + } else { + vis.Pack(0); + } +} + +template +void ReflectMember(JsonWriter &vis, const char *name, std::optional &v) { + // For TypeScript std::optional property key?: value in the spec, + // We omit both key and value if value is std::nullopt (null) for JsonWriter + // to reduce output. But keep it for other serialization formats. + if (v) { + vis.Key(name); + Reflect(vis, *v); + } +} +template +void ReflectMember(BinaryWriter &vis, const char *, std::optional &v) { + Reflect(vis, v); +} + +// The same as std::optional +template +void ReflectMember(JsonWriter &vis, const char *name, Maybe &v) { + if (v.Valid()) { + vis.Key(name); + Reflect(vis, v); + } +} +template +void ReflectMember(BinaryWriter &vis, const char *, Maybe &v) { + Reflect(vis, v); +} + +template +void Reflect(JsonReader &vis, std::pair &v) { + vis.Member("L", [&]() { Reflect(vis, v.first); }); + vis.Member("R", [&]() { Reflect(vis, v.second); }); +} +template +void Reflect(JsonWriter &vis, std::pair &v) { + vis.StartObject(); + ReflectMember(vis, "L", v.first); + ReflectMember(vis, "R", v.second); + vis.EndObject(); +} +template +void Reflect(BinaryReader &vis, std::pair &v) { + Reflect(vis, v.first); + Reflect(vis, v.second); +} +template +void Reflect(BinaryWriter &vis, std::pair &v) { + Reflect(vis, v.first); + Reflect(vis, v.second); +} + +// std::vector +template void Reflect(JsonReader &vis, std::vector &v) { + vis.IterArray([&]() { + v.emplace_back(); + Reflect(vis, v.back()); + }); +} +template void Reflect(JsonWriter &vis, std::vector &v) { + vis.StartArray(); + for (auto &it : v) + Reflect(vis, it); + vis.EndArray(); +} +template void Reflect(BinaryReader &vis, std::vector &v) { + for (auto n = vis.VarUInt(); n; n--) { + v.emplace_back(); + Reflect(vis, v.back()); + } +} +template void Reflect(BinaryWriter &vis, std::vector &v) { + vis.VarUInt(v.size()); + for (auto &it : v) + Reflect(vis, it); +} + +// ReflectMember + +template void ReflectMemberStart(T &) {} +inline void ReflectMemberStart(JsonWriter &vis) { vis.StartObject(); } + +template void ReflectMemberEnd(T &) {} +inline void ReflectMemberEnd(JsonWriter &vis) { vis.EndObject(); } + +template void ReflectMember(JsonReader &vis, const char *name, T &v) { + vis.Member(name, [&]() { Reflect(vis, v); }); +} +template void ReflectMember(JsonWriter &vis, const char *name, T &v) { + vis.Key(name); + Reflect(vis, v); +} +template void ReflectMember(BinaryReader &vis, const char *, T &v) { + Reflect(vis, v); +} +template void ReflectMember(BinaryWriter &vis, const char *, T &v) { + Reflect(vis, v); +} + +// API + +const char *Intern(llvm::StringRef str); +llvm::CachedHashStringRef InternH(llvm::StringRef str); +std::string Serialize(SerializeFormat format, IndexFile &file); +std::unique_ptr +Deserialize(SerializeFormat format, const std::string &path, + const std::string &serialized_index_content, + const std::string &file_content, + std::optional expected_version); +} // namespace ccls diff --git a/src/serializers/json.h b/src/serializers/json.h deleted file mode 100644 index 47e3b0355..000000000 --- a/src/serializers/json.h +++ /dev/null @@ -1,98 +0,0 @@ -#pragma once - -#include "serializer.h" - -#include -#include - -class JsonReader : public Reader { - rapidjson::GenericValue>* m_; - std::vector path_; - - public: - JsonReader(rapidjson::GenericValue>* m) : m_(m) {} - SerializeFormat Format() const override { return SerializeFormat::Json; } - - bool IsBool() override { return m_->IsBool(); } - bool IsNull() override { return m_->IsNull(); } - bool IsArray() override { return m_->IsArray(); } - bool IsInt() override { return m_->IsInt(); } - bool IsInt64() override { return m_->IsInt64(); } - bool IsUint64() override { return m_->IsUint64(); } - bool IsDouble() override { return m_->IsDouble(); } - bool IsString() override { return m_->IsString(); } - - void GetNull() override {} - bool GetBool() override { return m_->GetBool(); } - int GetInt() override { return m_->GetInt(); } - uint32_t GetUint32() override { return uint32_t(m_->GetUint64()); } - int64_t GetInt64() override { return m_->GetInt64(); } - uint64_t GetUint64() override { return m_->GetUint64(); } - double GetDouble() override { return m_->GetDouble(); } - std::string GetString() override { return m_->GetString(); } - - bool HasMember(const char* x) override { return m_->HasMember(x); } - std::unique_ptr operator[](const char* x) override { - auto& sub = (*m_)[x]; - return std::unique_ptr(new JsonReader(&sub)); - } - - void IterArray(std::function fn) override { - if (!m_->IsArray()) - throw std::invalid_argument("array"); - // Use "0" to indicate any element for now. - path_.push_back("0"); - for (auto& entry : m_->GetArray()) { - auto saved = m_; - m_ = &entry; - fn(*this); - m_ = saved; - } - path_.pop_back(); - } - - void DoMember(const char* name, std::function fn) override { - path_.push_back(name); - auto it = m_->FindMember(name); - if (it != m_->MemberEnd()) { - auto saved = m_; - m_ = &it->value; - fn(*this); - m_ = saved; - } - path_.pop_back(); - } - - std::string GetPath() const { - std::string ret; - for (auto& t : path_) { - ret += '/'; - ret += t; - } - ret.pop_back(); - return ret; - } -}; - -class JsonWriter : public Writer { - rapidjson::Writer* m_; - - public: - JsonWriter(rapidjson::Writer* m) : m_(m) {} - SerializeFormat Format() const override { return SerializeFormat::Json; } - - void Null() override { m_->Null(); } - void Bool(bool x) override { m_->Bool(x); } - void Int(int x) override { m_->Int(x); } - void Uint32(uint32_t x) override { m_->Uint64(x); } - void Int64(int64_t x) override { m_->Int64(x); } - void Uint64(uint64_t x) override { m_->Uint64(x); } - void Double(double x) override { m_->Double(x); } - void String(const char* x) override { m_->String(x); } - void String(const char* x, size_t len) override { m_->String(x, len); } - void StartArray(size_t) override { m_->StartArray(); } - void EndArray() override { m_->EndArray(); } - void StartObject() override { m_->StartObject(); } - void EndObject() override { m_->EndObject(); } - void Key(const char* name) override { m_->Key(name); } -}; diff --git a/src/serializers/msgpack.h b/src/serializers/msgpack.h deleted file mode 100644 index f9b1666b5..000000000 --- a/src/serializers/msgpack.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -#include "serializer.h" - -#include - -class MessagePackReader : public Reader { - msgpack::unpacker* pk_; - msgpack::object_handle oh_; - - template - T Get() { - T ret = oh_.get().as(); - pk_->next(oh_); - return ret; - } - - public: - MessagePackReader(msgpack::unpacker* pk) : pk_(pk) { pk->next(oh_); } - SerializeFormat Format() const override { - return SerializeFormat::MessagePack; - } - - bool IsBool() override { return oh_.get().type == msgpack::type::BOOLEAN; } - bool IsNull() override { return oh_.get().is_nil(); } - bool IsArray() override { return oh_.get().type == msgpack::type::ARRAY; } - bool IsInt() override { - return oh_.get().type == msgpack::type::POSITIVE_INTEGER || - oh_.get().type == msgpack::type::NEGATIVE_INTEGER; - } - bool IsInt64() override { return IsInt(); } - bool IsUint64() override { return IsInt(); } - bool IsDouble() override { return oh_.get().type == msgpack::type::FLOAT64; }; - bool IsString() override { return oh_.get().type == msgpack::type::STR; } - - void GetNull() override { pk_->next(oh_); } - bool GetBool() override { return Get(); } - int GetInt() override { return Get(); } - uint32_t GetUint32() override { return Get(); } - int64_t GetInt64() override { return Get(); } - uint64_t GetUint64() override { return Get(); } - double GetDouble() override { return Get(); } - std::string GetString() override { return Get(); } - - bool HasMember(const char* x) override { return true; } - std::unique_ptr operator[](const char* x) override { return {}; } - - void IterArray(std::function fn) override { - size_t n = Get(); - for (size_t i = 0; i < n; i++) - fn(*this); - } - - void DoMember(const char*, std::function fn) override { - fn(*this); - } -}; - -class MessagePackWriter : public Writer { - msgpack::packer* m_; - - public: - MessagePackWriter(msgpack::packer* m) : m_(m) {} - SerializeFormat Format() const override { - return SerializeFormat::MessagePack; - } - - void Null() override { m_->pack_nil(); } - void Bool(bool x) override { m_->pack(x); } - void Int(int x) override { m_->pack(x); } - void Uint32(uint32_t x) override { m_->pack(x); } - void Int64(int64_t x) override { m_->pack(x); } - void Uint64(uint64_t x) override { m_->pack(x); } - void Double(double x) override { m_->pack(x); } - void String(const char* x) override { m_->pack(x); } - // TODO Remove std::string - void String(const char* x, size_t len) override { - m_->pack(std::string(x, len)); - } - void StartArray(size_t n) override { m_->pack(n); } - void EndArray() override {} - void StartObject() override {} - void EndObject() override {} - void Key(const char* name) override {} -}; diff --git a/src/standard_includes.cc b/src/standard_includes.cc deleted file mode 100644 index 42cc35b3f..000000000 --- a/src/standard_includes.cc +++ /dev/null @@ -1,182 +0,0 @@ -#include "standard_includes.h" - -// See http://stackoverflow.com/a/2029106. -const char* kStandardLibraryIncludes[177] = { - "aio.h", - "algorithm", - "any", - "arpa/inet.h", - "array", - "assert.h", - "atomic", - "bitset", - "cassert", - "ccomplex", - "cctype", - "cerrno", - "cfenv", - "cfloat", - "chrono", - "cinttypes", - "ciso646", - "climits", - "clocale", - "cmath", - "codecvt", - "complex", - "complex.h", - "condition_variable", - "cpio.h", - "csetjmp", - "csignal", - "cstdalign", - "cstdarg", - "cstdbool", - "cstddef", - "cstdint", - "cstdio", - "cstdlib", - "cstring", - "ctgmath", - "ctime", - "ctype.h", - "cuchar", - "curses.h", - "cwchar", - "cwctype", - "deque", - "dirent.h", - "dlfcn.h", - "errno.h", - "exception", - "execution", - "fcntl.h", - "fenv.h", - "filesystem", - "float.h", - "fmtmsg.h", - "fnmatch.h", - "forward_list", - "fstream", - "ftw.h", - "functional", - "future", - "glob.h", - "grp.h", - "iconv.h", - "initializer_list", - "inttypes.h", - "iomanip", - "ios", - "iosfwd", - "iostream", - "iso646.h", - "istream", - "iterator", - "langinfo.h", - "libgen.h", - "limits", - "limits.h", - "list", - "locale", - "locale.h", - "map", - "math.h", - "memory", - "memory_resource", - "monetary.h", - "mqueue.h", - "mutex", - "ndbm.h", - "net/if.h", - "netdb.h", - "netinet/in.h", - "netinet/tcp.h", - "new", - "nl_types.h", - "numeric", - "optional", - "ostream", - "poll.h", - "pthread.h", - "pwd.h", - "queue", - "random", - "ratio", - "regex", - "regex.h", - "sched.h", - "scoped_allocator", - "search.h", - "semaphore.h", - "set", - "setjmp.h", - "shared_mutex", - "signal.h", - "spawn.h", - "sstream", - "stack", - "stdalign.h", - "stdarg.h", - "stdatomic.h", - "stdbool.h", - "stddef.h", - "stdexcept", - "stdint.h", - "stdio.h", - "stdlib.h", - "stdnoreturn.h", - "streambuf", - "string", - "string.h", - "string_view", - "strings.h", - "stropts.h", - "strstream", - "sys/ipc.h", - "sys/mman.h", - "sys/msg.h", - "sys/resource.h", - "sys/select.h", - "sys/sem.h", - "sys/shm.h", - "sys/socket.h", - "sys/stat.h", - "sys/statvfs.h", - "sys/time.h", - "sys/times.h", - "sys/types.h", - "sys/uio.h", - "sys/un.h", - "sys/utsname.h", - "sys/wait.h", - "syslog.h", - "system_error", - "tar.h", - "term.h", - "termios.h", - "tgmath.h", - "thread", - "threads.h", - "time.h", - "trace.h", - "tuple", - "type_traits", - "typeindex", - "typeinfo", - "uchar.h", - "ulimit.h", - "uncntrl.h", - "unistd.h", - "unordered_map", - "unordered_set", - "utility", - "utime.h", - "utmpx.h", - "valarray", - "variant", - "vector", - "wchar.h", - "wctype.h", - "wordexp.h", -}; \ No newline at end of file diff --git a/src/standard_includes.h b/src/standard_includes.h deleted file mode 100644 index 6c50227d1..000000000 --- a/src/standard_includes.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -// A set of standard libary header names, ie, "vector". -extern const char* kStandardLibraryIncludes[177]; \ No newline at end of file diff --git a/src/symbol.h b/src/symbol.h deleted file mode 100644 index bcbc6c64d..000000000 --- a/src/symbol.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include "lsp.h" -#include "serializer.h" - -// The order matters. In FindSymbolsAtLocation, we want Var/Func ordered in -// front of others. -enum class SymbolKind : uint8_t { Invalid, File, Type, Func, Var }; -MAKE_REFLECT_TYPE_PROXY(SymbolKind); -MAKE_ENUM_HASHABLE(SymbolKind); - -// clang/Basic/Specifiers.h clang::StorageClass -enum class StorageClass : uint8_t { - // In |CX_StorageClass| but not in |clang::StorageClass| - // e.g. non-type template parameters - Invalid, - - // These are legal on both functions and variables. - // e.g. global functions/variables, local variables - None, - Extern, - Static, - // e.g. |__private_extern__ int a;| - PrivateExtern, - - // These are only legal on variables. - // e.g. explicit |auto int a;| - Auto, - Register -}; -MAKE_REFLECT_TYPE_PROXY(StorageClass); - -enum class Role : uint16_t { - None = 0, - Declaration = 1 << 0, - Definition = 1 << 1, - Reference = 1 << 2, - Read = 1 << 3, - Write = 1 << 4, - Call = 1 << 5, - Dynamic = 1 << 6, - Address = 1 << 7, - Implicit = 1 << 8, - All = (1 << 9) - 1, -}; -MAKE_REFLECT_TYPE_PROXY(Role); -MAKE_ENUM_HASHABLE(Role); - -inline uint16_t operator&(Role lhs, Role rhs) { - return uint16_t(lhs) & uint16_t(rhs); -} - -inline Role operator|(Role lhs, Role rhs) { - return Role(uint16_t(lhs) | uint16_t(rhs)); -} - -// A document highlight kind. -enum class lsDocumentHighlightKind { - // A textual occurrence. - Text = 1, - // Read-access of a symbol, like reading a variable. - Read = 2, - // Write-access of a symbol, like writing to a variable. - Write = 3 -}; -MAKE_REFLECT_TYPE_PROXY(lsDocumentHighlightKind); - -// A document highlight is a range inside a text document which deserves -// special attention. Usually a document highlight is visualized by changing -// the background color of its range. -struct lsDocumentHighlight { - // The range this highlight applies to. - lsRange range; - - // The highlight kind, default is DocumentHighlightKind.Text. - lsDocumentHighlightKind kind = lsDocumentHighlightKind::Text; - - // cquery extension - Role role = Role::None; -}; -MAKE_REFLECT_STRUCT(lsDocumentHighlight, range, kind, role); - -struct lsSymbolInformation { - std::string_view name; - lsSymbolKind kind; - lsLocation location; - std::string_view containerName; -}; -MAKE_REFLECT_STRUCT(lsSymbolInformation, name, kind, location, containerName); diff --git a/src/task.cc b/src/task.cc deleted file mode 100644 index b26f7b600..000000000 --- a/src/task.cc +++ /dev/null @@ -1,152 +0,0 @@ -#if false -#include "task.h" - -#include "utils.h" - -#include - -TaskManager::TaskManager() { - pending_tasks_[TaskThread::Indexer] = std::make_unique(); - pending_tasks_[TaskThread::QueryDb] = std::make_unique(); -} - -void TaskManager::Post(TaskThread thread, const TTask& task) { - TaskQueue* queue = pending_tasks_[thread].get(); - std::lock_guard lock_guard(queue->tasks_mutex); - queue->tasks.push_back(task); -} - -void TaskManager::SetIdle(TaskThread thread, const TIdleTask& task) { - TaskQueue* queue = pending_tasks_[thread].get(); - std::lock_guard lock_guard(queue->tasks_mutex); - assert(!queue->idle_task && "There is already an idle task"); - queue->idle_task = task; -} - -bool TaskManager::RunTasks(TaskThread thread, optional> max_time) { - auto start = std::chrono::high_resolution_clock::now(); - TaskQueue* queue = pending_tasks_[thread].get(); - - bool ran_task = false; - - while (true) { - optional task; - - // Get a task. - { - std::lock_guard lock_guard(queue->tasks_mutex); - if (queue->tasks.empty()) - break; - task = std::move(queue->tasks[queue->tasks.size() - 1]); - queue->tasks.pop_back(); - } - - // Execute task. - assert(task); - (*task)(); - ran_task = true; - - // Stop if we've run past our max time. Don't run idle_task. - auto elapsed = std::chrono::high_resolution_clock::now() - start; - if (max_time && elapsed > *max_time) - return ran_task; - } - - if (queue->idle_task) { - // Even if the idle task returns false we still ran something before. - ran_task = (*queue->idle_task)() || ran_task; - } - - return ran_task; -} - -TEST_SUITE("Task"); - -TEST_CASE("tasks are run as soon as they are posted") { - TaskManager tm; - - // Post three tasks. - int next = 1; - int a = 0, b = 0, c = 0; - tm.Post(TaskThread::QueryDb, [&] { - a = next++; - }); - tm.Post(TaskThread::QueryDb, [&] { - b = next++; - }); - tm.Post(TaskThread::QueryDb, [&] { - c = next++; - }); - - // Execute all tasks. - tm.RunTasks(TaskThread::QueryDb, nullopt); - - // Tasks are executed in reverse order. - REQUIRE(a == 3); - REQUIRE(b == 2); - REQUIRE(c == 1); -} - -TEST_CASE("post from inside task manager") { - TaskManager tm; - - // Post three tasks. - int next = 1; - int a = 0, b = 0, c = 0; - tm.Post(TaskThread::QueryDb, [&] () { - a = next++; - - tm.Post(TaskThread::QueryDb, [&] { - b = next++; - - tm.Post(TaskThread::QueryDb, [&] { - c = next++; - }); - }); - }); - - // Execute all tasks. - tm.RunTasks(TaskThread::QueryDb, nullopt); - - // Tasks are executed in normal order because the next task is not posted - // until the previous one is executed. - REQUIRE(a == 1); - REQUIRE(b == 2); - REQUIRE(c == 3); -} - -TEST_CASE("idle task is run after nested tasks") { - TaskManager tm; - - int count = 0; - tm.SetIdle(TaskThread::QueryDb, [&]() { - ++count; - return true; - }); - - // No tasks posted - idle runs once. - REQUIRE(tm.RunTasks(TaskThread::QueryDb, nullopt)); - REQUIRE(count == 1); - count = 0; - - // Idle runs after other posted tasks. - bool did_run = false; - tm.Post(TaskThread::QueryDb, [&]() { - did_run = true; - }); - REQUIRE(tm.RunTasks(TaskThread::QueryDb, nullopt)); - REQUIRE(did_run); - REQUIRE(count == 1); -} - -TEST_CASE("RunTasks returns false when idle task returns false and no other tasks were run") { - TaskManager tm; - - REQUIRE(tm.RunTasks(TaskThread::QueryDb, nullopt) == false); - - tm.SetIdle(TaskThread::QueryDb, []() { return false; }); - REQUIRE(tm.RunTasks(TaskThread::QueryDb, nullopt) == false); -} - -TEST_SUITE_END(); -#endif \ No newline at end of file diff --git a/src/task.h b/src/task.h deleted file mode 100644 index 04feeedb3..000000000 --- a/src/task.h +++ /dev/null @@ -1,41 +0,0 @@ -#if false -#pragma once - -#include - -#include -#include -#include -#include -#include - -enum class TaskThread { - Indexer, - QueryDb, -}; - -struct TaskManager { - using TTask = std::function; - using TIdleTask = std::function; - - TaskManager(); - - // Run |task| at some point in the future. This will run the task as soon as possible. - void Post(TaskThread thread, const TTask& task); - - // Run |task| whenever there is nothing else to run. - void SetIdle(TaskThread thread, const TIdleTask& idle_task); - - // Run pending tasks for |thread|. Stop running tasks after |max_time| has - // elapsed. Returns true if tasks were run. - bool RunTasks(TaskThread thread, optional> max_time); - - struct TaskQueue { - optional idle_task; - std::vector tasks; - std::mutex tasks_mutex; - }; - - std::unordered_map> pending_tasks_; -}; -#endif \ No newline at end of file diff --git a/src/test.cc b/src/test.cc index 429da3297..7faf948c8 100644 --- a/src/test.cc +++ b/src/test.cc @@ -1,21 +1,39 @@ -#include "test.h" +/* Copyright 2017-2018 ccls Authors -#include "indexer.h" -#include "platform.h" -#include "serializer.h" -#include "utils.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "test.hh" + +#include "sema_manager.hh" +#include "filesystem.hh" +#include "indexer.hh" +#include "pipeline.hh" +#include "platform.hh" +#include "serializer.hh" +#include "utils.hh" + +#include +#include -#include -#include #include #include #include #include +#include #include #include -#include -#include // The 'diff' utility is available and we can use dprintf(3). #if _POSIX_C_SOURCE >= 200809L @@ -23,12 +41,12 @@ #include #endif -void Write(const std::vector& strs) { - for (const std::string& str : strs) - std::cout << str << std::endl; -} +using namespace llvm; + +extern bool gTestOutputMode; -std::string ToString(const rapidjson::Document& document) { +namespace ccls { +std::string ToString(const rapidjson::Document &document) { rapidjson::StringBuffer buffer; rapidjson::PrettyWriter writer(buffer); writer.SetFormatOptions( @@ -37,90 +55,72 @@ std::string ToString(const rapidjson::Document& document) { buffer.Clear(); document.Accept(writer); - std::string output = buffer.GetString(); - return UpdateToRnNewlines(output); + return buffer.GetString(); } -void ParseTestExpectation( - const std::string& filename, - const std::vector& lines_with_endings, - TextReplacer* replacer, - std::vector* flags, - std::unordered_map* output_sections) { -#if false -#include "bar.h" - - void foo(); - - /* - // DOCS for TEXT_REPLACE: - // Each line under TEXT_REPLACE is a replacement, ie, the two entries will be - // considered equivalent. This is useful for USRs which vary across files. - - // DOCS for EXTRA_FLAGS: - // Additional flags to pass to clang. +struct TextReplacer { + struct Replacement { + std::string from; + std::string to; + }; - // DOCS for OUTPUT: - // If no name is given assume to be this file name. If there is not an output - // section for a file it is not checked. + std::vector replacements; - TEXT_REPLACE: - foo <===> bar - one <===> two + std::string Apply(const std::string &content) { + std::string result = content; - EXTRA_FLAGS: - -std=c++14 + for (const Replacement &replacement : replacements) { + while (true) { + size_t idx = result.find(replacement.from); + if (idx == std::string::npos) + break; - OUTPUT: - {} + result.replace(result.begin() + idx, + result.begin() + idx + replacement.from.size(), + replacement.to); + } + } - OUTPUT: bar.cc - {} + return result; + } +}; - OUTPUT: bar.h - {} - */ -#endif +void TrimInPlace(std::string &s) { + auto f = [](char c) { return !isspace(c); }; + s.erase(s.begin(), std::find_if(s.begin(), s.end(), f)); + s.erase(std::find_if(s.rbegin(), s.rend(), f).base(), s.end()); +} - // Scan for TEXT_REPLACE: - { - bool in_output = false; - for (std::string line : lines_with_endings) { - TrimInPlace(line); +std::vector SplitString(const std::string &str, + const std::string &delimiter) { + // http://stackoverflow.com/a/13172514 + std::vector strings; - if (StartsWith(line, "TEXT_REPLACE:")) { - assert(!in_output && "multiple TEXT_REPLACE sections"); - in_output = true; - continue; - } + std::string::size_type pos = 0; + std::string::size_type prev = 0; + while ((pos = str.find(delimiter, prev)) != std::string::npos) { + strings.push_back(str.substr(prev, pos - prev)); + prev = pos + 1; + } - if (in_output && line.empty()) - break; + // To get the last substring (or only, if delimiter is not found) + strings.push_back(str.substr(prev)); - if (in_output) { - static const std::string kKey = " <===> "; - size_t index = line.find(kKey); - LOG_IF_S(FATAL, index == std::string::npos) - << " No '" << kKey << "' in replacement string '" << line << "'" - << ", index=" << index; - - TextReplacer::Replacement replacement; - replacement.from = line.substr(0, index); - replacement.to = line.substr(index + kKey.size()); - TrimInPlace(replacement.from); - TrimInPlace(replacement.to); - replacer->replacements.push_back(replacement); - } - } - } + return strings; +} +void ParseTestExpectation( + const std::string &filename, + const std::vector &lines_with_endings, TextReplacer *replacer, + std::vector *flags, + std::unordered_map *output_sections) { // Scan for EXTRA_FLAGS: { bool in_output = false; - for (std::string line : lines_with_endings) { - TrimInPlace(line); + for (StringRef line : lines_with_endings) { + line = line.trim(); - if (StartsWith(line, "EXTRA_FLAGS:")) { + if (line.startswith("EXTRA_FLAGS:")) { assert(!in_output && "multiple EXTRA_FLAGS sections"); in_output = true; continue; @@ -130,7 +130,7 @@ void ParseTestExpectation( break; if (in_output) - flags->push_back(line); + flags->push_back(line.str()); } } @@ -140,11 +140,11 @@ void ParseTestExpectation( std::string active_output_contents; bool in_output = false; - for (std::string line_with_ending : lines_with_endings) { - if (StartsWith(line_with_ending, "*/")) + for (StringRef line_with_ending : lines_with_endings) { + if (line_with_ending.startswith("*/")) break; - if (StartsWith(line_with_ending, "OUTPUT:")) { + if (line_with_ending.startswith("OUTPUT:")) { // Terminate the previous output section if we found a new one. if (in_output) { (*output_sections)[active_output_filename] = active_output_contents; @@ -152,18 +152,20 @@ void ParseTestExpectation( // Try to tokenize OUTPUT: based one whitespace. If there is more than // one token assume it is a filename. - std::vector tokens = SplitString(line_with_ending, " "); + SmallVector tokens; + line_with_ending.split(tokens, ' '); if (tokens.size() > 1) { - active_output_filename = tokens[1]; - TrimInPlace(active_output_filename); + active_output_filename = tokens[1].str(); } else { active_output_filename = filename; } active_output_contents = ""; in_output = true; - } else if (in_output) + } else if (in_output) { active_output_contents += line_with_ending; + active_output_contents.push_back('\n'); + } } if (in_output) @@ -171,9 +173,9 @@ void ParseTestExpectation( } } -void UpdateTestExpectation(const std::string& filename, - const std::string& expectation, - const std::string& actual) { +void UpdateTestExpectation(const std::string &filename, + const std::string &expectation, + const std::string &actual) { // Read the entire file into a string. std::ifstream in(filename); std::string str; @@ -190,18 +192,15 @@ void UpdateTestExpectation(const std::string& filename, WriteToFile(filename, str); } -void DiffDocuments(std::string path, - std::string path_section, - rapidjson::Document& expected, - rapidjson::Document& actual) { +void DiffDocuments(std::string path, std::string path_section, + rapidjson::Document &expected, rapidjson::Document &actual) { std::string joined_actual_output = ToString(actual); std::string joined_expected_output = ToString(expected); - std::cout << "[FAILED] " << path << " (section " << path_section << ")" - << std::endl; + printf("[FAILED] %s (section %s)\n", path.c_str(), path_section.c_str()); #if _POSIX_C_SOURCE >= 200809L - char expected_file[] = "/tmp/cquery.expected.XXXXXX"; - char actual_file[] = "/tmp/cquery.actual.XXXXXX"; + char expected_file[] = "/tmp/ccls.expected.XXXXXX"; + char actual_file[] = "/tmp/ccls.actual.XXXXXX"; int expected_fd = mkstemp(expected_file); int actual_fd = mkstemp(actual_file); dprintf(expected_fd, "%s", joined_expected_output.c_str()); @@ -227,293 +226,160 @@ void DiffDocuments(std::string path, std::vector expected_output = SplitString(joined_expected_output, "\n"); - std::cout << "Expected output for " << path << " (section " << path_section - << "):" << std::endl; - std::cout << joined_expected_output << std::endl; - std::cout << "Actual output for " << path << " (section " << path_section - << "):" << std::endl; - std::cout << joined_actual_output << std::endl; - std::cout << std::endl; - - int max_diff = 5; - - size_t len = std::min(actual_output.size(), expected_output.size()); - for (size_t i = 0; i < len; ++i) { - if (actual_output[i] != expected_output[i]) { - if (--max_diff < 0) { - std::cout << "(... more lines may differ ...)" << std::endl; - break; - } - - std::cout << "Line " << i << " differs:" << std::endl; - std::cout << " expected: " << expected_output[i] << std::endl; - std::cout << " actual: " << actual_output[i] << std::endl; - } - } - - if (actual_output.size() > len) { - std::cout << "Additional output in actual:" << std::endl; - for (size_t i = len; i < actual_output.size(); ++i) - std::cout << " " << actual_output[i] << std::endl; - } - - if (expected_output.size() > len) { - std::cout << "Additional output in expected:" << std::endl; - for (size_t i = len; i < expected_output.size(); ++i) - std::cout << " " << expected_output[i] << std::endl; - } + printf("Expected output for %s (section %s)\n:%s\n", path.c_str(), + path_section.c_str(), joined_expected_output.c_str()); + printf("Actual output for %s (section %s)\n:%s\n", path.c_str(), + path_section.c_str(), joined_actual_output.c_str()); } -void VerifySerializeToFrom(IndexFile* file) { +void VerifySerializeToFrom(IndexFile *file) { std::string expected = file->ToString(); - std::string serialized = Serialize(SerializeFormat::Json, *file); + std::string serialized = ccls::Serialize(SerializeFormat::Json, *file); std::unique_ptr result = - Deserialize(SerializeFormat::Json, "--.cc", serialized, "", - nullopt /*expected_version*/); + ccls::Deserialize(SerializeFormat::Json, "--.cc", serialized, "", + std::nullopt /*expected_version*/); std::string actual = result->ToString(); if (expected != actual) { - std::cerr << "Serialization failure" << std::endl; - assert(false); + fprintf(stderr, "Serialization failure\n"); + // assert(false); } } std::string FindExpectedOutputForFilename( std::string filename, - const std::unordered_map& expected) { - for (const auto& entry : expected) { - if (EndsWith(entry.first, filename)) + const std::unordered_map &expected) { + for (const auto &entry : expected) { + if (StringRef(entry.first).endswith(filename)) return entry.second; } - std::cerr << "Couldn't find expected output for " << filename << std::endl; + fprintf(stderr, "Couldn't find expected output for %s\n", filename.c_str()); getchar(); getchar(); return "{}"; } -IndexFile* FindDbForPathEnding( - const std::string& path, - const std::vector>& dbs) { - for (auto& db : dbs) { - if (EndsWith(db->path, path)) +IndexFile * +FindDbForPathEnding(const std::string &path, + const std::vector> &dbs) { + for (auto &db : dbs) { + if (StringRef(db->path).endswith(path)) return db.get(); } return nullptr; } -bool RunIndexTests(const std::string& filter_path, bool enable_update) { - SetTestOutputMode(); +bool RunIndexTests(const std::string &filter_path, bool enable_update) { + gTestOutputMode = true; + std::string version = LLVM_VERSION_STRING; // Index tests change based on the version of clang used. - static constexpr const char* kRequiredClangVersion = - "clang version 6.0.0 (tags/RELEASE_600/final)"; - if (GetClangVersion() != kRequiredClangVersion && - GetClangVersion().find("trunk") == std::string::npos) { - std::cerr << "Index tests must be run using clang version \"" - << kRequiredClangVersion << "\" (cquery is running with \"" - << GetClangVersion() << "\")" << std::endl; + static const char kRequiredClangVersion[] = "6.0.0"; + if (version != kRequiredClangVersion && + version.find("svn") == std::string::npos) { + fprintf(stderr, + "Index tests must be run using clang version %s, ccls is running " + "with %s\n", + kRequiredClangVersion, version.c_str()); return false; } bool success = true; bool update_all = false; // FIXME: show diagnostics in STL/headers when running tests. At the moment - // this can be done by constructing ClangIndex index(1, 1); - ClangIndex index; - for (std::string path : GetFilesInFolder("index_tests", true /*recursive*/, - true /*add_folder_to_path*/)) { - bool is_fail_allowed = false; - - if (EndsWithAny(path, {".m", ".mm"})) { - if (!RunObjectiveCIndexTests()) { - std::cout << "Skipping \"" << path << "\" since this platform does not " - << "support running Objective-C tests." << std::endl; - continue; - } - - // objective-c tests are often not updated right away. do not bring down - // CI if they fail. - if (!enable_update) - is_fail_allowed = true; - } - - if (path.find(filter_path) == std::string::npos) - continue; - - if (!filter_path.empty()) - std::cout << "Running " << path << std::endl; - - // Parse expected output from the test, parse it into JSON document. - std::vector lines_with_endings = ReadLinesWithEnding(path); - TextReplacer text_replacer; - std::vector flags; - std::unordered_map all_expected_output; - ParseTestExpectation(path, lines_with_endings, &text_replacer, &flags, - &all_expected_output); - - // Build flags. - bool had_extra_flags = !flags.empty(); - if (!AnyStartsWith(flags, "-x")) - flags.push_back("-xc++"); - // Use c++14 by default, because MSVC STL is written assuming that. - if (!AnyStartsWith(flags, "-std")) - flags.push_back("-std=c++14"); - flags.push_back("-resource-dir=" + GetDefaultResourceDirectory()); - if (had_extra_flags) { - std::cout << "For " << path << std::endl; - std::cout << " flags: " << StringJoin(flags) << std::endl; - } - flags.push_back(path); - - // Run test. - Config config; - FileConsumerSharedState file_consumer_shared; - PerformanceImportFile perf; - auto dbs = Parse(&config, &file_consumer_shared, path, flags, {}, &perf, - &index, false /*dump_ast*/); - assert(dbs); - - for (const auto& entry : all_expected_output) { - const std::string& expected_path = entry.first; - std::string expected_output = text_replacer.Apply(entry.second); - - // FIXME: promote to utils, find and remove duplicates (ie, - // cquery_call_tree.cc, maybe something in project.cc). - auto basename = [](const std::string& path) -> std::string { - size_t last_index = path.find_last_of('/'); - if (last_index == std::string::npos) - return path; - return path.substr(last_index + 1); - }; - - auto severity_to_string = [](const lsDiagnosticSeverity& severity) { - switch (severity) { - case lsDiagnosticSeverity::Error: - return "error "; - case lsDiagnosticSeverity::Warning: - return "warning "; - case lsDiagnosticSeverity::Information: - return "information "; - case lsDiagnosticSeverity::Hint: - return "hint "; + // this can be done by conRequestIdex index(1, 1); + SemaManager completion( + nullptr, nullptr, [&](std::string, std::vector) {}, + [](RequestId id) {}); + GetFilesInFolder( + "index_tests", true /*recursive*/, true /*add_folder_to_path*/, + [&](const std::string &path) { + bool is_fail_allowed = false; + + if (path.find(filter_path) == std::string::npos) + return; + + if (!filter_path.empty()) + printf("Running %s\n", path.c_str()); + + // Parse expected output from the test, parse it into JSON document. + std::vector lines_with_endings; + { + std::ifstream fin(path); + for (std::string line; std::getline(fin, line);) + lines_with_endings.push_back(line); } - assert(false && "not reached"); - return ""; - }; - - // Get output from index operation. - IndexFile* db = FindDbForPathEnding(expected_path, *dbs); - assert(db); - if (!db->diagnostics_.empty()) { - std::cout << "For " << path << std::endl; - for (const lsDiagnostic& diagnostic : db->diagnostics_) { - std::cout << " "; - if (diagnostic.severity) - std::cout << severity_to_string(*diagnostic.severity); - std::cout << basename(db->path) << ":" - << diagnostic.range.start.ToString() << "-" - << diagnostic.range.end.ToString() << ": " - << diagnostic.message << std::endl; - } - } - std::string actual_output = "{}"; - if (db) { - VerifySerializeToFrom(db); - actual_output = db->ToString(); - } - actual_output = text_replacer.Apply(actual_output); - - // Compare output via rapidjson::Document to ignore any formatting - // differences. - rapidjson::Document actual; - actual.Parse(actual_output.c_str()); - rapidjson::Document expected; - expected.Parse(expected_output.c_str()); - - if (actual == expected) { - // std::cout << "[PASSED] " << path << std::endl; - } else { - if (!is_fail_allowed) - success = false; - DiffDocuments(path, expected_path, expected, actual); - std::cout << std::endl; - std::cout << std::endl; - if (enable_update) { - std::cout - << "[Enter to continue - type u to update test, a to update all]"; - char c = 'u'; - if (!update_all) { - c = getchar(); - getchar(); + TextReplacer text_replacer; + std::vector flags; + std::unordered_map all_expected_output; + ParseTestExpectation(path, lines_with_endings, &text_replacer, &flags, + &all_expected_output); + + // Build flags. + flags.push_back("-resource-dir=" + GetDefaultResourceDirectory()); + flags.push_back(path); + + // Run test. + g_config = new Config; + VFS vfs; + WorkingFiles wfiles; + std::vector cargs; + for (auto &arg : flags) + cargs.push_back(arg.c_str()); + bool ok; + auto dbs = ccls::idx::Index(&completion, &wfiles, &vfs, "", path, cargs, {}, ok); + + for (const auto &entry : all_expected_output) { + const std::string &expected_path = entry.first; + std::string expected_output = text_replacer.Apply(entry.second); + + // Get output from index operation. + IndexFile *db = FindDbForPathEnding(expected_path, dbs); + std::string actual_output = "{}"; + if (db) { + VerifySerializeToFrom(db); + actual_output = db->ToString(); } - - if (c == 'a') - update_all = true; - - if (update_all || c == 'u') { - // Note: we use |entry.second| instead of |expected_output| because - // |expected_output| has had text replacements applied. - UpdateTestExpectation(path, entry.second, ToString(actual) + "\n"); + actual_output = text_replacer.Apply(actual_output); + + // Compare output via rapidjson::Document to ignore any formatting + // differences. + rapidjson::Document actual; + actual.Parse(actual_output.c_str()); + rapidjson::Document expected; + expected.Parse(expected_output.c_str()); + + if (actual == expected) { + // std::cout << "[PASSED] " << path << std::endl; + } else { + if (!is_fail_allowed) + success = false; + DiffDocuments(path, expected_path, expected, actual); + puts("\n"); + if (enable_update) { + printf("[Enter to continue - type u to update test, a to update " + "all]"); + char c = 'u'; + if (!update_all) { + c = getchar(); + getchar(); + } + + if (c == 'a') + update_all = true; + + if (update_all || c == 'u') { + // Note: we use |entry.second| instead of |expected_output| + // because + // |expected_output| has had text replacements applied. + UpdateTestExpectation(path, entry.second, + ToString(actual) + "\n"); + } + } } } - } - } - } + }); return success; } - -// TODO: ctor/dtor, copy ctor -// TODO: Always pass IndexFile by pointer, ie, search and remove all IndexFile& -// refs. - -TEST_SUITE("ParseTestExpectation") { - TEST_CASE("Parse TEXT_REPLACE") { - // clang-format off - std::vector lines_with_endings = { - "/*\n", - "TEXT_REPLACE:\n", - " foo <===> \tbar \n", - "01 <===> 2\n", - "\n", - "*/\n"}; - // clang-format on - - TextReplacer text_replacer; - std::vector flags; - std::unordered_map all_expected_output; - ParseTestExpectation("foo.cc", lines_with_endings, &text_replacer, &flags, - &all_expected_output); - - REQUIRE(text_replacer.replacements.size() == 2); - REQUIRE(text_replacer.replacements[0].from == "foo"); - REQUIRE(text_replacer.replacements[0].to == "bar"); - REQUIRE(text_replacer.replacements[1].from == "01"); - REQUIRE(text_replacer.replacements[1].to == "2"); - } - - TEST_CASE("Apply TEXT_REPLACE") { - TextReplacer replacer; - replacer.replacements.push_back(TextReplacer::Replacement{"foo", "bar"}); - replacer.replacements.push_back(TextReplacer::Replacement{"01", "2"}); - replacer.replacements.push_back(TextReplacer::Replacement{"3", "456"}); - - // Equal-length. - REQUIRE(replacer.Apply("foo") == "bar"); - REQUIRE(replacer.Apply("bar") == "bar"); - - // Shorter replacement. - REQUIRE(replacer.Apply("01") == "2"); - REQUIRE(replacer.Apply("2") == "2"); - - // Longer replacement. - REQUIRE(replacer.Apply("3") == "456"); - REQUIRE(replacer.Apply("456") == "456"); - - // Content before-after replacement. - REQUIRE(replacer.Apply("aaaa01bbbb") == "aaaa2bbbb"); - - // Multiple replacements. - REQUIRE(replacer.Apply("foofoobar0123") == "barbarbar22456"); - } -} +} // namespace ccls diff --git a/src/test.h b/src/test.h deleted file mode 100644 index 294d1ff92..000000000 --- a/src/test.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#include - -bool RunIndexTests(const std::string& filter_path, bool enable_update); diff --git a/src/test.hh b/src/test.hh new file mode 100644 index 000000000..d7221537f --- /dev/null +++ b/src/test.hh @@ -0,0 +1,22 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include + +namespace ccls { +bool RunIndexTests(const std::string &filter_path, bool enable_update); +} diff --git a/src/third_party_impl.cc b/src/third_party_impl.cc deleted file mode 100644 index 231ab76c9..000000000 --- a/src/third_party_impl.cc +++ /dev/null @@ -1,5 +0,0 @@ -#define DOCTEST_CONFIG_IMPLEMENT -#include - -#define LOGURU_IMPLEMENTATION 1 -#include diff --git a/src/threaded_queue.h b/src/threaded_queue.hh similarity index 50% rename from src/threaded_queue.h rename to src/threaded_queue.hh index aeaab5d19..ced88be43 100644 --- a/src/threaded_queue.h +++ b/src/threaded_queue.hh @@ -1,54 +1,55 @@ -#pragma once +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 -#include "utils.h" +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once -#include +#include "utils.hh" -#include #include #include #include #include +#include #include #include -// TODO: cleanup includes. +// std::lock accepts two or more arguments. We define an overload for one +// argument. +namespace std { +template void lock(Lockable &l) { l.lock(); } +} // namespace std +namespace ccls { struct BaseThreadQueue { virtual bool IsEmpty() = 0; virtual ~BaseThreadQueue() = default; }; -// std::lock accepts two or more arguments. We define an overload for one -// argument. -namespace std { -template -void lock(Lockable& l) { - l.lock(); -} -} // namespace std - -template -struct MultiQueueLock { +template struct MultiQueueLock { MultiQueueLock(Queue... lockable) : tuple_{lockable...} { lock(); } ~MultiQueueLock() { unlock(); } - void lock() { - lock_impl(typename std::index_sequence_for{}); - } - void unlock() { - unlock_impl(typename std::index_sequence_for{}); - } + void lock() { lock_impl(typename std::index_sequence_for{}); } + void unlock() { unlock_impl(typename std::index_sequence_for{}); } - private: - template - void lock_impl(std::index_sequence) { +private: + template void lock_impl(std::index_sequence) { std::lock(std::get(tuple_)->mutex_...); } - template - void unlock_impl(std::index_sequence) { - (void)std::initializer_list{ - (std::get(tuple_)->mutex_.unlock(), 0)...}; + template void unlock_impl(std::index_sequence) { + (std::get(tuple_)->mutex_.unlock(), ...); } std::tuple tuple_; @@ -57,16 +58,15 @@ struct MultiQueueLock { struct MultiQueueWaiter { std::condition_variable_any cv; - static bool HasState(std::initializer_list queues) { - for (BaseThreadQueue* queue : queues) { + static bool HasState(std::initializer_list queues) { + for (BaseThreadQueue *queue : queues) { if (!queue->IsEmpty()) return true; } return false; } - template - void Wait(BaseThreadQueue... queues) { + template void Wait(BaseThreadQueue... queues) { MultiQueueLock l(queues...); while (!HasState({queues...})) cv.wait(l); @@ -74,27 +74,19 @@ struct MultiQueueWaiter { }; // A threadsafe-queue. http://stackoverflow.com/a/16075550 -template -struct ThreadedQueue : public BaseThreadQueue { - public: - ThreadedQueue() : total_count_(0) { +template struct ThreadedQueue : public BaseThreadQueue { +public: + ThreadedQueue() { owned_waiter_ = std::make_unique(); waiter_ = owned_waiter_.get(); - owned_waiter1_ = std::make_unique(); - waiter1_ = owned_waiter1_.get(); } - - // TODO remove waiter1 after split of on_indexed - explicit ThreadedQueue(MultiQueueWaiter* waiter, - MultiQueueWaiter* waiter1 = nullptr) - : total_count_(0), waiter_(waiter), waiter1_(waiter1) {} + explicit ThreadedQueue(MultiQueueWaiter *waiter) : waiter_(waiter) {} // Returns the number of elements in the queue. This is lock-free. size_t Size() const { return total_count_; } // Add an element to the queue. - template ::*push)(T&&)> - void Push(T&& t, bool priority) { + template ::*push)(T &&)> void Push(T &&t, bool priority) { std::lock_guard lock(mutex_); if (priority) (priority_.*push)(std::move(t)); @@ -102,38 +94,12 @@ struct ThreadedQueue : public BaseThreadQueue { (queue_.*push)(std::move(t)); ++total_count_; waiter_->cv.notify_one(); - if (waiter1_) - waiter1_->cv.notify_one(); } - void PushFront(T&& t, bool priority = false) { - Push<&std::deque::push_front>(std::move(t), priority); - } - - void PushBack(T&& t, bool priority = false) { + void PushBack(T &&t, bool priority = false) { Push<&std::deque::push_back>(std::move(t), priority); } - // Add a set of elements to the queue. - void EnqueueAll(std::vector&& elements, bool priority = false) { - if (elements.empty()) - return; - - std::lock_guard lock(mutex_); - - total_count_ += elements.size(); - - for (T& element : elements) { - if (priority) - priority_.push_back(std::move(element)); - else - queue_.push_back(std::move(element)); - } - elements.clear(); - - waiter_->cv.notify_all(); - } - // Return all elements in the queue. std::vector DequeueAll() { std::lock_guard lock(mutex_); @@ -163,7 +129,7 @@ struct ThreadedQueue : public BaseThreadQueue { waiter_->cv.wait(lock, [&]() { return !priority_.empty() || !queue_.empty(); }); - auto execute = [&](std::deque* q) { + auto execute = [&](std::deque *q) { auto val = std::move(q->front()); q->pop_front(); --total_count_; @@ -176,52 +142,36 @@ struct ThreadedQueue : public BaseThreadQueue { // Get the first element from the queue without blocking. Returns a null // value if the queue is empty. - optional TryPopFrontHelper(int which) { + std::optional TryPopFront() { std::lock_guard lock(mutex_); - auto execute = [&](std::deque* q) { + auto execute = [&](std::deque *q) { auto val = std::move(q->front()); q->pop_front(); --total_count_; return std::move(val); }; - if (which & 2 && priority_.size()) + if (priority_.size()) return execute(&priority_); - if (which & 1 && queue_.size()) + if (queue_.size()) return execute(&queue_); - return nullopt; + return std::nullopt; } - optional TryPopFront() { return TryPopFrontHelper(3); } - - optional TryPopBack() { + template void Iterate(Fn fn) { std::lock_guard lock(mutex_); - auto execute = [&](std::deque* q) { - auto val = std::move(q->back()); - q->pop_back(); - --total_count_; - return std::move(val); - }; - // Reversed - if (queue_.size()) - return execute(&queue_); - if (priority_.size()) - return execute(&priority_); - return nullopt; + for (auto &entry : priority_) + fn(entry); + for (auto &entry : queue_) + fn(entry); } - optional TryPopFrontLow() { return TryPopFrontHelper(1); } - - optional TryPopFrontHigh() { return TryPopFrontHelper(2); } - mutable std::mutex mutex_; - private: - std::atomic total_count_; +private: + std::atomic total_count_{0}; std::deque priority_; std::deque queue_; - MultiQueueWaiter* waiter_; + MultiQueueWaiter *waiter_; std::unique_ptr owned_waiter_; - // TODO remove waiter1 after split of on_indexed - MultiQueueWaiter* waiter1_; - std::unique_ptr owned_waiter1_; }; +} // namespace ccls diff --git a/src/timer.cc b/src/timer.cc deleted file mode 100644 index c2b37c0b1..000000000 --- a/src/timer.cc +++ /dev/null @@ -1,62 +0,0 @@ -#include "timer.h" - -#include - -Timer::Timer() { - Reset(); -} - -long long Timer::ElapsedMicroseconds() const { - std::chrono::time_point end = Clock::now(); - long long elapsed = elapsed_; - if (start_.has_value()) { - elapsed += - std::chrono::duration_cast(end - *start_) - .count(); - } - return elapsed; -} - -long long Timer::ElapsedMicrosecondsAndReset() { - long long elapsed = ElapsedMicroseconds(); - Reset(); - return elapsed; -} - -void Timer::Reset() { - start_ = Clock::now(); - elapsed_ = 0; -} - -void Timer::ResetAndPrint(const std::string& message) { - long long elapsed = ElapsedMicroseconds(); - long long milliseconds = elapsed / 1000; - long long remaining = elapsed - milliseconds; - LOG_S(INFO) << message << " took " << milliseconds << "." << remaining - << "ms"; - Reset(); -} - -void Timer::Pause() { - assert(start_.has_value()); - - std::chrono::time_point end = Clock::now(); - long long elapsed = - std::chrono::duration_cast(end - *start_) - .count(); - - elapsed_ += elapsed; - start_ = nullopt; -} - -void Timer::Resume() { - assert(!start_.has_value()); - start_ = Clock::now(); -} - -ScopedPerfTimer::ScopedPerfTimer(const std::string& message) - : message_(message) {} - -ScopedPerfTimer::~ScopedPerfTimer() { - timer_.ResetAndPrint(message_); -} diff --git a/src/timer.h b/src/timer.h deleted file mode 100644 index bbe2a955b..000000000 --- a/src/timer.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include - -#include -#include - -struct Timer { - using Clock = std::chrono::high_resolution_clock; - - // Creates a new timer. A timer is always running. - Timer(); - - // Returns elapsed microseconds. - long long ElapsedMicroseconds() const; - // Returns elapsed microseconds and restarts/resets the timer. - long long ElapsedMicrosecondsAndReset(); - // Restart/reset the timer. - void Reset(); - // Resets timer and prints a message like " took 5ms" - void ResetAndPrint(const std::string& message); - // Pause the timer. - void Pause(); - // Resume the timer after it has been paused. - void Resume(); - - // Raw start time. - optional> start_; - // Elapsed time. - long long elapsed_ = 0; -}; - -struct ScopedPerfTimer { - ScopedPerfTimer(const std::string& message); - ~ScopedPerfTimer(); - - Timer timer_; - std::string message_; -}; diff --git a/src/timestamp_manager.cc b/src/timestamp_manager.cc deleted file mode 100644 index c99241477..000000000 --- a/src/timestamp_manager.cc +++ /dev/null @@ -1,27 +0,0 @@ -#include "timestamp_manager.h" - -#include "cache_manager.h" -#include "indexer.h" - -optional TimestampManager::GetLastCachedModificationTime( - ICacheManager* cache_manager, - const std::string& path) { - { - std::lock_guard guard(mutex_); - auto it = timestamps_.find(path); - if (it != timestamps_.end()) - return it->second; - } - IndexFile* file = cache_manager->TryLoad(path); - if (!file) - return nullopt; - - UpdateCachedModificationTime(path, file->last_modification_time); - return file->last_modification_time; -} - -void TimestampManager::UpdateCachedModificationTime(const std::string& path, - int64_t timestamp) { - std::lock_guard guard(mutex_); - timestamps_[path] = timestamp; -} diff --git a/src/timestamp_manager.h b/src/timestamp_manager.h deleted file mode 100644 index 5fbc0865e..000000000 --- a/src/timestamp_manager.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -#include -#include - -struct ICacheManager; - -// Caches timestamps of cc files so we can avoid a filesystem reads. This is -// important for import perf, as during dependency checking the same files are -// checked over and over again if they are common headers. -struct TimestampManager { - optional GetLastCachedModificationTime(ICacheManager* cache_manager, - const std::string& path); - - void UpdateCachedModificationTime(const std::string& path, int64_t timestamp); - - // TODO: use std::shared_mutex so we can have multiple readers. - std::mutex mutex_; - std::unordered_map timestamps_; -}; \ No newline at end of file diff --git a/src/type_printer.cc b/src/type_printer.cc deleted file mode 100644 index c5aac6343..000000000 --- a/src/type_printer.cc +++ /dev/null @@ -1,106 +0,0 @@ -#include "type_printer.h" -#include -#include "loguru.hpp" - -namespace { - -int GetNameInsertingPosition(const std::string& type_desc, - const std::string& return_type) { - // Check if type_desc contains an (. - if (type_desc.size() <= return_type.size() || - type_desc.find("(", 0) == std::string::npos) - return type_desc.size(); - // Find the first character where the return_type differs from the - // function_type. In most cases this is the place where the function name - // should be inserted. - int ret = 0; - while (return_type[ret] == type_desc[ret]) - ret++; - - if (ret == 0) { - // If return type and function type do not overlap at all, - // check if it is `auto (...) ->` trailing return type. - if (type_desc.compare(0, 5, "auto ") == 0 && - type_desc.find(" -> ") != std::string::npos) - ret = 5; - } else { - // Otherwise return type is just a prefix of the function_type. - // Skip any eventual spaces after the return type. - while (type_desc[ret] == ' ') - ret++; - } - return ret; -} -} // namespace - -// Build a detailed function signature, including argument names. -std::string GetFunctionSignature(IndexFile* db, - NamespaceHelper* ns, - const CXIdxDeclInfo* decl) { - int num_args = clang_Cursor_getNumArguments(decl->cursor); - std::string function_name = - ns->QualifiedName(decl->semanticContainer, decl->entityInfo->name); - - std::vector> args; - for (int i = 0; i < num_args; i++) { - args.emplace_back(-1, ::ToString(clang_getCursorDisplayName( - clang_Cursor_getArgument(decl->cursor, i)))); - } - if (clang_Cursor_isVariadic(decl->cursor)) { - args.emplace_back(-1, ""); - num_args++; - } - - std::string type_desc = ClangCursor(decl->cursor).get_type_description(); - int function_name_offset = GetNameInsertingPosition( - type_desc, ::ToString(clang_getTypeSpelling( - clang_getCursorResultType(decl->cursor)))); - - if (type_desc[function_name_offset] == '(') { - // Find positions to insert argument names. - // Argument name are before ',' or closing ')'. - num_args = 0; - for (int balance = 0, i = function_name_offset; - i < int(type_desc.size()) && num_args < int(args.size()); i++) { - if (type_desc[i] == '(' || type_desc[i] == '[') - balance++; - else if (type_desc[i] == ')' || type_desc[i] == ']') { - if (--balance <= 0) { - args[num_args].first = i; - break; - } - } else if (type_desc[i] == ',' && balance == 1) - args[num_args++].first = i; - } - - // Second pass: insert argument names before each comma and closing paren. - int i = function_name_offset; - std::string type_desc_with_names(type_desc.begin(), type_desc.begin() + i); - type_desc_with_names.append(function_name); - for (auto& arg : args) { - if (arg.first < 0) { - LOG_S(ERROR) - << "When adding argument names to '" << type_desc - << "', failed to detect positions to insert argument names"; - break; - } - if (arg.second.empty()) - continue; - // TODO Use inside-out syntax. Note, clang/lib/AST/TypePrinter.cpp does - // not print arg names. - type_desc_with_names.insert(type_desc_with_names.end(), &type_desc[i], - &type_desc[arg.first]); - i = arg.first; - ConcatTypeAndName(type_desc_with_names, arg.second); - } - type_desc_with_names.insert(type_desc_with_names.end(), - type_desc.begin() + i, type_desc.end()); - type_desc = std::move(type_desc_with_names); - } else { - // type_desc is either a typedef, or some complicated type we cannot handle. - // Append the function_name in this case. - ConcatTypeAndName(type_desc, function_name); - } - - return type_desc; -} diff --git a/src/type_printer.h b/src/type_printer.h deleted file mode 100644 index 2e724c693..000000000 --- a/src/type_printer.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "indexer.h" - -std::string GetFunctionSignature(IndexFile* db, - NamespaceHelper* ns, - const CXIdxDeclInfo* decl); diff --git a/src/utils.cc b/src/utils.cc index 5d3cda96c..48e9c0d06 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -1,70 +1,99 @@ -#include "utils.h" +/* Copyright 2017-2018 ccls Authors -#include "platform.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "utils.hh" + +#include "log.hh" +#include "message_handler.hh" +#include "pipeline.hh" +#include "platform.hh" -#include #include -#include -#include + +#include +#include +#include +using namespace llvm; #include -#include -#include -#include -#include +#include +#include +#include #include -#include -#include -#include +#include +#include #include -#if !defined(__APPLE__) -#include -#endif - -// DEFAULT_RESOURCE_DIRECTORY is passed with quotes for non-MSVC compilers, ie, -// foo vs "foo". -#if defined(_MSC_VER) -#define _STRINGIFY(x) #x -#define ENSURE_STRING_MACRO_ARGUMENT(x) _STRINGIFY(x) -#else -#define ENSURE_STRING_MACRO_ARGUMENT(x) x -#endif +namespace ccls { +struct Matcher::Impl { + std::regex regex; +}; -// See http://stackoverflow.com/a/217605 -void TrimStartInPlace(std::string& s) { - s.erase(s.begin(), - std::find_if(s.begin(), s.end(), - std::not1(std::ptr_fun(std::isspace)))); -} -void TrimEndInPlace(std::string& s) { - s.erase(std::find_if(s.rbegin(), s.rend(), - std::not1(std::ptr_fun(std::isspace))) - .base(), - s.end()); -} -void TrimInPlace(std::string& s) { - TrimStartInPlace(s); - TrimEndInPlace(s); -} -std::string Trim(std::string s) { - TrimInPlace(s); - return s; +Matcher::Matcher(const std::string &pattern) + : impl(std::make_unique()), pattern(pattern) { + impl->regex = std::regex(pattern, std::regex_constants::ECMAScript | + std::regex_constants::icase | + std::regex_constants::optimize); } -void RemoveLastCR(std::string& s) { - if (!s.empty() && *s.rbegin() == '\r') - s.pop_back(); + +Matcher::~Matcher() {} + +bool Matcher::Matches(const std::string &text) const { + return std::regex_search(text, impl->regex, std::regex_constants::match_any); } -uint64_t HashUsr(const std::string& s) { - return HashUsr(s.c_str(), s.size()); +GroupMatch::GroupMatch(const std::vector &whitelist, + const std::vector &blacklist) { + auto err = [](const std::string &pattern, const char *what) { + ShowMessageParam params; + params.type = MessageType::Error; + params.message = + "failed to parse EMCAScript regex " + pattern + " : " + what; + pipeline::Notify(window_showMessage, params); + }; + for (const std::string &pattern : whitelist) { + try { + this->whitelist.emplace_back(pattern); + } catch (const std::exception &e) { + err(pattern, e.what()); + } + } + for (const std::string &pattern : blacklist) { + try { + this->blacklist.emplace_back(pattern); + } catch (const std::exception &e) { + err(pattern, e.what()); + } + } } -uint64_t HashUsr(const char* s) { - return HashUsr(s, strlen(s)); +bool GroupMatch::Matches(const std::string &text, + std::string *blacklist_pattern) const { + for (const Matcher &m : whitelist) + if (m.Matches(text)) + return true; + for (const Matcher &m : blacklist) + if (m.Matches(text)) { + if (blacklist_pattern) + *blacklist_pattern = m.pattern; + return false; + } + return true; } -uint64_t HashUsr(const char* s, size_t n) { +uint64_t HashUsr(llvm::StringRef s) { union { uint64_t ret; uint8_t out[8]; @@ -72,403 +101,92 @@ uint64_t HashUsr(const char* s, size_t n) { // k is an arbitrary key. Don't change it. const uint8_t k[16] = {0xd0, 0xe5, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x61, 0x79, 0xea, 0x70, 0xca, 0x70, 0xf0, 0x0d}; - (void)siphash(reinterpret_cast(s), n, k, out, 8); + (void)siphash(reinterpret_cast(s.data()), s.size(), k, out, + 8); return ret; } -// See http://stackoverflow.com/a/2072890 -bool EndsWith(const std::string& value, const std::string& ending) { - if (ending.size() > value.size()) - return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); -} - -bool StartsWith(const std::string& value, const std::string& start) { - if (start.size() > value.size()) - return false; - return std::equal(start.begin(), start.end(), value.begin()); -} - -bool AnyStartsWith(const std::vector& values, - const std::string& start) { - return std::any_of( - std::begin(values), std::end(values), - [&start](const std::string& value) { return StartsWith(value, start); }); -} - -bool StartsWithAny(const std::string& value, - const std::vector& startings) { - return std::any_of(std::begin(startings), std::end(startings), - [&value](const std::string& starting) { - return StartsWith(value, starting); - }); -} - -bool EndsWithAny(const std::string& value, - const std::vector& endings) { - return std::any_of( - std::begin(endings), std::end(endings), - [&value](const std::string& ending) { return EndsWith(value, ending); }); -} - -bool FindAnyPartial(const std::string& value, - const std::vector& values) { - return std::any_of(std::begin(values), std::end(values), - [&value](const std::string& v) { - return value.find(v) != std::string::npos; - }); -} - -std::string GetDirName(std::string path) { - if (path.size() && path.back() == '/') - path.pop_back(); - size_t last_slash = path.find_last_of('/'); - if (last_slash == std::string::npos) - return "."; - if (last_slash == 0) - return "/"; - return path.substr(0, last_slash); -} - -std::string GetBaseName(const std::string& path) { - size_t last_slash = path.find_last_of('/'); - if (last_slash != std::string::npos && (last_slash + 1) < path.size()) - return path.substr(last_slash + 1); - return path; -} - -std::string StripFileType(const std::string& path) { - size_t last_period = path.find_last_of('.'); - if (last_period != std::string::npos) - return path.substr(0, last_period); - return path; -} - -// See http://stackoverflow.com/a/29752943 -std::string ReplaceAll(const std::string& source, - const std::string& from, - const std::string& to) { - std::string result; - result.reserve(source.length()); // avoids a few memory allocations - - std::string::size_type last_pos = 0; - std::string::size_type find_pos; - - while (std::string::npos != (find_pos = source.find(from, last_pos))) { - result.append(source, last_pos, find_pos - last_pos); - result += to; - last_pos = find_pos + from.length(); - } - - // Care for the rest after last occurrence - result += source.substr(last_pos); - - return result; -} - -std::vector SplitString(const std::string& str, - const std::string& delimiter) { - // http://stackoverflow.com/a/13172514 - std::vector strings; - - std::string::size_type pos = 0; - std::string::size_type prev = 0; - while ((pos = str.find(delimiter, prev)) != std::string::npos) { - strings.push_back(str.substr(prev, pos - prev)); - prev = pos + 1; - } - - // To get the last substring (or only, if delimiter is not found) - strings.push_back(str.substr(prev)); - - return strings; -} - -std::string LowerPathIfCaseInsensitive(const std::string& path) { - std::string result = path; +std::string LowerPathIfInsensitive(const std::string &path) { #if defined(_WIN32) - for (size_t i = 0; i < result.size(); ++i) - result[i] = (char)tolower(result[i]); + std::string ret = path; + for (char &c : ret) + c = tolower(c); + return ret; +#else + return path; #endif - return result; -} - -static void GetFilesInFolderHelper( - std::string folder, - bool recursive, - std::string output_prefix, - const std::function& handler) { - std::queue> q; - q.push(make_pair(folder, output_prefix)); - while (!q.empty()) { - tinydir_dir dir; - if (tinydir_open(&dir, q.front().first.c_str()) == -1) { - LOG_S(WARNING) << "Unable to open directory " << folder; - goto bail; - } - - while (dir.has_next) { - tinydir_file file; - if (tinydir_readfile(&dir, &file) == -1) { - LOG_S(WARNING) << "Unable to read file " << file.name - << " when reading directory " << folder; - goto bail; - } - - // Skip all dot files except .cquery. - // - // The nested ifs are intentional, branching order is subtle here. - // - // Note that in the future if we do support dot directories/files, we must - // always ignore the '.' and '..' directories otherwise this will loop - // infinitely. - if (file.name[0] != '.' || strcmp(file.name, ".cquery") == 0) { - if (file.is_dir) { - if (recursive) { - std::string child_dir = q.front().second + file.name + "/"; - if (!IsSymLink(file.path)) - q.push(make_pair(file.path, child_dir)); - } - } else { - handler(q.front().second + file.name); - } - } - - if (tinydir_next(&dir) == -1) { - LOG_S(WARNING) << "Unable to fetch next file when reading directory " - << folder; - goto bail; - } - } - - bail: - tinydir_close(&dir); - q.pop(); - } } -std::vector GetFilesInFolder(std::string folder, - bool recursive, - bool add_folder_to_path) { - EnsureEndsInSlash(folder); - std::vector result; - GetFilesInFolderHelper( - folder, recursive, add_folder_to_path ? folder : "", - [&result](const std::string& path) { result.push_back(path); }); - return result; -} - -void GetFilesInFolder(std::string folder, - bool recursive, - bool add_folder_to_path, - const std::function& handler) { - EnsureEndsInSlash(folder); - GetFilesInFolderHelper(folder, recursive, add_folder_to_path ? folder : "", - handler); -} - -void EnsureEndsInSlash(std::string& path) { +void EnsureEndsInSlash(std::string &path) { if (path.empty() || path[path.size() - 1] != '/') path += '/'; } std::string EscapeFileName(std::string path) { - if (path.size() && path.back() == '/') - path.pop_back(); - std::replace(path.begin(), path.end(), '\\', '@'); - std::replace(path.begin(), path.end(), '/', '@'); - std::replace(path.begin(), path.end(), ':', '@'); + bool slash = path.size() && path.back() == '/'; + for (char &c : path) + if (c == '\\' || c == '/' || c == ':') + c = '@'; + if (slash) + path += '/'; return path; } -// http://stackoverflow.com/a/6089413 -std::istream& SafeGetline(std::istream& is, std::string& t) { - t.clear(); - - // The characters in the stream are read one-by-one using a std::streambuf. - // That is faster than reading them one-by-one using the std::istream. Code - // that uses streambuf this way must be guarded by a sentry object. The sentry - // object performs various tasks, such as thread synchronization and updating - // the stream state. - - std::istream::sentry se(is, true); - std::streambuf* sb = is.rdbuf(); - - for (;;) { - int c = sb->sbumpc(); - if (c == EOF) { - // Also handle the case when the last line has no line ending - if (t.empty()) - is.setstate(std::ios::eofbit); - return is; - } - - t += (char)c; - - if (c == '\n') - return is; - } -} - -bool FileExists(const std::string& filename) { - std::ifstream cache(filename); - return cache.is_open(); -} - -optional ReadContent(const std::string& filename) { - LOG_S(INFO) << "Reading " << filename; - std::ifstream cache; - cache.open(filename); - - try { - return std::string(std::istreambuf_iterator(cache), - std::istreambuf_iterator()); - } catch (std::ios_base::failure&) { - return nullopt; - } -} - -std::vector ReadLinesWithEnding(std::string filename) { - std::vector result; - - std::ifstream input(filename); - for (std::string line; SafeGetline(input, line);) - result.push_back(line); - - return result; -} - -std::vector ToLines(const std::string& content, - bool trim_whitespace) { - std::vector result; - - std::istringstream lines(content); - - std::string line; - while (getline(lines, line)) { - if (trim_whitespace) - TrimInPlace(line); - else - RemoveLastCR(line); - result.push_back(line); - } - - return result; -} - -std::string TextReplacer::Apply(const std::string& content) { - std::string result = content; - - for (const Replacement& replacement : replacements) { - while (true) { - size_t idx = result.find(replacement.from); - if (idx == std::string::npos) - break; - - result.replace(result.begin() + idx, - result.begin() + idx + replacement.from.size(), - replacement.to); - } - } - - return result; +std::string ResolveIfRelative(const std::string &directory, + const std::string &path) { + if (sys::path::is_absolute(path)) + return path; + SmallString<256> Ret; + sys::path::append(Ret, directory, path); + return NormalizePath(Ret.str()); +} + +std::optional LastWriteTime(const std::string &path) { + sys::fs::file_status Status; + if (sys::fs::status(path, Status)) + return {}; + return sys::toTimeT(Status.getLastModificationTime()); +} + +std::optional ReadContent(const std::string &filename) { + char buf[4096]; + std::string ret; + FILE *f = fopen(filename.c_str(), "rb"); + if (!f) + return {}; + size_t n; + while ((n = fread(buf, 1, sizeof buf, f)) > 0) + ret.append(buf, n); + fclose(f); + return ret; } -void WriteToFile(const std::string& filename, const std::string& content) { - std::ofstream file(filename, - std::ios::out | std::ios::trunc | std::ios::binary); - if (!file.good()) { - LOG_S(ERROR) << "Cannot write to " << filename; +void WriteToFile(const std::string &filename, const std::string &content) { + FILE *f = fopen(filename.c_str(), "wb"); + if (!f || + (content.size() && fwrite(content.c_str(), content.size(), 1, f) != 1)) { + LOG_S(ERROR) << "failed to write to " << filename << ' ' << strerror(errno); return; } - - file << content; -} - -float GetProcessMemoryUsedInMb() { -#if defined(__APPLE__) - return 0.f; -#else - const float kBytesToMb = 1000000; - uint64_t memory_after = spp::GetProcessMemoryUsed(); - return memory_after / kBytesToMb; -#endif -} - -std::string FormatMicroseconds(long long microseconds) { - long long milliseconds = microseconds / 1000; - long long remaining = microseconds - milliseconds; - - // Only show two digits after the dot. - while (remaining >= 100) - remaining /= 10; - - return std::to_string(milliseconds) + "." + std::to_string(remaining) + "ms"; -} - -std::string GetDefaultResourceDirectory() { - std::string result; - - std::string resource_directory = - std::string(ENSURE_STRING_MACRO_ARGUMENT(DEFAULT_RESOURCE_DIRECTORY)); - // Remove double quoted resource dir if it was passed with quotes - // by the build system. - if (resource_directory.size() >= 2 && resource_directory[0] == '"' && - resource_directory[resource_directory.size() - 1] == '"') { - resource_directory = - resource_directory.substr(1, resource_directory.size() - 2); - } - if (resource_directory.compare(0, 2, "..") == 0) { - std::string executable_path = GetExecutablePath(); - size_t pos = executable_path.find_last_of('/'); - result = executable_path.substr(0, pos + 1); - result += resource_directory; - } else { - result = resource_directory; - } - - return NormalizePath(result); -} - -std::string UpdateToRnNewlines(std::string output) { - size_t idx = 0; - while (true) { - idx = output.find('\n', idx); - - // No more matches. - if (idx == std::string::npos) - break; - - // Skip an existing "\r\n" match. - if (idx > 0 && output[idx - 1] == '\r') { - ++idx; - continue; - } - - // Replace "\n" with "\r|n". - output.replace(output.begin() + idx, output.begin() + idx + 1, "\r\n"); - } - - return output; -}; - -TEST_SUITE("Update \\n to \\r\\n") { - TEST_CASE("all") { - REQUIRE(UpdateToRnNewlines("\n") == "\r\n"); - REQUIRE(UpdateToRnNewlines("\n\n") == "\r\n\r\n"); - REQUIRE(UpdateToRnNewlines("\r\n\n") == "\r\n\r\n"); - REQUIRE(UpdateToRnNewlines("\n\r\n") == "\r\n\r\n"); - REQUIRE(UpdateToRnNewlines("\r\n\r\n") == "\r\n\r\n"); - REQUIRE(UpdateToRnNewlines("f1\nfo2\nfoo3") == "f1\r\nfo2\r\nfoo3"); - REQUIRE(UpdateToRnNewlines("f1\r\nfo2\r\nfoo3") == "f1\r\nfo2\r\nfoo3"); - } -} - -TEST_SUITE("StripFileType") { - TEST_CASE("all") { - REQUIRE(StripFileType("") == ""); - REQUIRE(StripFileType("bar") == "bar"); - REQUIRE(StripFileType("bar.cc") == "bar"); - REQUIRE(StripFileType("foo/bar.cc") == "foo/bar"); - } + fclose(f); +} + +// Find discontinous |search| in |content|. +// Return |found| and the count of skipped chars before found. +int ReverseSubseqMatch(std::string_view pat, std::string_view text, + int case_sensitivity) { + if (case_sensitivity == 1) + case_sensitivity = std::any_of(pat.begin(), pat.end(), isupper) ? 2 : 0; + int j = pat.size(); + if (!j) + return text.size(); + for (int i = text.size(); i--;) + if ((case_sensitivity ? text[i] == pat[j - 1] + : tolower(text[i]) == tolower(pat[j - 1])) && + !--j) + return i; + return -1; +} + +std::string GetDefaultResourceDirectory() { return DEFAULT_RESOURCE_DIRECTORY; } } diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index a602c760f..000000000 --- a/src/utils.h +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include -#include - -// Trim from start (in place) -void TrimStartInPlace(std::string& s); -// Trim from end (in place) -void TrimEndInPlace(std::string& s); -// Trim from both ends (in place) -void TrimInPlace(std::string& s); -std::string Trim(std::string s); - -uint64_t HashUsr(const std::string& s); -uint64_t HashUsr(const char* s); -uint64_t HashUsr(const char* s, size_t n); - -// Returns true if |value| starts/ends with |start| or |ending|. -bool StartsWith(const std::string& value, const std::string& start); -bool EndsWith(const std::string& value, const std::string& ending); -bool AnyStartsWith(const std::vector& values, - const std::string& start); -bool StartsWithAny(const std::string& value, - const std::vector& startings); -bool EndsWithAny(const std::string& value, - const std::vector& endings); -bool FindAnyPartial(const std::string& value, - const std::vector& values); -// Returns the dirname of |path|, i.e. "foo/bar.cc" => "foo", "foo" => ".", -// "/foo" => "/". -std::string GetDirName(std::string path); -// Returns the basename of |path|, ie, "foo/bar.cc" => "bar.cc". -std::string GetBaseName(const std::string& path); -// Returns |path| without the filetype, ie, "foo/bar.cc" => "foo/bar". -std::string StripFileType(const std::string& path); - -std::string ReplaceAll(const std::string& source, - const std::string& from, - const std::string& to); - -std::vector SplitString(const std::string& str, - const std::string& delimiter); - -std::string LowerPathIfCaseInsensitive(const std::string& path); - -template -std::string StringJoinMap(const TValues& values, - const TMap& map, - const std::string& sep = ", ") { - std::string result; - bool first = true; - for (auto& entry : values) { - if (!first) - result += sep; - first = false; - result += map(entry); - } - return result; -} - -template -std::string StringJoin(const TValues& values, const std::string& sep = ", ") { - return StringJoinMap(values, [](const std::string& entry) { return entry; }, - sep); -} - -template -bool ContainsValue(const TCollection& collection, const TValue& value) { - return collection.find(value) != collection.end(); -} - -// Finds all files in the given folder. This is recursive. -std::vector GetFilesInFolder(std::string folder, - bool recursive, - bool add_folder_to_path); -void GetFilesInFolder(std::string folder, - bool recursive, - bool add_folder_to_path, - const std::function& handler); - -// Ensures that |path| ends in a slash. -void EnsureEndsInSlash(std::string& path); - -// Converts a file path to one that can be used as filename. -// e.g. foo/bar.c => foo_bar.c -std::string EscapeFileName(std::string path); - -// FIXME: Move ReadContent into ICacheManager? -bool FileExists(const std::string& filename); -optional ReadContent(const std::string& filename); -std::vector ReadLinesWithEnding(std::string filename); -std::vector ToLines(const std::string& content, - bool trim_whitespace); - -struct TextReplacer { - struct Replacement { - std::string from; - std::string to; - }; - - std::vector replacements; - - std::string Apply(const std::string& content); -}; - -void WriteToFile(const std::string& filename, const std::string& content); - -template -void AddRange(std::vector* dest, const std::vector& to_add) { - dest->insert(dest->end(), to_add.begin(), to_add.end()); -} - -template -void AddRange(std::vector* dest, std::vector&& to_add) { - dest->insert(dest->end(), std::make_move_iterator(to_add.begin()), - std::make_move_iterator(to_add.end())); -} - -// http://stackoverflow.com/a/38140932 -// -// struct SomeHashKey { -// std::string key1; -// std::string key2; -// bool key3; -// }; -// MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3) - -inline void hash_combine(std::size_t& seed) {} - -template -inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - hash_combine(seed, rest...); -} - -#define MAKE_HASHABLE(type, ...) \ - namespace std { \ - template <> \ - struct hash { \ - std::size_t operator()(const type& t) const { \ - std::size_t ret = 0; \ - hash_combine(ret, __VA_ARGS__); \ - return ret; \ - } \ - }; \ - } - -#define MAKE_ENUM_HASHABLE(type) \ - namespace std { \ - template <> \ - struct hash { \ - std::size_t operator()(const type& t) const { \ - return hash()(static_cast(t)); \ - } \ - }; \ - } - -float GetProcessMemoryUsedInMb(); - -std::string FormatMicroseconds(long long microseconds); - -std::string GetDefaultResourceDirectory(); - -// Makes sure all newlines in |output| are in \r\n format. -std::string UpdateToRnNewlines(std::string output); diff --git a/src/utils.hh b/src/utils.hh new file mode 100644 index 000000000..fcda9d8e7 --- /dev/null +++ b/src/utils.hh @@ -0,0 +1,141 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +namespace llvm { +class StringRef; +} + +namespace ccls { +struct Matcher { + struct Impl; + std::unique_ptr impl; + std::string pattern; + + Matcher(const std::string &pattern); // throw + Matcher(Matcher&&) = default; + ~Matcher(); + bool Matches(const std::string &text) const; +}; + +struct GroupMatch { + std::vector whitelist, blacklist; + + GroupMatch(const std::vector &whitelist, + const std::vector &blacklist); + bool Matches(const std::string &text, + std::string *blacklist_pattern = nullptr) const; +}; + +uint64_t HashUsr(llvm::StringRef s); + +std::string LowerPathIfInsensitive(const std::string &path); + +// Ensures that |path| ends in a slash. +void EnsureEndsInSlash(std::string &path); + +// Converts a file path to one that can be used as filename. +// e.g. foo/bar.c => foo_bar.c +std::string EscapeFileName(std::string path); + +std::string ResolveIfRelative(const std::string &directory, + const std::string &path); + +std::optional LastWriteTime(const std::string &path); +std::optional ReadContent(const std::string &filename); +void WriteToFile(const std::string &filename, const std::string &content); + +int ReverseSubseqMatch(std::string_view pat, std::string_view text, + int case_sensitivity); + +// http://stackoverflow.com/a/38140932 +// +// struct SomeHashKey { +// std::string key1; +// std::string key2; +// bool key3; +// }; +// MAKE_HASHABLE(SomeHashKey, t.key1, t.key2, t.key3) + +inline void hash_combine(std::size_t &seed) {} + +template +inline void hash_combine(std::size_t &seed, const T &v, Rest... rest) { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + hash_combine(seed, rest...); +} + +#define MAKE_HASHABLE(type, ...) \ + namespace std { \ + template <> struct hash { \ + std::size_t operator()(const type &t) const { \ + std::size_t ret = 0; \ + ccls::hash_combine(ret, __VA_ARGS__); \ + return ret; \ + } \ + }; \ + } + +std::string GetDefaultResourceDirectory(); + +// Like std::optional, but the stored data is responsible for containing the +// empty state. T should define a function `bool T::Valid()`. +template class Maybe { + T storage; + +public: + constexpr Maybe() = default; + Maybe(const Maybe &) = default; + Maybe(std::nullopt_t) {} + Maybe(const T &x) : storage(x) {} + Maybe(T &&x) : storage(std::forward(x)) {} + + Maybe &operator=(const Maybe &) = default; + Maybe &operator=(const T &x) { + storage = x; + return *this; + } + + const T *operator->() const { return &storage; } + T *operator->() { return &storage; } + const T &operator*() const { return storage; } + T &operator*() { return storage; } + + bool Valid() const { return storage.Valid(); } + explicit operator bool() const { return Valid(); } + operator std::optional() const { + if (Valid()) + return storage; + return std::nullopt; + } + + void operator=(std::optional &&o) { storage = o ? *o : T(); } + + // Does not test if has_value() + bool operator==(const Maybe &o) const { return storage == o.storage; } + bool operator!=(const Maybe &o) const { return !(*this == o); } +}; +} // namespace ccls diff --git a/src/work_thread.cc b/src/work_thread.cc deleted file mode 100644 index b627485fd..000000000 --- a/src/work_thread.cc +++ /dev/null @@ -1,12 +0,0 @@ -#include "work_thread.h" - -#include "platform.h" - -// static -void WorkThread::StartThread(const std::string& thread_name, - std::function entry_point) { - new std::thread([thread_name, entry_point]() { - SetCurrentThreadName(thread_name); - entry_point(); - }); -} \ No newline at end of file diff --git a/src/work_thread.h b/src/work_thread.h deleted file mode 100644 index d11c237a7..000000000 --- a/src/work_thread.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -// Helper methods for starting threads that do some work. Enables test code to -// wait for all work to complete. -struct WorkThread { - // Launch a new thread. |entry_point| will be called continously. It should - // return true if it there is still known work to be done. - static void StartThread(const std::string& thread_name, - std::function entry_point); - - // Static-only class. - WorkThread() = delete; -}; \ No newline at end of file diff --git a/src/working_files.cc b/src/working_files.cc index 64c091c31..0165ee96a 100644 --- a/src/working_files.cc +++ b/src/working_files.cc @@ -1,15 +1,36 @@ -#include "working_files.h" +/* Copyright 2017-2018 ccls Authors -#include "lex_utils.h" -#include "position.h" +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -#include -#include + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#include "working_files.hh" + +#include "log.hh" +#include "position.hh" + +#include #include +#include #include #include +#include +namespace chrono = std::chrono; +using namespace clang; +using namespace llvm; + +namespace ccls { namespace { // When finding a best match of buffer line and index line, limit the max edit @@ -19,22 +40,27 @@ constexpr int kMaxDiff = 20; // |kMaxColumnAlignSize|. constexpr int kMaxColumnAlignSize = 200; -lsPosition GetPositionForOffset(const std::string& content, int offset) { +Position GetPositionForOffset(const std::string &content, int offset) { if (offset >= content.size()) offset = (int)content.size() - 1; - lsPosition result; + int line = 0, col = 0; int i = 0; - while (i < offset) { - if (content[i] == '\n') { - result.line += 1; - result.character = 0; - } else { - result.character += 1; - } - ++i; + for (; i < offset; i++) { + if (content[i] == '\n') + line++, col = 0; + else + col++; } + return {line, col}; +} +std::vector ToLines(const std::string &content) { + std::vector result; + std::istringstream lines(content); + std::string line; + while (getline(lines, line)) + result.push_back(line); return result; } @@ -42,7 +68,7 @@ lsPosition GetPositionForOffset(const std::string& content, int offset) { // Myers' O(ND) diff algorithm. // Costs: insertion=1, deletion=1, no substitution. // If the distance is larger than threshold, returns threshould + 1. -int MyersDiff(const char* a, int la, const char* b, int lb, int threshold) { +int MyersDiff(const char *a, int la, const char *b, int lb, int threshold) { assert(threshold <= kMaxDiff); static int v_static[2 * kMaxColumnAlignSize + 2]; const char *ea = a + la, *eb = b + lb; @@ -58,7 +84,7 @@ int MyersDiff(const char* a, int la, const char* b, int lb, int threshold) { if (la + lb > 2 * kMaxColumnAlignSize) return std::min(abs(la - lb), threshold + 1); - int* v = v_static + lb; + int *v = v_static + lb; v[1] = 0; for (int di = 0; di <= threshold; di++) { int low = -di + 2 * std::max(0, di - lb), @@ -77,7 +103,7 @@ int MyersDiff(const char* a, int la, const char* b, int lb, int threshold) { return threshold + 1; } -int MyersDiff(const std::string& a, const std::string& b, int threshold) { +int MyersDiff(const std::string &a, const std::string &b, int threshold) { return MyersDiff(a.data(), a.size(), b.data(), b.size(), threshold); } @@ -104,7 +130,7 @@ std::vector EditDistanceVector(std::string a, std::string b) { // Find matching position of |a[column]| in |b|. // This is actually a single step of Hirschberg's sequence alignment algorithm. -int AlignColumn(const std::string& a, int column, std::string b, bool is_end) { +int AlignColumn(const std::string &a, int column, std::string b, bool is_end) { int head = 0, tail = 0; while (head < (int)a.size() && head < (int)b.size() && a[head] == b[head]) head++; @@ -146,12 +172,10 @@ int AlignColumn(const std::string& a, int column, std::string b, bool is_end) { // Find matching buffer line of index_lines[line]. // By symmetry, this can also be used to find matching index line of a buffer // line. -optional FindMatchingLine(const std::vector& index_lines, - const std::vector& index_to_buffer, - int line, - int* column, - const std::vector& buffer_lines, - bool is_end) { +std::optional +FindMatchingLine(const std::vector &index_lines, + const std::vector &index_to_buffer, int line, int *column, + const std::vector &buffer_lines, bool is_end) { // If this is a confident mapping, returns. if (index_to_buffer[line] >= 0) { int ret = index_to_buffer[line]; @@ -171,12 +195,12 @@ optional FindMatchingLine(const std::vector& index_lines, down = down >= int(index_to_buffer.size()) ? int(buffer_lines.size()) - 1 : index_to_buffer[down]; if (up > down) - return nullopt; + return std::nullopt; // Search for lines [up,down] and use Myers's diff algorithm to find the best // match (least edit distance). int best = up, best_dist = kMaxDiff + 1; - const std::string& needle = index_lines[line]; + const std::string &needle = index_lines[line]; for (int i = up; i <= down; i++) { int dist = MyersDiff(needle, buffer_lines[i], kMaxDiff); if (dist < best_dist) { @@ -190,39 +214,25 @@ optional FindMatchingLine(const std::vector& index_lines, return best; } -} // namespace +} // namespace -std::vector WorkingFiles::Snapshot::AsUnsavedFiles() const { - std::vector result; - result.reserve(files.size()); - for (auto& file : files) { - CXUnsavedFile unsaved; - unsaved.Filename = file.filename.c_str(); - unsaved.Contents = file.content.c_str(); - unsaved.Length = (unsigned long)file.content.size(); - - result.push_back(unsaved); - } - return result; -} - -WorkingFile::WorkingFile(const std::string& filename, - const std::string& buffer_content) +WorkingFile::WorkingFile(const std::string &filename, + const std::string &buffer_content) : filename(filename), buffer_content(buffer_content) { OnBufferContentUpdated(); // SetIndexContent gets called when the file is opened. } -void WorkingFile::SetIndexContent(const std::string& index_content) { - index_lines = ToLines(index_content, false /*trim_whitespace*/); +void WorkingFile::SetIndexContent(const std::string &index_content) { + index_lines = ToLines(index_content); index_to_buffer.clear(); buffer_to_index.clear(); } void WorkingFile::OnBufferContentUpdated() { - buffer_lines = ToLines(buffer_content, false /*trim_whitespace*/); + buffer_lines = ToLines(buffer_content); index_to_buffer.clear(); buffer_to_index.clear(); @@ -245,9 +255,8 @@ void WorkingFile::ComputeLineMapping() { // For index line i, set index_to_buffer[i] to -1 if line i is duplicated. int i = 0; - for (auto& line : index_lines) { - std::string trimmed = Trim(line); - uint64_t h = HashUsr(trimmed); + for (StringRef line : index_lines) { + uint64_t h = HashUsr(line); auto it = hash_to_unique.find(h); if (it == hash_to_unique.end()) { hash_to_unique[h] = i; @@ -263,9 +272,8 @@ void WorkingFile::ComputeLineMapping() { // For buffer line i, set buffer_to_index[i] to -1 if line i is duplicated. i = 0; hash_to_unique.clear(); - for (auto& line : buffer_lines) { - std::string trimmed = Trim(line); - uint64_t h = HashUsr(trimmed); + for (StringRef line : buffer_lines) { + uint64_t h = HashUsr(line); auto it = hash_to_unique.find(h); if (it == hash_to_unique.end()) { hash_to_unique[h] = i; @@ -313,27 +321,14 @@ void WorkingFile::ComputeLineMapping() { buffer_to_index[index_to_buffer[i]] = i; } -optional WorkingFile::GetBufferPosFromIndexPos(int line, - int* column, - bool is_end) { - // The implementation is simple but works pretty well for most cases. We - // lookup the line contents in the indexed file contents, and try to find the - // most similar line in the current buffer file. - // - // Previously, this was implemented by tracking edits and by running myers - // diff algorithm. They were complex implementations that did not work as - // well. - - // Note: |index_line| and |buffer_line| are 1-based. - - // TODO: reenable this assert once we are using the real indexed file. - // assert(index_line >= 1 && index_line <= index_lines.size()); +std::optional WorkingFile::GetBufferPosFromIndexPos(int line, int *column, + bool is_end) { + if (line == (int)index_lines.size() && !*column) + return buffer_content.size(); if (line < 0 || line >= (int)index_lines.size()) { - loguru::Text stack = loguru::stacktrace(); - LOG_S(WARNING) << "Bad index_line (got " << line << ", expected [0, " - << index_lines.size() << ")) in " << filename - << stack.c_str(); - return nullopt; + LOG_S(WARNING) << "bad index_line (got " << line << ", expected [0, " + << index_lines.size() << ")) in " << filename; + return std::nullopt; } if (index_to_buffer.empty()) @@ -342,12 +337,10 @@ optional WorkingFile::GetBufferPosFromIndexPos(int line, buffer_lines, is_end); } -optional WorkingFile::GetIndexPosFromBufferPos(int line, - int* column, - bool is_end) { - // See GetBufferLineFromIndexLine for additional comments. +std::optional WorkingFile::GetIndexPosFromBufferPos(int line, int *column, + bool is_end) { if (line < 0 || line >= (int)buffer_lines.size()) - return nullopt; + return std::nullopt; if (buffer_to_index.empty()) ComputeLineMapping(); @@ -355,149 +348,76 @@ optional WorkingFile::GetIndexPosFromBufferPos(int line, index_lines, is_end); } -std::string WorkingFile::FindClosestCallNameInBuffer( - lsPosition position, - int* active_parameter, - lsPosition* completion_position) const { - *active_parameter = 0; - - int offset = GetOffsetForPosition(position, buffer_content); - - // If vscode auto-inserts closing ')' we will begin on ')' token in foo() - // which will make the below algorithm think it's a nested call. - if (offset > 0 && buffer_content[offset] == ')') - --offset; - - // Scan back out of call context. - int balance = 0; - while (offset > 0) { - char c = buffer_content[offset]; - if (c == ')') - ++balance; - else if (c == '(') - --balance; - - if (balance == 0 && c == ',') - *active_parameter += 1; - - --offset; - - if (balance == -1) - break; - } - - if (offset < 0) - return ""; - - // Scan back entire identifier. - int start_offset = offset; - while (offset > 0) { - char c = buffer_content[offset - 1]; - if (isalnum(c) == false && c != '_') - break; - --offset; - } - - if (completion_position) - *completion_position = GetPositionForOffset(buffer_content, offset); - - return buffer_content.substr(offset, start_offset - offset + 1); +Position +WorkingFile::FindStableCompletionSource(Position position, + std::string *existing_completion, + Position *replace_end_pos) const { + int start = GetOffsetForPosition(position, buffer_content); + int i = start; + while (i > 0 && isIdentifierBody(buffer_content[i - 1])) + --i; + + *replace_end_pos = position; + for (int i = start; + i < buffer_content.size() && isIdentifierBody(buffer_content[i]); i++) + replace_end_pos->character++; + + *existing_completion = buffer_content.substr(i, start - i); + return GetPositionForOffset(buffer_content, i); } -lsPosition WorkingFile::FindStableCompletionSource( - lsPosition position, - bool* is_global_completion, - std::string* existing_completion) const { - *is_global_completion = true; - - int start_offset = GetOffsetForPosition(position, buffer_content); - int offset = start_offset; - - while (offset > 0) { - char c = buffer_content[offset - 1]; - if (!isalnum(c) && c != '_') { - // Global completion is everything except for dot (.), arrow (->), and - // double colon (::) - if (c == '.') - *is_global_completion = false; - if (offset > 2) { - char pc = buffer_content[offset - 2]; - if (pc == ':' && c == ':') - *is_global_completion = false; - else if (pc == '-' && c == '>') - *is_global_completion = false; - } - - break; - } - --offset; - } - - *existing_completion = buffer_content.substr(offset, start_offset - offset); - return GetPositionForOffset(buffer_content, offset); -} - -WorkingFile* WorkingFiles::GetFileByFilename(const std::string& filename) { - std::lock_guard lock(files_mutex); - return GetFileByFilenameNoLock(filename); -} - -WorkingFile* WorkingFiles::GetFileByFilenameNoLock( - const std::string& filename) { - for (auto& file : files) { - if (file->filename == filename) - return file.get(); - } - return nullptr; +WorkingFile *WorkingFiles::GetFile(const std::string &path) { + std::lock_guard lock(mutex); + return GetFileUnlocked(path); } -void WorkingFiles::DoAction(const std::function& action) { - std::lock_guard lock(files_mutex); - action(); +WorkingFile *WorkingFiles::GetFileUnlocked(const std::string &path) { + auto it = files.find(path); + return it != files.end() ? it->second.get() : nullptr; } -void WorkingFiles::DoActionOnFile( - const std::string& filename, - const std::function& action) { - std::lock_guard lock(files_mutex); - WorkingFile* file = GetFileByFilenameNoLock(filename); - action(file); +std::string WorkingFiles::GetContent(const std::string &path) { + std::lock_guard lock(mutex); + auto it = files.find(path); + return it != files.end() ? it->second->buffer_content : ""; } -WorkingFile* WorkingFiles::OnOpen(const lsTextDocumentItem& open) { - std::lock_guard lock(files_mutex); +WorkingFile *WorkingFiles::OnOpen(const TextDocumentItem &open) { + std::lock_guard lock(mutex); - std::string filename = open.uri.GetPath(); + std::string path = open.uri.GetPath(); std::string content = open.text; - // The file may already be open. - if (WorkingFile* file = GetFileByFilenameNoLock(filename)) { - file->version = open.version; - file->buffer_content = content; - file->OnBufferContentUpdated(); - return file; + auto &wf = files[path]; + if (wf) { + wf->version = open.version; + wf->buffer_content = content; + wf->OnBufferContentUpdated(); + } else { + wf = std::make_unique(path, content); } - - files.push_back(std::make_unique(filename, content)); - return files[files.size() - 1].get(); + return wf.get(); } -void WorkingFiles::OnChange(const lsTextDocumentDidChangeParams& change) { - std::lock_guard lock(files_mutex); +void WorkingFiles::OnChange(const TextDocumentDidChangeParam &change) { + std::lock_guard lock(mutex); - std::string filename = change.textDocument.uri.GetPath(); - WorkingFile* file = GetFileByFilenameNoLock(filename); + std::string path = change.textDocument.uri.GetPath(); + WorkingFile *file = GetFileUnlocked(path); if (!file) { - LOG_S(WARNING) << "Could not change " << filename - << " because it was not open"; + LOG_S(WARNING) << "Could not change " << path << " because it was not open"; return; } + file->timestamp = chrono::duration_cast( + chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + // version: number | null - if (std::holds_alternative(change.textDocument.version)) - file->version = std::get(change.textDocument.version); + if (change.textDocument.version) + file->version = *change.textDocument.version; - for (const lsTextDocumentContentChangeEvent& diff : change.contentChanges) { + for (const TextDocumentContentChangeEvent &diff : change.contentChanges) { // Per the spec replace everything if the rangeLength and range are not set. // See https://github.com/Microsoft/language-server-protocol/issues/9. if (!diff.range) { @@ -518,134 +438,47 @@ void WorkingFiles::OnChange(const lsTextDocumentDidChangeParams& change) { } } -void WorkingFiles::OnClose(const lsTextDocumentIdentifier& close) { - std::lock_guard lock(files_mutex); - - std::string filename = close.uri.GetPath(); +void WorkingFiles::OnClose(const std::string &path) { + std::lock_guard lock(mutex); + files.erase(path); +} - for (int i = 0; i < files.size(); ++i) { - if (files[i]->filename == filename) { - files.erase(files.begin() + i); - return; +// VSCode (UTF-16) disagrees with Emacs lsp-mode (UTF-8) on how to represent +// text documents. +// We use a UTF-8 iterator to approximate UTF-16 in the specification (weird). +// This is good enough and fails only for UTF-16 surrogate pairs. +int GetOffsetForPosition(Position pos, std::string_view content) { + size_t i = 0; + for (; pos.line > 0 && i < content.size(); i++) + if (content[i] == '\n') + pos.line--; + for (; pos.character > 0 && i < content.size() && content[i] != '\n'; + pos.character--) + if (uint8_t(content[i++]) >= 128) { + // Skip 0b10xxxxxx + while (i < content.size() && uint8_t(content[i]) >= 128 && + uint8_t(content[i]) < 192) + i++; } - } - - LOG_S(WARNING) << "Could not close " << filename - << " because it was not open"; + return int(i); } -WorkingFiles::Snapshot WorkingFiles::AsSnapshot( - const std::vector& filter_paths) { - std::lock_guard lock(files_mutex); - - Snapshot result; - result.files.reserve(files.size()); - for (const auto& file : files) { - if (filter_paths.empty() || FindAnyPartial(file->filename, filter_paths)) - result.files.push_back({file->filename, file->buffer_content}); +std::string_view LexIdentifierAroundPos(Position position, + std::string_view content) { + int start = GetOffsetForPosition(position, content), end = start + 1; + char c; + + // We search for :: before the cursor but not after to get the qualifier. + for (; start > 0; start--) { + c = content[start - 1]; + if (c == ':' && start > 1 && content[start - 2] == ':') + start--; + else if (!isIdentifierBody(c)) + break; } - return result; -} + for (; end < content.size() && isIdentifierBody(content[end]); end++) + ; -lsPosition CharPos(const WorkingFile& file, - char character, - int character_offset = 0) { - return CharPos(file.buffer_content, character, character_offset); + return content.substr(start, end - start); } - -TEST_SUITE("WorkingFile") { - TEST_CASE("simple call") { - WorkingFile f("foo.cc", "abcd(1, 2"); - int active_param = 0; - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) == - "abcd"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '1'), &active_param) == - "abcd"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ','), &active_param) == - "abcd"); - REQUIRE(active_param == 1); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ' '), &active_param) == - "abcd"); - REQUIRE(active_param == 1); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '2'), &active_param) == - "abcd"); - REQUIRE(active_param == 1); - } - - TEST_CASE("nested call") { - WorkingFile f("foo.cc", "abcd(efg(), 2"); - int active_param = 0; - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, '('), &active_param) == - "abcd"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'e'), &active_param) == - "abcd"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'f'), &active_param) == - "abcd"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g'), &active_param) == - "abcd"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g', 1), &active_param) == - "efg"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, 'g', 2), &active_param) == - "efg"); - REQUIRE(active_param == 0); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ','), &active_param) == - "abcd"); - REQUIRE(active_param == 1); - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ' '), &active_param) == - "abcd"); - REQUIRE(active_param == 1); - } - - TEST_CASE("auto-insert )") { - WorkingFile f("foo.cc", "abc()"); - int active_param = 0; - REQUIRE(f.FindClosestCallNameInBuffer(CharPos(f, ')'), &active_param) == - "abc"); - REQUIRE(active_param == 0); - } - - TEST_CASE("existing completion") { - WorkingFile f("foo.cc", "zzz.asdf"); - bool is_global_completion; - std::string existing_completion; - - f.FindStableCompletionSource(CharPos(f, '.'), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "zzz"); - f.FindStableCompletionSource(CharPos(f, 'a', 1), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "a"); - f.FindStableCompletionSource(CharPos(f, 's', 1), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "as"); - f.FindStableCompletionSource(CharPos(f, 'd', 1), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "asd"); - f.FindStableCompletionSource(CharPos(f, 'f', 1), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "asdf"); - } - - TEST_CASE("existing completion underscore") { - WorkingFile f("foo.cc", "ABC_DEF"); - bool is_global_completion; - std::string existing_completion; - - f.FindStableCompletionSource(CharPos(f, 'C'), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "AB"); - f.FindStableCompletionSource(CharPos(f, '_'), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "ABC"); - f.FindStableCompletionSource(CharPos(f, 'D'), &is_global_completion, - &existing_completion); - REQUIRE(existing_completion == "ABC_"); - } } diff --git a/src/working_files.h b/src/working_files.h deleted file mode 100644 index 60ce33750..000000000 --- a/src/working_files.h +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once - -#include "lsp_diagnostic.h" -#include "utils.h" - -#include -#include - -#include -#include - -struct WorkingFile { - int version = 0; - std::string filename; - - std::string buffer_content; - // Note: This assumes 0-based lines (1-based lines are normally assumed). - std::vector index_lines; - // Note: This assumes 0-based lines (1-based lines are normally assumed). - std::vector buffer_lines; - // Mappings between index line number and buffer line number. - // Empty indicates either buffer or index has been changed and re-computation - // is required. - // For index_to_buffer[i] == j, if j >= 0, we are confident that index line - // i maps to buffer line j; if j == -1, FindMatchingLine will use the nearest - // confident lines to resolve its line number. - std::vector index_to_buffer; - std::vector buffer_to_index; - // A set of diagnostics that have been reported for this file. - // NOTE: _ is appended because it must be accessed under the WorkingFiles - // lock! - std::vector diagnostics_; - - WorkingFile(const std::string& filename, const std::string& buffer_content); - - // This should be called when the indexed content has changed. - void SetIndexContent(const std::string& index_content); - // This should be called whenever |buffer_content| has changed. - void OnBufferContentUpdated(); - - // Finds the buffer line number which maps to index line number |line|. - // Also resolves |column| if not NULL. - // When resolving a range, use is_end = false for begin() and is_end = - // true for end() to get a better alignment of |column|. - optional GetBufferPosFromIndexPos(int line, int* column, bool is_end); - // Finds the index line number which maps to buffer line number |line|. - // Also resolves |column| if not NULL. - optional GetIndexPosFromBufferPos(int line, int* column, bool is_end); - - // TODO: Move FindClosestCallNameInBuffer and FindStableCompletionSource into - // lex_utils.h/cc - - // Finds the closest 'callable' name prior to position. This is used for - // signature help to filter code completion results. - // - // |completion_position| will be point to a good code completion location to - // for fetching signatures. - std::string FindClosestCallNameInBuffer( - lsPosition position, - int* active_parameter, - lsPosition* completion_position = nullptr) const; - - // Returns a relatively stable completion position (it jumps back until there - // is a non-alphanumeric character). - // - // The out param |is_global_completion| is set to true if this looks like a - // global completion. - // The out param |existing_completion| is set to any existing completion - // content the user has entered. - lsPosition FindStableCompletionSource(lsPosition position, - bool* is_global_completion, - std::string* existing_completion) const; - - private: - // Compute index_to_buffer and buffer_to_index. - void ComputeLineMapping(); -}; - -struct WorkingFiles { - struct Snapshot { - struct File { - std::string filename; - std::string content; - }; - - std::vector AsUnsavedFiles() const; - std::vector files; - }; - - // - // :: IMPORTANT :: All methods in this class are guarded by a single lock. - // - - // Find the file with the given filename. - WorkingFile* GetFileByFilename(const std::string& filename); - WorkingFile* GetFileByFilenameNoLock(const std::string& filename); - - // Run |action| under the lock. - void DoAction(const std::function& action); - // Run |action| on the file identified by |filename|. This executes under the - // lock. - void DoActionOnFile(const std::string& filename, - const std::function& action); - - WorkingFile* OnOpen(const lsTextDocumentItem& open); - void OnChange(const lsTextDocumentDidChangeParams& change); - void OnClose(const lsTextDocumentIdentifier& close); - - // If |filter_paths| is non-empty, only files which contain any of the given - // strings. For example, {"foo", "bar"} means that every result has either the - // string "foo" or "bar" contained within it. - Snapshot AsSnapshot(const std::vector& filter_paths); - - // Use unique_ptrs so we can handout WorkingFile ptrs and not have them - // invalidated if we resize files. - std::vector> files; - std::mutex files_mutex; // Protects |files|. -}; diff --git a/src/working_files.hh b/src/working_files.hh new file mode 100644 index 000000000..f71be64f7 --- /dev/null +++ b/src/working_files.hh @@ -0,0 +1,102 @@ +/* Copyright 2017-2018 ccls Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +==============================================================================*/ + +#pragma once + +#include "lsp.hh" +#include "utils.hh" + +#include +#include +#include + +namespace ccls { +struct WorkingFile { + int64_t timestamp = 0; + int version = 0; + std::string filename; + + std::string buffer_content; + // Note: This assumes 0-based lines (1-based lines are normally assumed). + std::vector index_lines; + // Note: This assumes 0-based lines (1-based lines are normally assumed). + std::vector buffer_lines; + // Mappings between index line number and buffer line number. + // Empty indicates either buffer or index has been changed and re-computation + // is required. + // For index_to_buffer[i] == j, if j >= 0, we are confident that index line + // i maps to buffer line j; if j == -1, FindMatchingLine will use the nearest + // confident lines to resolve its line number. + std::vector index_to_buffer; + std::vector buffer_to_index; + // A set of diagnostics that have been reported for this file. + std::vector diagnostics; + + WorkingFile(const std::string &filename, const std::string &buffer_content); + + // This should be called when the indexed content has changed. + void SetIndexContent(const std::string &index_content); + // This should be called whenever |buffer_content| has changed. + void OnBufferContentUpdated(); + + // Finds the buffer line number which maps to index line number |line|. + // Also resolves |column| if not NULL. + // When resolving a range, use is_end = false for begin() and is_end = + // true for end() to get a better alignment of |column|. + std::optional GetBufferPosFromIndexPos(int line, int *column, + bool is_end); + // Finds the index line number which maps to buffer line number |line|. + // Also resolves |column| if not NULL. + std::optional GetIndexPosFromBufferPos(int line, int *column, + bool is_end); + // Returns a relatively stable completion position (it jumps back until there + // is a non-alphanumeric character). + // + // The out param |is_global_completion| is set to true if this looks like a + // global completion. + // The out param |existing_completion| is set to any existing completion + // content the user has entered. + Position FindStableCompletionSource(Position position, + std::string *existing_completion, + Position *replace_end_pos) const; + +private: + // Compute index_to_buffer and buffer_to_index. + void ComputeLineMapping(); +}; + +struct WorkingFiles { + WorkingFile *GetFile(const std::string &path); + WorkingFile *GetFileUnlocked(const std::string &path); + std::string GetContent(const std::string &path); + + template void WithLock(Fn &&fn) { + std::lock_guard lock(mutex); + fn(); + } + + WorkingFile *OnOpen(const TextDocumentItem &open); + void OnChange(const TextDocumentDidChangeParam &change); + void OnClose(const std::string &close); + + std::mutex mutex; + std::unordered_map> files; +}; + +int GetOffsetForPosition(Position pos, std::string_view content); + +std::string_view LexIdentifierAroundPos(Position position, + std::string_view content); +} // namespace ccls diff --git a/third_party/doctest b/third_party/doctest deleted file mode 160000 index b40b7e799..000000000 --- a/third_party/doctest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b40b7e799deabac916d631d181a7f19f3060acc5 diff --git a/third_party/loguru b/third_party/loguru deleted file mode 160000 index 2c35b5e72..000000000 --- a/third_party/loguru +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2c35b5e7251ab5d364b1b3164eccef7b5d2293c5 diff --git a/third_party/msgpack-c b/third_party/msgpack-c deleted file mode 160000 index 208595b26..000000000 --- a/third_party/msgpack-c +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 208595b2620cf6260ce3d6d4cf8543f13b206449 diff --git a/third_party/rapidjson b/third_party/rapidjson index daabb88e0..6a905f931 160000 --- a/third_party/rapidjson +++ b/third_party/rapidjson @@ -1 +1 @@ -Subproject commit daabb88e001f562e1f7df5f44d7fed32a0c107c2 +Subproject commit 6a905f9311f82d306da77bd963ec5aa5da07da9c diff --git a/third_party/sparsepp b/third_party/sparsepp deleted file mode 160000 index 1ca7189fe..000000000 --- a/third_party/sparsepp +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1ca7189fe81ee8c59bf08196852f70843a68a63a diff --git a/third_party/string_view.h b/third_party/string_view.h deleted file mode 100644 index 3c858d730..000000000 --- a/third_party/string_view.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once -#define STX_NAMESPACE_NAME std -#include "string_view.hpp" diff --git a/wscript b/wscript deleted file mode 100644 index fa6c0f761..000000000 --- a/wscript +++ /dev/null @@ -1,425 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -try: - from urllib2 import urlopen # Python 2 -except ImportError: - from urllib.request import urlopen # Python 3 - -import os.path -import string -import subprocess -import sys -import re -import ctypes -import shutil - -VERSION = '0.0.1' -APPNAME = 'cquery' - -top = '.' -out = 'build' - -CLANG_TARBALL_NAME = None -CLANG_TARBALL_EXT = '.tar.xz' -if sys.platform == 'darwin': - CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-apple-darwin' -elif sys.platform.startswith('freebsd'): - CLANG_TARBALL_NAME = 'clang+llvm-$version-amd64-unknown-freebsd10' -# It is either 'linux2' or 'linux3' before Python 3.3 -elif sys.platform.startswith('linux'): - # These executable depend on libtinfo.so.5 - CLANG_TARBALL_NAME = 'clang+llvm-$version-x86_64-linux-gnu-ubuntu-14.04' -elif sys.platform == 'win32': - CLANG_TARBALL_NAME = 'LLVM-$version-win64' - CLANG_TARBALL_EXT = '.exe' - -from waflib.Tools.compiler_cxx import cxx_compiler -cxx_compiler['linux'] = ['clang++', 'g++'] - -# Creating symbolic link on Windows requires a special priviledge SeCreateSymboliclinkPrivilege, -# which an non-elevated process lacks. Starting with Windows 10 build 14972, this got relaxed -# when Developer Mode is enabled. Triggering this new behaviour requires a new flag. -SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 - -# Python 2 has no symlink function and Python 3's symlink function does not allow creation of -# symlinks without admin rights (even when Developer Mode is enabled) so we define our own -# symlink function when on Windows. -if sys.platform == 'win32': - kdll = ctypes.windll.kernel32 - def symlink(source, link_name, target_is_directory=False): - # SYMBOLIC_LINK_FLAG_DIRECTORY: 0x1 - flags = int(target_is_directory) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE - - # Use unicode version (W suffix) of Windows symbolic link function and convert strings to - # unicode if Python 2 is used (strings are unicode by default in Python 3). - if sys.version_info < (3, 0): - ret = kdll.CreateSymbolicLinkW(unicode(link_name), unicode(source), flags) - else: - ret = kdll.CreateSymbolicLinkW(link_name, source, flags) - - if ret == 0: - err = ctypes.WinError() - raise err - os.symlink = symlink -elif sys.version_info.major < 3: - os_symlink_bak = os.symlink - os.symlink = lambda src, dst, target_is_directory=False: os_symlink_bak(src, dst) - -# There is a null pointer dereference issue in tools/libclang/CXIndexDataConsumer.cpp handleReference. -def patch_byte_in_libclang(filename, offset, old, new): - with open(filename, 'rb+') as f: - f.seek(offset) - t = f.read(1) - if t == old: - f.seek(offset) - f.write(new) - print('Applied byte patch hack at 0x{:x}'.format(offset)) - print('This is a makeshift for indexing default arguments of template template parameters, which otherwise would crash libclang') - print('See https://github.com/jacobdufault/cquery/issues/219 for details') - else: - assert t == new - -def options(opt): - opt.load('compiler_cxx') - grp = opt.add_option_group('Configuration options related to use of clang from the system (not recommended)') - grp.add_option('--enable-assert', action='store_true') - grp.add_option('--use-clang-cxx', dest='use_clang_cxx', default=False, action='store_true', - help='use clang C++ API') - grp.add_option('--bundled-clang', dest='bundled_clang', default='6.0.0', - help='bundled clang version, downloaded from https://releases.llvm.org/ , e.g. 5.0.1 6.0.0') - grp.add_option('--llvm-config', dest='llvm_config', - help='path to llvm-config to use system libclang, e.g. llvm-config llvm-config-6.0') - grp.add_option('--clang-prefix', dest='clang_prefix', - help='enable fallback configuration method by specifying a clang installation prefix (e.g. /opt/llvm)') - grp.add_option('--variant', default='release', - help='variant name for saving configuration and build results. Variants other than "debug" turn on -O3') - -def download_and_extract(destdir, url, ext): - dest = destdir + ext - - # Extract the tarball. - if not os.path.isdir(os.path.join(destdir, 'include')): - # Download and save the compressed tarball as |compressed_file_name|. - if not os.path.isfile(dest): - print('Downloading tarball') - print(' destination: {0}'.format(dest)) - print(' source: {0}'.format(url)) - # TODO: verify checksum - response = urlopen(url, timeout=60) - with open(dest, 'wb') as f: - f.write(response.read()) - else: - print('Found tarball at {0}'.format(dest)) - - print('Extracting {0}'.format(dest)) - # TODO: make portable. - if ext == '.exe': - subprocess.call(['7z', 'x', '-o{0}'.format(destdir), '-xr!$PLUGINSDIR', dest]) - else: - subprocess.call(['tar', '-x', '-C', out, '-f', dest]) - # TODO Remove after migrating to a clang release newer than 5.0.1 - # For 5.0.1 Mac OS X, the directory and the tarball have different name - if destdir == 'build/clang+llvm-5.0.1-x86_64-apple-darwin': - os.rename(destdir.replace('5.0.1', '5.0.1-final'), destdir) - # TODO Byte patch hack for other prebuilt binaries - elif destdir == 'build/clang+llvm-4.0.0-x86_64-linux-gnu-ubuntu-14.04': - patch_byte_in_libclang(os.path.join(destdir, 'lib/libclang.so.4.0'), - 0x4172b5, b'\x48', b'\x4d') - elif destdir == 'build/clang+llvm-5.0.1-x86_64-linux-gnu-ubuntu-14.04': - patch_byte_in_libclang(os.path.join(destdir, 'lib/libclang.so.5.0'), - 0x47aece, b'\x48', b'\x4d') - else: - print('Found extracted at {0}'.format(destdir)) - -def copy_or_symlink(src, dst): - print('copy_or_symlink src={0}, dst={1}'.format(src, dst)) - try: - os.makedirs(os.path.dirname(dst)) - except OSError: - pass - - try: - os.symlink(src, dst) - except (OSError, NotImplementedError): - shutil.copy(src, dst) - -def configure(ctx): - ctx.resetenv(ctx.options.variant) - - ctx.load('compiler_cxx') - if ctx.env.CXX_NAME == 'msvc': - # /Zi: -g, /WX: -Werror, /W3: roughly -Wall, there is no -std=c++11 equivalent in MSVC. - # /wd4722: ignores warning C4722 (destructor never returns) in loguru - # /wd4267: ignores warning C4267 (conversion from 'size_t' to 'type'), roughly -Wno-sign-compare - # /MD: use multithread c library from DLL - cxxflags = ['/nologo', '/FS', '/EHsc', '/Zi', '/W3', '/wd4996', '/wd4722', '/wd4267', '/wd4800', '/MD'] - ldflags = [] - if 'release' in ctx.options.variant: - cxxflags.append('/O2') # There is no O3 - else: - cxxflags += ['/Zi', '/FS'] - ldflags += ['/DEBUG'] - else: - # So that dladdr() called in loguru.hpp gives symbol names in main executable - ldflags = ['-rdynamic'] - if ctx.env.CXXFLAGS: - cxxflags = ctx.env.CXXFLAGS - else: - cxxflags = ['-g', '-Wall', '-Wno-sign-compare'] - if ctx.env.CXX_NAME == 'gcc': - cxxflags.append('-Wno-return-type') - # Otherwise (void)write(...) => -Werror=unused-result - cxxflags.append('-Wno-unused-result') - - if all(not x.startswith('-std=') for x in ctx.env.CXXFLAGS): - cxxflags.append('-std=c++14') - - if ctx.options.use_clang_cxx: - # include/clang/Format/Format.h error: multi-line comment - cxxflags.append('-Wno-comment') - # Without -fno-rtti, some Clang C++ functions may report `undefined references to typeinfo` - cxxflags.append('-fno-rtti') - - if 'asan' in ctx.options.variant: - cxxflags.append('-fsanitize=address,undefined') - ldflags.append('-fsanitize=address,undefined') - if 'release' in ctx.options.variant: - cxxflags.append('-O' if 'asan' in ctx.options.variant else '-O3') - - if ctx.options.enable_assert is None: - if 'debug' in ctx.options.variant: - ctx.options.enable_assert = True - if not ctx.options.enable_assert: - ctx.define('NDEBUG', 1) - - if ctx.env.CXX_NAME == 'clang' and 'debug' in ctx.options.variant: - cxxflags.append('-fno-limit-debug-info') - - ctx.env.CXXFLAGS = cxxflags - if not ctx.env.LDFLAGS: - ctx.env.LDFLAGS = ldflags - - ctx.check(header_name='stdio.h', features='cxx cxxprogram', mandatory=True) - - ctx.load('clang_compilation_database', tooldir='.') - - ctx.env['use_clang_cxx'] = ctx.options.use_clang_cxx - ctx.env['llvm_config'] = ctx.options.llvm_config - ctx.env['bundled_clang'] = ctx.options.bundled_clang - def libname(lib): - # Newer MinGW and MSVC both wants full file name - if sys.platform == 'win32': - return 'lib' + lib - return lib - - # Do not use bundled clang+llvm - if ctx.options.llvm_config is not None or ctx.options.clang_prefix is not None: - if ctx.options.llvm_config is not None: - # Ask llvm-config for cflags and ldflags - ctx.find_program(ctx.options.llvm_config, msg='checking for llvm-config', var='LLVM_CONFIG', mandatory=False) - - ctx.env.rpath = [str(subprocess.check_output( - [ctx.options.llvm_config, '--libdir'], - stderr=subprocess.STDOUT).decode()).strip()] - - if ctx.options.clang_prefix: - ctx.start_msg('Checking for clang prefix') - prefix = ctx.root.find_node(ctx.options.clang_prefix) - if not prefix: - raise ctx.errors.ConfigurationError('clang prefix not found: "%s"'%ctx.options.clang_prefix) - ctx.end_msg(prefix) - - includes = [ n.abspath() for n in [ prefix.find_node('include') ] if n ] - libpath = [ n.abspath() for n in [ prefix.find_node(l) for l in ('lib', 'lib64')] if n ] - ctx.check_cxx(msg='Checking for library clang', lib=libname('clang'), uselib_store='clang', includes=includes, libpath=libpath) - else: - ctx.check_cfg(msg='Checking for clang flags', - path=ctx.env.LLVM_CONFIG, - package='', - uselib_store='clang', - args='--cppflags --ldflags') - # llvm-config does not provide the actual library we want so we check for it - # using the provided info so far. - ctx.check_cxx(lib=libname('clang'), uselib_store='clang', use='clang') - - - # Use CXX set by --check-cxx-compiler if it is "clang". - # See https://github.com/jacobdufault/cquery/issues/237 - clang = ctx.env.get_flat('CXX') - if 'clang' not in clang: - # Otherwise, infer the clang executable path with llvm-config --bindir - output = str(subprocess.check_output( - [ctx.options.llvm_config, '--bindir'], - stderr=subprocess.STDOUT).decode()).strip() - clang = os.path.join(output, 'clang') - - # Use the detected clang executable to infer resource directory - # Use `clang -### -xc /dev/null` instead of `clang -print-resource-dir` because the option is unavailable in 4.0.0 - devnull = '/dev/null' if sys.platform != 'win32' else 'NUL' - output = str(subprocess.check_output( - [clang, '-###', '-xc', devnull], - stderr=subprocess.STDOUT).decode()) - match = re.search(r'"-resource-dir" "([^"]*)"', output, re.M) - if match: - ctx.env.default_resource_directory = match.group(1) - else: - bld.fatal("Failed to found system clang resource directory.") - - else: - global CLANG_TARBALL_NAME, CLANG_TARBALL_EXT - - if CLANG_TARBALL_NAME is None: - sys.stderr.write('ERROR: releases.llvm.org does not provide prebuilt binary for your platform {0}\n'.format(sys.platform)) - sys.exit(1) - - # TODO Remove after 5.0.1 is stable - if ctx.options.bundled_clang == '5.0.0' and sys.platform.startswith('linux'): - CLANG_TARBALL_NAME = 'clang+llvm-$version-linux-x86_64-ubuntu14.04' - - CLANG_TARBALL_NAME = string.Template(CLANG_TARBALL_NAME).substitute(version=ctx.options.bundled_clang) - # Directory clang has been extracted to. - CLANG_DIRECTORY = '{0}/{1}'.format(out, CLANG_TARBALL_NAME) - # URL of the tarball to download. - CLANG_TARBALL_URL = 'http://releases.llvm.org/{0}/{1}{2}'.format(ctx.options.bundled_clang, CLANG_TARBALL_NAME, CLANG_TARBALL_EXT) - - print('Checking for clang') - download_and_extract(CLANG_DIRECTORY, CLANG_TARBALL_URL, CLANG_TARBALL_EXT) - - bundled_clang_dir = os.path.join(out, ctx.options.variant, 'lib', CLANG_TARBALL_NAME) - try: - os.makedirs(os.path.dirname(bundled_clang_dir)) - except OSError: - pass - clang_dir = os.path.normpath('../../' + CLANG_TARBALL_NAME) - try: - if not os.path.exists(bundled_clang_dir): - os.symlink(clang_dir, bundled_clang_dir, target_is_directory=True) - except (OSError, NotImplementedError): - # Copying the whole directory instead. - print ('shutil.copytree({0}, {1})'.format(os.path.join(out, CLANG_TARBALL_NAME), bundled_clang_dir)) - try: - shutil.copytree(os.path.join(out, CLANG_TARBALL_NAME), bundled_clang_dir) - except Exception as e: - print('Failed to copy tree, ', e) - - clang_node = ctx.path.find_dir(bundled_clang_dir) - ctx.check_cxx(uselib_store='clang', - includes=clang_node.find_dir('include').abspath(), - libpath=clang_node.find_dir('lib').abspath(), - lib=libname('clang')) - - clang_tarball_name = os.path.basename(os.path.dirname(ctx.env['LIBPATH_clang'][0])) - ctx.env.clang_tarball_name = clang_tarball_name - ctx.env.default_resource_directory = '../lib/{}/lib/clang/{}'.format(clang_tarball_name, ctx.env.bundled_clang) - - if sys.platform.startswith('freebsd') or sys.platform.startswith('linux'): - ctx.env.rpath = ['$ORIGIN/../lib/' + clang_tarball_name + '/lib'] - elif sys.platform == 'darwin': - ctx.env.rpath = ['@loader_path/../lib/' + clang_tarball_name + '/lib'] - elif sys.platform == 'win32': - # Poor Windows users' RPATH - copy libclang.lib and libclang.dll to the build directory. - ctx.env.rpath = [] # Unsupported - clang_dir = os.path.dirname(ctx.env['LIBPATH_clang'][0]) - dest_dir = os.path.join(ctx.path.get_bld().abspath(), ctx.options.variant, 'bin') - # copy_or_symlink(os.path.join(clang_dir, 'lib', 'libclang.lib'), os.path.join(dest_dir, 'libclang.lib')) - copy_or_symlink(os.path.join(clang_dir, 'bin', 'libclang.dll'), os.path.join(dest_dir, 'libclang.dll')) - else: - ctx.env.rpath = ctx.env['LIBPATH_clang'] - - ctx.msg('Clang includes', ctx.env.INCLUDES_clang) - ctx.msg('Clang library dir', ctx.env.LIBPATH_clang) - -def build(bld): - cc_files = bld.path.ant_glob(['src/*.cc', 'src/messages/*.cc', 'third_party/*.cc']) - if bld.env['use_clang_cxx']: - cc_files += bld.path.ant_glob(['src/clang_cxx/*.cc']) - - lib = [] - if sys.platform.startswith('linux'): - # For __atomic_* when lock free instructions are unavailable - # (either through hardware or OS support) - lib.append('atomic') - lib.append('pthread') - # loguru calls dladdr - lib.append('dl') - elif sys.platform.startswith('freebsd'): - # loguru::stacktrace_as_stdstring calls backtrace_symbols - lib.append('execinfo') - # sparsepp/spp_memory.h uses libkvm - lib.append('kvm') - - lib.append('pthread') - lib.append('thr') - elif sys.platform == 'darwin': - lib.append('pthread') - elif sys.platform == 'msys': - lib.append('psapi') # GetProcessMemoryInfo - - if bld.env['use_clang_cxx']: - # -fno-rtti is required for object files using clang/llvm C++ API - - # The order is derived by topological sorting LINK_LIBS in clang/lib/*/CMakeLists.txt - lib.append('clangFormat') - lib.append('clangToolingCore') - lib.append('clangRewrite') - lib.append('clangAST') - lib.append('clangLex') - lib.append('clangBasic') - - # The order is derived from llvm-config --libs core - lib.append('LLVMCore') - lib.append('LLVMBinaryFormat') - lib.append('LLVMSupport') - lib.append('LLVMDemangle') - - lib.append('ncurses') - - # https://waf.io/apidocs/tools/c_aliases.html#waflib.Tools.c_aliases.program - bld.program( - source=cc_files, - use=['clang'], - includes=[ - 'src/', - 'third_party/', - 'third_party/doctest/', - 'third_party/loguru/', - 'third_party/msgpack-c/include', - 'third_party/rapidjson/include/', - 'third_party/sparsepp/'] + - (['libclang'] if bld.env['use_clang_cxx'] else []), - defines=[ - 'LOGURU_WITH_STREAMS=1', - 'LOGURU_FILENAME_WIDTH=18', - 'LOGURU_THREADNAME_WIDTH=13', - 'DEFAULT_RESOURCE_DIRECTORY="' + bld.env.get_flat('default_resource_directory') + '"'] + - (['USE_CLANG_CXX=1', 'LOGURU_RTTI=0'] - if bld.env['use_clang_cxx'] - else []), - lib=lib, - rpath=bld.env.rpath, - target='bin/cquery') - - if bld.cmd == 'install' and 'clang_tarball_name' in bld.env: - clang_tarball_name = bld.env.clang_tarball_name - if sys.platform != 'win32': - bld.install_files('${PREFIX}/lib/' + clang_tarball_name + '/lib', bld.path.get_bld().ant_glob('lib/' + clang_tarball_name + '/lib/libclang.(dylib|so.[4-9])', quiet=True)) - # TODO This may be cached and cannot be re-triggered. Use proper shell escape. - bld(rule='rsync -rtR {}/./lib/{}/lib/clang/*/include {}/'.format(bld.path.get_bld(), clang_tarball_name, bld.env['PREFIX'])) - -def init(ctx): - from waflib.Build import BuildContext, CleanContext, InstallContext, UninstallContext - for y in (BuildContext, CleanContext, InstallContext, UninstallContext): - class tmp(y): - variant = ctx.options.variant - - # This is needed because waf initializes the ConfigurationContext with - # an arbitrary setenv('') which would rewrite the previous configuration - # cache for the default variant if the configure step finishes. - # Ideally ConfigurationContext should just let us override this at class - # level like the other Context subclasses do with variant - from waflib.Configure import ConfigurationContext - class cctx(ConfigurationContext): - def resetenv(self, name): - self.all_envs = {} - self.setenv(name)