Compare commits

..

363 Commits

Author SHA1 Message Date
Fangrui Song
1976fec595 Adapt clang rC357037: removal of setVirtualFileSystem 2019-03-27 21:21:12 -07:00
Fangrui Song
304f4d7f9e Add initialization option index.initialNoLinkage: false
By default, the background indexer doesn't handle names of no linkage.
They are indexed when their files are opened. This saves memory and
makes cache files smaller.
2019-03-26 09:02:16 -07:00
Paul Smith
67fa377077 Enhance the output of --version with Git describe results. (#342) 2019-03-25 07:34:43 -07:00
Fangrui Song
b8f9d2ca2c clang.pathMappings: use > instead of : as the separator 2019-03-25 07:34:43 -07:00
Fangrui Song
95360fe36a Change containers of Query*::Def fields from std::vector to ccls::Vec
Query*::Def contain several immutable std::vector fields. Change them to
ccls::Vec to save bytes which were wasted by `capacity`.
2019-03-25 07:34:43 -07:00
Fangrui Song
f8ad4c01fd Add initialization option index.name.suppressUnwrittenScope (default: false) 2019-03-25 07:34:43 -07:00
Fangrui Song
86e4a6afb9 If the workspace folder is a symlink, convert paths relative to it (#314)
If the workspace folder is a symlink and the client doesn't follow it.
Treat /tmp/symlink/ as canonical and convert every /tmp/real/ path to
/tmp/symlink/.
2019-03-25 07:34:43 -07:00
Fangrui Song
f8061e76cc cmake: use {LLVM,Clang}Config.cmake
Combined Daan De Meyer's #227 with other simplification

* USE_SHARED_LLVM is deleted in favor of LLVM_LINK_LLVM_DYLIB
* LLVM_ENABLE_RTTI is deleted as it is provided by LLVMConfig.cmake
* Only direct Clang/LLVM dependencies are required in target_link_libraries
* Restrict -DCLANG_RESOURCE_DIRECTORY= to src/utils.cc
2019-03-25 07:34:43 -07:00
Fangrui Song
5a0271ef68 Add excludeRole to documentSymbol and override declaration's range/selectionRange with definition's 2019-03-25 07:34:43 -07:00
Fangrui Song
6996e60c28 cmake: add option to use system rapidjson if exists 2019-03-25 07:34:43 -07:00
Fangrui Song
a81bb8d125 Misc 2019-03-25 07:34:43 -07:00
Fangrui Song
fe88679da1 Make clang.excludeArgs accept glob patterns 2019-03-25 07:34:43 -07:00
Fangrui Song
a1cfff978f stdin: synthesize an "exit" NotificationMessage in abnormal termination 2019-03-25 07:34:43 -07:00
Fangrui Song
423c7c54cd textDocument/rename: mitigate edits in the same place and edits in macro replacement
Mitigate edits in the same place (#294) and:

// textDocument/rename on `f`
void f();
void g() { m(); } // incorrectly rewrote m() before
2019-03-25 07:34:43 -07:00
Fangrui Song
f5dca57819 Add .github/ISSUE_TEMPLATE
Adapted from https://github.com/hlissner/doom-emacs
2019-03-25 07:34:40 -07:00
Fangrui Song
a89e82b517 working_files: normalize \r\n and \n to \n
Clients may normalize end-of-line sequences, thus cause a mismatch
between index_lines and buffer_lines.

Thanks to CXuesong for reporting this issue!
2019-03-17 20:37:44 -07:00
Fangrui Song
b110decc1a Change Pos::line from int16_t to uint16_t
This allows representing line 0 ~ 65535.
2019-03-17 20:37:44 -07:00
Fangrui Song
4f66741f9c Make hover more detailed (e.g. include inheritance info) 2019-03-17 20:37:44 -07:00
Fangrui Song
952b5d79fd indexer: index TemplateTypeParmDecl and ParmVarDecl in declarations for clang >= 9
Index ParmVarDecl in declarations if index.parametersInDeclarations is true

And support some unhandled Decl::Kind
2019-03-17 20:37:43 -07:00
Fangrui Song
3102c54742 Add cache.{hierarchicalPath,retainInMemory}
cache.hierarchicalPath: store cache files as $directory/a/b/c.cc.blob to
work around NAME_MAX limitation.

cache.retainInMemory: after this number of loads, keep a copy of file
index in memory. If set to 1, it avoids cache corruption if the index
file is changed after the initial load, which may happen if several
language clients open the same project and share the same cache
directory.

Also rename cacheDirectory cacheFormat to cache.{directory,format}
2019-03-17 20:37:41 -07:00
Leszek Swirski
0d92b72248 Use DiagnosticRelatedInformation if client supports publishDiagnostics.relatedInformation (#276)
In clients that support DiagnosticRelatedInformation, display
clang notes as these nested diagnostics rather than appending
them to the parent diagnostic's message. Behaviour for clients
that don't support related information should be unchanged.
2019-03-17 18:50:25 -07:00
Fangrui Song
ea774dadf5 indexer: change Pos computation from byte offset to UTF-8 encoded code point offset 2019-03-17 18:50:25 -07:00
Fangrui Song
3f6ece0a44 Add initialization option capabilities.* and index.maxInitializerLines
indexer.cc: use index.maxInitializerLines instead of kInitializerMaxLines

messages/initialize.cc: some ServerCapabilities are toggable:

documentOnTypeFormattingProvider.firstTriggerCharacter
foldingRangeProvider
workspace.workspaceFolders.supported
2019-03-17 18:50:25 -07:00
Fangrui Song
6185d69d9d GetFallback: append clang.extraArgs
When compile_commands.json is absent, GetFallback is called to get
default clang command line when there is no .ccls or .ccls is empty.
2019-03-17 18:50:25 -07:00
Fangrui Song
8724985388 Compute CompletionItemKind from Declaration instead of CursorKind 2019-03-17 18:50:25 -07:00
Riatre Foo
233ed4f741 Fix is_local for vars with non-auto storage period 2019-03-17 18:50:25 -07:00
Riatre Foo
e4ba51aea3 textDocument/signatureHelp: enable documentation 2019-03-17 18:50:25 -07:00
Fangrui Song
f1efcb80c7 Log {Request,Notification}Message, and timestamp change due to dependency 2019-03-17 18:50:25 -07:00
Riatre Foo
d4de474be1 Fix completion result sorting in VSCode (#210)
Fix #207
2019-03-17 18:50:25 -07:00
Fangrui Song
9bc762961a cmake: delete SYSTEM_CLANG and auto-download mechanism 2019-03-17 18:50:25 -07:00
Fangrui Song
b2fcec4b97 Implement initialization option compilationDatabaseCommand on Windows 2019-03-17 18:50:25 -07:00
Fangrui Song
0dbf6c89f1 Drop support for clang 6 2019-03-17 18:50:25 -07:00
Fangrui Song
26fb0a9dd3 Add -log-file=stderr and make it default
Change -log-file-append to a boolean flag
2019-03-17 18:50:25 -07:00
Fangrui Song
d6329ea328 completion: if preamble size changes, rebuild it
Fix #190

If a new header is added, the preamble size changes. Language clients may cache completion results, thus we rebuild preamble to avoid inaccurate results.
2019-03-17 18:50:25 -07:00
Fangrui Song
87d7c40903 Update wiki link 2019-03-17 18:50:25 -07:00
Fangrui Song
7b3aca2952 textDocument/didOpen: index related files when a header is opened
Fix #180

index.initialBlacklist: ["."] can inhibit initial indexing (useful for larger code bases).
Opened files are still indexed, though.
This heuristic allows related files (a/foo.c a/b/foo.cc) to be indexed when a header (foo.h) is opened.
2019-03-17 18:50:25 -07:00
Fangrui Song
9a529bd691 Delay requests if the document has not not indexed (#176)
This fixes a plethora of "not indexed" errors when the document has not been indexed.

* Message handler throws NotIndexed if not overdue
* The message is put into backlog and tagged with backlog_path
* path2backlog[path] tracks backlog associated with document `path`
* The backlog is cleared when the index is merged
* backlog[0] is forced to run if it becomes overdue
2019-03-17 18:50:25 -07:00
Fangrui Song
e8cacf1efa Adjust FrontendOpts.Inputs[0] for inferred files 2019-03-17 18:50:25 -07:00
Fangrui Song
cbd36aeedb Handle file deletion and register workspace/didChangeWatchedFiles
* In the "initialized" callback, send client/registerCapability with DidChangeWatchedFilesRegistrationOptions
* In workspace/didChangeWatchedFiles callback, call pipeline::Index
* In pipeline::Index, add a `deleted` status
2019-03-17 18:50:25 -07:00
Fangrui Song
828c21c8d7 Make cacheDirectory related to project root; delete Timer 2019-03-17 18:50:25 -07:00
Fangrui Song
f2df43055f completion: ignore CXXDeductionGuide
Fix #173
2019-03-17 18:50:25 -07:00
Fangrui Song
573bfc27a1 Extend .ccls
* Add %h for C header files (the suffix .h is considered a C header, not a C++ header)
* Add %hpp for C++ header files
* If .ccls exists, it provides full command line for files not specified by compile_commands.json (before, compile_commands.json was ignored)
* If the first line of .ccls is %compile_commands.json, it appends flags to compile_commands.json "arguments", instead of overriding.
  Files not specified by compile_commands.json will not be added to folder.entries, but their command line can be inferred from other files.

Also fix `#include <` completion of -I flags for clang < 8
2019-03-17 18:50:21 -07:00
Fangrui Song
8ca0978804 Make -v=1 work and log cflags for SemaManager session and Indexer 2019-01-12 00:40:49 +08:00
Fangrui Song
18e4be616c Add strict to FuzzyMatcher::Match
In completion, underscore prefixed builtin macros may be annoying when the first type character is not an underscore.

When `strict` is true, `Match` enforces the first characters should be loosely of the same category.
2019-01-12 00:40:49 +08:00
Fangrui Song
e0a6db8d9b ParameterInformation: use label: [number, number]
Don't bother checking signatureHelp.signatureInformationparameterInformation.labelOffsetSupport
2019-01-12 00:40:49 +08:00
Fangrui Song
fc38442967 Support textDocument/declaration & LocationLink
textDocument/{declaration,definition,typeDefinition} return either LocationLink[] or Location[]
Add an initialization option client.linkSupport . When it is false, ccls will return Location[] disregarding client's linkSupport.
`struct LocationLink` does not include originSelectionRange as it is wasteful.
2019-01-12 00:40:49 +08:00
Fangrui Song
37a9ad3f81 cmake_minimum_required 3.8; clean up 2019-01-12 00:40:49 +08:00
Fangrui Song
df7221affc Rendezvous after receiving "exit" notification (#159) 2019-01-12 00:40:49 +08:00
Fangrui Song
6945a56fb8 Support multiple -init=
Initialization options are applied (deserialized to the same object) in the following order:

* "initializationOptions" from client
* first -init=
* second -init=
* ...

Scalar options will be overridden but arrays will get concatenated, e.g.

ccls -log-file=/dev/stderr -index . -init='{"clang":{"extraArgs":["-DA"]}}' -init='{"clang":{"extraArgs":["-DB"]}}'

results in clang.extraArgs: ["-DA", "-DB"]
2019-01-12 00:40:49 +08:00
Leszek Swirski
259b9fefb3 Spin IncludeComplete's destructor until scanning completes (#147) 2019-01-12 00:40:49 +08:00
Fangrui Song
d43b994557 query: fix UpdateUses when a new entity is seen; simplify {DeclRef,Use,Usr}Update
Thanks to Leszek Swirski
2019-01-12 00:40:49 +08:00
Fangrui Song
04e80544b9 Refactor serializer
Delete virtual bases Reader & Writer
Delete unused MAKE_REFLECT_STRUCT_WRITER_AS_ARRAY
Merge serializers/{json,binary}.hh into serializer.{hh,cc}
MAKE_REFLECT_STRUCT => REFLECT_STRUCT
MAKE_REFLECT_TYPE_PROXY => REFLECT_UNDERLYING
2019-01-12 00:40:49 +08:00
Fangrui Song
872d7c5de9 Add ReplyOnce::NotReady and error if didOpen is not seen
Use IgnoringDiagConsumer to override default TextDiagnosticPrinter
2019-01-12 00:40:49 +08:00
Fangrui Song
ab48663ca0 Refactor WorkingFiles and CompletionManager
* WorkingFiles::files : vector -> unordered_map
* Add timestamp to WorkingFile

* Rename "comp-preload" thread to "preamble"
* Rename CompletionManager to SemaManager as it is used by "diag" "comp" "preamble"
* Rename clang_complete.* to sema_manager.*
* Merge SemaManager::{preloads,sessions}
* Add initialization option session.maxNum
* In DiagnosticMain, if an included file was modified, cancel the DiagTask and create a PreambleTask instead. The task sets `from_diag` so as to trigger immediate DiagTask after the preamble is built.
2019-01-12 00:40:49 +08:00
Fangrui Song
e5b4a404df completion: use Text for Macro{Instantiation,Definition} 2019-01-12 00:40:49 +08:00
Fangrui Song
a37782dc0c Fix ComputeGuessScore and delete dead code
Thanks to CXuesong
2019-01-12 00:40:49 +08:00
Fangrui Song
72ee893d26 Merge maybe.hh into utils.hh 2019-01-12 00:40:49 +08:00
Fangrui Song
5a5165faa8 Merge query.hh and query_util.hh 2019-01-12 00:40:49 +08:00
Fangrui Song
5a723b489a Refactor Matcher to use pimpl and merge match.hh into utils.hh 2019-01-12 00:40:49 +08:00
Fangrui Song
e6510f7428 Make EmptyParam empty & rewrite LruCache 2019-01-12 00:40:49 +08:00
Fangrui Song
0606b95754 constexpr std::string_view -> const std::string_view
This works around gcc 7.2/clang rC347417 which have a bad interaction with libstdc++'s implementation of P0426
constexpr std::string_view also emits a string_view object in .rodata that cannot be optimized out by clang (which means larger object file size)

So use good old const.
2019-01-12 00:40:49 +08:00
Fangrui Song
9ffbf3c52e codeAction: use codeActionProvider: CodeActionOptions and respect CodeActionParams::range 2019-01-12 00:40:49 +08:00
Fangrui Song
eacbc1e1e7 Make DocumentLink::range narrower
Thanks to Riatre #135
2019-01-12 00:40:49 +08:00
Fangrui Song
7a363d2259 completion: delete insertText; don't set filterText if it is the same as label
It decreases Content-Length: from 32K to 25K for the following case:

 #include <bits/stdc++.h>
int main() { std::| }

Also

* make results deterministic when completion text is empty
* sort by newText, label, filterText
2019-01-12 00:40:49 +08:00
Fangrui Song
3bcb5f23a4 serializer: make visitor/vis value/v consistent 2019-01-12 00:40:49 +08:00
Fangrui Song
58e996366d Refactor ReplyOnce; error if InitializeParams.rootUri is null 2019-01-12 00:40:49 +08:00
Fangrui Song
f6fca76088 indexer: handle DecltypeType and empty main file; diag: -Wno-unused-function for headers
Don't replace name with qualified name in Cls::*name
2019-01-12 00:40:49 +08:00
Fangrui Song
afa654f0d1 .ccls: add %objective-c %objective-cpp
Also allow multiple directives on a line, e.g. %c %cpp -DFOO
2019-01-12 00:40:47 +08:00
Fangrui Song
8c73bbc3c7 Use clang::isIdentifierBody and clean up utils/working_files 2019-01-09 15:19:23 +08:00
Fangrui Song
b31a1c6b3e hierarchicalDocumentSymbol: support SymbolKind::Function declaration and uniquify by range
Also ensure selectionRange is a subrange of range, otherwise VSCode won't show the item.
Use detailed_name for 'detail'
2019-01-09 15:19:23 +08:00
Fangrui Song
06dff21720 README: add client feature table 2019-01-09 15:19:23 +08:00
Nikolaus Wittenstein
6767b9bf24 Add Apache LICENSE file (#121) 2019-01-09 15:19:23 +08:00
Dso Tsin
77bec58a62 Fix VS2017 build issues and add Appveyor CI script (#118) 2019-01-09 15:19:23 +08:00
Fangrui Song
63a510ac21 Use SM.isWrittenInMainFile; suppress -Werror in preamble
SM.isWrittenInMainFile is to work around preamble bug: spurious err_pp_unterminated_conditional with circular #include
2019-01-09 15:19:23 +08:00
Fangrui Song
a24fe5a386 hierarchicalDocumentSymbol: display member function declarations 2019-01-09 15:19:23 +08:00
Fangrui Song
5736dd094d Fix some MSVC 2017 errors
Thanks to Dso Tsin!
2019-01-09 15:19:23 +08:00
Fangrui Song
eeeb03c068 If clang >= 8, delete search path detection and use Sema::CodeCompleteIncludedFile 2019-01-09 15:19:23 +08:00
Fangrui Song
94d2b5821e Work around relative --sysroot= 2019-01-09 15:19:23 +08:00
Fangrui Song
58c701d98a Improve semantic highlight in templates 2019-01-09 15:19:23 +08:00
Fangrui Song
e0e00cb48a Reduce MAKE_REFLECT_STRUCT in lsp.hh
Position -> Pos; lsPosition -> Position
2019-01-09 15:19:23 +08:00
Fangrui Song
11ba6b64ff Remove ls prefix from many LSP interfaces
Rename SymbolKind to Kind & lsSymbolKind to SymbolKind
Use textDocumentSync: TextDocumentSyncOptions
2019-01-09 15:19:23 +08:00
Fangrui Song
ac09b085ff Misc 2019-01-09 15:19:23 +08:00
Fangrui Song
18e5d5c498 Simplify and work around vscode _sortTextLow 2019-01-09 15:19:23 +08:00
Fangrui Song
42b6b7b3f0 project.cc: deduplicate more cases 2019-01-09 15:19:23 +08:00
Fangrui Song
334557e9fe Fix EnumConstantDecl's kind & EnumDecl's vars 2019-01-09 15:19:23 +08:00
Fangrui Song
90a94cbb4f textDocument/references workspace/symbol: add folders
For textDocument/reference, base/excludeRole/role has been lifted from params.context.* to params.*
2019-01-09 15:19:23 +08:00
Fangrui Song
26d76b75c7 Report InvalidParams for serialization error 2019-01-09 15:19:23 +08:00
Fangrui Song
5599ddd343 Simplify and better compatibility with encodings retaining the feature of low bytes being 1-byte characters 2019-01-09 15:19:23 +08:00
Fangrui Song
df20969788 *.h -> *.hh 2019-01-09 15:19:23 +08:00
Fangrui Song
ea1271a84e Refactor message handler and namespace ccls 2019-01-09 15:19:23 +08:00
Fangrui Song
6e19a5964e Implement textDocument/documentLink 2019-01-09 15:19:23 +08:00
Fangrui Song
1d67a40ce8 Implement textDocument/foldingRange 2019-01-09 15:19:23 +08:00
David F
c0c7cfed8d Fix broken link in README.md (#101) 2019-01-09 15:19:23 +08:00
Fangrui Song
16c2e0643b Deprioritize completion items with additionTextEdits 2019-01-09 15:19:23 +08:00
Fangrui Song
32a658ad24 Fix textDocument/implementation 2019-01-09 15:19:23 +08:00
Fangrui Song
283d887271 Add command line option -index=root to index without starting language server 2019-01-09 15:19:23 +08:00
Fangrui Song
ce1c7ec76a Improve DeducedType 2019-01-09 15:19:23 +08:00
Fangrui Song
119a05597d Improve extent of definition/declaration; uniquify typeDefinition 2019-01-09 15:19:23 +08:00
Fangrui Song
87ea7d244d Simplify 2019-01-09 15:19:23 +08:00
Fangrui Song
cb7ed9415d Add ExtentRef; merge symbol2refcnt and outline2refcnt
Fix hierarchical document symbol for namespaces when there are multiple declarations.
2019-01-09 15:19:23 +08:00
Fangrui Song
fc1db06538 Add pipeline::{Notify,Reply,ReplyError} and simplify message handling
Delete method.{cc,h}
Rename $ccls/setSkippedRanges to $ccls/publishSkippedRanges
Rename $ccls/publishSemanticHighlighting to $ccls/publishSemanticHighlight; stableId -> id
2019-01-09 15:19:17 +08:00
Fangrui Song
79352b451c Misc changes to project
* Better LanguageId detection with clangDriver (e.g. .cu -> types::TY_CUDA)
* fallback when there is no .ccls or compile_commands.json

Also Hide clangTooling options from --help
2019-01-09 15:17:19 +08:00
Fangrui Song
4743124370 Adapt trunk change and write comments to PCH 2019-01-09 15:17:19 +08:00
Fangrui Song
61b361320b -DSYSTEM_CLANG=off: 6.0.1 -> 7.0.0 2019-01-09 15:17:19 +08:00
Riatre Foo
f5816e3be3 Fix hierarchical document symbol
1. Fixed a bug on building document symbol tree: As sym2ds was updated in
place, nested funcs/types may be moved into children of another
lsDocumentSymbol before itself got processed.

2. Namespaces only have declarations, in the old implementation it wasn't included in the result, making the result less hierarchical. This
commit fixes this by including the declarations of a symbol if no
definitions found.
2019-01-09 15:17:19 +08:00
Fangrui Song
51081c3cd2 Add namespace alias clang::vfs = llvm::vfs to adapt D52783
vfs::x should be written as llvm::vfs::x to work around a [namepace.udir] bug before GCC 8 when namespace alias is used
2019-01-09 15:17:19 +08:00
Fangrui Song
c5ae521d36 Namespace: improve indexer and don't trace bases in $ccls/member 2019-01-09 15:17:19 +08:00
Riatre Foo
ac2d921ab9 Fix additionalTextEdits -> textEdit hack for VS Code (#89)
* Fix additionalTextEdits -> textEdit hack for VS Code

Visual Studio Code filters the completion result according to
textEdit.range and filterText, if the textEdit.range overlaps with
existing text, we have to include it in filterText, otherwise it would
be filtered out.

* Fix has_open_paren in FilterCandidates
2019-01-09 15:17:19 +08:00
Fangrui Song
5a1ed4c943 Support workspace folders 2019-01-09 15:17:19 +08:00
Fangrui Song
de9c77e1cc Improve completion
blacklist some undesired candidates
additionalTextEdits if clang>=7
Use CodePatterns for preprocessor directive completion if there is a #
Prefer textEdit over insertText
2019-01-09 15:17:19 +08:00
Fangrui Song
10c1c28dd1 Clean and update tests after Index* refactoring 2019-01-09 15:17:13 +08:00
Fangrui Song
8c2170172d Use DeclRef spell to represent Use spell + Use extent 2018-10-04 17:16:39 -07:00
Fangrui Song
6ec032c2a0 Redesign SymbolRef, Ref, Use
Remove lsLocationEx
2018-10-04 17:16:34 -07:00
Fangrui Song
38feb8d277 Add completion.maxNum: 100 2018-10-04 00:13:50 -07:00
Fangrui Song
c7ee3d85f3 For $ccls/member, use unadjusted RecordDecl (if there is forward declaration) and handle ClassTemplateSpecialization 2018-10-03 00:42:02 -07:00
Fangrui Song
fc8a60c630 Add PreambleStatCache 2018-10-02 22:15:21 -07:00
Fangrui Song
29f05d96fb Use pthread if defined(__unix__) || defined(__APPLE__) 2018-10-02 17:51:36 -07:00
firstlove
8d49b44154 regard conversion as method instead of constructor 2018-10-01 20:49:49 -07:00
Fangrui Song
da07cb2da4 Add $ccls/info 2018-10-01 16:56:53 -07:00
Fangrui Song
f2227cbaa2 Clean 2018-10-01 16:56:53 -07:00
Fangrui Song
84984c6c27 Use non-inferred entries and build preamble for .h; index on didOpen if no pending requests; documentHighlight 2018-10-01 16:56:50 -07:00
Fangrui Song
79373ba486 Rename some initialization options
* Delete index.enabled which can be achieved with index.blacklist: ['.']
* Move completion.include* to completion.include.*
* move largeFileSize to highlight.largeFileSize
2018-09-30 11:46:34 -07:00
Fangrui Song
da704521b5 Revamp codeLens & codeAction
b.ref: references of bases
d.ref: references of derived
when b.ref > 0, don't display 0 ref or x bases
2018-09-30 11:44:24 -07:00
Fangrui Song
d4871207ed Construct SourceManager with UserFilesAreVolatile
Prettify pipeline
2018-09-28 14:06:01 -07:00
Fangrui Song
a127ca9b02 Support textDocument/{formatting,onTypeFormatting,rangeFormatting} 2018-09-28 10:18:04 -07:00
Fangrui Song
05109b6fa4 Merge textDocument_did{Change,Close,Open,Save}.cc 2018-09-27 22:16:42 -07:00
Amos Bird
d6ad864f11 Update threaded_queue.h (#82)
https://en.cppreference.com/w/cpp/language/fold
2018-09-26 17:47:03 -07:00
Fangrui Song
41fcc0272c Simplify semantic highlighting; improve hover of auto && 2018-09-25 11:57:42 -07:00
Fangrui Song
0eb9428a32 Add index.trackDependency and improve pipeline 2018-09-24 00:38:56 -07:00
Fangrui Song
eb644bb78e Add index.initial{Blacklist,Whitelist}
index.{blacklist,whitelist}: disable indexes thoroughly
index.initial{Blacklist,Whitelist}: disable initial loading. will still be indexed after opening
2018-09-24 00:38:56 -07:00
Fangrui Song
ce68028caf Add GetAdjustedDecl to adjust Decl's that are missed by clangIndex 2018-09-24 00:38:54 -07:00
Fangrui Song
8f40c0c244 Remove clang_utils.* 2018-09-23 20:34:40 -07:00
Fangrui Song
854225bd30 Misc
Move using below #include to make preamble happy
textDocument/references: if no references, first line or last line => list where this file is included
malloc_trim() only if files have been indexed in last cycle
Intern: use CachedHashStringRef
2018-09-23 20:34:38 -07:00
Riatre Foo
32f7d148ca Allow force disabling snippet via client.snippetSupport 2018-09-23 11:32:57 -07:00
Fangrui Song
71e9835b8c documentSymbol: ignore TypeParameter
Reported by Riatre
2018-09-23 10:44:08 -07:00
Riatre Foo
e320ce42ab Include macros in completion result 2018-09-23 10:40:41 -07:00
Fangrui Song
22daed7001 Add kind to $ccls/member and iterate all QueryType::def
kind:2 => member functions
kind:3 => nested classes / namespace members
2018-09-23 10:40:41 -07:00
Fangrui Song
7eb58bb5e1 Misc 2018-09-23 10:40:41 -07:00
Fangrui Song
6ea399559d Fix spurious "Failed to index" errors 2018-09-23 10:40:41 -07:00
Fangrui Song
28d33324b1 Publish diagnostics of inferred files and change diagnostics.{onChange,onOpen,onSave} from bool to debounce time 2018-09-23 10:40:39 -07:00
Riatre Foo
1a519163da Remove chunks with CK_Information kind from insertText. (#78)
Without this ccls inserts "size() const" in the following scenario:

std::string text;
text.si| <-- Trigger completion here and pick "size"
2018-09-22 08:57:36 -07:00
Fangrui Song
4d76108d6b Delete file_consumer.* 2018-09-20 19:48:20 -07:00
Fangrui Song
41756297ef Simplify semantic highlighting 2018-09-20 17:49:39 -07:00
Fangrui Song
f9bd84a975 Clean up FileConsumer and improve pipeline 2018-09-20 09:20:07 -07:00
Fangrui Song
f515b4b466 Improve VarDef::type for textDocument/typeDefinition 2018-09-20 01:08:33 -07:00
Fangrui Song
08645d64c1 intern args 2018-09-20 01:08:33 -07:00
Fangrui Song
525b6da1ac intern strings in dependencies and IndexInclude::resolved_path 2018-09-20 01:08:33 -07:00
Fangrui Song
763106c3d4 Simplify pipeline and fix race 2018-09-20 01:08:31 -07:00
Fangrui Song
14b73f0d6f Add hierarchicalDocumentSymbolSupport 2018-09-16 20:53:52 -07:00
Fangrui Song
a18977b9fc Add clang.pathMappings to reuse cache files with differect source paths 2018-09-16 20:53:52 -07:00
Fangrui Song
34c1ebcefd Remove [spell.start, spell.end) -> [spell.start, extent.end) hack 2018-09-16 20:53:50 -07:00
Fangrui Song
56c6ec43df Skip informative scope foo:: 2018-09-16 17:00:59 -07:00
Fangrui Song
70deeca8ad Rename hierarchies to $ccls/{call,inheritance,member}
bool flat = false; -> bool hierarchy = false; (set it to true to enable hierarchical view)

Delete $ccls/callers (which is what $ccls/call does now)
2018-09-13 14:55:27 -07:00
Fangrui Song
a174105abe Better diagnostics 2018-09-13 14:55:27 -07:00
Fangrui Song
1969731781 textDocument/documentSymbol 2018-09-13 14:55:27 -07:00
Fangrui Song
6bca153ee3 Make $ccls/inheritanceHierarchy and textDocument/typeDefinition find declarations if definitions do not exist; spelling ranges of operator= 2018-09-13 14:55:24 -07:00
Fangrui Song
e2f29d7b1b pipeline improvement for files not having a project entry (e.g. .h) 2018-09-12 17:01:52 -07:00
Fangrui Song
c7a6c5cd12 Make $ccls/reload reset DB and reload cached index files
$ccls/reload is renamed from $ccls/freshenIndex

This is useful when DB (merged index) diverges from backing IndexFile.

Also fix a semantic highlighting bug.
2018-09-12 17:01:52 -07:00
Riatre Foo
a607dcec24 Normalize paths on Windows
1. Normalize paths in LSP document URIs and project root to forward
slash and uppercase drive letters.
2. Normalize paths in compile_commands.json to forward slash and
uppercase drive letters.
3. Normalize paths from directory listing to forward slash. (Drive
letter should be same as input dir path, which is already uppercase
since path of project root dir is normalized)
4. Add llvm::sys::path::convert_to_slash after certain llvm::sys::path
and llvm::fs calls.
2018-09-12 17:01:52 -07:00
Fangrui Song
a45686ae1b diagnostics; use custom DenseMapInfo 2018-09-12 17:01:52 -07:00
Fangrui Song
c9e6b31dd0 Revamp completion and signatureHelp, set completion.detailedLabel: true and add completion.duplicateOptional 2018-09-12 17:01:48 -07:00
Fangrui Song
a7c1633b51 Misc 2018-09-11 20:22:49 -07:00
Fangrui Song
c202dd3775 Make CXXConversion references wider; use getTypedefNameForAnonDecl; improve CXXDestructor CXXConversion spell 2018-09-11 16:21:46 -07:00
Fangrui Song
92ee7f3e0f Add diagnostics.onSave 2018-09-11 16:21:42 -07:00
Fangrui Song
da982a6506 Reuse preamble (built by "comp-preload") in indexer 2018-09-10 00:45:56 -07:00
Fangrui Song
58191fd335 Support empty cacheDirectory and fix cache load 2018-09-08 15:46:52 -07:00
Fangrui Song
82d31a2012 Implement index.onChange: true; enable spell checking for diagnostics 2018-09-08 14:27:49 -07:00
Fangrui Song
e20a8e7a1b Add DeclUse as element type of declarations 2018-09-08 14:27:44 -07:00
Fangrui Song
8b3ebf234b Remove $ccls/base and clean up; deduplicate codeLens 2018-09-07 15:00:15 -07:00
Fangrui Song
b77fba6ea8 Improve hover and documentSymbol 2018-09-06 00:22:40 -07:00
Fangrui Song
56da577df9 Add clang.excludeArgs and rename diagnostics.onType to onChange 2018-09-04 13:10:18 -07:00
Fangrui Song
0a51424c5c Add $ccls/navigate and improve textDocument/definition 2018-09-04 01:04:19 -07:00
Fangrui Song
e92378df9c Add all: boolean to textDocument/documentSymbol 2018-09-03 14:25:49 -07:00
Fangrui Song
aebf2dfaed Add index.multiVersion{Black,White}list
They allow files matching specified patterns to be indexed only once
even if index.multiVersion > 0. An example is to exclude system headers
with index.multiVersionBlacklist: ["^/usr/include"]
2018-09-03 12:28:55 -07:00
Fangrui Song
a33f4df404 index.multiVersion: int
Before, QueryFile::Def::{all_symbols,outline} were built by indexers.
Now, {symbol,outline}2refcnt are used instead, built by main thread.
*_removed are augmented with Query*:Def to allow removal of old {symbol,outline}2refcnt entries.
2018-09-03 12:27:26 -07:00
Fangrui Song
145630ba1a Fix unaligned load/store; add index.multiVersion prototype, rename index.onParse to index.OnOpen
Don't call getFieldOffset() on RD->isInvalidDecl()
2018-09-02 23:10:03 -07:00
Fangrui Song
11e92d52a8 README 2018-09-02 14:01:28 -07:00
Fangrui Song
407c7cc29d Remove ASTUnit from indexer and clean up 2018-08-31 09:47:37 -07:00
scturtle
5cc3006a3a Filter deps with index.blacklist. (#64) 2018-08-30 09:46:03 -07:00
Fangrui Song
1ae97c64ed detailed_name: "{\n}" -> "{}" ; completion 2018-08-29 23:40:40 -07:00
firstlove
74790e2421 fix clang_tu.h && clang_complete.cc (#61) 2018-08-29 20:52:44 -07:00
Fangrui Song
87c5491536 New diagnostics 2018-08-29 20:52:44 -07:00
Fangrui Song
f808dd8f8a Use StoreInMemory Preamble for CodeComplete 2018-08-29 20:52:32 -07:00
Fangrui Song
bd3e06796e SkipFunctionBodiesScope; improve fuzzy 2018-08-28 00:27:16 -07:00
Fangrui Song
caddc18860 Uniquify $ccls/inheritanceHierarchy and add flat to $ccls/memberHierarchy 2018-08-24 10:37:47 -07:00
Fangrui Song
48c92c05a1 Add LLVM_ENABLE_RTTI (default: OFF) to allow linking against Arch Linux extra/{llvm,clang} (-DLLVM_ENABLE_RTTI=ON) 2018-08-23 15:21:46 -07:00
Fangrui Song
ec2b893ee4 Fix VarDef::is_local; default cacheDirectory to ".ccls-cache" 2018-08-22 20:45:29 -07:00
Fangrui Song
f3490a3e6c Add license notice 2018-08-20 22:27:52 -07:00
Amos Bird
66b027910a correctly grabbing the ownership of index files. (#54) 2018-08-20 09:58:27 -07:00
Fangrui Song
431eef2167 textDocument/hover: prefer definitions in the same file 2018-08-19 13:11:47 -07:00
Fangrui Song
2f5fcadc39 Remove CompileCommandsEntry and reduce clangDriver invocations 2018-08-19 11:03:37 -07:00
inengch
06aa252335 Fix typo error (#49)
- The word strengthened is spelled mistakenly.
- Unpaired symbol:')' seems to be missing.
2018-08-12 19:22:30 -07:00
Fangrui Song
8cbb317dc2 clang-format
DEF CON 26 CTF
2018-08-09 10:13:54 -07:00
Riatre Foo
344ade0420 Fix compile on Windows + MSYS2-MinGW64 (#46)
1. libClangDriver.a shipped by MSYS2 needs -lversion which is not
   included in CMAKE_CXX_STANDARD_LIBRARIES by default.
2. Use string literal for DEFAULT_RESOURCE_DIRECTORY to support
   backslashes in path.
2018-08-08 00:25:46 -07:00
Fangrui Song
b9e1c2ee07 Add flat to $ccls/inheritanceHierarchy 2018-08-08 00:25:46 -07:00
Fangrui Song
3931951406 Validate RecordDecl 2018-08-08 00:25:43 -07:00
Damon Kwok
b5c2a48bb0 rapidjson url (#43) 2018-08-03 09:37:31 -07:00
Fangrui Song
924fedbb02 Uniquify textDocument/references
There can be duplicates with template instantiation.
2018-07-30 18:00:54 -07:00
Fangrui Song
b4aa0705a1 cmake: for -DSYSTEM_CLANG=off, 6.0.0 -> 6.0.1 2018-07-29 18:02:37 -07:00
Fangrui Song
0bb311ac56 Add textDocument/codeAction for clang FixIt
What do you think of the challenge ccls-fringe in Real World CTF?
2018-07-29 01:03:30 -07:00
Fangrui Song
c71047189f Misc 2018-07-29 01:03:24 -07:00
Chao Shen
b95b47540d Fix diagnostics on MacOS and duplicated bases. 2018-07-26 20:51:30 -07:00
Fangrui Song
122eda1c53 Improve comment and outline 2018-07-25 10:53:36 -07:00
Chao Shen
03aa024fe6 Misc. 2018-07-24 20:48:46 -07:00
Chao Shen
ff102c9b7e Fix preload completion session. 2018-07-24 20:48:41 -07:00
Fangrui Song
8b4a8d2d48 Fix Twine; index TypedefNameDecl to specialization; anonymous RecordDecl fields 2018-07-23 00:26:05 -07:00
Fangrui Song
e67ea3af87 Use ContainerDC for extent, index callees, set StaticMethod 2018-07-21 00:54:34 -07:00
Fangrui Song
27d641bf64 Add Query*::file_id to allow textDocument/hover on declarations
Also change StorageClass storage to uint8_t
2018-07-20 23:48:19 -07:00
Fangrui Song
19fa911908 links 2018-07-18 22:13:50 -07:00
Fangrui Song
46d397f4b3 --init 2018-07-18 10:51:18 -07:00
Fangrui Song
0732d37817 Better bases/derived and initializer 2018-07-17 21:13:56 -07:00
Fangrui Song
af54645e5e Fix spell for clang < 7 2018-07-16 09:49:37 -07:00
Fangrui Song
73ac788f9e Remove libclang 2018-07-16 09:34:48 -07:00
Fangrui Song
344f00fbb2 CrashRecoveryContext 2018-07-15 23:06:27 -07:00
Fangrui Song
8912b00381 Support BindingDecl and VarTemplate{,Partial}SpecializationDecl 2018-07-15 18:56:04 -07:00
Fangrui Song
814f054e6e Misc improvement to indexer
* Make CXXConstructor span one more column to left/right
* Use OrigD to collect Decl::Record members
* Better comment parsing
* Limit lines of initializers and macro definition to 3
* Attribute macro arg uses to spelling loc
* Remove FuncDef::declaring_type
2018-07-15 16:51:20 -07:00
Fangrui Song
dd05ad9f65 Diagnostics 2018-07-15 13:29:47 -07:00
Fangrui Song
eea1b92825 Enable clangDriver in project.cc
https://bugs.llvm.org/show_bug.cgi?id=37695 is not fixed. But since we have eliminated libclang for indexing and completion the bug no longer bothers us.
2018-07-15 01:22:02 -07:00
Fangrui Song
df72a9eb72 Simplify 2018-07-15 00:55:11 -07:00
Fangrui Song
5dcccea285 Use Clang C++ for completion and diagnostics 2018-07-15 00:15:31 -07:00
Fangrui Song
4612aa062b $ccls/publishSemanticHighlighting: support both line/character-style and position-style ranges 2018-07-14 14:03:51 -07:00
Fangrui Song
d604fc38dc Use Sema/CodeCompleteConsumer 2018-07-14 11:41:16 -07:00
Fangrui Song
0780e80f8a Fix FreeBSD build with -DUSE_SHARED_LLVM=off 2018-07-14 09:19:27 -07:00
Fangrui Song
39d4bbfe67 Use clangTooling 2018-07-14 00:58:33 -07:00
Fangrui Song
3737d4c60f Support uses from other files and improve references in macro replacement-list 2018-07-13 16:44:48 -07:00
Fangrui Song
0c50ee79f2 Cleanup 2018-07-12 12:42:02 -07:00
Fangrui Song
c04d0620c0 Add some ObjC kinds 2018-07-12 12:27:34 -07:00
Fangrui Song
7c1ff07dc9 Fix memberHierarchy 2018-07-08 23:31:40 -07:00
Fangrui Song
7d1d4b410b $ccls/publishSemanticHighlighting: use pair<int,int> in place of lsRange 2018-07-08 20:38:23 -07:00
Fangrui Song
0a304096df cmake: remove -lc++experimental -ldl -lexecinfo 2018-07-08 20:38:23 -07:00
Fangrui Song
c6553c79ab clangIndex 2018-07-08 20:38:16 -07:00
Fangrui Song
ed1b221fab Remove submodule doctest 2018-07-08 17:20:48 -07:00
Fangrui Song
eb8acf9cdb pipeline 2018-07-08 13:40:19 -07:00
Fangrui Song
cc65ea94ed Misc changes to clangIndex 2018-07-08 12:24:42 -07:00
Fangrui Song
df02c29a7d clangIndex 2018-07-08 00:34:31 -07:00
Fangrui Song
2196e17222 Clean up clang_tu 2018-07-07 15:43:49 -07:00
Fangrui Song
7dd0241a4c First draft: replace libclang indexer with clangIndex 2018-07-07 15:43:05 -07:00
Fangrui Song
775c72b0e6 clang+llvm libs 2018-07-06 23:41:24 -07:00
Fangrui Song
f81454b9ec textDocument/references: add excludeRole 2018-07-04 19:16:56 -07:00
Fangrui Song
6a8837d612 diagnostics 2018-07-03 21:51:24 -07:00
Fangrui Song
0df5a2cd66 rename 2018-07-03 11:19:01 -07:00
Fangrui Song
8a9640a56b Add all to textDocument/documentSymbol 2018-07-01 10:19:37 -07:00
Fangrui Song
207e79ea98 Use ChangeStd{in,out}ToBinary 2018-07-01 10:19:35 -07:00
Fangrui Song
559a68a261 Display diagnostics from header files 2018-06-23 10:50:15 -07:00
Fangrui Song
c5dc759831 Put static const into IndexVar::def (a definition is not required unless odr-used) 2018-06-17 13:30:19 -07:00
Fangrui Song
6a1902aeb6 Fix .. in compdb path; better type alias 2018-06-17 12:45:16 -07:00
Fangrui Song
e5d8153d4b Internalize strings & remove diagnostics_publisher.cc 2018-06-08 10:39:38 -07:00
Fangrui Song
c8a81aeae3 Inject anonymous struct/union into parent scopes 2018-06-06 10:29:16 -07:00
Fangrui Song
134126629a Infer system include paths from CompilerInvocation 2018-06-04 16:33:03 -07:00
Fangrui Song
0decb01a0f Add Config->reparseForDependency
Specify 1 if for large projects you don't want to reparse dependents at load time when a common .h changes.
2018-06-02 00:57:18 -07:00
Fangrui Song
7b1ff448b9 Use clang::PrintingPolicy and remove type_printer 2018-06-01 17:47:03 -07:00
Fangrui Song
66580104ba Add Config->largeFileSize; pure virtual or defaulted methods are definitions; fix $ccls/callHierarchy 2018-06-01 16:06:21 -07:00
Fangrui Song
a36e548e03 Remove Timer and PerformanceImportFile 2018-05-31 23:52:51 -07:00
Fangrui Song
39a17a9fd7 Remove WithFileContent & lex_utils.{cc,h} 2018-05-31 21:07:59 -07:00
Fangrui Song
ec00f854a0 $ccls/vars: differentiate local/field/parameter 2018-05-31 19:51:58 -07:00
Fangrui Song
b35d3c8fa8 Remove $ccls/random; remove DB::symbols; decrease DB::entities grow rate 2018-05-31 12:55:13 -07:00
Fangrui Song
23c9c3a061 $ccls/memberHierarchy: add field offset 2018-05-28 21:24:42 -07:00
Fangrui Song
c9f0b65062 Simplify pipeline 2018-05-28 16:13:44 -07:00
Fangrui Song
8fabe3d1ae Replace loguru with a custom logger 2018-05-27 15:14:33 -07:00
Fangrui Song
07f0cdbf38 Simplify; improve $ccls/inheritanceHierarchy 2018-05-26 15:17:26 -07:00
Fangrui Song
32bde07df6 filesystem.cc: deduplicate with UniqueID 2018-05-20 00:42:35 -07:00
Fangrui Song
4e2f64893c textDocument/references: include base references by default 2018-05-18 08:53:50 -07:00
Fangrui Song
ba45e7ca63 cmake: link against zlib; use StringMap 2018-05-16 00:51:07 -07:00
Fangrui Song
19d0aad2ca clean up clang_complete found by scturtle; clean up project 2018-05-14 10:02:37 -07:00
Fangrui Song
576959e460 Congratulations to Tea Deliverers 2018-05-14 09:47:21 -07:00
Fangrui Song
f145c4422f experimental/filesystem -> LLVM/Support/FileSystem.h; sparsepp -> DenseMap 2018-05-13 15:24:04 -07:00
Fangrui Song
d3a36a4ae6 Use llvm::cl and simplify main.cc import_pipeline.cc 2018-05-13 12:08:33 -07:00
Fangrui Song
87dcb8ffb2 clang+llvm-6.0.0-x86_64-linux-gnu-ubuntu-14.04.tar.xz.SHA256 -> 16.04 2018-05-13 09:07:19 -07:00
Fangrui Song
224ba97f27 Use clang+llvm C++ in cmake; parse args with clang driver 2018-05-13 01:13:14 -07:00
Fangrui Song
99e7c56956 cmake: make FreeBSD 11,12 build 2018-05-12 11:45:21 -07:00
Fangrui Song
bac704f17b Backport and cleanup 2018-05-09 22:55:40 -07:00
scturtle
72433643bf Fix file_id. (#8) 2018-05-08 08:56:20 -07:00
scturtle
b55819a8a1 Random changes. (#6) 2018-05-08 00:35:32 -07:00
scturtle
04a848e065 Add CLANG_USE_BUNDLED_LIBC++ to cmake 2018-05-07 23:56:53 -07:00
Fangrui Song
a4a07120a1 Reflect optional by Brandon Tolsch 2018-05-07 20:59:08 -07:00
Fangrui Song
984c6367d1 Redesign import_pipeline.cc and mitigate race (duplicate Query*::uses for initial indexing) 2018-05-06 21:56:14 -07:00
Fangrui Song
86efddf032 README 2018-05-05 15:25:43 -07:00
Fangrui Song
ccb5cba720 . 2018-05-04 09:45:35 -07:00
Fangrui Song
d337d9bff7 . 2018-05-03 00:32:21 -07:00
Fangrui Song
fac5c56682 Use usr as primary key and remove id; simplify import pipeline
Remove on_id_map, IndexMergeIndexUpdates
2018-05-01 22:45:01 -07:00
Fangrui Song
01f1064576 Improve workspace/symbol sorting heuristic 2018-04-29 20:20:20 -07:00
Fangrui Song
d821ac34d8 Merge {timestamp_manager,iindexer}.{cc,h}; remove standard_includes.*; use last_write_time 2018-04-28 23:37:55 -07:00
Amos Bird
b4cca890c6 using SIGSTOP (avoid interactive signal blocking) (#2) 2018-04-18 00:17:24 -07:00
Fangrui Song
fa9df5bcef Remove variant and clean up 2018-04-17 00:12:59 -07:00
Fangrui Song
4d519dcbcb Update loguru 2018-04-16 00:14:54 -07:00
Fangrui Song
2c4d387222 MessagePack -> custom binary format 2018-04-15 00:40:19 -07:00
Fangrui Song
f8752cdca0 Add caseSensitivity to config->{completion,workspaceSymbol} 2018-04-14 12:38:58 -07:00
Fangrui Song
d45c057dd4 Backport recent update of completion 2018-04-14 11:38:06 -07:00
Fangrui Song
a7c89fbe21 Catch filesystem_error 2018-04-10 23:32:53 -07:00
Fangrui Song
fa4b8c78c1 Remove eyesore 2018-04-09 20:38:23 -07:00
Fangrui Song
236e7c1393 Simplify indexer and query 2018-04-09 00:52:04 -07:00
Fangrui Song
97b1592475 Simplify lsp.h and fix qual_name_offset when SetVarDetail is called on an existing variable 2018-04-09 00:31:34 -07:00
Fangrui Song
2a06fb55dd Better definition/references on #include lines 2018-04-08 10:25:50 -07:00
Fangrui Song
75638b5387 Simplify Position & Range; prettify Maybe; remove file_contents.{h,cc} 2018-04-08 01:10:56 -07:00
Fangrui Song
8d19e0a4f1 Remove import_manager.cc 2018-04-07 22:01:41 -07:00
Fangrui Song
6e68e9edbd Simplify 2018-04-07 21:04:20 -07:00
Fangrui Song
992b3cce16 Use clang_File_tryGetRealPathName 2018-04-07 16:46:22 -07:00
Fangrui Song
98a4ef5288 Import cmake improvement by Daan De Meyer 2018-04-07 10:21:07 -07:00
Fangrui Song
348240b44f Add qual_name_offset to Index* structs; improve workspace/symbol and documentSymbol 2018-04-06 00:18:02 -07:00
Fangrui Song
9f9420519e Set typeDefinitionProvider 2018-04-05 00:15:21 -07:00
Fangrui Song
1dc55843e7 Remove unused fs code. 2018-04-04 10:48:13 -07:00
Fangrui Song
d9bcaecf25 Use global config. 2018-04-04 00:29:27 -07:00
Fangrui Song
c0b4d69268 Simplify optional. 2018-04-02 00:28:18 -07:00
Fangrui Song
78250bde34 Fuzzy 2018-04-01 23:38:35 -07:00
Fangrui Song
d8fbc752d0 . 2018-03-31 17:49:32 -07:00
Fangrui Song
46fc3b8323 . 2018-03-31 16:30:53 -07:00
Fangrui Song
d83be5adcc . 2018-03-31 13:54:19 -07:00
Fangrui Song
9aca6119ed . 2018-03-31 13:38:43 -07:00
Fangrui Song
233e377137 . 2018-03-31 11:27:28 -07:00
Fangrui Song
6c8fee4141 . 2018-03-31 09:47:27 -07:00
Fangrui Song
f8a816d110 . 2018-03-31 09:07:53 -07:00
Fangrui Song
89dd4b066b . 2018-03-31 00:53:39 -07:00
Fangrui Song
da649891ae Resurrection of ccls 2018-03-30 22:02:26 -07:00
Fangrui Song
512cd8cbd3 Better textDocument/definition heuristic for T::name style dependent names 2018-03-29 22:36:54 -07:00
Chao Shen
2f0b9ccfdc Extract LLVM to specific directory. 2018-03-28 19:40:39 -07:00
Fangrui Song
7e80959ce1 Improve fuzzy matching heuristics. 2018-03-27 13:33:14 -07:00
Chao Shen
5ef55f993f Fix lost index update. 2018-03-27 13:29:48 -07:00
DaanDeMeyer
975c5646a4 Enable gcc extensions on cygwin only 2018-03-26 08:37:11 -07:00
DaanDeMeyer
5f8418cd9b enable gcc extensions (needed for cygwin support) 2018-03-26 08:20:15 -07:00
Pavel Davydov
410fb69e25 Add comment with short issue description. (#545) 2018-03-26 08:09:19 -07:00
Pavel Davydov
59769df8b5 Blacklist '-include' and '-include-pch' flags. (#545) 2018-03-26 08:09:19 -07:00
Daan De Meyer
401d542ba7 Switch to -print-resource-dir (drop support for system clang 4.0) 2018-03-26 08:07:40 -07:00
Maxim Kot
9c729e1937 UB fixed.
Forgotten return was removed.
2018-03-26 08:06:04 -07:00
Jacob Dufault
33bd27b913 Work on e2e tests 2018-03-24 10:26:17 -07:00
Jacob Dufault
4270b4fdef Minor type cleanup 2018-03-24 10:26:17 -07:00
DaanDeMeyer
a566167db6 Change default cmake build type to Release 2018-03-24 10:23:31 -07:00
Chao Shen
ddc318eef3 Preprocessor keyword completion. 2018-03-23 21:26:07 -07:00
Patrick Reisert
d87afce1fc Ignore cl's -showIncludes 2018-03-23 10:44:48 -07:00
Chao Shen
e235dbedfe Fix missing include completions. 2018-03-23 10:42:26 -07:00
Jacob Dufault
7ff4a9aac9 ipc.h -> method.h, rename some types 2018-03-21 22:01:21 -07:00
Jacob Dufault
cad6dcda0c Remove global list of message ids.
Also do some naming cleanup. Also remove xmacros.
2018-03-21 21:05:25 -07:00
Jacob Dufault
5f085729bd Fix some warnings due to missing returns 2018-03-21 21:04:41 -07:00
Boris Staletic
bdabb7596c Make overridden flags persistent
didOpen can override flags from compilation database.
didSave was able to reset the flags back.
This makes sure that the overridden flags persist.
2018-03-21 18:50:42 -07:00
Jacob Dufault
7e6965afe3 Don't use an IterationLoop for main indexer.
This causes the indexer to exit.
2018-03-21 11:57:09 -07:00
Elliot Berman
1afb6c3988 Flush sessions on textDocument/didOpen 2018-03-20 16:48:44 -07:00
Elliot Berman
958422e77f Undo changes to diagnostics engine and import pipeline
They didn't seem necessary
2018-03-20 16:48:44 -07:00
Elliot Berman
f17196e3da Flush all clang-complete sessions on workspace/didChangeConfiguration 2018-03-20 16:48:44 -07:00
Jacob Dufault
f137ec6a6d Reduce queue lengths by running index updates as iteration loop
A single translation unit can create many index updates, so give
IndexMain_DoCreateIndexUpdate a chance to run a few times. This should also be
faster as it is more icache friendly.
2018-03-20 11:55:40 -07:00
Jacob Dufault
07b3208cb4 Ensure IndexMergeIndexUpdates always returns a bool 2018-03-20 10:12:52 -07:00
Boris Staletic
c6ea1f1946 Conditionally allow reindex on didChange 2018-03-20 00:09:40 -07:00
Jacob Dufault
7c1155392a Try to prevent long queue lengths. 2018-03-19 20:01:23 -07:00
Jacob Dufault
c7e5299bee Reformat 2018-03-19 19:51:42 -07:00
Jacob Dufault
3f5e34ef20 Restore include completion on # 2018-03-19 19:50:22 -07:00
Jacob Dufault
6d42b40319 Fix crash when dynamically adding completion item 2018-03-19 17:30:21 -07:00
Jacob Dufault
0dbe8a9171 Don't use std::mismatch. Unit tests crash on Windows. 2018-03-19 17:25:00 -07:00
Jacob Dufault
b272fc427c Restore call to GetPlatformClangArguments 2018-03-19 16:48:07 -07:00
Jacob Dufault
b5bd29b702 Restore platform arguments 2018-03-19 16:25:35 -07:00
DaanDeMeyer
0365e447f0 Remove leftover comment 2018-03-19 12:05:18 -07:00
Fangrui Song
a3f66468ab Simplify ComputeGuessScore 2018-03-19 01:05:29 -07:00
Daan De Meyer
fb795d02f9 Add SHA256 hashes of LLVM downloads and check them when downloading LLVM 2018-03-18 16:32:23 -07:00
Boris Staletic
0b087421a7 Make CL mode detection fool-proof (#528) 2018-03-18 15:52:01 -07:00
Fangrui Song
b9c3af0be9 Don't include system header files for #include " (#i") completion 2018-03-18 14:56:30 -07:00
DaanDeMeyer
4d23e9fa10 [CMake] Remove byte hacks + add correct url's for different clang versions + abort for unsupported combinations (#532) 2018-03-18 13:16:42 -07:00
Fangrui Song
1e019f4984 Simplify and optimize completion. 2018-03-18 13:06:40 -07:00
Fangrui Song
639f587e01 Optimize FuzzyMatcher and add tests. 2018-03-18 12:18:16 -07:00
Boris Staletic
42b47ae77b Add -idirafter ot kPathArgs (#529) 2018-03-18 09:15:32 -07:00
Fangrui Song
9ad268af29 Use clang+llvm-6.0.0-amd64-unknown-freebsd-10.tar.xz
before 6.0.0 it was named freebsd10
2018-03-17 23:42:11 -07:00
Fangrui Song
55bfe58fea textDocument/didOpen: add args to override project entry 2018-03-17 13:24:21 -07:00
DaanDeMeyer
1882bd0256 Add CMake as alternative build system (#526) 2018-03-17 12:03:41 -07:00
Fangrui Song
38f7195280 Update tests and README 2018-03-17 10:45:49 -07:00
Fangrui Song
1d6c718bae Fix skipped region in clang 6.0.0; add repology badge; remove -latomic 2018-03-17 10:07:05 -07:00
Fangrui Song
4e8d21e306 Fix g++ build 2018-03-16 08:30:31 -07:00
Fangrui Song
552658f967 Variant of clangd fuzzy matcher 2018-03-16 00:41:58 -07:00
68 changed files with 5763 additions and 4804 deletions

View File

@ -1,2 +1 @@
BasedOnStyle: LLVM
ColumnLimit: 120

17
.gitignore vendored
View File

@ -1,11 +1,6 @@
/*
!/.github/
!/index_tests/
!/src/
!/third_party/
!/.appveyor.yml
!/.clang-format
!/CMakeLists.txt
!/LICENSE
!/README.md
!/meow/
.*
build
debug
release
/compile_commands.json
!.github/

View File

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.8)
project(ccls LANGUAGES CXX C)
project(ccls LANGUAGES CXX)
option(USE_SYSTEM_RAPIDJSON "Use system RapidJSON instead of the git submodule if exists" ON)
@ -45,7 +45,6 @@ if(MSVC)
/wd4800
/wd4068 # Disable unknown pragma warning
/std:c++17
/Zc:__cplusplus
$<$<CONFIG:Debug>:/FS>
)
# relink system libs
@ -71,33 +70,27 @@ endif()
find_package(Clang REQUIRED)
if(CLANG_LINK_CLANG_DYLIB)
target_link_libraries(ccls PRIVATE clang-cpp)
else()
target_link_libraries(ccls PRIVATE
clangIndex
clangFormat
clangTooling
clangToolingInclusions
clangToolingCore
clangFrontend
clangParse
clangSerialization
clangSema
clangAST
clangLex
clangDriver
clangBasic
)
endif()
target_link_libraries(ccls PRIVATE
clangIndex
clangFormat
clangTooling
clangToolingInclusions
clangToolingCore
clangFrontend
clangParse
clangSerialization
clangSema
clangAST
clangLex
clangDriver
clangBasic
)
if(LLVM_LINK_LLVM_DYLIB)
target_link_libraries(ccls PRIVATE LLVM)
else()
# In llvm 7, clangDriver headers reference LLVMOption
target_link_libraries(ccls PRIVATE LLVMOption LLVMSupport)
if(LLVM_VERSION_MAJOR GREATER_EQUAL 16) # llvmorg-16-init-15123-gf09cf34d0062
target_link_libraries(ccls PRIVATE LLVMTargetParser)
endif()
endif()
if(NOT LLVM_ENABLE_RTTI)
@ -123,32 +116,26 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD)
target_link_libraries(ccls PRIVATE thr)
endif()
if(LLVM_ENABLE_ZLIB)
find_package(ZLIB)
endif()
### Definitions
# Find Clang resource directory with Clang executable.
if(NOT CLANG_RESOURCE_DIR)
find_program(CLANG_EXECUTABLE clang)
if(NOT CLANG_EXECUTABLE)
message(FATAL_ERROR "clang executable not found.")
endif()
find_program(CLANG_EXECUTABLE clang)
if(NOT CLANG_EXECUTABLE)
message(FATAL_ERROR "clang executable not found.")
endif()
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
)
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()
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()
set_property(SOURCE src/utils.cc APPEND PROPERTY COMPILE_DEFINITIONS
@ -156,36 +143,17 @@ set_property(SOURCE src/utils.cc APPEND PROPERTY COMPILE_DEFINITIONS
### Includes
target_include_directories(ccls PRIVATE src)
target_include_directories(ccls SYSTEM PRIVATE
third_party ${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS})
if(USE_SYSTEM_RAPIDJSON)
set(RapidJSON_MIN_VERSION "1.1.0")
find_package(RapidJSON ${RapidJSON_MIN_VERSION} QUIET)
if(NOT DEFINED RapidJSON_INCLUDE_DIRS AND DEFINED RAPIDJSON_INCLUDE_DIRS)
set(RapidJSON_INCLUDE_DIRS "${RAPIDJSON_INCLUDE_DIRS}")
endif()
find_package(RapidJSON QUIET)
endif()
if(NOT RapidJSON_FOUND)
if(EXISTS "${CMAKE_SOURCE_DIR}/third_party/rapidjson/include")
message(STATUS "Using local RapidJSON")
set(RapidJSON_INCLUDE_DIRS third_party/rapidjson/include)
else()
message(STATUS "Plase initialize rapidJSON git submodule as currently installed version is to old:")
message(STATUS "git submodule init && git submodule update")
message(FATAL_ERROR "RapidJSON version is likely too old. See https://github.com/MaskRay/ccls/issues/455")
endif()
set(RapidJSON_INCLUDE_DIRS third_party/rapidjson/include)
endif()
target_include_directories(ccls PRIVATE src)
foreach(include_dir third_party
${LLVM_INCLUDE_DIRS} ${CLANG_INCLUDE_DIRS} ${RapidJSON_INCLUDE_DIRS})
get_filename_component(include_dir_realpath ${include_dir} REALPATH)
# Don't add as SYSTEM if they are in CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES.
# It would reorder the system search paths and cause issues with libstdc++'s
# use of #include_next. See https://github.com/MaskRay/ccls/pull/417
if(NOT "${include_dir_realpath}" IN_LIST CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES)
target_include_directories(ccls SYSTEM PRIVATE ${include_dir})
endif()
endforeach()
target_include_directories(ccls SYSTEM PRIVATE ${RapidJSON_INCLUDE_DIRS})
### Install
@ -201,6 +169,7 @@ target_sources(ccls PRIVATE
src/filesystem.cc
src/fuzzy_match.cc
src/main.cc
src/include_complete.cc
src/indexer.cc
src/log.cc
src/lsp.cc
@ -257,5 +226,3 @@ endif()
set_property(SOURCE src/main.cc APPEND PROPERTY
COMPILE_DEFINITIONS CCLS_VERSION=\"${CCLS_VERSION}\")
set_property(SOURCE src/messages/initialize.cc APPEND PROPERTY
COMPILE_DEFINITIONS CCLS_VERSION=\"${CCLS_VERSION}\")

View File

@ -1,6 +1,6 @@
# ccls
[![Telegram](https://img.shields.io/badge/telegram-@cclsp-blue.svg)](https://telegram.me/ccls_lsp)
[![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)
ccls, which originates from [cquery](https://github.com/cquery-project/cquery), is a C/C++/Objective-C language server.
@ -11,7 +11,7 @@ ccls, which originates from [cquery](https://github.com/cquery-project/cquery),
* 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_document.cc) and approximate search of [workspace symbol](src/messages/workspace.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
@ -21,9 +21,26 @@ It has a global view of the code base and support a lot of cross reference featu
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/Home) (CLICK HERE) <<<
* [Build](../../wiki/Build)
* [FAQ](../../wiki/FAQ)
ccls can index itself (~180MiB RSS when idle, noted on 2018-09-01), FreeBSD, glibc, Linux, LLVM (~1800MiB RSS), musl (~60MiB RSS), ... with decent memory footprint. See [wiki/Project-Setup](../../wiki/Project-Setup) for examples.
ccls can index itself (~180MiB RSS when idle, 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.

45
ci/before_deploy.sh Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env bash
root=$(cd "$(dirname "$0")/.."; pwd)
version=$(TZ=UTC date +v%Y%m%d)
cd "$root/build/release"
case $(uname -s) in
Darwin)
libclang=(lib/clang+llvm-*/lib/libclang.dylib)
strip_option="-x"
name=ccls-$version-x86_64-apple-darwin ;;
FreeBSD)
libclang=(lib/clang+llvm-*/lib/libclang.so.?)
strip_option="-s"
name=ccls-$version-x86_64-unknown-freebsd10 ;;
Linux)
libclang=(lib/clang+llvm-*/lib/libclang.so.?)
strip_option="-s"
name=ccls-$version-x86_64-unknown-linux-gnu ;;
*)
echo Unsupported >&2
exit 1 ;;
esac
pkg=$(mktemp -d)
mkdir "$pkg/$name"
rsync -rtLR bin "./${libclang[-1]}" ./lib/clang+llvm-*/lib/clang/*/include "$pkg/$name"
cd "$pkg"
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
# macOS's bsdtar is lack of flags to set uid/gid.
# First, we generate a list of file in mtree format.
tar -cf filelist --format=mtree --options="!all,time,mode,type" "$name"
# Then add a line "/set uid=0 gid=0" after the first line "#mtree".
awk '/#mtree/{print;print "/set uid=0 gid=0";next}1' filelist > newflielist
# Finally, use the list to generate the tarball.
tar -zcf "$root/build/$name.tar.gz" @newflielist ;;
Linux)
tar -Jcf "$root/build/$name.tar.xz" --owner 0 --group 0 $name ;;
*)
tar -Jcf "$root/build/$name.tar.xz" --uid 0 --gid 0 $name ;;
esac
rm -r "$pkg"

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -7,196 +19,112 @@
#include "platform.hh"
#include <clang/AST/Type.h>
#include <clang/Driver/Action.h>
#include <clang/Driver/Compilation.h>
#include <clang/Driver/Driver.h>
#include <clang/Driver/Tool.h>
#include <clang/Lex/Lexer.h>
#include <clang/Lex/PreprocessorOptions.h>
#if LLVM_VERSION_MAJOR >= 16 // llvmorg-16-init-15123-gf09cf34d0062
#include <llvm/TargetParser/Host.h>
#else
#include <llvm/Support/Host.h>
#endif
#include <llvm/Support/Path.h>
using namespace clang;
namespace ccls {
#if LLVM_VERSION_MAJOR < 19
std::string pathFromFileEntry(const FileEntry &file) {
#else
std::string pathFromFileEntry(FileEntryRef file) {
#endif
std::string ret;
if (file.getName().startswith("/../")) {
// Resolve symlinks outside of working folders. This handles leading path
// components, e.g. (/lib -> /usr/lib) in
// /../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/utility
#if LLVM_VERSION_MAJOR < 19
ret = file.tryGetRealPathName();
#else
ret = file.getFileEntry().tryGetRealPathName();
#endif
} else {
// If getName() refers to a file within a workspace folder, we prefer it
// (which may be a symlink).
ret = normalizePath(file.getName());
}
if (normalizeFolder(ret))
return ret;
ret = realPath(ret);
normalizeFolder(ret);
return ret;
std::string PathFromFileEntry(const FileEntry &file) {
StringRef Name = file.tryGetRealPathName();
if (Name.empty())
Name = file.getName();
std::string ret = NormalizePath(Name);
// Resolve symlinks outside of workspace folders, e.g. /usr/include/c++/7.3.0
return NormalizeFolder(ret) ? ret : RealPath(ret);
}
bool isInsideMainFile(const SourceManager &sm, SourceLocation sl) {
if (!sl.isValid())
return false;
FileID fid = sm.getFileID(sm.getExpansionLoc(sl));
return fid == sm.getMainFileID() || fid == sm.getPreambleFileID();
}
static Pos decomposed2LineAndCol(const SourceManager &sm, std::pair<FileID, unsigned> i) {
int l = (int)sm.getLineNumber(i.first, i.second) - 1, c = (int)sm.getColumnNumber(i.first, i.second) - 1;
bool invalid = false;
StringRef buf = sm.getBufferData(i.first, &invalid);
if (!invalid) {
StringRef p = buf.substr(i.second - c, c);
static Pos Decomposed2LineAndCol(const SourceManager &SM,
std::pair<FileID, unsigned> I) {
int l = SM.getLineNumber(I.first, I.second) - 1,
c = SM.getColumnNumber(I.first, I.second) - 1;
bool Invalid = false;
StringRef Buf = SM.getBufferData(I.first, &Invalid);
if (!Invalid) {
StringRef P = Buf.substr(I.second - c, c);
c = 0;
for (size_t i = 0; i < p.size();)
if (c++, (uint8_t)p[i++] >= 128)
while (i < p.size() && (uint8_t)p[i] >= 128 && (uint8_t)p[i] < 192)
for (size_t i = 0; i < P.size(); )
if (c++, (uint8_t)P[i++] >= 128)
while (i < P.size() && (uint8_t)P[i] >= 128 && (uint8_t)P[i] < 192)
i++;
}
return {(uint16_t)std::min<int>(l, UINT16_MAX), (int16_t)std::min<int>(c, INT16_MAX)};
return {(uint16_t)std::min<int>(l, UINT16_MAX),
(int16_t)std::min<int>(c, INT16_MAX)};
}
Range fromCharSourceRange(const SourceManager &sm, const LangOptions &lang, CharSourceRange csr, FileID *fid) {
SourceLocation bloc = csr.getBegin(), eloc = csr.getEnd();
std::pair<FileID, unsigned> binfo = sm.getDecomposedLoc(bloc), einfo = sm.getDecomposedLoc(eloc);
if (csr.isTokenRange())
einfo.second += Lexer::MeasureTokenLength(eloc, sm, lang);
if (fid)
*fid = binfo.first;
return {decomposed2LineAndCol(sm, binfo), decomposed2LineAndCol(sm, einfo)};
Range FromCharSourceRange(const SourceManager &SM, const LangOptions &LangOpts,
CharSourceRange R,
llvm::sys::fs::UniqueID *UniqueID) {
SourceLocation BLoc = R.getBegin(), ELoc = R.getEnd();
std::pair<FileID, unsigned> 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 fromTokenRange(const SourceManager &sm, const LangOptions &lang, SourceRange sr, FileID *fid) {
return fromCharSourceRange(sm, lang, CharSourceRange::getTokenRange(sr), fid);
Range FromCharRange(const SourceManager &SM, const LangOptions &Lang,
SourceRange R, llvm::sys::fs::UniqueID *UniqueID) {
return FromCharSourceRange(SM, Lang, CharSourceRange::getCharRange(R),
UniqueID);
}
Range fromTokenRangeDefaulted(const SourceManager &sm, const LangOptions &lang, SourceRange sr, FileID fid,
Range range) {
auto decomposed = sm.getDecomposedLoc(sm.getExpansionLoc(sr.getBegin()));
if (decomposed.first == fid)
range.start = decomposed2LineAndCol(sm, decomposed);
SourceLocation sl = sm.getExpansionLoc(sr.getEnd());
decomposed = sm.getDecomposedLoc(sl);
if (decomposed.first == fid) {
decomposed.second += Lexer::MeasureTokenLength(sl, sm, lang);
range.end = decomposed2LineAndCol(sm, decomposed);
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<CompilerInvocation> buildCompilerInvocation(const std::string &main, std::vector<const char *> args,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs) {
std::unique_ptr<CompilerInvocation>
BuildCompilerInvocation(const std::string &main, std::vector<const char *> args,
IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
std::string save = "-resource-dir=" + g_config->clang.resourceDir;
args.push_back(save.c_str());
args.push_back("-fsyntax-only");
// Similar to clang/tools/driver/driver.cpp:insertTargetAndModeArgs but don't
// require llvm::InitializeAllTargetInfos().
auto target_and_mode = driver::ToolChain::getTargetAndModeFromProgramName(args[0]);
if (target_and_mode.DriverMode)
args.insert(args.begin() + 1, target_and_mode.DriverMode);
if (!target_and_mode.TargetPrefix.empty()) {
const char *arr[] = {"-target", target_and_mode.TargetPrefix.c_str()};
args.insert(args.begin() + 1, std::begin(arr), std::end(arr));
IntrusiveRefCntPtr<DiagnosticsEngine> Diags(
CompilerInstance::createDiagnostics(new DiagnosticOptions,
new IgnoringDiagConsumer, true));
std::unique_ptr<CompilerInvocation> CI =
createInvocationFromCommandLine(args, Diags, VFS);
if (CI) {
CI->getDiagnosticOpts().IgnoreWarnings = true;
CI->getFrontendOpts().DisableFree = false;
CI->getLangOpts()->SpellChecking = false;
auto &IS = CI->getFrontendOpts().Inputs;
if (IS.size())
IS[0] = FrontendInputFile(main, IS[0].getKind(), IS[0].isSystem());
}
IntrusiveRefCntPtr<DiagnosticsEngine> diags(CompilerInstance::createDiagnostics(
#if LLVM_VERSION_MAJOR >= 20
*vfs,
#endif
new DiagnosticOptions, new IgnoringDiagConsumer, true));
#if LLVM_VERSION_MAJOR < 12 // llvmorg-12-init-5498-g257b29715bb
driver::Driver d(args[0], llvm::sys::getDefaultTargetTriple(), *diags, vfs);
#else
driver::Driver d(args[0], llvm::sys::getDefaultTargetTriple(), *diags, "ccls", vfs);
#endif
d.setCheckInputsExist(false);
#if LLVM_VERSION_MAJOR >= 15
// For -include b.hh, don't probe b.hh.{gch,pch} and change to -include-pch.
d.setProbePrecompiled(false);
#endif
std::unique_ptr<driver::Compilation> comp(d.BuildCompilation(args));
if (!comp)
return nullptr;
const driver::JobList &jobs = comp->getJobs();
bool offload_compilation = false;
if (jobs.size() > 1) {
for (auto &a : comp->getActions()) {
// On MacOSX real actions may end up being wrapped in BindArchAction
if (isa<driver::BindArchAction>(a))
a = *a->input_begin();
if (isa<driver::OffloadAction>(a)) {
offload_compilation = true;
break;
}
}
if (!offload_compilation)
return nullptr;
}
if (jobs.size() == 0 || !isa<driver::Command>(*jobs.begin()))
return nullptr;
const driver::Command &cmd = cast<driver::Command>(*jobs.begin());
if (StringRef(cmd.getCreator().getName()) != "clang")
return nullptr;
const llvm::opt::ArgStringList &cc_args = cmd.getArguments();
auto ci = std::make_unique<CompilerInvocation>();
if (!CompilerInvocation::CreateFromArgs(*ci, cc_args, *diags))
return nullptr;
ci->getDiagnosticOpts().IgnoreWarnings = true;
ci->getFrontendOpts().DisableFree = false;
// Enable IndexFrontendAction::shouldSkipFunctionBody.
ci->getFrontendOpts().SkipFunctionBodies = true;
#if LLVM_VERSION_MAJOR >= 18
ci->getLangOpts().SpellChecking = false;
ci->getLangOpts().RecoveryAST = true;
ci->getLangOpts().RecoveryASTType = true;
#else
ci->getLangOpts()->SpellChecking = false;
#if LLVM_VERSION_MAJOR >= 11
ci->getLangOpts()->RecoveryAST = true;
ci->getLangOpts()->RecoveryASTType = true;
#endif
#endif
auto &isec = ci->getFrontendOpts().Inputs;
if (isec.size())
isec[0] = FrontendInputFile(main, isec[0].getKind(), isec[0].isSystem());
ci->getPreprocessorOpts().DisablePragmaDebugCrash = true;
// clangSerialization has an unstable format. Disable PCH reading/writing
// to work around PCH mismatch problems.
ci->getPreprocessorOpts().ImplicitPCHInclude.clear();
ci->getPreprocessorOpts().PrecompiledPreambleBytes = {0, false};
ci->getPreprocessorOpts().PCHThroughHeader.clear();
ci->getHeaderSearchOpts().ModuleFormat = "raw";
return ci;
return CI;
}
// clang::BuiltinType::getName without PrintingPolicy
const char *clangBuiltinTypeName(int kind) {
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:
@ -324,12 +252,8 @@ const char *clangBuiltinTypeName(int kind) {
return "queue_t";
case BuiltinType::OCLReserveID:
return "reserve_id_t";
#if LLVM_VERSION_MAJOR >= 19 // llvmorg-19-init-9465-g39adc8f42329
case BuiltinType::ArraySection:
#else
case BuiltinType::OMPArraySection:
#endif
return "<array section type>";
return "<OpenMP array section type>";
default:
return "";
}

View File

@ -1,45 +1,59 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 <clang/Basic/FileManager.h>
#include <clang/Basic/LangOptions.h>
#include <clang/Basic/FileManager.h>
#include <clang/Basic/SourceManager.h>
#include <clang/Frontend/CompilerInstance.h>
#if LLVM_VERSION_MAJOR < 14 // llvmorg-14-init-3863-g601102d282d5
#define isAsciiIdentifierContinue isIdentifierBody
#endif
#if LLVM_VERSION_MAJOR >= 19
#define startswith starts_with
#define endswith ends_with
#if LLVM_VERSION_MAJOR < 8
// D52783 Lift VFS from clang to llvm
namespace llvm {
namespace vfs = clang::vfs;
}
#endif
namespace ccls {
#if LLVM_VERSION_MAJOR < 19
std::string pathFromFileEntry(const clang::FileEntry &file);
#else
std::string pathFromFileEntry(clang::FileEntryRef file);
#endif
std::string PathFromFileEntry(const clang::FileEntry &file);
bool isInsideMainFile(const clang::SourceManager &sm, clang::SourceLocation sl);
Range FromCharSourceRange(const clang::SourceManager &SM,
const clang::LangOptions &LangOpts,
clang::CharSourceRange R,
llvm::sys::fs::UniqueID *UniqueID = nullptr);
Range fromCharSourceRange(const clang::SourceManager &sm, const clang::LangOptions &lang, clang::CharSourceRange csr,
clang::FileID *fid = 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 &lang, clang::SourceRange sr,
clang::FileID *fid = 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 sr,
clang::FileID fid, Range range);
Range FromTokenRangeDefaulted(const clang::SourceManager &SM,
const clang::LangOptions &Lang,
clang::SourceRange R, const clang::FileEntry *FE,
Range range);
std::unique_ptr<clang::CompilerInvocation> buildCompilerInvocation(const std::string &main,
std::vector<const char *> args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS);
std::unique_ptr<clang::CompilerInvocation>
BuildCompilerInvocation(const std::string &main,
std::vector<const char *> args,
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS);
const char *clangBuiltinTypeName(int);
const char *ClangBuiltinTypeName(int);
} // namespace ccls

View File

@ -1,19 +1,31 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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) {
void DoPathMapping(std::string &arg) {
for (const std::string &mapping : g_config->clang.pathMappings) {
auto sep = mapping.find('>');
if (sep != std::string::npos) {
auto p = arg.find(mapping.substr(0, sep));
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, sep, mapping.substr(sep + 1));
arg.replace(p, colon, mapping.substr(colon + 1));
}
}
}
} // namespace ccls
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -111,18 +123,14 @@ struct Config {
} clang;
struct ClientCapability {
// TextDocumentClientCapabilities.publishDiagnostics.relatedInformation
bool diagnosticsRelatedInformation = true;
// TextDocumentClientCapabilities.documentSymbol.hierarchicalDocumentSymbolSupport
bool hierarchicalDocumentSymbolSupport = true;
// TextDocumentClientCapabilities.definition.linkSupport
bool linkSupport = true;
// ClientCapabilities.workspace.semanticTokens.refreshSupport
bool semanticTokensRefresh = true;
// If false, disable snippets and complete just the identifier part.
// TextDocumentClientCapabilities.completion.completionItem.snippetSupport
bool snippetSupport = true;
// TextDocumentClientCapabilities.publishDiagnostics.relatedInformation
bool diagnosticsRelatedInformation = true;
} client;
struct CodeLens {
@ -168,6 +176,9 @@ struct Config {
// 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.
@ -189,15 +200,6 @@ struct Config {
std::vector<std::string> whitelist;
} include;
// Maxmum number of results.
int maxNum = 100;
// Add placeholder text. Effective only if client.snippetSupport is true.
//
// false: foo($1)$0
// true: foo(${1:int a}, ${2:int b})$0
bool placeholder = true;
} completion;
struct Diagnostics {
@ -228,9 +230,8 @@ struct Config {
// Disable semantic highlighting for files larger than the size.
int64_t largeFileSize = 2 * 1024 * 1024;
// If non-zero, enable rainbow semantic tokens by assinging an extra modifier
// indicating the rainbow ID to each symbol.
int rainbow = 0;
// true: LSP line/character; false: position
bool lsRanges = false;
// Like index.{whitelist,blacklist}, don't publish semantic highlighting to
// blacklisted files.
@ -267,8 +268,7 @@ struct Config {
// lines, include the initializer in detailed_name.
int maxInitializerLines = 5;
// If not 0, a file will be indexed in each tranlation unit that includes
// it.
// 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
@ -324,32 +324,44 @@ struct Config {
int maxNum = 2000;
} xref;
};
REFLECT_STRUCT(Config::Cache, directory, format, hierarchicalPath, retainInMemory);
REFLECT_STRUCT(Config::ServerCap::DocumentOnTypeFormattingOptions, firstTriggerCharacter, moreTriggerCharacter);
REFLECT_STRUCT(Config::ServerCap::Workspace::WorkspaceFolders, supported, changeNotifications);
REFLECT_STRUCT(Config::Cache, directory, format, hierarchicalPath,
retainInMemory);
REFLECT_STRUCT(Config::ServerCap::DocumentOnTypeFormattingOptions,
firstTriggerCharacter, moreTriggerCharacter);
REFLECT_STRUCT(Config::ServerCap::Workspace::WorkspaceFolders, supported,
changeNotifications);
REFLECT_STRUCT(Config::ServerCap::Workspace, workspaceFolders);
REFLECT_STRUCT(Config::ServerCap, documentOnTypeFormattingProvider, foldingRangeProvider, workspace);
REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, pathMappings, resourceDir);
REFLECT_STRUCT(Config::ClientCapability, diagnosticsRelatedInformation, hierarchicalDocumentSymbolSupport, linkSupport,
snippetSupport);
REFLECT_STRUCT(Config::ServerCap, documentOnTypeFormattingProvider,
foldingRangeProvider, workspace);
REFLECT_STRUCT(Config::Clang, excludeArgs, extraArgs, pathMappings,
resourceDir);
REFLECT_STRUCT(Config::ClientCapability, hierarchicalDocumentSymbolSupport,
linkSupport, 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, placeholder);
REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave, spellChecking, whitelist)
REFLECT_STRUCT(Config::Highlight, largeFileSize, rainbow, blacklist, whitelist)
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::Name, suppressUnwrittenScope);
REFLECT_STRUCT(Config::Index, blacklist, comments, initialNoLinkage, initialBlacklist, initialWhitelist,
maxInitializerLines, multiVersion, multiVersionBlacklist, multiVersionWhitelist, name, onChange,
parametersInDeclarations, threads, trackDependency, whitelist);
REFLECT_STRUCT(Config::Index, blacklist, comments, initialNoLinkage,
initialBlacklist, initialWhitelist, maxInitializerLines,
multiVersion, multiVersionBlacklist, multiVersionWhitelist, name,
onChange, parametersInDeclarations, threads, trackDependency,
whitelist);
REFLECT_STRUCT(Config::Request, timeout);
REFLECT_STRUCT(Config::Session, maxNum);
REFLECT_STRUCT(Config::WorkspaceSymbol, caseSensitivity, maxNum, sort);
REFLECT_STRUCT(Config::Xref, maxNum);
REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory, cache, capabilities, clang, client,
codeLens, completion, diagnostics, highlight, index, request, session, workspaceSymbol, xref);
REFLECT_STRUCT(Config, compilationDatabaseCommand, compilationDatabaseDirectory,
cache, capabilities, clang, client, codeLens, completion,
diagnostics, highlight, index, request, session, workspaceSymbol,
xref);
extern Config *g_config;
void doPathMapping(std::string &arg);
} // namespace ccls
void DoPathMapping(std::string &arg);
}

View File

@ -1,36 +0,0 @@
#ifndef TOKEN_MODIFIER
#define TOKEN_MODIFIER(name, str)
#endif
// vscode
TOKEN_MODIFIER(Declaration, "declaration")
TOKEN_MODIFIER(Definition, "definition")
TOKEN_MODIFIER(Static, "static")
// ccls extensions
TOKEN_MODIFIER(Read, "read")
TOKEN_MODIFIER(Write, "write")
TOKEN_MODIFIER(ClassScope, "classScope")
TOKEN_MODIFIER(FunctionScope, "functionScope")
TOKEN_MODIFIER(NamespaceScope, "namespaceScope")
// Rainbow semantic tokens
TOKEN_MODIFIER(Id0, "id0")
TOKEN_MODIFIER(Id1, "id1")
TOKEN_MODIFIER(Id2, "id2")
TOKEN_MODIFIER(Id3, "id3")
TOKEN_MODIFIER(Id4, "id4")
TOKEN_MODIFIER(Id5, "id5")
TOKEN_MODIFIER(Id6, "id6")
TOKEN_MODIFIER(Id7, "id7")
TOKEN_MODIFIER(Id8, "id8")
TOKEN_MODIFIER(Id9, "id9")
TOKEN_MODIFIER(Id10, "id10")
TOKEN_MODIFIER(Id11, "id11")
TOKEN_MODIFIER(Id12, "id12")
TOKEN_MODIFIER(Id13, "id13")
TOKEN_MODIFIER(Id14, "id14")
TOKEN_MODIFIER(Id15, "id15")
TOKEN_MODIFIER(Id16, "id16")
TOKEN_MODIFIER(Id17, "id17")
TOKEN_MODIFIER(Id18, "id18")
TOKEN_MODIFIER(Id19, "id19")

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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;
@ -9,16 +21,16 @@ using namespace llvm;
#include <set>
#include <vector>
void getFilesInFolder(std::string folder, bool recursive, bool dir_prefix,
void GetFilesInFolder(std::string folder, bool recursive, bool dir_prefix,
const std::function<void(const std::string &)> &handler) {
ccls::ensureEndsInSlash(folder);
sys::fs::file_status status;
if (sys::fs::status(folder, status, true))
ccls::EnsureEndsInSlash(folder);
sys::fs::file_status Status;
if (sys::fs::status(folder, Status, true))
return;
sys::fs::UniqueID id;
sys::fs::UniqueID ID;
std::vector<std::string> curr{folder};
std::vector<std::pair<std::string, sys::fs::file_status>> succ;
std::set<sys::fs::UniqueID> seen{status.getUniqueID()};
std::set<sys::fs::UniqueID> seen{Status.getUniqueID()};
while (curr.size() || succ.size()) {
if (curr.empty()) {
for (auto &it : succ)
@ -29,27 +41,29 @@ void getFilesInFolder(std::string folder, bool recursive, bool dir_prefix,
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();
std::string filename(sys::path::filename(path));
if ((filename[0] == '.' && filename != ".ccls") || sys::fs::status(path, status, false))
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))
if (sys::fs::is_symlink_file(Status)) {
if (sys::fs::status(path, Status, true))
continue;
if (sys::fs::is_directory(status)) {
if (sys::fs::is_directory(Status)) {
if (recursive)
succ.emplace_back(path, status);
succ.emplace_back(path, Status);
continue;
}
}
if (sys::fs::is_regular_file(status)) {
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())) {
} else if (recursive && sys::fs::is_directory(Status) &&
!seen.count(ID = Status.getUniqueID())) {
curr.push_back(path);
seen.insert(id);
seen.insert(ID);
}
}
}

View File

@ -1,6 +1,3 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <llvm/Support/FileSystem.h>
@ -9,5 +6,7 @@
#include <functional>
#include <string>
void getFilesInFolder(std::string folder, bool recursive, bool add_folder_to_path,
const std::function<void(const std::string &)> &handler);
void GetFilesInFolder(std::string folder,
bool recursive,
bool add_folder_to_path,
const std::function<void(const std::string&)>& handler);

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -13,7 +25,7 @@ namespace {
enum CharClass { Other, Lower, Upper };
enum CharRole { None, Tail, Head };
CharClass getCharClass(int c) {
CharClass GetCharClass(int c) {
if (islower(c))
return Lower;
if (isupper(c))
@ -21,21 +33,23 @@ CharClass getCharClass(int c) {
return Other;
}
void calculateRoles(std::string_view s, int roles[], int *class_set) {
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;
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;
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]);
suc = GetCharClass(s[i + 1]);
*class_set |= 1 << suc;
roles[i] = fn();
pre = cur;
@ -45,7 +59,7 @@ void calculateRoles(std::string_view s, int roles[], int *class_set) {
}
} // namespace
int FuzzyMatcher::missScore(int j, bool last) {
int FuzzyMatcher::MissScore(int j, bool last) {
int s = -3;
if (last)
s -= 10;
@ -54,7 +68,7 @@ int FuzzyMatcher::missScore(int j, bool last) {
return s;
}
int FuzzyMatcher::matchScore(int i, int j, bool last) {
int FuzzyMatcher::MatchScore(int i, int j, bool last) {
int s = 0;
// Case matching.
if (pat[i] == text[j]) {
@ -79,7 +93,7 @@ int FuzzyMatcher::matchScore(int i, int j, bool last) {
}
FuzzyMatcher::FuzzyMatcher(std::string_view pattern, int sensitivity) {
calculateRoles(pattern, pat_role, &pat_set);
CalculateRoles(pattern, pat_role, &pat_set);
if (sensitivity == 1)
sensitivity = pat_set & 1 << Upper ? 2 : 0;
case_sensitivity = sensitivity;
@ -87,13 +101,13 @@ FuzzyMatcher::FuzzyMatcher(std::string_view pattern, int sensitivity) {
for (size_t i = 0; i < pattern.size(); i++)
if (pattern[i] != ' ') {
pat += pattern[i];
low_pat[n] = (char)::tolower(pattern[i]);
low_pat[n] = ::tolower(pattern[i]);
pat_role[n] = pat_role[i];
n++;
}
}
int FuzzyMatcher::match(std::string_view text, bool strict) {
int FuzzyMatcher::Match(std::string_view text, bool strict) {
if (pat.empty() != text.empty())
return kMinScore;
int n = int(text.size());
@ -101,13 +115,13 @@ int FuzzyMatcher::match(std::string_view text, bool strict) {
return kMinScore + 1;
this->text = text;
for (int i = 0; i < n; i++)
low_text[i] = (char)::tolower(text[i]);
calculateRoles(text, text_role, &text_set);
low_text[i] = ::tolower(text[i]);
CalculateRoles(text, text_role, &text_set);
if (strict && n && !!pat_role[0] != !!text_role[0])
return kMinScore;
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][0] = dp[0][j][0] + MissScore(j, false);
dp[0][j + 1][1] = kMinScore * 2;
}
for (int i = 0; i < int(pat.size()); i++) {
@ -115,12 +129,16 @@ int FuzzyMatcher::match(std::string_view text, bool strict) {
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));
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))
: 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;
}
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -17,7 +29,7 @@ public:
constexpr static int kMinScore = INT_MIN / 4;
FuzzyMatcher(std::string_view pattern, int case_sensitivity);
int match(std::string_view text, bool strict);
int Match(std::string_view text, bool strict);
private:
int case_sensitivity;
@ -28,7 +40,7 @@ private:
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);
int MatchScore(int i, int j, bool last);
int MissScore(int j, bool last);
};
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -9,7 +21,8 @@
#include <queue>
namespace ccls {
template <typename Node> std::vector<Location> flattenHierarchy(const std::optional<Node> &root) {
template <typename Node>
std::vector<Location> FlattenHierarchy(const std::optional<Node> &root) {
if (!root)
return {};
std::vector<Location> ret;

200
src/include_complete.cc Normal file
View File

@ -0,0 +1,200 @@
/* 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 "filesystem.hh"
#include "platform.hh"
#include "project.hh"
#include <llvm/ADT/Twine.h>
#include <llvm/Support/Threading.h>
#include <llvm/Support/Timer.h>
#include <unordered_set>
using namespace llvm;
#include <thread>
namespace ccls {
namespace {
struct CompletionCandidate {
std::string absolute_path;
CompletionItem completion_item;
};
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() - g_config->completion.include.maxPathSize;
return ".." + path.substr(start + 2);
}
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
return 0;
}
int TrimPath(Project *project, std::string &path) {
size_t pos = 0;
int kind = 0;
for (auto &[root, folder] : project->root2folder)
for (auto &[search, search_dir_kind] : folder.search_dir2kind)
if (int t = TrimCommonPathPrefix(path, search); t > pos)
pos = t, kind = search_dir_kind;
path = path.substr(pos);
return kind;
}
CompletionItem BuildCompletionItem(const std::string &path,
int kind) {
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.kind = CompletionItemKind::File;
item.quote_kind_ = kind;
item.priority_ = 0;
return item;
}
} // namespace
IncludeComplete::IncludeComplete(Project *project)
: is_scanning(false), 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 || LLVM_VERSION_MAJOR >= 8)
return;
completion_items.clear();
absolute_path_to_completion_item.clear();
inserted_paths.clear();
if (!match_ && (g_config->completion.include.whitelist.size() ||
g_config->completion.include.blacklist.size()))
match_ =
std::make_unique<GroupMatch>(g_config->completion.include.whitelist,
g_config->completion.include.blacklist);
is_scanning = true;
std::thread([this]() {
set_thread_name("include");
for (auto &[root, folder] : project_->root2folder) {
for (auto &search_kind : folder.search_dir2kind) {
const std::string &search = search_kind.first;
int kind = search_kind.second;
assert(search.back() == '/');
if (match_ && !match_->Matches(search))
return;
bool include_cpp = search.find("include/c++") != std::string::npos;
std::vector<CompletionCandidate> results;
GetFilesInFolder(search, true /*recursive*/,
false /*add_folder_to_path*/,
[&](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_->Matches(search + path))
return;
CompletionCandidate candidate;
candidate.absolute_path = search + path;
candidate.completion_item =
BuildCompletionItem(path, kind);
results.push_back(candidate);
});
std::lock_guard lock(completion_items_mutex);
for (CompletionCandidate &result : results)
InsertCompletionItem(result.absolute_path,
std::move(result.completion_item));
}
}
is_scanning = false;
})
.detach();
}
void IncludeComplete::InsertCompletionItem(const std::string &absolute_path,
CompletionItem &&item) {
if (inserted_paths.try_emplace(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() - 1;
}
}
}
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_->Matches(path))
return;
std::string trimmed_path = path;
int kind = TrimPath(project_, trimmed_path);
CompletionItem item = BuildCompletionItem(trimmed_path, kind);
std::unique_lock<std::mutex> lock(completion_items_mutex, std::defer_lock);
if (is_scanning)
lock.lock();
InsertCompletionItem(path, std::move(item));
}
std::optional<CompletionItem>
IncludeComplete::FindCompletionItemForAbsolutePath(
const std::string &absolute_path) {
std::lock_guard<std::mutex> lock(completion_items_mutex);
auto it = absolute_path_to_completion_item.find(absolute_path);
if (it == absolute_path_to_completion_item.end())
return std::nullopt;
return completion_items[it->second];
}
} // namespace ccls

61
src/include_complete.hh Normal file
View File

@ -0,0 +1,61 @@
/* 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 <atomic>
#include <mutex>
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);
std::optional<ccls::CompletionItem>
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<bool> is_scanning;
std::vector<ccls::CompletionItem> completion_items;
// Absolute file path to the completion item in |completion_items|.
// Keep the one with shortest include path.
std::unordered_map<std::string, int> absolute_path_to_completion_item;
// Only one completion item per include path.
std::unordered_map<std::string, int> inserted_paths;
// Cached references
Project *project_;
std::unique_ptr<GroupMatch> match_;
};
} // namespace ccls

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -8,7 +20,7 @@
#include "serializer.hh"
#include "utils.hh"
#include <clang/Basic/SourceLocation.h>
#include <clang/Basic/FileManager.h>
#include <clang/Basic/Specifiers.h>
#include <llvm/ADT/CachedHashString.h>
#include <llvm/ADT/DenseMap.h>
@ -18,21 +30,20 @@
#include <unordered_map>
#include <vector>
#if LLVM_VERSION_MAJOR >= 19
#define startswith starts_with
#define endswith ends_with
#endif
namespace std {
template <> struct hash<clang::FileID> {
std::size_t operator()(clang::FileID fid) const { return fid.getHashValue(); }
template <> struct hash<llvm::sys::fs::UniqueID> {
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
// 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);
@ -51,15 +62,23 @@ enum class Role : uint16_t {
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)); }
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; }
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.
@ -69,25 +88,31 @@ struct SymbolRef {
Kind kind;
Role role;
operator SymbolIdx() const { return {usr, kind}; }
std::tuple<Range, Usr, Kind, Role> 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(); }
std::tuple<Range, Usr, Kind, Role> 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<Range, Usr, Kind, Role, Range> toTuple() const { return std::make_tuple(range, usr, kind, role, extent); }
bool operator==(const ExtentRef &o) const { return toTuple() == o.toTuple(); }
std::tuple<Range, Usr, Kind, Role, Range> 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<Range, Role> 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(); }
bool Valid() const { return range.Valid(); }
std::tuple<Range, Role> 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
@ -105,37 +130,37 @@ struct DeclRef : Use {
Range extent;
};
void reflect(JsonReader &visitor, SymbolRef &value);
void reflect(JsonReader &visitor, Use &value);
void reflect(JsonReader &visitor, DeclRef &value);
void reflect(JsonWriter &visitor, SymbolRef &value);
void reflect(JsonWriter &visitor, Use &value);
void reflect(JsonWriter &visitor, DeclRef &value);
void reflect(BinaryReader &visitor, SymbolRef &value);
void reflect(BinaryReader &visitor, Use &value);
void reflect(BinaryReader &visitor, DeclRef &value);
void reflect(BinaryWriter &visitor, SymbolRef &value);
void reflect(BinaryWriter &visitor, Use &value);
void reflect(BinaryWriter &visitor, DeclRef &value);
void Reflect(JsonReader &visitor, SymbolRef &value);
void Reflect(JsonReader &visitor, Use &value);
void Reflect(JsonReader &visitor, DeclRef &value);
void Reflect(JsonWriter &visitor, SymbolRef &value);
void Reflect(JsonWriter &visitor, Use &value);
void Reflect(JsonWriter &visitor, DeclRef &value);
void Reflect(BinaryReader &visitor, SymbolRef &value);
void Reflect(BinaryReader &visitor, Use &value);
void Reflect(BinaryReader &visitor, DeclRef &value);
void Reflect(BinaryWriter &visitor, SymbolRef &value);
void Reflect(BinaryWriter &visitor, Use &value);
void Reflect(BinaryWriter &visitor, DeclRef &value);
enum class TokenModifier {
#define TOKEN_MODIFIER(name, str) name,
#include "enum.inc"
#undef TOKEN_MODIFIER
};
template <typename T> using VectorAdapter = std::vector<T, std::allocator<T>>;
template <typename T>
using VectorAdapter = std::vector<T, std::allocator<T>>;
template <typename D> struct NameMixin {
std::string_view name(bool qualified) const {
std::string_view Name(bool qualified) const {
auto self = static_cast<const D *>(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);
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);
}
};
template <template <typename T> class V> struct FuncDef : NameMixin<FuncDef<V>> {
template <template <typename T> class V>
struct FuncDef : NameMixin<FuncDef<V>> {
// General metadata.
const char *detailed_name = "";
const char *hover = "";
@ -160,8 +185,9 @@ template <template <typename T> class V> struct FuncDef : NameMixin<FuncDef<V>>
const Usr *bases_begin() const { return bases.begin(); }
const Usr *bases_end() const { return bases.end(); }
};
REFLECT_STRUCT(FuncDef<VectorAdapter>, detailed_name, hover, comments, spell, bases, vars, callees, qual_name_offset,
short_name_offset, short_name_size, kind, parent_kind, storage);
REFLECT_STRUCT(FuncDef<VectorAdapter>, detailed_name, hover, comments, spell,
bases, vars, callees, qual_name_offset, short_name_offset,
short_name_size, kind, parent_kind, storage);
struct IndexFunc : NameMixin<IndexFunc> {
using Def = FuncDef<VectorAdapter>;
@ -172,7 +198,8 @@ struct IndexFunc : NameMixin<IndexFunc> {
std::vector<Use> uses;
};
template <template <typename T> class V> struct TypeDef : NameMixin<TypeDef<V>> {
template <template <typename T> class V>
struct TypeDef : NameMixin<TypeDef<V>> {
const char *detailed_name = "";
const char *hover = "";
const char *comments = "";
@ -197,8 +224,9 @@ template <template <typename T> class V> struct TypeDef : NameMixin<TypeDef<V>>
const Usr *bases_begin() const { return bases.begin(); }
const Usr *bases_end() const { return bases.end(); }
};
REFLECT_STRUCT(TypeDef<VectorAdapter>, detailed_name, hover, comments, spell, bases, funcs, types, vars, alias_of,
qual_name_offset, short_name_offset, short_name_size, kind, parent_kind);
REFLECT_STRUCT(TypeDef<VectorAdapter>, 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<VectorAdapter>;
@ -231,16 +259,20 @@ struct VarDef : NameMixin<VarDef> {
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 || storage == clang::SC_Auto || storage == clang::SC_Register);
(parent_kind == SymbolKind::Function ||
parent_kind == SymbolKind::Method ||
parent_kind == SymbolKind::StaticMethod ||
parent_kind == SymbolKind::Constructor) &&
(storage == clang::SC_None || storage == clang::SC_Auto ||
storage == clang::SC_Register);
}
const Usr *bases_begin() const { return nullptr; }
const Usr *bases_end() const { return nullptr; }
};
REFLECT_STRUCT(VarDef, detailed_name, hover, comments, spell, type, qual_name_offset, short_name_offset,
short_name_size, kind, parent_kind, storage);
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;
@ -275,7 +307,8 @@ struct IndexFile {
bool no_linkage;
// uid2lid_and_path is used to generate lid2path, but not serialized.
std::unordered_map<clang::FileID, std::pair<int, std::string>> uid2lid_and_path;
std::unordered_map<llvm::sys::fs::UniqueID, std::pair<int, std::string>>
uid2lid_and_path;
std::vector<std::pair<int, std::string>> lid2path;
// The path to the translation unit cc file which caused the creation of this
@ -296,19 +329,14 @@ struct IndexFile {
// File contents at the time of index. Not serialized.
std::string file_contents;
IndexFile(const std::string &path, const std::string &contents, bool no_linkage);
IndexFile(const std::string &path, const std::string &contents,
bool no_linkage);
IndexFunc &toFunc(Usr usr);
IndexType &toType(Usr usr);
IndexVar &toVar(Usr usr);
IndexFunc &ToFunc(Usr usr);
IndexType &ToType(Usr usr);
IndexVar &ToVar(Usr usr);
std::string toString();
};
struct IndexResult {
std::vector<std::unique_ptr<IndexFile>> indexes;
int n_errs = 0;
std::string first_error;
std::string ToString();
};
struct SemaManager;
@ -316,10 +344,13 @@ struct WorkingFiles;
struct VFS;
namespace idx {
void init();
IndexResult index(SemaManager *complete, WorkingFiles *wfiles, VFS *vfs, const std::string &opt_wdir,
const std::string &file, const std::vector<const char *> &args,
const std::vector<std::pair<std::string, std::string>> &remapped, bool all_linkages, bool &ok);
void Init();
std::vector<std::unique_ptr<IndexFile>>
Index(SemaManager *complete, WorkingFiles *wfiles, VFS *vfs,
const std::string &opt_wdir, const std::string &file,
const std::vector<const char *> &args,
const std::vector<std::pair<std::string, std::string>> &remapped,
bool all_linkages, bool &ok);
} // namespace idx
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -17,7 +29,8 @@ static std::mutex mtx;
FILE *file;
Verbosity verbosity;
Message::Message(Verbosity verbosity, const char *file, int line) : verbosity_(verbosity) {
Message::Message(Verbosity verbosity, const char *file, int line)
: verbosity_(verbosity) {
using namespace llvm;
time_t tim = time(NULL);
struct tm t;
@ -29,15 +42,16 @@ Message::Message(Verbosity verbosity, const char *file, int line) : verbosity_(v
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();
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_ << std::right << std::setw(15) << file << ':' << std::left
<< std::setw(3) << line;
}
stream_ << ' ';
// clang-format off

View File

@ -1,16 +1,13 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include <sstream>
#include <stdio.h>
#include <sstream>
namespace ccls::log {
extern FILE *file;
extern FILE* file;
struct Voidify {
void operator&(const std::ostream &) {}
void operator&(const std::ostream&) {}
};
enum Verbosity {
@ -25,13 +22,20 @@ struct Message {
std::stringstream stream_;
int verbosity_;
Message(Verbosity verbosity, const char *file, int line);
Message(Verbosity verbosity, const char* file, int line);
~Message();
};
} // namespace ccls::log
}
#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_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_ENABLED(v) (v <= ccls::log::verbosity)
#define LOG_V(v) LOG_IF(ccls::log::Verbosity(v), LOG_V_ENABLED(v))

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "lsp.hh"
@ -11,49 +23,51 @@
#include <stdio.h>
namespace ccls {
void reflect(JsonReader &vis, RequestId &v) {
void Reflect(JsonReader &vis, RequestId &v) {
if (vis.m->IsInt64()) {
v.type = RequestId::kInt;
v.value = std::to_string(int(vis.m->GetInt64()));
v.value = int(vis.m->GetInt64());
} else if (vis.m->IsInt()) {
v.type = RequestId::kInt;
v.value = std::to_string(vis.m->GetInt());
v.value = vis.m->GetInt();
} else if (vis.m->IsString()) {
v.type = RequestId::kString;
v.value = vis.m->GetString();
v.value = atoll(vis.m->GetString());
} else {
v.type = RequestId::kNone;
v.value.clear();
v.value = -1;
}
}
void reflect(JsonWriter &visitor, RequestId &value) {
void Reflect(JsonWriter &visitor, RequestId &value) {
switch (value.type) {
case RequestId::kNone:
visitor.null_();
visitor.Null();
break;
case RequestId::kInt:
visitor.int64(atoll(value.value.c_str()));
visitor.Int(value.value);
break;
case RequestId::kString:
visitor.string(value.value.c_str(), value.value.size());
auto s = std::to_string(value.value);
visitor.String(s.c_str(), s.size());
break;
}
}
DocumentUri DocumentUri::fromPath(const std::string &path) {
DocumentUri DocumentUri::FromPath(const std::string &path) {
DocumentUri result;
result.setPath(path);
result.SetPath(path);
return result;
}
void DocumentUri::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(':');
size_t index = raw_uri.find(":");
if (index == 1) { // widows drive letters must always be 1 char
raw_uri.replace(raw_uri.begin() + index, raw_uri.begin() + index + 1, "%3A");
raw_uri.replace(raw_uri.begin() + index, raw_uri.begin() + index + 1,
"%3A");
}
// subset of reserved characters from the URI standard
@ -87,9 +101,11 @@ void DocumentUri::setPath(const std::string &path) {
raw_uri = std::move(t);
}
std::string DocumentUri::getPath() const {
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;
LOG_S(WARNING)
<< "Received potentially bad URI (not starting with file://): "
<< raw_uri;
return raw_uri;
}
std::string ret;
@ -99,7 +115,9 @@ std::string DocumentUri::getPath() const {
#else
size_t i = 7;
#endif
auto from_hex = [](unsigned char c) { return c - '0' < 10 ? c - '0' : (c | 32) - 'a' + 10; };
auto from_hex = [](unsigned char c) {
return c - '0' < 10 ? c - '0' : (c | 32) - 'a' + 10;
};
for (; i < raw_uri.size(); i++) {
if (i + 3 <= raw_uri.size() && raw_uri[i] == '%') {
ret.push_back(from_hex(raw_uri[i + 1]) * 16 + from_hex(raw_uri[i + 2]));
@ -114,9 +132,11 @@ std::string DocumentUri::getPath() const {
}
#endif
if (g_config)
normalizeFolder(ret);
NormalizeFolder(ret);
return ret;
}
std::string Position::toString() const { return std::to_string(line) + ":" + std::to_string(character); }
std::string Position::ToString() const {
return std::to_string(line) + ":" + std::to_string(character);
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -11,7 +23,6 @@
#include <chrono>
#include <iosfwd>
#include <string>
namespace ccls {
struct RequestId {
@ -20,12 +31,12 @@ struct RequestId {
enum Type { kNone, kInt, kString };
Type type = kNone;
std::string value;
int value = -1;
bool valid() const { return type != kNone; }
bool Valid() const { return type != kNone; }
};
void reflect(JsonReader &visitor, RequestId &value);
void reflect(JsonWriter &visitor, RequestId &value);
void Reflect(JsonReader &visitor, RequestId &value);
void Reflect(JsonWriter &visitor, RequestId &value);
struct InMessage {
RequestId id;
@ -61,13 +72,13 @@ constexpr char ccls_xref[] = "ccls.xref";
constexpr char window_showMessage[] = "window/showMessage";
struct DocumentUri {
static DocumentUri fromPath(const std::string &path);
static DocumentUri FromPath(const std::string &path);
bool operator==(const DocumentUri &o) const { return raw_uri == o.raw_uri; }
bool operator<(const DocumentUri &o) const { return raw_uri < o.raw_uri; }
void setPath(const std::string &path);
std::string getPath() const;
void SetPath(const std::string &path);
std::string GetPath() const;
std::string raw_uri;
};
@ -75,26 +86,44 @@ struct DocumentUri {
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;
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; }
bool intersects(const lsRange &o) const { return start < o.end && o.start < 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;
}
bool Intersects(const lsRange &o) const {
return start < o.end && o.start < 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 == o.uri) ? uri < o.uri : range < o.range; }
bool operator==(const Location &o) const {
return uri == o.uri && range == o.range;
}
bool operator<(const Location &o) const {
return !(uri == o.uri) ? uri < o.uri : range < o.range;
}
};
struct LocationLink {
@ -102,12 +131,17 @@ struct LocationLink {
lsRange targetRange;
lsRange targetSelectionRange;
explicit operator bool() const { return targetUri.size(); }
explicit operator Location() && { return {DocumentUri{std::move(targetUri)}, targetSelectionRange}; }
explicit operator Location() && {
return {DocumentUri{std::move(targetUri)}, targetSelectionRange};
}
bool operator==(const LocationLink &o) const {
return targetUri == o.targetUri && targetSelectionRange == o.targetSelectionRange;
return targetUri == o.targetUri &&
targetSelectionRange == o.targetSelectionRange;
}
bool operator<(const LocationLink &o) const {
return !(targetUri == o.targetUri) ? targetUri < o.targetUri : targetSelectionRange < o.targetSelectionRange;
return !(targetUri == o.targetUri)
? targetUri < o.targetUri
: targetSelectionRange < o.targetSelectionRange;
}
};
@ -143,7 +177,6 @@ enum class SymbolKind : uint8_t {
// For C++, this is interpreted as "template parameter" (including
// non-type template parameters).
TypeParameter = 26,
FirstNonStandard,
// ccls extensions
// See also https://github.com/Microsoft/language-server-protocol/issues/344
@ -152,8 +185,6 @@ enum class SymbolKind : uint8_t {
Parameter = 253,
StaticMethod = 254,
Macro = 255,
FirstExtension = TypeAlias,
LastExtension = Macro,
};
struct SymbolInformation {
@ -199,17 +230,6 @@ struct TextDocumentDidChangeParam {
std::vector<TextDocumentContentChangeEvent> contentChanges;
};
struct WorkDoneProgress {
const char *kind;
std::optional<std::string> title;
std::optional<std::string> message;
std::optional<int> percentage;
};
struct WorkDoneProgressParam {
const char *token;
WorkDoneProgress value;
};
struct WorkspaceFolder {
DocumentUri uri;
std::string name;
@ -241,13 +261,6 @@ struct ShowMessageParam {
// 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,
Cuda = 4,
};
enum class LanguageId { Unknown = -1, C = 0, Cpp = 1, ObjC = 2, ObjCpp = 3 };
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -37,25 +49,36 @@ namespace {
OptionCategory C("ccls options");
opt<bool> opt_help("h", desc("Alias for -help"), cat(C));
opt<int> opt_verbose("v", desc("verbosity, from -3 (fatal) to 2 (verbose)"), init(0), cat(C));
opt<std::string> opt_test_index("test-index", ValueOptional, init("!"), desc("run index tests"), cat(C));
opt<int> opt_verbose("v", desc("verbosity, from -3 (fatal) to 2 (verbose)"),
init(0), cat(C));
opt<std::string> opt_test_index("test-index", ValueOptional, init("!"),
desc("run index tests"), cat(C));
opt<std::string> opt_index("index", desc("standalone mode: index a project and exit"), value_desc("root"), cat(C));
list<std::string> opt_init("init", desc("extra initialization options in JSON"), cat(C));
opt<std::string> opt_log_file("log-file", desc("stderr or log file"), value_desc("file"), init("stderr"), cat(C));
opt<bool> opt_log_file_append("log-file-append", desc("append to log file"), cat(C));
opt<std::string> opt_index("index",
desc("standalone mode: index a project and exit"),
value_desc("root"), cat(C));
list<std::string> opt_init("init", desc("extra initialization options in JSON"),
cat(C));
opt<std::string> opt_log_file("log-file", desc("stderr or log file"),
value_desc("file"), init("stderr"), cat(C));
opt<bool> opt_log_file_append("log-file-append", desc("append to log file"),
cat(C));
void closeLog() { fclose(ccls::log::file); }
void CloseLog() { fclose(ccls::log::file); }
} // namespace
int main(int argc, char **argv) {
traceMe();
TraceMe();
sys::PrintStackTraceOnErrorSignal(argv[0]);
cl::SetVersionPrinter(
[](raw_ostream &os) { os << clang::getClangToolFullVersion("ccls version " CCLS_VERSION "\nclang") << "\n"; });
cl::SetVersionPrinter([](raw_ostream &OS) {
OS << clang::getClangToolFullVersion("ccls version " CCLS_VERSION "\nclang")
<< "\n";
});
cl::HideUnrelatedOptions(C);
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"
@ -67,7 +90,7 @@ int main(int argc, char **argv) {
}
ccls::log::verbosity = ccls::log::Verbosity(opt_verbose.getValue());
pipeline::init();
pipeline::Init();
const char *env = getenv("CCLS_CRASH_RECOVERY");
if (!env || strcmp(env, "0") != 0)
CrashRecoveryContext::Enable();
@ -76,18 +99,20 @@ int main(int argc, char **argv) {
if (opt_log_file.size()) {
ccls::log::file =
opt_log_file == "stderr" ? stderr : fopen(opt_log_file.c_str(), opt_log_file_append ? "ab" : "wb");
opt_log_file == "stderr"
? stderr
: fopen(opt_log_file.c_str(), opt_log_file_append ? "ab" : "wb");
if (!ccls::log::file) {
fprintf(stderr, "failed to open %s\n", opt_log_file.c_str());
return 2;
}
setbuf(ccls::log::file, NULL);
atexit(closeLog);
atexit(CloseLog);
}
if (opt_test_index != "!") {
language_server = false;
if (!ccls::runIndexTests(opt_test_index, sys::Process::StandardInIsUserInput()))
if (!ccls::RunIndexTests(opt_test_index, sys::Process::StandardInIsUserInput()))
return 1;
}
@ -100,17 +125,18 @@ int main(int argc, char **argv) {
for (const std::string &str : g_init_options) {
rapidjson::ParseResult ok = reader.Parse(str.c_str());
if (!ok) {
fprintf(stderr, "Failed to parse --init as JSON: %s (%zd)\n", rapidjson::GetParseError_En(ok.Code()),
ok.Offset());
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);
Reflect(json_reader, config);
} catch (std::invalid_argument &e) {
fprintf(stderr, "Failed to parse --init %s, expected %s\n",
static_cast<JsonReader &>(json_reader).getPath().c_str(), e.what());
static_cast<JsonReader &>(json_reader).GetPath().c_str(),
e.what());
return 1;
}
}
@ -119,18 +145,18 @@ int main(int argc, char **argv) {
sys::ChangeStdinToBinary();
sys::ChangeStdoutToBinary();
if (opt_index.size()) {
SmallString<256> root(opt_index);
sys::fs::make_absolute(root);
pipeline::standalone(std::string(root.data(), root.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();
pipeline::LaunchStdin();
// The thread that writes responses from the main thread to stdout.
pipeline::launchStdout();
pipeline::LaunchStdout();
// Main thread which also spawns indexer threads upon the "initialize"
// request.
pipeline::mainLoop();
pipeline::MainLoop();
}
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -11,37 +23,23 @@
#include <rapidjson/document.h>
#include <rapidjson/reader.h>
#include <llvm/ADT/STLExtras.h>
#include <algorithm>
#include <stdexcept>
using namespace clang;
#if LLVM_VERSION_MAJOR < 15 // llvmorg-15-init-6118-gb39f43775796
namespace llvm {
template <typename T, typename E> constexpr bool is_contained(std::initializer_list<T> set, const E &e) {
for (const T &v : set)
if (v == e)
return true;
return false;
}
} // namespace llvm
#endif
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 &) {}
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);
REFLECT_STRUCT(CallsParam, item);
// completion
REFLECT_UNDERLYING(CompletionTriggerKind);
@ -51,7 +49,8 @@ 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(DocumentOnTypeFormattingParam, textDocument, position, ch,
options);
REFLECT_STRUCT(DocumentRangeFormattingParam, textDocument, range, options);
// workspace
@ -63,10 +62,6 @@ REFLECT_STRUCT(DidChangeWorkspaceFoldersParam, event);
REFLECT_STRUCT(WorkspaceSymbolParam, query, folders);
namespace {
struct Occur {
lsRange range;
Role role;
};
struct CclsSemanticHighlightSymbol {
int id = 0;
SymbolKind parentKind;
@ -74,15 +69,16 @@ struct CclsSemanticHighlightSymbol {
uint8_t storage;
std::vector<std::pair<int, int>> ranges;
// `lsOccur` is used to compute `ranges`.
std::vector<Occur> lsOccurs;
// `lsRanges` is used to compute `ranges`.
std::vector<lsRange> lsRanges;
};
struct CclsSemanticHighlight {
DocumentUri uri;
std::vector<CclsSemanticHighlightSymbol> symbols;
};
REFLECT_STRUCT(CclsSemanticHighlightSymbol, id, parentKind, kind, storage, ranges);
REFLECT_STRUCT(CclsSemanticHighlightSymbol, id, parentKind, kind, storage,
ranges, lsRanges);
REFLECT_STRUCT(CclsSemanticHighlight, uri, symbols);
struct CclsSetSkippedRanges {
@ -91,16 +87,10 @@ struct CclsSetSkippedRanges {
};
REFLECT_STRUCT(CclsSetSkippedRanges, uri, skippedRanges);
struct SemanticTokensPartialResult {
std::vector<int> data;
};
REFLECT_STRUCT(SemanticTokensPartialResult, data);
struct ScanLineEvent {
Position pos;
Position end_pos; // Second key when there is a tie for insertion events.
int id;
Role role;
CclsSemanticHighlightSymbol *symbol;
bool operator<(const ScanLineEvent &o) const {
// See the comments below when insertion/deletion events are inserted.
@ -119,11 +109,11 @@ struct ScanLineEvent {
};
} // namespace
void ReplyOnce::notOpened(std::string_view path) {
error(ErrorCode::InvalidRequest, std::string(path) + " is not opened");
void ReplyOnce::NotOpened(std::string_view path) {
Error(ErrorCode::InvalidRequest, std::string(path) + " is not opened");
}
void ReplyOnce::replyLocationLink(std::vector<LocationLink> &result) {
void ReplyOnce::ReplyLocationLink(std::vector<LocationLink> &result) {
std::sort(result.begin(), result.end());
result.erase(std::unique(result.begin(), result.end()), result.end());
if (result.size() > g_config->xref.maxNum)
@ -131,106 +121,117 @@ void ReplyOnce::replyLocationLink(std::vector<LocationLink> &result) {
if (g_config->client.linkSupport) {
(*this)(result);
} else {
(*this)(std::vector<Location>(std::make_move_iterator(result.begin()), std::make_move_iterator(result.end())));
std::vector<Location> result1;
for (auto &loc : result)
result1.emplace_back(std::move(loc));
(*this)(result1);
}
}
void MessageHandler::bind(const char *method, void (MessageHandler::*handler)(JsonReader &)) {
method2notification[method] = [this, handler](JsonReader &reader) { (this->*handler)(reader); };
void MessageHandler::Bind(const char *method,
void (MessageHandler::*handler)(JsonReader &)) {
method2notification[method] = [this, handler](JsonReader &reader) {
(this->*handler)(reader);
};
}
template <typename Param> void MessageHandler::bind(const char *method, void (MessageHandler::*handler)(Param &)) {
template <typename Param>
void MessageHandler::Bind(const char *method,
void (MessageHandler::*handler)(Param &)) {
method2notification[method] = [this, handler](JsonReader &reader) {
Param param{};
reflect(reader, param);
Reflect(reader, param);
(this->*handler)(param);
};
}
void MessageHandler::bind(const char *method, void (MessageHandler::*handler)(JsonReader &, ReplyOnce &)) {
method2request[method] = [this, handler](JsonReader &reader, ReplyOnce &reply) { (this->*handler)(reader, reply); };
void MessageHandler::Bind(const char *method,
void (MessageHandler::*handler)(JsonReader &,
ReplyOnce &)) {
method2request[method] = [this, handler](JsonReader &reader,
ReplyOnce &reply) {
(this->*handler)(reader, reply);
};
}
template <typename Param>
void MessageHandler::bind(const char *method, void (MessageHandler::*handler)(Param &, ReplyOnce &)) {
method2request[method] = [this, handler](JsonReader &reader, ReplyOnce &reply) {
void MessageHandler::Bind(const char *method,
void (MessageHandler::*handler)(Param &,
ReplyOnce &)) {
method2request[method] = [this, handler](JsonReader &reader,
ReplyOnce &reply) {
Param param{};
reflect(reader, param);
Reflect(reader, param);
(this->*handler)(param, reply);
};
}
MessageHandler::MessageHandler() {
// clang-format off
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("callHierarchy/incomingCalls", &MessageHandler::callHierarchy_incomingCalls);
bind("callHierarchy/outgoingCalls", &MessageHandler::callHierarchy_outgoingCalls);
bind("exit", &MessageHandler::exit);
bind("initialize", &MessageHandler::initialize);
bind("initialized", &MessageHandler::initialized);
bind("shutdown", &MessageHandler::shutdown);
bind("textDocument/codeAction", &MessageHandler::textDocument_codeAction);
bind("textDocument/codeLens", &MessageHandler::textDocument_codeLens);
bind("textDocument/completion", &MessageHandler::textDocument_completion);
bind("textDocument/declaration", &MessageHandler::textDocument_declaration);
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/prepareCallHierarchy", &MessageHandler::textDocument_prepareCallHierarchy);
bind("textDocument/rangeFormatting", &MessageHandler::textDocument_rangeFormatting);
bind("textDocument/references", &MessageHandler::textDocument_references);
bind("textDocument/rename", &MessageHandler::textDocument_rename);
bind("textDocument/semanticTokens/full", &MessageHandler::textDocument_semanticTokensFull);
bind("textDocument/semanticTokens/range", &MessageHandler::textDocument_semanticTokensRange);
bind("textDocument/signatureHelp", &MessageHandler::textDocument_signatureHelp);
bind("textDocument/switchSourceHeader", &MessageHandler::textDocument_switchSourceHeader);
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);
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("exit", &MessageHandler::exit);
Bind("initialize", &MessageHandler::initialize);
Bind("initialized", &MessageHandler::initialized);
Bind("shutdown", &MessageHandler::shutdown);
Bind("textDocument/codeAction", &MessageHandler::textDocument_codeAction);
Bind("textDocument/codeLens", &MessageHandler::textDocument_codeLens);
Bind("textDocument/completion", &MessageHandler::textDocument_completion);
Bind("textDocument/declaration", &MessageHandler::textDocument_declaration);
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);
// clang-format on
}
void MessageHandler::run(InMessage &msg) {
void MessageHandler::Run(InMessage &msg) {
rapidjson::Document &doc = *msg.document;
rapidjson::Value null;
auto it = doc.FindMember("params");
JsonReader reader(it != doc.MemberEnd() ? &it->value : &null);
if (msg.id.valid()) {
if (msg.id.Valid()) {
ReplyOnce reply{*this, 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 + ": expected " + ex.what() + " for " + reader.getPath());
reply.Error(ErrorCode::InvalidParams,
"invalid params of " + msg.method + ": expected " +
ex.what() + " for " + reader.GetPath());
} catch (NotIndexed &) {
throw;
} catch (...) {
reply.error(ErrorCode::InternalError, "failed to process " + msg.method);
reply.Error(ErrorCode::InternalError, "failed to process " + msg.method);
}
} else {
reply.error(ErrorCode::MethodNotFound, "unknown request " + msg.method);
reply.Error(ErrorCode::MethodNotFound, "unknown request " + msg.method);
}
} else {
auto it = method2notification.find(msg.method);
@ -238,15 +239,17 @@ void MessageHandler::run(InMessage &msg) {
try {
it->second(reader);
} catch (...) {
ShowMessageParam param{MessageType::Error, std::string("failed to process ") + msg.method};
pipeline::notify(window_showMessage, param);
ShowMessageParam param{MessageType::Error,
std::string("failed to process ") + msg.method};
pipeline::Notify(window_showMessage, param);
}
}
}
QueryFile *MessageHandler::findFile(const std::string &path, int *out_file_id) {
QueryFile *MessageHandler::FindFile(const std::string &path,
int *out_file_id) {
QueryFile *ret = nullptr;
auto it = db->name2file_id.find(lowerPathIfInsensitive(path));
auto it = db->name2file_id.find(LowerPathIfInsensitive(path));
if (it != db->name2file_id.end()) {
QueryFile &file = db->files[it->second];
if (file.def) {
@ -261,44 +264,45 @@ QueryFile *MessageHandler::findFile(const std::string &path, int *out_file_id) {
return ret;
}
std::pair<QueryFile *, WorkingFile *> MessageHandler::findOrFail(const std::string &path, ReplyOnce &reply,
int *out_file_id, bool allow_unopened) {
WorkingFile *wf = wfiles->getFile(path);
if (!wf && !allow_unopened) {
reply.notOpened(path);
std::pair<QueryFile *, WorkingFile *>
MessageHandler::FindOrFail(const std::string &path, ReplyOnce &reply,
int *out_file_id) {
WorkingFile *wf = wfiles->GetFile(path);
if (!wf) {
reply.NotOpened(path);
return {nullptr, nullptr};
}
QueryFile *file = findFile(path, out_file_id);
QueryFile *file = FindFile(path, out_file_id);
if (!file) {
if (!overdue)
throw NotIndexed{path};
reply.error(ErrorCode::InvalidRequest, "not indexed");
reply.Error(ErrorCode::InvalidRequest, "not indexed");
return {nullptr, nullptr};
}
return {file, wf};
}
void emitSkippedRanges(WorkingFile *wfile, QueryFile &file) {
void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file) {
CclsSetSkippedRanges params;
params.uri = DocumentUri::fromPath(wfile->filename);
params.uri = DocumentUri::FromPath(wfile->filename);
for (Range skipped : file.def->skipped_ranges)
if (auto ls_skipped = getLsRange(wfile, skipped))
if (auto ls_skipped = GetLsRange(wfile, skipped))
params.skippedRanges.push_back(*ls_skipped);
pipeline::notify("$ccls/publishSkippedRanges", params);
pipeline::Notify("$ccls/publishSkippedRanges", params);
}
static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanticTokens(DB *db, WorkingFile *wfile,
QueryFile &file) {
static GroupMatch match(g_config->highlight.whitelist, g_config->highlight.blacklist);
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;
// Group symbols together.
std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> grouped_symbols;
if (!match.matches(file.def->path))
return grouped_symbols;
for (auto [sym, refcnt] : file.symbol2refcnt) {
if (refcnt <= 0)
continue;
if (refcnt <= 0) continue;
std::string_view detailed_name;
SymbolKind parent_kind = SymbolKind::Unknown;
SymbolKind kind = SymbolKind::Unknown;
@ -309,12 +313,12 @@ static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanti
case Kind::Func: {
idx = db->func_usr[sym.usr];
const QueryFunc &func = db->funcs[idx];
const QueryFunc::Def *def = func.anyDef();
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);
std::string_view short_name = def->Name(false);
if (short_name.compare(0, 8, "operator") == 0)
continue; // applies to for loop
kind = def->kind;
@ -326,7 +330,8 @@ static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanti
// 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('<'));
std::string_view concise_name =
detailed_name.substr(0, detailed_name.find('<'));
uint16_t start_line = sym.range.start.line;
int16_t start_col = sym.range.start.column;
if (start_line >= wfile->index_lines.size())
@ -370,17 +375,17 @@ static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanti
continue; // applies to for loop
}
if (std::optional<lsRange> loc = getLsRange(wfile, sym.range)) {
if (std::optional<lsRange> loc = GetLsRange(wfile, sym.range)) {
auto it = grouped_symbols.find(sym);
if (it != grouped_symbols.end()) {
it->second.lsOccurs.push_back({*loc, sym.role});
it->second.lsRanges.push_back(*loc);
} else {
CclsSemanticHighlightSymbol symbol;
symbol.id = idx;
symbol.parentKind = parent_kind;
symbol.kind = kind;
symbol.storage = storage;
symbol.lsOccurs.push_back({*loc, sym.role});
symbol.lsRanges.push_back(*loc);
grouped_symbols[sym] = symbol;
}
}
@ -391,17 +396,17 @@ static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanti
int id = 0;
for (auto &entry : grouped_symbols) {
CclsSemanticHighlightSymbol &symbol = entry.second;
for (auto &occur : symbol.lsOccurs) {
for (auto &loc : symbol.lsRanges) {
// For ranges sharing the same start point, the one with leftmost end
// point comes first.
events.push_back({occur.range.start, occur.range.end, id, occur.role, &symbol});
events.push_back({loc.start, loc.end, id, &symbol});
// For ranges sharing the same end point, their relative order does not
// matter, therefore we arbitrarily assign occur.range.end to them. We use
// matter, therefore we arbitrarily assign loc.end to them. We use
// negative id to indicate a deletion event.
events.push_back({occur.range.end, occur.range.end, ~id, occur.role, &symbol});
events.push_back({loc.end, loc.end, ~id, &symbol});
id++;
}
symbol.lsOccurs.clear();
symbol.lsRanges.clear();
}
std::sort(events.begin(), events.end());
@ -417,33 +422,26 @@ static std::unordered_map<SymbolIdx, CclsSemanticHighlightSymbol> computeSemanti
// 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->lsOccurs.push_back({{events[i - 1].pos, events[i].pos}, events[i].role});
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;
}
return grouped_symbols;
}
void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
// Disable $ccls/publishSemanticHighlight if semantic tokens support is
// enabled or the file is too large.
if (g_config->client.semanticTokensRefresh || wfile->buffer_content.size() > g_config->highlight.largeFileSize)
return;
auto grouped_symbols = computeSemanticTokens(db, wfile, file);
CclsSemanticHighlight params;
params.uri = DocumentUri::fromPath(wfile->filename);
params.uri = DocumentUri::FromPath(wfile->filename);
// Transform lsRange into pair<int, int> (offset pairs)
{
std::vector<std::pair<Occur, CclsSemanticHighlightSymbol *>> scratch;
if (!g_config->highlight.lsRanges) {
std::vector<std::pair<lsRange, CclsSemanticHighlightSymbol *>> scratch;
for (auto &entry : grouped_symbols) {
for (auto &occur : entry.second.lsOccurs)
scratch.push_back({occur, &entry.second});
entry.second.lsOccurs.clear();
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.range < r.first.range; });
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) {
@ -460,12 +458,13 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
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)
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.range;
lsRange &r = entry.first;
if (mov(r.start.line, r.start.character))
continue;
int beg = p;
@ -476,84 +475,8 @@ void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file) {
}
for (auto &entry : grouped_symbols)
if (entry.second.ranges.size() || entry.second.lsOccurs.size())
if (entry.second.ranges.size() || entry.second.lsRanges.size())
params.symbols.push_back(std::move(entry.second));
pipeline::notify("$ccls/publishSemanticHighlight", params);
pipeline::Notify("$ccls/publishSemanticHighlight", params);
}
void MessageHandler::textDocument_semanticTokensFull(TextDocumentParam &param, ReplyOnce &reply) {
SemanticTokensRangeParams parameters{param.textDocument, lsRange{{0, 0}, {UINT16_MAX, INT16_MAX}}};
textDocument_semanticTokensRange(parameters, reply);
}
void MessageHandler::textDocument_semanticTokensRange(SemanticTokensRangeParams &param, ReplyOnce &reply) {
int file_id;
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id);
if (!wf)
return;
auto grouped_symbols = computeSemanticTokens(db, wf, *file);
std::vector<std::pair<Occur, CclsSemanticHighlightSymbol *>> scratch;
for (auto &entry : grouped_symbols) {
for (auto &occur : entry.second.lsOccurs)
scratch.emplace_back(occur, &entry.second);
entry.second.lsOccurs.clear();
}
std::sort(scratch.begin(), scratch.end(), [](auto &l, auto &r) { return l.first.range < r.first.range; });
SemanticTokensPartialResult result;
int line = 0, column = 0;
for (auto &entry : scratch) {
lsRange &r = entry.first.range;
CclsSemanticHighlightSymbol &symbol = *entry.second;
if (r.start.line != line)
column = 0;
result.data.push_back(r.start.line - line);
line = r.start.line;
result.data.push_back(r.start.character - column);
column = r.start.character;
result.data.push_back(r.end.character - r.start.character);
int tokenType = (int)symbol.kind, modifier = 0;
if (tokenType == (int)SymbolKind::StaticMethod) {
tokenType = (int)SymbolKind::Method;
modifier |= 1 << (int)TokenModifier::Static;
} else if (tokenType >= (int)SymbolKind::FirstExtension) {
tokenType += (int)SymbolKind::FirstNonStandard - (int)SymbolKind::FirstExtension;
}
// Set modifiers.
if (entry.first.role & Role::Declaration)
modifier |= 1 << (int)TokenModifier::Declaration;
if (entry.first.role & Role::Definition)
modifier |= 1 << (int)TokenModifier::Definition;
if (entry.first.role & Role::Read)
modifier |= 1 << (int)TokenModifier::Read;
if (entry.first.role & Role::Write)
modifier |= 1 << (int)TokenModifier::Write;
if (symbol.storage == SC_Static)
modifier |= 1 << (int)TokenModifier::Static;
if (llvm::is_contained({SymbolKind::Constructor, SymbolKind::Field, SymbolKind::Method, SymbolKind::StaticMethod},
symbol.kind))
modifier |= 1 << (int)TokenModifier::ClassScope;
else if (llvm::is_contained({SymbolKind::File, SymbolKind::Namespace}, symbol.parentKind))
modifier |= 1 << (int)TokenModifier::NamespaceScope;
else if (llvm::is_contained(
{SymbolKind::Constructor, SymbolKind::Function, SymbolKind::Method, SymbolKind::StaticMethod},
symbol.parentKind))
modifier |= 1 << (int)TokenModifier::FunctionScope;
// Rainbow semantic tokens
static_assert((int)TokenModifier::Id0 + 20 < 31);
if (int rainbow = g_config->highlight.rainbow)
modifier |= 1 << ((int)TokenModifier::Id0 + symbol.id % std::min(rainbow, 20));
result.data.push_back(tokenType);
result.data.push_back(modifier);
}
reply(result);
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -14,14 +26,15 @@
namespace ccls {
struct SemaManager;
struct VFS;
struct IncludeComplete;
struct Project;
struct WorkingFile;
struct WorkingFiles;
namespace pipeline {
void reply(const RequestId &id, const std::function<void(JsonWriter &)> &fn);
void replyError(const RequestId &id, const std::function<void(JsonWriter &)> &fn);
} // namespace pipeline
void Reply(RequestId id, const std::function<void(JsonWriter &)> &fn);
void ReplyError(RequestId id, const std::function<void(JsonWriter &)> &fn);
}
struct CodeActionParam {
TextDocumentIdentifier textDocument;
@ -39,11 +52,6 @@ struct RenameParam {
Position position;
std::string newName;
};
struct SemanticTokensRangeParams {
TextDocumentIdentifier textDocument;
lsRange range;
};
REFLECT_STRUCT(SemanticTokensRangeParams, textDocument, range);
struct TextDocumentParam {
TextDocumentIdentifier textDocument;
};
@ -61,21 +69,6 @@ struct WorkspaceEdit {
};
REFLECT_STRUCT(WorkspaceEdit, documentChanges);
struct CallHierarchyItem {
std::string name;
SymbolKind kind;
std::string detail;
DocumentUri uri;
lsRange range;
lsRange selectionRange;
std::string data;
};
REFLECT_STRUCT(CallHierarchyItem, name, kind, detail, uri, range, selectionRange, data);
struct CallsParam {
CallHierarchyItem item;
};
// completion
enum class CompletionTriggerKind {
Invoked = 1,
@ -116,7 +109,10 @@ enum class CompletionItemKind {
Operator = 24,
TypeParameter = 25,
};
enum class InsertTextFormat { PlainText = 1, Snippet = 2 };
enum class InsertTextFormat {
PlainText = 1,
Snippet = 2
};
struct CompletionItem {
std::string label;
CompletionItemKind kind = CompletionItemKind::Text;
@ -181,18 +177,11 @@ struct WorkspaceSymbolParam {
};
REFLECT_STRUCT(WorkspaceFolder, uri, name);
inline void reflect(JsonReader &vis, DocumentUri &v) { reflect(vis, v.raw_uri); }
inline void reflect(JsonWriter &vis, DocumentUri &v) { reflect(vis, v.raw_uri); }
inline void reflect(JsonReader &vis, VersionedTextDocumentIdentifier &v) {
REFLECT_MEMBER(uri);
REFLECT_MEMBER(version);
inline void Reflect(JsonReader &visitor, DocumentUri &value) {
Reflect(visitor, value.raw_uri);
}
inline void reflect(JsonWriter &vis, VersionedTextDocumentIdentifier &v) {
vis.startObject();
REFLECT_MEMBER(uri);
vis.key("version");
reflect(vis, v.version);
vis.endObject();
inline void Reflect(JsonWriter &visitor, DocumentUri &value) {
Reflect(visitor, value.raw_uri);
}
REFLECT_UNDERLYING(ErrorCode);
@ -205,8 +194,7 @@ REFLECT_UNDERLYING_B(SymbolKind);
REFLECT_STRUCT(TextDocumentIdentifier, uri);
REFLECT_STRUCT(TextDocumentItem, uri, languageId, version, text);
REFLECT_STRUCT(TextEdit, range, newText);
REFLECT_STRUCT(WorkDoneProgress, kind, title, message, percentage);
REFLECT_STRUCT(WorkDoneProgressParam, token, value);
REFLECT_STRUCT(VersionedTextDocumentIdentifier, uri, version);
REFLECT_STRUCT(DiagnosticRelatedInformation, location, message);
REFLECT_STRUCT(Diagnostic, range, severity, code, source, message, relatedInformation);
REFLECT_STRUCT(ShowMessageParam, type, message);
@ -221,51 +209,56 @@ struct ReplyOnce {
MessageHandler &handler;
RequestId id;
template <typename Res> void operator()(Res &&result) const {
if (id.valid())
pipeline::reply(id, [&](JsonWriter &w) { reflect(w, result); });
if (id.Valid())
pipeline::Reply(id, [&](JsonWriter &w) { Reflect(w, result); });
}
void error(ErrorCode code, std::string message) const {
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); });
if (id.Valid())
pipeline::ReplyError(id, [&](JsonWriter &w) { Reflect(w, err); });
}
void notOpened(std::string_view path);
void replyLocationLink(std::vector<LocationLink> &result);
void NotOpened(std::string_view path);
void ReplyLocationLink(std::vector<LocationLink> &result);
};
struct MessageHandler {
SemaManager *manager = nullptr;
DB *db = nullptr;
IncludeComplete *include_complete = nullptr;
Project *project = nullptr;
VFS *vfs = nullptr;
WorkingFiles *wfiles = nullptr;
llvm::StringMap<std::function<void(JsonReader &)>> method2notification;
llvm::StringMap<std::function<void(JsonReader &, ReplyOnce &)>> method2request;
llvm::StringMap<std::function<void(JsonReader &, ReplyOnce &)>>
method2request;
bool overdue = false;
MessageHandler();
void run(InMessage &msg);
QueryFile *findFile(const std::string &path, int *out_file_id = nullptr);
std::pair<QueryFile *, WorkingFile *> findOrFail(const std::string &path, ReplyOnce &reply,
int *out_file_id = nullptr, bool allow_unopened = false);
void Run(InMessage &msg);
QueryFile *FindFile(const std::string &path, int *out_file_id = nullptr);
std::pair<QueryFile *, WorkingFile *> FindOrFail(const std::string &path,
ReplyOnce &reply,
int *out_file_id = nullptr);
private:
void bind(const char *method, void (MessageHandler::*handler)(JsonReader &));
template <typename Param> void bind(const char *method, void (MessageHandler::*handler)(Param &));
void bind(const char *method, void (MessageHandler::*handler)(JsonReader &, ReplyOnce &));
template <typename Param> void bind(const char *method, void (MessageHandler::*handler)(Param &, ReplyOnce &));
void Bind(const char *method, void (MessageHandler::*handler)(JsonReader &));
template <typename Param>
void Bind(const char *method, void (MessageHandler::*handler)(Param &));
void Bind(const char *method,
void (MessageHandler::*handler)(JsonReader &, ReplyOnce &));
template <typename Param>
void Bind(const char *method,
void (MessageHandler::*handler)(Param &, ReplyOnce &));
void ccls_call(JsonReader &, ReplyOnce &);
void ccls_fileInfo(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 callHierarchy_incomingCalls(CallsParam &param, ReplyOnce &);
void callHierarchy_outgoingCalls(CallsParam &param, ReplyOnce &);
void exit(EmptyParam &);
void initialize(JsonReader &, ReplyOnce &);
void initialized(EmptyParam &);
@ -286,15 +279,13 @@ private:
void textDocument_formatting(DocumentFormattingParam &, ReplyOnce &);
void textDocument_hover(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_implementation(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &, ReplyOnce &);
void textDocument_prepareCallHierarchy(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_rangeFormatting(DocumentRangeFormattingParam &, ReplyOnce &);
void textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &,
ReplyOnce &);
void textDocument_rangeFormatting(DocumentRangeFormattingParam &,
ReplyOnce &);
void textDocument_references(JsonReader &, ReplyOnce &);
void textDocument_rename(RenameParam &, ReplyOnce &);
void textDocument_semanticTokensFull(TextDocumentParam &, ReplyOnce &);
void textDocument_semanticTokensRange(SemanticTokensRangeParams &, ReplyOnce &);
void textDocument_signatureHelp(TextDocumentPositionParam &, ReplyOnce &);
void textDocument_switchSourceHeader(TextDocumentIdentifier &, ReplyOnce &);
void textDocument_typeDefinition(TextDocumentPositionParam &, ReplyOnce &);
void workspace_didChangeConfiguration(EmptyParam &);
void workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam &);
@ -303,7 +294,7 @@ private:
void workspace_symbol(WorkspaceSymbolParam &, ReplyOnce &);
};
void emitSkippedRanges(WorkingFile *wfile, QueryFile &file);
void EmitSkippedRanges(WorkingFile *wfile, QueryFile &file);
void emitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file);
void EmitSemanticHighlight(DB *db, WorkingFile *wfile, QueryFile &file);
} // namespace ccls

View File

@ -1,22 +1,40 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 <map>
#include <unordered_set>
namespace ccls {
namespace {
enum class CallType : uint8_t { Direct = 0, Base = 1, Derived = 2, All = 1 | 2 };
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); }
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
@ -34,7 +52,8 @@ struct Param : TextDocumentPositionParam {
int levels = 1;
bool hierarchy = false;
};
REFLECT_STRUCT(Param, textDocument, position, id, callee, callType, qualified, levels, hierarchy);
REFLECT_STRUCT(Param, textDocument, position, id, callee, callType, qualified,
levels, hierarchy);
struct Out_cclsCall {
Usr usr;
@ -45,26 +64,18 @@ struct Out_cclsCall {
int numChildren;
// Empty if the |levels| limit is reached.
std::vector<Out_cclsCall> children;
bool operator==(const Out_cclsCall &o) const { return location == o.location; }
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);
REFLECT_STRUCT(Out_cclsCall, id, name, location, callType, numChildren,
children);
struct Out_incomingCall {
CallHierarchyItem from;
std::vector<lsRange> fromRanges;
};
REFLECT_STRUCT(Out_incomingCall, from, fromRanges);
struct Out_outgoingCall {
CallHierarchyItem to;
std::vector<lsRange> fromRanges;
};
REFLECT_STRUCT(Out_outgoingCall, to, fromRanges);
bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_type, bool qualified, int levels) {
const QueryFunc &func = m->db->getFunc(entry->usr);
const QueryFunc::Def *def = func.anyDef();
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;
@ -74,16 +85,17 @@ bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_t
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}))
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))
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())
if (const auto *def = func.AnyDef())
for (SymbolRef sym : def->callees)
if (sym.kind == Kind::Func)
handle(sym, def->file_id, call_type);
@ -92,8 +104,10 @@ bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_t
const QueryFile &file1 = m->db->files[use.file_id];
Maybe<ExtentRef> 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))
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);
@ -104,7 +118,7 @@ bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_t
std::unordered_set<Usr> seen;
seen.insert(func.usr);
std::vector<const QueryFunc *> stack;
entry->name = def->name(qualified);
entry->name = def->Name(qualified);
handle_uses(func, CallType::Direct);
// Callers/callees of base functions.
@ -113,8 +127,8 @@ bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_t
while (stack.size()) {
const QueryFunc &func1 = *stack.back();
stack.pop_back();
if (auto *def1 = func1.anyDef()) {
eachDefinedFunc(m->db, def1->bases, [&](QueryFunc &func2) {
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);
@ -131,7 +145,7 @@ bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_t
while (stack.size()) {
const QueryFunc &func1 = *stack.back();
stack.pop_back();
eachDefinedFunc(m->db, func1.derived, [&](QueryFunc &func2) {
EachDefinedFunc(m->db, func1.derived, [&](QueryFunc &func2) {
if (!seen.count(func2.usr)) {
seen.insert(func2.usr);
stack.push_back(&func2);
@ -142,13 +156,16 @@ bool expand(MessageHandler *m, Out_cclsCall *entry, bool callee, CallType call_t
}
std::sort(entry->children.begin(), entry->children.end());
entry->children.erase(std::unique(entry->children.begin(), entry->children.end()), entry->children.end());
entry->children.erase(
std::unique(entry->children.begin(), entry->children.end()),
entry->children.end());
return true;
}
std::optional<Out_cclsCall> buildInitial(MessageHandler *m, Usr root_usr, bool callee, CallType call_type,
std::optional<Out_cclsCall> BuildInitial(MessageHandler *m, Usr root_usr,
bool callee, CallType call_type,
bool qualified, int levels) {
const auto *def = m->db->getFunc(root_usr).anyDef();
const auto *def = m->db->Func(root_usr).AnyDef();
if (!def)
return {};
@ -157,17 +174,17 @@ std::optional<Out_cclsCall> buildInitial(MessageHandler *m, Usr root_usr, bool c
entry.usr = root_usr;
entry.callType = CallType::Direct;
if (def->spell) {
if (auto loc = getLsLocation(m->db, m->wfiles, *def->spell))
if (auto loc = GetLsLocation(m->db, m->wfiles, *def->spell))
entry.location = *loc;
}
expand(m, &entry, callee, call_type, qualified, levels);
Expand(m, &entry, callee, call_type, qualified, levels);
return entry;
}
} // namespace
void MessageHandler::ccls_call(JsonReader &reader, ReplyOnce &reply) {
Param param;
reflect(reader, param);
Reflect(reader, param);
std::optional<Out_cclsCall> result;
if (param.id.size()) {
try {
@ -179,15 +196,17 @@ void MessageHandler::ccls_call(JsonReader &reader, ReplyOnce &reply) {
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);
if (db->HasFunc(param.usr))
Expand(this, &*result, param.callee, param.callType, param.qualified,
param.levels);
} else {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
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);
result = BuildInitial(this, sym.usr, param.callee, param.callType,
param.qualified, param.levels);
break;
}
}
@ -196,114 +215,6 @@ void MessageHandler::ccls_call(JsonReader &reader, ReplyOnce &reply) {
if (param.hierarchy)
reply(result);
else
reply(flattenHierarchy(result));
}
void MessageHandler::textDocument_prepareCallHierarchy(TextDocumentPositionParam &param, ReplyOnce &reply) {
std::string path = param.textDocument.uri.getPath();
auto [file, wf] = findOrFail(path, reply);
if (!file)
return;
std::vector<CallHierarchyItem> result;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
if (sym.kind != Kind::Func)
continue;
const auto *def = db->getFunc(sym.usr).anyDef();
if (!def)
continue;
auto r = getLsRange(wf, sym.range);
if (!r)
continue;
CallHierarchyItem &item = result.emplace_back();
item.name = def->name(false);
item.kind = def->kind;
item.detail = def->name(true);
item.uri = DocumentUri::fromPath(path);
item.range = item.selectionRange = *r;
item.data = std::to_string(sym.usr);
}
reply(result);
}
static lsRange toLsRange(Range r) { return {{r.start.line, r.start.column}, {r.end.line, r.end.column}}; }
static void add(std::map<SymbolIdx, std::pair<int, std::vector<lsRange>>> &sym2ranges, SymbolRef sym, int file_id) {
auto [it, inserted] = sym2ranges.try_emplace(SymbolIdx{sym.usr, sym.kind});
if (inserted)
it->second.first = file_id;
if (it->second.first == file_id)
it->second.second.push_back(toLsRange(sym.range));
}
template <typename Out>
static std::vector<Out> toCallResult(DB *db,
const std::map<SymbolIdx, std::pair<int, std::vector<lsRange>>> &sym2ranges) {
std::vector<Out> result;
for (auto &[sym, ranges] : sym2ranges) {
CallHierarchyItem item;
item.uri = getLsDocumentUri(db, ranges.first);
auto r = ranges.second[0];
item.range = {{uint16_t(r.start.line), int16_t(r.start.character)},
{uint16_t(r.end.line), int16_t(r.end.character)}};
item.selectionRange = item.range;
switch (sym.kind) {
default:
continue;
case Kind::Func: {
auto idx = db->func_usr[sym.usr];
const QueryFunc &func = db->funcs[idx];
const QueryFunc::Def *def = func.anyDef();
if (!def)
continue;
item.name = def->name(false);
item.kind = def->kind;
item.detail = def->name(true);
item.data = std::to_string(sym.usr);
}
}
result.push_back({std::move(item), std::move(ranges.second)});
}
return result;
}
void MessageHandler::callHierarchy_incomingCalls(CallsParam &param, ReplyOnce &reply) {
Usr usr;
try {
usr = std::stoull(param.item.data);
} catch (...) {
return;
}
const QueryFunc &func = db->getFunc(usr);
std::map<SymbolIdx, std::pair<int, std::vector<lsRange>>> sym2ranges;
for (Use use : func.uses) {
const QueryFile &file = db->files[use.file_id];
Maybe<ExtentRef> best;
for (auto [sym, refcnt] : file.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)
add(sym2ranges, *best, use.file_id);
}
reply(toCallResult<Out_incomingCall>(db, sym2ranges));
}
void MessageHandler::callHierarchy_outgoingCalls(CallsParam &param, ReplyOnce &reply) {
Usr usr;
try {
usr = std::stoull(param.item.data);
} catch (...) {
return;
}
const QueryFunc &func = db->getFunc(usr);
std::map<SymbolIdx, std::pair<int, std::vector<lsRange>>> sym2ranges;
if (const auto *def = func.anyDef())
for (SymbolRef sym : def->callees)
if (sym.kind == Kind::Func) {
add(sym2ranges, sym, def->file_id);
}
reply(toCallResult<Out_outgoingCall>(db, sym2ranges));
reply(FlattenHierarchy(result));
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -7,8 +19,8 @@
#include "query.hh"
namespace ccls {
REFLECT_STRUCT(IndexInclude, line, resolved_path);
REFLECT_STRUCT(QueryFile::Def, path, args, language, dependencies, includes, skipped_ranges);
REFLECT_STRUCT(QueryFile::Def, path, args, language, skipped_ranges,
dependencies);
namespace {
struct Out_cclsInfo {
@ -16,14 +28,14 @@ struct Out_cclsInfo {
int files, funcs, types, vars;
} db;
struct Pipeline {
int64_t lastIdle, completed, enqueued, opened;
int pendingIndexRequests;
} pipeline;
struct Project {
int entries;
} project;
};
REFLECT_STRUCT(Out_cclsInfo::DB, files, funcs, types, vars);
REFLECT_STRUCT(Out_cclsInfo::Pipeline, lastIdle, completed, enqueued, opened);
REFLECT_STRUCT(Out_cclsInfo::Pipeline, pendingIndexRequests);
REFLECT_STRUCT(Out_cclsInfo::Project, entries);
REFLECT_STRUCT(Out_cclsInfo, db, pipeline, project);
} // namespace
@ -34,27 +46,15 @@ void MessageHandler::ccls_info(EmptyParam &, ReplyOnce &reply) {
result.db.funcs = db->funcs.size();
result.db.types = db->types.size();
result.db.vars = db->vars.size();
result.pipeline.lastIdle = pipeline::stats.last_idle;
result.pipeline.completed = pipeline::stats.completed;
result.pipeline.enqueued = pipeline::stats.enqueued;
result.pipeline.opened = pipeline::stats.opened;
result.pipeline.pendingIndexRequests = pipeline::pending_index_requests;
result.project.entries = 0;
for (auto &[_, folder] : project->root2folder)
result.project.entries += folder.entries.size();
reply(result);
}
struct FileInfoParam : TextDocumentParam {
bool dependencies = false;
bool includes = false;
bool skipped_ranges = false;
};
REFLECT_STRUCT(FileInfoParam, textDocument, dependencies, includes, skipped_ranges);
void MessageHandler::ccls_fileInfo(JsonReader &reader, ReplyOnce &reply) {
FileInfoParam param;
reflect(reader, param);
QueryFile *file = findFile(param.textDocument.uri.getPath());
void MessageHandler::ccls_fileInfo(TextDocumentParam &param, ReplyOnce &reply) {
QueryFile *file = FindFile(param.textDocument.uri.GetPath());
if (!file)
return;
@ -63,12 +63,8 @@ void MessageHandler::ccls_fileInfo(JsonReader &reader, ReplyOnce &reply) {
result.path = file->def->path;
result.args = file->def->args;
result.language = file->def->language;
if (param.dependencies)
result.dependencies = file->def->dependencies;
if (param.includes)
result.includes = file->def->includes;
if (param.skipped_ranges)
result.skipped_ranges = file->def->skipped_ranges;
result.includes = file->def->includes;
result.skipped_ranges = file->def->skipped_ranges;
reply(result);
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -24,7 +36,8 @@ struct Param : TextDocumentPositionParam {
bool hierarchy = false;
};
REFLECT_STRUCT(Param, textDocument, position, id, kind, derived, qualified, levels, hierarchy);
REFLECT_STRUCT(Param, textDocument, position, id, kind, derived, qualified,
levels, hierarchy);
struct Out_cclsInheritance {
Usr usr;
@ -38,20 +51,23 @@ struct Out_cclsInheritance {
// Empty if the |levels| limit is reached.
std::vector<Out_cclsInheritance> children;
};
REFLECT_STRUCT(Out_cclsInheritance, id, kind, name, location, numChildren, children);
REFLECT_STRUCT(Out_cclsInheritance, id, kind, name, location, numChildren,
children);
bool expand(MessageHandler *m, Out_cclsInheritance *entry, bool derived, bool qualified, int levels);
bool Expand(MessageHandler *m, Out_cclsInheritance *entry, bool derived,
bool qualified, int levels);
template <typename Q>
bool expandHelper(MessageHandler *m, Out_cclsInheritance *entry, bool derived, bool qualified, int levels, Q &entity) {
const auto *def = entity.anyDef();
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);
entry->name = def->Name(qualified);
if (def->spell) {
if (auto loc = getLsLocation(m->db, m->wfiles, *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]))
if (auto loc = GetLsLocation(m->db, m->wfiles, entity.declarations[0]))
entry->location = *loc;
}
} else if (!derived) {
@ -68,7 +84,7 @@ bool expandHelper(MessageHandler *m, Out_cclsInheritance *entry, bool derived, b
entry1.id = std::to_string(usr);
entry1.usr = usr;
entry1.kind = entry->kind;
if (expand(m, &entry1, derived, qualified, levels - 1))
if (Expand(m, &entry1, derived, qualified, levels - 1))
entry->children.push_back(std::move(entry1));
}
entry->numChildren = int(entry->children.size());
@ -83,7 +99,7 @@ bool expandHelper(MessageHandler *m, Out_cclsInheritance *entry, bool derived, b
entry1.id = std::to_string(usr);
entry1.usr = usr;
entry1.kind = entry->kind;
if (expand(m, &entry1, derived, qualified, levels - 1))
if (Expand(m, &entry1, derived, qualified, levels - 1))
entry->children.push_back(std::move(entry1));
}
entry->numChildren = int(entry->children.size());
@ -93,24 +109,27 @@ bool expandHelper(MessageHandler *m, Out_cclsInheritance *entry, bool derived, b
return true;
}
bool expand(MessageHandler *m, Out_cclsInheritance *entry, bool derived, bool qualified, int levels) {
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->getFunc(entry->usr));
return ExpandHelper(m, entry, derived, qualified, levels,
m->db->Func(entry->usr));
else
return expandHelper(m, entry, derived, qualified, levels, m->db->getType(entry->usr));
return ExpandHelper(m, entry, derived, qualified, levels,
m->db->Type(entry->usr));
}
std::optional<Out_cclsInheritance> buildInitial(MessageHandler *m, SymbolRef sym, bool derived, bool qualified,
int levels) {
std::optional<Out_cclsInheritance> 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);
Expand(m, &entry, derived, qualified, levels);
return entry;
}
void inheritance(MessageHandler *m, Param &param, ReplyOnce &reply) {
void Inheritance(MessageHandler *m, Param &param, ReplyOnce &reply) {
std::optional<Out_cclsInheritance> result;
if (param.id.size()) {
try {
@ -122,18 +141,19 @@ void inheritance(MessageHandler *m, Param &param, ReplyOnce &reply) {
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)))
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 {
auto [file, wf] = m->findOrFail(param.textDocument.uri.getPath(), reply);
auto [file, wf] = m->FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf) {
return;
}
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position))
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);
result = BuildInitial(m, sym, param.derived, param.qualified,
param.levels);
break;
}
}
@ -141,21 +161,22 @@ void inheritance(MessageHandler *m, Param &param, ReplyOnce &reply) {
if (param.hierarchy)
reply(result);
else
reply(flattenHierarchy(result));
reply(FlattenHierarchy(result));
}
} // namespace
void MessageHandler::ccls_inheritance(JsonReader &reader, ReplyOnce &reply) {
Param param;
reflect(reader, param);
inheritance(this, param, reply);
Reflect(reader, param);
Inheritance(this, param, reply);
}
void MessageHandler::textDocument_implementation(TextDocumentPositionParam &param, ReplyOnce &reply) {
void MessageHandler::textDocument_implementation(
TextDocumentPositionParam &param, ReplyOnce &reply) {
Param param1;
param1.textDocument = param.textDocument;
param1.position = param.position;
param1.derived = true;
inheritance(this, param1, reply);
Inheritance(this, param1, reply);
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -30,7 +42,8 @@ struct Param : TextDocumentPositionParam {
bool hierarchy = false;
};
REFLECT_STRUCT(Param, textDocument, position, id, qualified, levels, kind, hierarchy);
REFLECT_STRUCT(Param, textDocument, position, id, qualified, levels, kind,
hierarchy);
struct Out_cclsMember {
Usr usr;
@ -44,14 +57,16 @@ struct Out_cclsMember {
// Empty if the |levels| limit is reached.
std::vector<Out_cclsMember> children;
};
REFLECT_STRUCT(Out_cclsMember, id, name, fieldName, location, numChildren, children);
REFLECT_STRUCT(Out_cclsMember, id, name, fieldName, location, numChildren,
children);
bool expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, int levels, Kind memberKind);
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();
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;
@ -69,17 +84,19 @@ void doField(MessageHandler *m, Out_cclsMember *entry, const QueryVar &var, int6
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);
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<Location> loc = getLsLocation(m->db, m->wfiles, *def1->spell))
if (std::optional<Location> 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))
if (Expand(m, &entry1, qualified, levels, Kind::Var))
entry->children.push_back(std::move(entry1));
} else {
entry1.id = "0";
@ -89,17 +106,18 @@ void doField(MessageHandler *m, Out_cclsMember *entry, const QueryVar &var, int6
}
// Expand a type node by adding members recursively to it.
bool expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, int levels, Kind memberKind) {
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));
entry->name = ClangBuiltinTypeName(int(entry->usr));
return true;
}
const QueryType *type = &m->db->getType(entry->usr);
const QueryType::Def *def = type->anyDef();
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);
entry->name = def->Name(qualified);
std::unordered_set<Usr> seen;
if (levels > 0) {
std::vector<const QueryType *> stack;
@ -108,35 +126,37 @@ bool expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, int levels
while (stack.size()) {
type = stack.back();
stack.pop_back();
const auto *def = type->anyDef();
const auto *def = type->AnyDef();
if (!def)
continue;
if (def->kind != SymbolKind::Namespace)
for (Usr usr : def->bases) {
auto &type1 = m->db->getType(usr);
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->getType(def->alias_of).anyDef();
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<Location> loc = getLsLocation(m->db, m->wfiles, *def1->spell))
if (std::optional<Location> 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<Location> loc = getLsLocation(m->db, m->wfiles, *def->spell))
if (std::optional<Location> 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)) {
if (Expand(m, &entry1, qualified, levels - 1, memberKind)) {
// For builtin types |name| is set.
if (entry1.fieldName.empty())
entry1.fieldName = std::string(entry1.name);
@ -147,15 +167,16 @@ bool expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, int levels
for (auto &def : type->def)
for (Usr usr : def.funcs)
if (seen1.insert(usr).second) {
QueryFunc &func1 = m->db->getFunc(usr);
if (const QueryFunc::Def *def1 = func1.anyDef()) {
QueryFunc &func1 = m->db->Func(usr);
if (const QueryFunc::Def *def1 = func1.AnyDef()) {
Out_cclsMember entry1;
entry1.fieldName = def1->name(false);
entry1.fieldName = def1->Name(false);
if (def1->spell) {
if (auto loc = getLsLocation(m->db, m->wfiles, *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]))
if (auto loc = GetLsLocation(m->db, m->wfiles,
func1.declarations[0]))
entry1.location = *loc;
}
entry->children.push_back(std::move(entry1));
@ -166,15 +187,16 @@ bool expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, int levels
for (auto &def : type->def)
for (Usr usr : def.types)
if (seen1.insert(usr).second) {
QueryType &type1 = m->db->getType(usr);
if (const QueryType::Def *def1 = type1.anyDef()) {
QueryType &type1 = m->db->Type(usr);
if (const QueryType::Def *def1 = type1.AnyDef()) {
Out_cclsMember entry1;
entry1.fieldName = def1->name(false);
entry1.fieldName = def1->Name(false);
if (def1->spell) {
if (auto loc = getLsLocation(m->db, m->wfiles, *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]))
if (auto loc = GetLsLocation(m->db, m->wfiles,
type1.declarations[0]))
entry1.location = *loc;
}
entry->children.push_back(std::move(entry1));
@ -185,9 +207,9 @@ bool expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, int levels
for (auto &def : type->def)
for (auto it : def.vars)
if (seen1.insert(it.first).second) {
QueryVar &var = m->db->getVar(it.first);
QueryVar &var = m->db->Var(it.first);
if (!var.def.empty())
doField(m, entry, var, it.second, qualified, levels - 1);
DoField(m, entry, var, it.second, qualified, levels - 1);
}
}
}
@ -197,32 +219,33 @@ bool expand(MessageHandler *m, Out_cclsMember *entry, bool qualified, int levels
return true;
}
std::optional<Out_cclsMember> buildInitial(MessageHandler *m, Kind kind, Usr root_usr, bool qualified, int levels,
Kind memberKind) {
std::optional<Out_cclsMember> 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->getFunc(root_usr).anyDef();
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);
entry.name = def->Name(qualified);
if (def->spell) {
if (auto loc = getLsLocation(m->db, m->wfiles, *def->spell))
if (auto loc = GetLsLocation(m->db, m->wfiles, *def->spell))
entry.location = *loc;
}
for (Usr usr : def->vars) {
auto &var = m->db->getVar(usr);
auto &var = m->db->Var(usr);
if (var.def.size())
doField(m, &entry, var, -1, qualified, levels - 1);
DoField(m, &entry, var, -1, qualified, levels - 1);
}
return entry;
}
case Kind::Type: {
const auto *def = m->db->getType(root_usr).anyDef();
const auto *def = m->db->Type(root_usr).AnyDef();
if (!def)
return {};
@ -230,10 +253,10 @@ std::optional<Out_cclsMember> buildInitial(MessageHandler *m, Kind kind, Usr roo
entry.id = std::to_string(root_usr);
entry.usr = root_usr;
if (def->spell) {
if (auto loc = getLsLocation(m->db, m->wfiles, *def->spell))
if (auto loc = GetLsLocation(m->db, m->wfiles, *def->spell))
entry.location = *loc;
}
expand(m, &entry, qualified, levels, memberKind);
Expand(m, &entry, qualified, levels, memberKind);
return entry;
}
}
@ -242,7 +265,7 @@ std::optional<Out_cclsMember> buildInitial(MessageHandler *m, Kind kind, Usr roo
void MessageHandler::ccls_member(JsonReader &reader, ReplyOnce &reply) {
Param param;
reflect(reader, param);
Reflect(reader, param);
std::optional<Out_cclsMember> result;
if (param.id.size()) {
try {
@ -254,22 +277,25 @@ void MessageHandler::ccls_member(JsonReader &reader, ReplyOnce &reply) {
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)))
if (!(db->HasType(param.usr) && Expand(this, &*result, param.qualified,
param.levels, param.kind)))
result.reset();
} else {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
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);
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();
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);
result = BuildInitial(this, Kind::Type, def->type, param.qualified,
param.levels, param.kind);
break;
}
default:
@ -282,6 +308,6 @@ void MessageHandler::ccls_member(JsonReader &reader, ReplyOnce &reply) {
if (param.hierarchy)
reply(result);
else
reply(flattenHierarchy(result));
reply(FlattenHierarchy(result));
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -13,12 +25,14 @@ struct Param {
};
REFLECT_STRUCT(Param, textDocument, position, direction);
Maybe<Range> findParent(QueryFile *file, Pos pos) {
Maybe<Range> FindParent(QueryFile *file, Pos pos) {
Maybe<Range> 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)))
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;
}
@ -26,57 +40,62 @@ Maybe<Range> findParent(QueryFile *file, Pos pos) {
void MessageHandler::ccls_navigate(JsonReader &reader, ReplyOnce &reply) {
Param param;
reflect(reader, param);
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
Reflect(reader, param);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf) {
return;
}
Position ls_pos = param.position;
if (wf->index_lines.size())
if (auto line = wf->getIndexPosFromBufferPos(ls_pos.line, &ls_pos.character, false))
if (auto line =
wf->GetIndexPosFromBufferPos(ls_pos.line, &ls_pos.character, false))
ls_pos.line = *line;
Pos pos{(uint16_t)ls_pos.line, (int16_t)ls_pos.character};
Maybe<Range> res;
switch (param.direction[0]) {
case 'D': {
Maybe<Range> parent = findParent(file, pos);
Maybe<Range> parent = FindParent(file, pos);
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && pos < sym.extent.start && (!parent || sym.extent.end <= parent->end) &&
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)))
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<Range> parent = findParent(file, pos);
Maybe<Range> 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)))
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))
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<Location> result;
if (res)
if (auto ls_range = getLsRange(wf, *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;

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -22,13 +34,13 @@ REFLECT_STRUCT(Param, dependencies, whitelist, blacklist);
void MessageHandler::ccls_reload(JsonReader &reader) {
Param param;
reflect(reader, param);
// Send index requests for every file.
Reflect(reader, param);
// Send index requests for every file.
if (param.whitelist.empty() && param.blacklist.empty()) {
vfs->clear();
vfs->Clear();
db->clear();
project->index(wfiles, RequestId());
manager->clear();
project->Index(wfiles, RequestId());
manager->Clear();
return;
}
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -18,28 +30,29 @@ REFLECT_STRUCT(Param, textDocument, position, kind);
void MessageHandler::ccls_vars(JsonReader &reader, ReplyOnce &reply) {
Param param;
reflect(reader, param);
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
Reflect(reader, param);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf) {
return;
}
std::vector<Location> result;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
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();
const QueryVar::Def *def = db->GetVar(sym).AnyDef();
if (!def || !def->type)
continue;
usr = def->type;
[[fallthrough]];
}
case Kind::Type: {
for (DeclRef dr : getVarDeclarations(db, db->getType(usr).instances, param.kind))
if (auto loc = getLocationLink(db, wfiles, dr))
for (DeclRef dr :
GetVarDeclarations(db, db->Type(usr).instances, param.kind))
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(Location(std::move(loc)));
break;
}

View File

@ -1,74 +1,41 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "sema_manager.hh"
#include "working_files.hh"
#include <llvm/ADT/Twine.h>
#include <llvm/Config/llvm-config.h>
#include <llvm/Support/Threading.h>
#include <rapidjson/document.h>
#include <rapidjson/writer.h>
#include <stdexcept>
#include <stdlib.h>
#include <stdexcept>
#include <thread>
namespace ccls {
using namespace llvm;
const char *const kTokenTypes[] = {
"unknown",
"file",
"module",
"namespace",
"package",
"class",
"method",
"property",
"field",
"constructor",
"enum",
"interface",
"function",
"variable",
"constant",
"string",
"number",
"boolean",
"array",
"object",
"key",
"null",
"enumMember",
"struct",
"event",
"operator",
"typeParameter",
// 252 => 27
"typeAlias",
"parameter",
"staticMethod",
"macro",
};
static_assert(std::size(kTokenTypes) ==
int(SymbolKind::FirstNonStandard) + int(SymbolKind::LastExtension) - int(SymbolKind::FirstExtension) + 1);
const char *const kTokenModifiers[] = {
#define TOKEN_MODIFIER(name, str) str,
#include "enum.inc"
#undef TOKEN_MODIFIER
};
extern std::vector<std::string> g_init_options;
namespace {
@ -99,12 +66,12 @@ struct ServerCap {
// for
// '::' and '>' for '->'. See
// https://github.com/Microsoft/language-server-protocol/issues/138.
std::vector<const char *> triggerCharacters = {".", ":", ">", "#", "<", "\"", "/"};
std::vector<const char *> triggerCharacters = {".", ":", ">", "#",
"<", "\"", "/"};
} completionProvider;
struct SignatureHelpOptions {
std::vector<const char *> triggerCharacters = {"(", "{", ","};
std::vector<const char *> triggerCharacters = {"(", ","};
} signatureHelpProvider;
bool declarationProvider = true;
bool definitionProvider = true;
bool typeDefinitionProvider = true;
bool implementationProvider = true;
@ -120,7 +87,8 @@ struct ServerCap {
} codeLensProvider;
bool documentFormattingProvider = true;
bool documentRangeFormattingProvider = true;
Config::ServerCap::DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
Config::ServerCap::DocumentOnTypeFormattingOptions
documentOnTypeFormattingProvider;
bool renameProvider = true;
struct DocumentLinkOptions {
bool resolveProvider = true;
@ -130,33 +98,28 @@ struct ServerCap {
struct ExecuteCommandOptions {
std::vector<const char *> commands = {ccls_xref};
} executeCommandProvider;
bool callHierarchyProvider = true;
struct SemanticTokenProvider {
struct SemanticTokensLegend {
std::vector<const char *> tokenTypes{std::begin(kTokenTypes), std::end(kTokenTypes)};
std::vector<const char *> tokenModifiers{std::begin(kTokenModifiers), std::end(kTokenModifiers)};
} legend;
bool range = true;
bool full = true;
} semanticTokensProvider;
Config::ServerCap::Workspace workspace;
};
REFLECT_STRUCT(ServerCap::CodeActionOptions, codeActionKinds);
REFLECT_STRUCT(ServerCap::CodeLensOptions, resolveProvider);
REFLECT_STRUCT(ServerCap::CompletionOptions, resolveProvider, triggerCharacters);
REFLECT_STRUCT(ServerCap::CompletionOptions, resolveProvider,
triggerCharacters);
REFLECT_STRUCT(ServerCap::DocumentLinkOptions, resolveProvider);
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, textDocumentSync, hoverProvider, completionProvider, signatureHelpProvider,
declarationProvider, definitionProvider, implementationProvider, typeDefinitionProvider,
referencesProvider, documentHighlightProvider, documentSymbolProvider, workspaceSymbolProvider,
codeActionProvider, codeLensProvider, documentFormattingProvider, documentRangeFormattingProvider,
documentOnTypeFormattingProvider, renameProvider, documentLinkProvider, foldingRangeProvider,
executeCommandProvider, callHierarchyProvider, semanticTokensProvider, workspace);
REFLECT_STRUCT(ServerCap::SemanticTokenProvider, legend, range, full);
REFLECT_STRUCT(ServerCap::SemanticTokenProvider::SemanticTokensLegend, tokenTypes, tokenModifiers);
REFLECT_STRUCT(ServerCap::TextDocumentSyncOptions, openClose, change, willSave,
willSaveWaitUntil, save);
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;
@ -179,16 +142,12 @@ struct WorkspaceClientCap {
DynamicReg didChangeWatchedFiles;
DynamicReg symbol;
DynamicReg executeCommand;
struct SemanticTokensWorkspace {
bool refreshSupport = false;
} semanticTokens;
};
REFLECT_STRUCT(WorkspaceClientCap::WorkspaceEdit, documentChanges);
REFLECT_STRUCT(WorkspaceClientCap::SemanticTokensWorkspace, refreshSupport);
REFLECT_STRUCT(WorkspaceClientCap, applyEdit, workspaceEdit, didChangeConfiguration, didChangeWatchedFiles, symbol,
executeCommand, semanticTokens);
REFLECT_STRUCT(WorkspaceClientCap, applyEdit, workspaceEdit,
didChangeConfiguration, didChangeWatchedFiles, symbol,
executeCommand);
// Text document specific client capabilities.
struct TextDocumentClientCap {
@ -218,9 +177,11 @@ struct TextDocumentClientCap {
} publishDiagnostics;
};
REFLECT_STRUCT(TextDocumentClientCap::Completion::CompletionItem, snippetSupport);
REFLECT_STRUCT(TextDocumentClientCap::Completion::CompletionItem,
snippetSupport);
REFLECT_STRUCT(TextDocumentClientCap::Completion, completionItem);
REFLECT_STRUCT(TextDocumentClientCap::DocumentSymbol, hierarchicalDocumentSymbolSupport);
REFLECT_STRUCT(TextDocumentClientCap::DocumentSymbol,
hierarchicalDocumentSymbolSupport);
REFLECT_STRUCT(TextDocumentClientCap::LinkSupport, linkSupport);
REFLECT_STRUCT(TextDocumentClientCap::PublishDiagnostics, relatedInformation);
REFLECT_STRUCT(TextDocumentClientCap, completion, definition, documentSymbol, publishDiagnostics);
@ -251,7 +212,7 @@ struct InitializeParam {
std::vector<WorkspaceFolder> workspaceFolders;
};
void reflect(JsonReader &reader, InitializeParam::Trace &value) {
void Reflect(JsonReader &reader, InitializeParam::Trace &value) {
if (!reader.m->IsString()) {
value = InitializeParam::Trace::Off;
return;
@ -265,19 +226,13 @@ void reflect(JsonReader &reader, InitializeParam::Trace &value) {
value = InitializeParam::Trace::Verbose;
}
// initializationOptions is deserialized separately.
REFLECT_STRUCT(InitializeParam, rootUri, capabilities, trace, workspaceFolders);
REFLECT_STRUCT(InitializeParam, rootUri, initializationOptions, capabilities,
trace, workspaceFolders);
struct InitializeResult {
ServerCap capabilities;
struct ServerInfo {
const char *name = "ccls";
const char *version = CCLS_VERSION;
} serverInfo;
const char *offsetEncoding = "utf-32";
};
REFLECT_STRUCT(InitializeResult::ServerInfo, name, version);
REFLECT_STRUCT(InitializeResult, capabilities, serverInfo, offsetEncoding);
REFLECT_STRUCT(InitializeResult, capabilities);
struct FileSystemWatcher {
std::string globPattern = "**/*";
@ -297,7 +252,7 @@ REFLECT_STRUCT(DidChangeWatchedFilesRegistration::Option, watchers);
REFLECT_STRUCT(DidChangeWatchedFilesRegistration, id, method, registerOptions);
REFLECT_STRUCT(RegistrationParam, registrations);
void *indexer(void *arg_) {
void *Indexer(void *arg_) {
MessageHandler *h;
int idx;
auto *arg = static_cast<std::pair<MessageHandler *, int> *>(arg_);
@ -305,21 +260,16 @@ void *indexer(void *arg_) {
delete arg;
std::string name = "indexer" + std::to_string(idx);
set_thread_name(name.c_str());
// Don't lower priority on __APPLE__. getpriority(2) says "When setting a
// thread into background state the scheduling priority is set to lowest
// value, disk and network IO are throttled."
#if LLVM_ENABLE_THREADS && !defined(__APPLE__)
set_thread_priority(ThreadPriority::Background);
#endif
pipeline::indexer_Main(h->manager, h->vfs, h->project, h->wfiles);
pipeline::threadLeave();
pipeline::Indexer_Main(h->manager, h->vfs, h->project, h->wfiles);
pipeline::ThreadLeave();
return nullptr;
}
} // namespace
void do_initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply) {
std::string project_path = normalizePath(param.rootUri->getPath());
LOG_S(INFO) << "initialize in directory " << project_path << " with uri " << param.rootUri->raw_uri;
void Initialize(MessageHandler *m, InitializeParam &param, 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);
@ -329,7 +279,7 @@ void do_initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply)
if (!reader.HasParseError()) {
JsonReader json_reader{&reader};
try {
reflect(json_reader, *g_config);
Reflect(json_reader, *g_config);
} catch (std::invalid_argument &) {
// This will not trigger because parse error is handled in
// MessageRegistry::Parse in lsp.cc
@ -340,35 +290,36 @@ void do_initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply)
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
JsonWriter json_writer(&writer);
reflect(json_writer, *g_config);
Reflect(json_writer, *g_config);
LOG_S(INFO) << "initializationOptions: " << output.GetString();
if (g_config->cache.directory.size()) {
SmallString<256> path(g_config->cache.directory);
sys::fs::make_absolute(project_path, path);
SmallString<256> Path(g_config->cache.directory);
sys::fs::make_absolute(project_path, Path);
// Use upper case for the Driver letter on Windows.
g_config->cache.directory = normalizePath(path.str());
ensureEndsInSlash(g_config->cache.directory);
g_config->cache.directory = NormalizePath(Path.str());
EnsureEndsInSlash(g_config->cache.directory);
}
}
// Client capabilities
const auto &capabilities = param.capabilities;
g_config->client.hierarchicalDocumentSymbolSupport &=
capabilities.textDocument.documentSymbol.hierarchicalDocumentSymbolSupport;
g_config->client.linkSupport &= capabilities.textDocument.definition.linkSupport;
g_config->client.snippetSupport &= capabilities.textDocument.completion.completionItem.snippetSupport;
g_config->client.diagnosticsRelatedInformation &= capabilities.textDocument.publishDiagnostics.relatedInformation;
didChangeWatchedFiles = capabilities.workspace.didChangeWatchedFiles.dynamicRegistration;
g_config->client.semanticTokensRefresh &= capabilities.workspace.semanticTokens.refreshSupport;
if (!g_config->client.snippetSupport)
g_config->completion.duplicateOptional = false;
capabilities.textDocument.documentSymbol
.hierarchicalDocumentSymbolSupport;
g_config->client.linkSupport &=
capabilities.textDocument.definition.linkSupport;
g_config->client.snippetSupport &=
capabilities.textDocument.completion.completionItem.snippetSupport;
g_config->client.diagnosticsRelatedInformation &=
capabilities.textDocument.publishDiagnostics.relatedInformation;
didChangeWatchedFiles =
capabilities.workspace.didChangeWatchedFiles.dynamicRegistration;
// Ensure there is a resource directory.
if (g_config->clang.resourceDir.empty())
g_config->clang.resourceDir = getDefaultResourceDirectory();
doPathMapping(g_config->clang.resourceDir);
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
@ -376,30 +327,32 @@ void do_initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply)
{
InitializeResult result;
auto &c = result.capabilities;
c.documentOnTypeFormattingProvider = g_config->capabilities.documentOnTypeFormattingProvider;
c.documentOnTypeFormattingProvider =
g_config->capabilities.documentOnTypeFormattingProvider;
c.foldingRangeProvider = g_config->capabilities.foldingRangeProvider;
c.workspace = g_config->capabilities.workspace;
reply(result);
}
// Set project root.
ensureEndsInSlash(project_path);
EnsureEndsInSlash(project_path);
g_config->fallbackFolder = project_path;
auto &workspaceFolders = g_config->workspaceFolders;
for (const WorkspaceFolder &wf : param.workspaceFolders) {
std::string path = wf.uri.getPath();
ensureEndsInSlash(path);
std::string real = realPath(path) + '/';
workspaceFolders.emplace_back(path, real);
std::string path = wf.uri.GetPath();
EnsureEndsInSlash(path);
std::string real = RealPath(path) + '/';
workspaceFolders.emplace_back(path, path == real ? "" : real);
}
if (workspaceFolders.empty()) {
std::string real = realPath(project_path) + '/';
workspaceFolders.emplace_back(project_path, real);
std::string real = RealPath(project_path) + '/';
workspaceFolders.emplace_back(project_path,
project_path == real ? "" : real);
}
std::sort(workspaceFolders.begin(), workspaceFolders.end(),
[](auto &l, auto &r) { return l.first.size() > r.first.size(); });
for (auto &[folder, real] : workspaceFolders)
if (real == folder)
if (real.empty())
LOG_S(INFO) << "workspace folder: " << folder;
else
LOG_S(INFO) << "workspace folder: " << folder << " -> " << real;
@ -410,67 +363,64 @@ void do_initialize(MessageHandler *m, InitializeParam &param, ReplyOnce &reply)
for (auto &[folder, _] : workspaceFolders) {
// Create two cache directories for files inside and outside of the
// project.
std::string escaped = escapeFileName(folder.substr(0, folder.size() - 1));
std::string escaped = EscapeFileName(folder.substr(0, folder.size() - 1));
sys::fs::create_directories(g_config->cache.directory + escaped);
sys::fs::create_directories(g_config->cache.directory + '@' + escaped);
}
idx::init();
idx::Init();
for (auto &[folder, _] : workspaceFolders)
m->project->load(folder);
m->project->Load(folder);
// 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 = (int)std::thread::hardware_concurrency();
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<MessageHandler *, int>{m, i});
SpawnThread(Indexer, new std::pair<MessageHandler *, int>{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->project->Index(m->wfiles, reply.id);
m->manager->sessions.setCapacity(g_config->session.maxNum);
m->manager->sessions.SetCapacity(g_config->session.maxNum);
}
void MessageHandler::initialize(JsonReader &reader, ReplyOnce &reply) {
InitializeParam param;
reflect(reader, param);
auto it = reader.m->FindMember("initializationOptions");
if (it != reader.m->MemberEnd() && it->value.IsObject()) {
JsonReader m1(&it->value);
try {
reflect(m1, param.initializationOptions);
} catch (std::invalid_argument &) {
reader.path_.push_back("initializationOptions");
reader.path_.insert(reader.path_.end(), m1.path_.begin(), m1.path_.end());
throw;
}
}
Reflect(reader, param);
if (!param.rootUri) {
reply.error(ErrorCode::InvalidRequest, "expected rootUri");
reply.Error(ErrorCode::InvalidRequest, "expected rootUri");
return;
}
do_initialize(this, param, reply);
Initialize(this, param, reply);
}
void standaloneInitialize(MessageHandler &handler, const std::string &root) {
void StandaloneInitialize(MessageHandler &handler, const std::string &root) {
InitializeParam param;
param.rootUri = DocumentUri::fromPath(root);
param.rootUri = DocumentUri::FromPath(root);
ReplyOnce reply{handler};
do_initialize(&handler, param, reply);
Initialize(&handler, param, reply);
}
void MessageHandler::initialized(EmptyParam &) {
if (didChangeWatchedFiles) {
RegistrationParam param;
pipeline::request("client/registerCapability", param);
pipeline::Request("client/registerCapability", param);
}
}
void MessageHandler::shutdown(EmptyParam &, ReplyOnce &reply) { reply(JsonNull{}); }
void MessageHandler::shutdown(EmptyParam &, ReplyOnce &reply) {
reply(JsonNull{});
}
void MessageHandler::exit(EmptyParam &) { pipeline::g_quit.store(true, std::memory_order_relaxed); }
void MessageHandler::exit(EmptyParam &) {
pipeline::quit.store(true, std::memory_order_relaxed);
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -20,18 +32,21 @@ struct CodeAction {
WorkspaceEdit edit;
};
REFLECT_STRUCT(CodeAction, title, kind, edit);
} // namespace
void MessageHandler::textDocument_codeAction(CodeActionParam &param, ReplyOnce &reply) {
WorkingFile *wf = findOrFail(param.textDocument.uri.getPath(), reply).second;
}
void MessageHandler::textDocument_codeAction(CodeActionParam &param,
ReplyOnce &reply) {
WorkingFile *wf = FindOrFail(param.textDocument.uri.GetPath(), reply).second;
if (!wf)
return;
std::vector<CodeAction> result;
std::vector<Diagnostic> diagnostics;
wfiles->withLock([&]() { diagnostics = wf->diagnostics; });
wfiles->WithLock([&]() { diagnostics = wf->diagnostics; });
for (Diagnostic &diag : diagnostics)
if (diag.fixits_.size() &&
(param.range.intersects(diag.range) ||
llvm::any_of(diag.fixits_, [&](const TextEdit &edit) { return param.range.intersects(edit.range); }))) {
(param.range.Intersects(diag.range) ||
llvm::any_of(diag.fixits_, [&](const TextEdit &edit) {
return param.range.Intersects(edit.range);
}))) {
CodeAction &cmd = result.emplace_back();
cmd.title = "FixIt: " + diag.message;
auto &edit = cmd.edit.documentChanges.emplace_back();
@ -61,11 +76,12 @@ REFLECT_STRUCT(Cmd_xref, usr, kind, field);
REFLECT_STRUCT(Command, title, command, arguments);
REFLECT_STRUCT(CodeLens, range, command);
template <typename T> std::string toString(T &v) {
template <typename T>
std::string ToString(T &v) {
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> writer(output);
JsonWriter json_writer(&writer);
reflect(json_writer, v);
Reflect(json_writer, v);
return output.GetString();
}
@ -76,16 +92,18 @@ struct CommonCodeLensParams {
};
} // namespace
void MessageHandler::textDocument_codeLens(TextDocumentParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_codeLens(TextDocumentParam &param,
ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
std::vector<CodeLens> result;
auto add = [&, wf = wf](const char *singular, Cmd_xref show, Range range, int num, bool force_display = false) {
auto Add = [&, wf = wf](const char *singular, Cmd_xref show, Range range,
int num, bool force_display = false) {
if (!num && !force_display)
return;
std::optional<lsRange> ls_range = getLsRange(wf, range);
std::optional<lsRange> ls_range = GetLsRange(wf, range);
if (!ls_range)
return;
CodeLens &code_lens = result.emplace_back();
@ -93,45 +111,55 @@ void MessageHandler::textDocument_codeLens(TextDocumentParam &param, ReplyOnce &
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));
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<Range> seen;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.valid() || !seen.insert(sym.range).second)
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();
QueryFunc &func = db->GetFunc(sym);
const QueryFunc::Def *def = func.AnyDef();
if (!def)
continue;
std::vector<Use> base_uses = getUsesForAllBases(db, func);
std::vector<Use> derived_uses = getUsesForAllDerived(db, func);
add("ref", {sym.usr, Kind::Func, "uses"}, sym.range, func.uses.size(), base_uses.empty());
std::vector<Use> base_uses = GetUsesForAllBases(db, func);
std::vector<Use> 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());
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());
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());
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());
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();
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);
Add("ref", {sym.usr, Kind::Var, "uses"}, sym.range, var.uses.size(),
def->kind != SymbolKind::Macro);
break;
}
case Kind::File:
@ -143,9 +171,10 @@ void MessageHandler::textDocument_codeLens(TextDocumentParam &param, ReplyOnce &
reply(result);
}
void MessageHandler::workspace_executeCommand(JsonReader &reader, ReplyOnce &reply) {
void MessageHandler::workspace_executeCommand(JsonReader &reader,
ReplyOnce &reply) {
Command param;
reflect(reader, param);
Reflect(reader, param);
if (param.arguments.empty()) {
return;
}
@ -154,45 +183,45 @@ void MessageHandler::workspace_executeCommand(JsonReader &reader, ReplyOnce &rep
JsonReader json_reader{&reader1};
if (param.command == ccls_xref) {
Cmd_xref cmd;
reflect(json_reader, cmd);
Reflect(json_reader, cmd);
std::vector<Location> result;
auto map = [&](auto &&uses) {
auto Map = [&](auto &&uses) {
for (auto &use : uses)
if (auto loc = getLsLocation(db, wfiles, use))
if (auto loc = GetLsLocation(db, wfiles, use))
result.push_back(std::move(*loc));
};
switch (cmd.kind) {
case Kind::Func: {
QueryFunc &func = db->getFunc(cmd.usr);
QueryFunc &func = db->Func(cmd.usr);
if (cmd.field == "bases") {
if (auto *def = func.anyDef())
map(getFuncDeclarations(db, def->bases));
if (auto *def = func.AnyDef())
Map(GetFuncDeclarations(db, def->bases));
} else if (cmd.field == "bases uses") {
map(getUsesForAllBases(db, func));
Map(GetUsesForAllBases(db, func));
} else if (cmd.field == "derived") {
map(getFuncDeclarations(db, func.derived));
Map(GetFuncDeclarations(db, func.derived));
} else if (cmd.field == "derived uses") {
map(getUsesForAllDerived(db, func));
Map(GetUsesForAllDerived(db, func));
} else if (cmd.field == "uses") {
map(func.uses);
Map(func.uses);
}
break;
}
case Kind::Type: {
QueryType &type = db->getType(cmd.usr);
QueryType &type = db->Type(cmd.usr);
if (cmd.field == "derived") {
map(getTypeDeclarations(db, type.derived));
Map(GetTypeDeclarations(db, type.derived));
} else if (cmd.field == "instances") {
map(getVarDeclarations(db, type.instances, 7));
Map(GetVarDeclarations(db, type.instances, 7));
} else if (cmd.field == "uses") {
map(type.uses);
Map(type.uses);
}
break;
}
case Kind::Var: {
QueryVar &var = db->getVar(cmd.usr);
QueryVar &var = db->Var(cmd.usr);
if (cmd.field == "uses")
map(var.uses);
Map(var.uses);
break;
}
default:

View File

@ -1,7 +1,20 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -10,10 +23,9 @@
#include <clang/Sema/CodeCompleteConsumer.h>
#include <clang/Sema/Sema.h>
#include <llvm/ADT/Twine.h>
#if LLVM_VERSION_MAJOR >= 18 // llvmorg-18-init-10631-gedd690b02e16
#define TTK_Struct TagTypeKind::Struct
#if LLVM_VERSION_MAJOR < 8
#include <regex>
#endif
namespace ccls {
@ -23,8 +35,8 @@ using namespace llvm;
REFLECT_UNDERLYING(InsertTextFormat);
REFLECT_UNDERLYING(CompletionItemKind);
void reflect(JsonWriter &vis, CompletionItem &v) {
reflectMemberStart(vis);
void Reflect(JsonWriter &vis, CompletionItem &v) {
ReflectMemberStart(vis);
REFLECT_MEMBER(label);
REFLECT_MEMBER(kind);
REFLECT_MEMBER(detail);
@ -37,7 +49,7 @@ void reflect(JsonWriter &vis, CompletionItem &v) {
REFLECT_MEMBER(textEdit);
if (v.additionalTextEdits.size())
REFLECT_MEMBER(additionalTextEdits);
reflectMemberEnd(vis);
ReflectMemberEnd(vis);
}
namespace {
@ -47,10 +59,60 @@ struct CompletionList {
};
REFLECT_STRUCT(CompletionList, isIncomplete, items);
#if LLVM_VERSION_MAJOR < 8
void DecorateIncludePaths(const std::smatch &match,
std::vector<CompletionItem> *items,
char quote) {
std::string spaces_after_include = " ";
if (match[3].compare("include") == 0 && quote != '\0')
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 (quote != '"')
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,
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;
@ -78,7 +140,8 @@ void filterCandidates(CompletionList &result, const std::string &complete_text,
if (edits.size() && edits[0].range.end == begin_pos) {
Position start = edits[0].range.start, end = edits[0].range.end;
if (start.line == begin_pos.line) {
overwrite_len = std::max(overwrite_len, end.character - start.character);
overwrite_len =
std::max(overwrite_len, end.character - start.character);
} else {
overwrite_len = -1;
break;
@ -86,7 +149,8 @@ void filterCandidates(CompletionList &result, const std::string &complete_text,
}
}
Position overwrite_begin = {begin_pos.line, begin_pos.character - overwrite_len};
Position overwrite_begin = {begin_pos.line,
begin_pos.character - overwrite_len};
std::string sort(4, ' ');
for (auto &item : items) {
item.textEdit.range = lsRange{begin_pos, end_pos};
@ -98,10 +162,12 @@ void filterCandidates(CompletionList &result, const std::string &complete_text,
if (overwrite_len > 0) {
item.textEdit.range.start = overwrite_begin;
std::string orig = buffer_line.substr(overwrite_begin.character, overwrite_len);
if (edits.size() && edits[0].range.end == begin_pos && edits[0].range.start.line == begin_pos.line) {
if (edits.size() && edits[0].range.end == begin_pos &&
edits[0].range.start.line == begin_pos.line) {
int cur_edit_len = edits[0].range.end.character - edits[0].range.start.character;
item.textEdit.newText = buffer_line.substr(overwrite_begin.character, overwrite_len - cur_edit_len) +
edits[0].newText + item.textEdit.newText;
item.textEdit.newText =
buffer_line.substr(overwrite_begin.character, overwrite_len - cur_edit_len) +
edits[0].newText + item.textEdit.newText;
edits.erase(edits.begin());
} else {
item.textEdit.newText = orig + item.textEdit.newText;
@ -126,40 +192,47 @@ void filterCandidates(CompletionList &result, const std::string &complete_text,
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, true)
: FuzzyMatcher::kMinScore;
const std::string &filter =
item.filterText.size() ? item.filterText : item.label;
item.score_ = ReverseSubseqMatch(complete_text, filter, sensitive) >= 0
? fuzzy.Match(filter, true)
: FuzzyMatcher::kMinScore;
}
items.erase(std::remove_if(items.begin(), items.end(),
[](const CompletionItem &item) { return item.score_ <= FuzzyMatcher::kMinScore; }),
[](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;
});
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(CodeCompletionContext::Kind k, const CodeCompletionResult &r) {
switch (r.Kind) {
CompletionItemKind GetCompletionKind(CodeCompletionContext::Kind K,
const CodeCompletionResult &R) {
switch (R.Kind) {
case CodeCompletionResult::RK_Declaration: {
const Decl *d = r.Declaration;
switch (d->getKind()) {
const Decl *D = R.Declaration;
switch (D->getKind()) {
case Decl::LinkageSpec:
return CompletionItemKind::Keyword;
case Decl::Namespace:
@ -182,7 +255,7 @@ CompletionItemKind getCompletionKind(CodeCompletionContext::Kind k, const CodeCo
case Decl::TypeAliasTemplate:
return CompletionItemKind::Class;
case Decl::VarTemplate:
if (cast<VarTemplateDecl>(d)->getTemplatedDecl()->isConstexpr())
if (cast<VarTemplateDecl>(D)->getTemplatedDecl()->isConstexpr())
return CompletionItemKind::Constant;
return CompletionItemKind::Variable;
case Decl::TemplateTemplateParm:
@ -191,8 +264,8 @@ CompletionItemKind getCompletionKind(CodeCompletionContext::Kind k, const CodeCo
return CompletionItemKind::Enum;
case Decl::CXXRecord:
case Decl::Record:
if (auto *rd = dyn_cast<RecordDecl>(d))
if (rd->getTagKind() == TTK_Struct)
if (auto *RD = dyn_cast<RecordDecl>(D))
if (RD->getTagKind() == TTK_Struct)
return CompletionItemKind::Struct;
return CompletionItemKind::Class;
case Decl::TemplateTypeParm:
@ -222,7 +295,7 @@ CompletionItemKind getCompletionKind(CodeCompletionContext::Kind k, const CodeCo
case Decl::ParmVar:
case Decl::VarTemplateSpecialization:
case Decl::VarTemplatePartialSpecialization:
if (cast<VarDecl>(d)->isConstexpr())
if (cast<VarDecl>(D)->isConstexpr())
return CompletionItemKind::Constant;
return CompletionItemKind::Variable;
case Decl::EnumConstant:
@ -231,7 +304,7 @@ CompletionItemKind getCompletionKind(CodeCompletionContext::Kind k, const CodeCo
return CompletionItemKind::Field;
default:
LOG_S(WARNING) << "Unhandled " << int(d->getKind());
LOG_S(WARNING) << "Unhandled " << int(D->getKind());
return CompletionItemKind::Text;
}
break;
@ -241,39 +314,42 @@ CompletionItemKind getCompletionKind(CodeCompletionContext::Kind k, const CodeCo
case CodeCompletionResult::RK_Macro:
return CompletionItemKind::Reference;
case CodeCompletionResult::RK_Pattern:
if (k == CodeCompletionContext::CCC_IncludedFile)
#if LLVM_VERSION_MAJOR >= 8
if (K == CodeCompletionContext::CCC_IncludedFile)
return CompletionItemKind::File;
#endif
return CompletionItemKind::Snippet;
}
}
void buildItem(const CodeCompletionResult &r, const CodeCompletionString &ccs, std::vector<CompletionItem> &out) {
void BuildItem(const CodeCompletionResult &R, const CodeCompletionString &CCS,
std::vector<CompletionItem> &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;
for (const auto &Chunk : CCS) {
CodeCompletionString::ChunkKind Kind = Chunk.Kind;
std::string text;
switch (kind) {
switch (Kind) {
case CodeCompletionString::CK_TypedText:
text = chunk.Text;
text = Chunk.Text;
for (auto i = first; i < out.size(); i++)
out[i].filterText = text;
break;
case CodeCompletionString::CK_Placeholder:
text = chunk.Text;
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("::"))
if (StringRef(Chunk.Text).endswith("::"))
continue;
text = chunk.Text;
text = Chunk.Text;
break;
case CodeCompletionString::CK_ResultType:
result_type = chunk.Text;
result_type = Chunk.Text;
continue;
case CodeCompletionString::CK_CurrentParameter:
// This should never be present while collecting completion items.
@ -283,28 +359,30 @@ void buildItem(const CodeCompletionResult &r, const CodeCompletionString &ccs, s
// Duplicate last element, the recursive call will complete it.
if (g_config->completion.duplicateOptional) {
out.push_back(out.back());
buildItem(r, *chunk.Optional, out);
BuildItem(R, *Chunk.Optional, out);
}
continue;
}
default:
text = chunk.Text;
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()))
if (ignore ||
(!g_config->client.snippetSupport && out[i].parameters_.size()))
continue;
if (kind == CodeCompletionString::CK_Placeholder) {
if (r.Kind == CodeCompletionResult::RK_Pattern) {
if (Kind == CodeCompletionString::CK_Placeholder) {
if (R.Kind == CodeCompletionResult::RK_Pattern) {
ignore = true;
continue;
}
out[i].textEdit.newText += ("${" + Twine(out[i].parameters_.size()) + ":" + text + "}").str();
out[i].textEdit.newText +=
"${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}";
out[i].insertTextFormat = InsertTextFormat::Snippet;
} else if (kind != CodeCompletionString::CK_Informative) {
} else if (Kind != CodeCompletionString::CK_Informative) {
out[i].textEdit.newText += text;
}
}
@ -320,100 +398,93 @@ void buildItem(const CodeCompletionResult &r, const CodeCompletionString &ccs, s
}
class CompletionConsumer : public CodeCompleteConsumer {
std::shared_ptr<clang::GlobalCodeCompletionAllocator> alloc;
CodeCompletionTUInfo cctu_info;
std::shared_ptr<clang::GlobalCodeCompletionAllocator> Alloc;
CodeCompletionTUInfo CCTUInfo;
public:
bool from_cache;
std::vector<CompletionItem> ls_items;
CompletionConsumer(const CodeCompleteOptions &opts, bool from_cache)
: CodeCompleteConsumer(opts), alloc(std::make_shared<clang::GlobalCodeCompletionAllocator>()), cctu_info(alloc),
from_cache(from_cache) {}
CompletionConsumer(const CodeCompleteOptions &Opts, bool from_cache)
: CodeCompleteConsumer(Opts, false),
Alloc(std::make_shared<clang::GlobalCodeCompletionAllocator>()),
CCTUInfo(Alloc), from_cache(from_cache) {}
void ProcessCodeCompleteResults(Sema &s, CodeCompletionContext context, CodeCompletionResult *results,
unsigned numResults) override {
if (context.getKind() == CodeCompletionContext::CCC_Recovery)
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)
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) {
Decl::Kind k = r.Declaration->getKind();
if (k == Decl::CXXDestructor)
if (R.Declaration) {
Decl::Kind K = R.Declaration->getKind();
if (K == Decl::CXXDestructor)
continue;
if (k == Decl::FunctionTemplate) {
if (K == Decl::FunctionTemplate) {
// Ignore CXXDeductionGuide which has empty TypedText.
auto *fd = cast<FunctionTemplateDecl>(r.Declaration);
if (fd->getTemplatedDecl()->getKind() == Decl::CXXDeductionGuide)
auto *FD = cast<FunctionTemplateDecl>(R.Declaration);
if (FD->getTemplatedDecl()->getKind() == Decl::CXXDeductionGuide)
continue;
}
if (auto *rd = dyn_cast<RecordDecl>(r.Declaration))
if (rd->isInjectedClassName())
if (auto *RD = dyn_cast<RecordDecl>(R.Declaration))
if (RD->isInjectedClassName())
continue;
auto nk = r.Declaration->getDeclName().getNameKind();
if (nk == DeclarationName::CXXOperatorName || nk == DeclarationName::CXXLiteralOperatorName)
auto NK = R.Declaration->getDeclName().getNameKind();
if (NK == DeclarationName::CXXOperatorName ||
NK == DeclarationName::CXXLiteralOperatorName)
continue;
}
CodeCompletionString *ccs =
r.CreateCodeCompletionString(s, context, getAllocator(), getCodeCompletionTUInfo(), includeBriefComments());
CodeCompletionString *CCS = R.CreateCodeCompletionString(
S, Context, getAllocator(), getCodeCompletionTUInfo(),
includeBriefComments());
CompletionItem ls_item;
ls_item.kind = getCompletionKind(context.getKind(), r);
if (const char *brief = ccs->getBriefComment())
ls_item.kind = GetCompletionKind(Context.getKind(), R);
if (const char *brief = CCS->getBriefComment())
ls_item.documentation = brief;
ls_item.detail = ccs->getParentContextName().str();
ls_item.detail = CCS->getParentContextName().str();
size_t first_idx = ls_items.size();
ls_items.push_back(ls_item);
buildItem(r, *ccs, ls_items);
BuildItem(R, *CCS, ls_items);
for (size_t j = first_idx; j < ls_items.size(); j++) {
std::string &s = ls_items[j].textEdit.newText;
if (!g_config->client.snippetSupport) {
if (s.size()) {
// Delete non-identifier parts.
if (s.back() == '(' || s.back() == '<')
s.pop_back();
else if (s.size() >= 2 && !s.compare(s.size() - 2, 2, "()"))
s.resize(s.size() - 2);
}
} else if (ls_items[j].insertTextFormat == InsertTextFormat::Snippet) {
if (!g_config->completion.placeholder) {
// foo(${1:int a}, ${2:int b}) -> foo($1)$0
auto p = s.find("${"), q = s.rfind('}');
s.replace(p, q - p + 1, "$1");
}
s += "$0";
}
ls_items[j].priority_ = ccs->getPriority();
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;
}
}
for (const FixItHint &fixIt : r.FixIts) {
auto &ast = s.getASTContext();
TextEdit ls_edit = ccls::toTextEdit(ast.getSourceManager(), ast.getLangOpts(), fixIt);
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);
}
}
}
CodeCompletionAllocator &getAllocator() override { return *alloc; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return cctu_info; }
CodeCompletionAllocator &getAllocator() override { return *Alloc; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
};
} // namespace
void MessageHandler::textDocument_completion(CompletionParam &param, ReplyOnce &reply) {
void MessageHandler::textDocument_completion(CompletionParam &param,
ReplyOnce &reply) {
static CompleteConsumerCache<std::vector<CompletionItem>> cache;
std::string path = param.textDocument.uri.getPath();
WorkingFile *wf = wfiles->getFile(path);
std::string path = param.textDocument.uri.GetPath();
WorkingFile *wf = wfiles->GetFile(path);
if (!wf) {
reply.notOpened(path);
reply.NotOpened(path);
return;
}
@ -425,20 +496,21 @@ void MessageHandler::textDocument_completion(CompletionParam &param, ReplyOnce &
if (param.position.line >= 0 && param.position.line < wf->buffer_lines.size())
buffer_line = wf->buffer_lines[param.position.line];
clang::CodeCompleteOptions ccOpts;
ccOpts.IncludeBriefComments = true;
ccOpts.IncludeCodePatterns = StringRef(buffer_line).ltrim().startswith("#");
ccOpts.IncludeFixIts = true;
ccOpts.IncludeMacros = true;
clang::CodeCompleteOptions CCOpts;
CCOpts.IncludeBriefComments = true;
CCOpts.IncludeCodePatterns = StringRef(buffer_line).ltrim().startswith("#");
CCOpts.IncludeFixIts = true;
CCOpts.IncludeMacros = true;
if (param.context.triggerKind == CompletionTriggerKind::TriggerCharacter && param.context.triggerCharacter) {
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 #
ok = CCOpts.IncludeCodePatterns; // start with #
break;
case ':':
ok = col >= 0 && buffer_line[col] == ':'; // ::
@ -454,37 +526,63 @@ void MessageHandler::textDocument_completion(CompletionParam &param, ReplyOnce &
}
std::string filter;
Position end_pos = param.position;
Position begin_pos = wf->getCompletionPosition(param.position, &filter);
Position end_pos;
Position begin_pos =
wf->GetCompletionPosition(param.position, &filter, &end_pos);
SemaManager::OnComplete callback = [filter, path, begin_pos, end_pos, reply,
buffer_line](CodeCompleteConsumer *optConsumer) {
if (!optConsumer)
return;
auto *consumer = static_cast<CompletionConsumer *>(optConsumer);
#if LLVM_VERSION_MAJOR < 8
ParseIncludeLineResult preprocess = ParseIncludeLine(buffer_line);
if (preprocess.ok && preprocess.keyword.compare("include") == 0) {
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.line = buffer_line;
cache.position = begin_pos;
cache.result = consumer->ls_items;
});
char quote = std::string(preprocess.match[5])[0];
{
std::unique_lock<std::mutex> lock(
include_complete->completion_items_mutex, std::defer_lock);
if (include_complete->is_scanning)
lock.lock();
for (auto &item : include_complete->completion_items)
if (quote == '\0' || (item.quote_kind_ & 1 && quote == '"') ||
(item.quote_kind_ & 2 && quote == '<'))
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, quote);
reply(result);
return;
}
#endif
if (cache.isCacheValid(path, buffer_line, begin_pos)) {
CompletionConsumer consumer(ccOpts, true);
cache.withLock([&]() { consumer.ls_items = cache.result; });
callback(&consumer);
SemaManager::OnComplete callback =
[filter, path, begin_pos, end_pos, reply,
buffer_line](CodeCompleteConsumer *OptConsumer) {
if (!OptConsumer)
return;
auto *Consumer = static_cast<CompletionConsumer *>(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<SemaManager::CompTask>(reply.id, param.textDocument.uri.getPath(), begin_pos,
std::make_unique<CompletionConsumer>(ccOpts, false), ccOpts, callback));
manager->comp_tasks.PushBack(std::make_unique<SemaManager::CompTask>(
reply.id, param.textDocument.uri.GetPath(), begin_pos,
std::make_unique<CompletionConsumer>(CCOpts, false), CCOpts, callback));
}
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -9,25 +21,52 @@
#include <stdlib.h>
namespace ccls {
void MessageHandler::textDocument_declaration(TextDocumentPositionParam &param, ReplyOnce &reply) {
namespace {
std::vector<DeclRef> GetNonDefDeclarationTargets(DB *db, SymbolRef sym) {
switch (sym.kind) {
case Kind::Var: {
std::vector<DeclRef> 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<DeclRef> use =
GetDefinitionSpell(db, SymbolIdx{def.type, Kind::Type})) {
ret.push_back(*use);
break;
}
}
}
return ret;
}
default:
return GetNonDefDeclarations(db, sym);
}
}
} // namespace
void MessageHandler::textDocument_declaration(TextDocumentPositionParam &param,
ReplyOnce &reply) {
int file_id;
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply, &file_id);
if (!wf)
return;
std::vector<LocationLink> result;
Position &ls_pos = param.position;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position))
for (DeclRef dr : getNonDefDeclarations(db, sym))
if (!(dr.file_id == file_id && dr.range.contains(ls_pos.line, ls_pos.character)))
if (auto loc = getLocationLink(db, wfiles, dr))
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position))
for (DeclRef dr : GetNonDefDeclarations(db, sym))
if (!(dr.file_id == file_id &&
dr.range.Contains(ls_pos.line, ls_pos.character)))
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(loc);
reply.replyLocationLink(result);
reply.ReplyLocationLink(result);
}
void MessageHandler::textDocument_definition(TextDocumentPositionParam &param, ReplyOnce &reply) {
void MessageHandler::textDocument_definition(TextDocumentPositionParam &param,
ReplyOnce &reply) {
int file_id;
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply, &file_id);
if (!wf)
return;
@ -35,15 +74,16 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param, R
Maybe<DeclRef> on_def;
Position &ls_pos = param.position;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, ls_pos, true)) {
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<DeclRef> drs;
eachEntityDef(db, sym, [&](const auto &def) {
EachEntityDef(db, sym, [&](const auto &def) {
if (def.spell) {
DeclRef spell = *def.spell;
if (spell.file_id == file_id && spell.range.contains(ls_pos.line, ls_pos.character)) {
if (spell.file_id == file_id &&
spell.range.Contains(ls_pos.line, ls_pos.character)) {
on_def = spell;
drs.clear();
return false;
@ -56,15 +96,16 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param, R
// |uses| is empty if on a declaration/definition, otherwise it includes
// all declarations/definitions.
if (drs.empty()) {
for (DeclRef dr : getNonDefDeclarations(db, sym))
if (!(dr.file_id == file_id && dr.range.contains(ls_pos.line, ls_pos.character)))
for (DeclRef dr : GetNonDefDeclarationTargets(db, sym))
if (!(dr.file_id == file_id &&
dr.range.Contains(ls_pos.line, ls_pos.character)))
drs.push_back(dr);
// There is no declaration but the cursor is on a definition.
if (drs.empty() && on_def)
drs.push_back(*on_def);
}
for (DeclRef dr : drs)
if (auto loc = getLocationLink(db, wfiles, dr))
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(loc);
}
@ -73,7 +114,8 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param, R
// Check #include
for (const IndexInclude &include : file->def->includes) {
if (include.line == ls_pos.line) {
result.push_back({DocumentUri::fromPath(include.resolved_path).raw_uri});
result.push_back(
{DocumentUri::FromPath(include.resolved_path).raw_uri});
range = {{0, 0}, {0, 0}};
break;
}
@ -82,7 +124,7 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param, R
if (!range) {
Position position = param.position;
const std::string &buffer = wf->buffer_content;
std::string_view query = lexIdentifierAroundPos(position, buffer);
std::string_view query = LexIdentifierAroundPos(position, buffer);
std::string_view short_query = query;
{
auto pos = query.rfind(':');
@ -97,13 +139,16 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param, R
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;
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<DeclRef> dr = getDefinitionSpell(db, sym)) {
std::tuple<int, int, bool, int> score{int(name.size() - short_query.size()), 0, dr->file_id != file_id,
std::abs(dr->range.start.line - position.line)};
if (Maybe<DeclRef> dr = GetDefinitionSpell(db, sym)) {
std::tuple<int, int, bool, int> 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);
@ -126,45 +171,46 @@ void MessageHandler::textDocument_definition(TextDocumentPositionParam &param, R
fn({var.usr, Kind::Var});
if (best_sym.kind != Kind::Invalid) {
Maybe<DeclRef> dr = getDefinitionSpell(db, best_sym);
Maybe<DeclRef> dr = GetDefinitionSpell(db, best_sym);
assert(dr);
if (auto loc = getLocationLink(db, wfiles, *dr))
if (auto loc = GetLocationLink(db, wfiles, *dr))
result.push_back(loc);
}
}
}
reply.replyLocationLink(result);
reply.ReplyLocationLink(result);
}
void MessageHandler::textDocument_typeDefinition(TextDocumentPositionParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_typeDefinition(
TextDocumentPositionParam &param, ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!file)
return;
std::vector<LocationLink> result;
auto add = [&](const QueryType &type) {
auto Add = [&](const QueryType &type) {
for (const auto &def : type.def)
if (def.spell)
if (auto loc = getLocationLink(db, wfiles, *def.spell))
if (auto loc = GetLocationLink(db, wfiles, *def.spell))
result.push_back(loc);
if (result.empty())
for (const DeclRef &dr : type.declarations)
if (auto loc = getLocationLink(db, wfiles, dr))
if (auto loc = GetLocationLink(db, wfiles, dr))
result.push_back(loc);
};
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) {
switch (sym.kind) {
case Kind::Var: {
const QueryVar::Def *def = db->getVar(sym).anyDef();
const QueryVar::Def *def = db->GetVar(sym).AnyDef();
if (def && def->type)
add(db->getType(def->type));
Add(db->Type(def->type));
break;
}
case Kind::Type: {
for (auto &def : db->getType(sym).def)
for (auto &def : db->GetType(sym).def)
if (def.alias_of) {
add(db->getType(def.alias_of));
Add(db->Type(def.alias_of));
break;
}
break;
@ -174,6 +220,6 @@ void MessageHandler::textDocument_typeDefinition(TextDocumentPositionParam &para
}
}
reply.replyLocationLink(result);
reply.ReplyLocationLink(result);
}
} // namespace ccls

View File

@ -1,6 +1,19 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -9,62 +22,51 @@
namespace ccls {
void MessageHandler::textDocument_didChange(TextDocumentDidChangeParam &param) {
std::string path = param.textDocument.uri.getPath();
wfiles->onChange(param);
std::string path = param.textDocument.uri.GetPath();
wfiles->OnChange(param);
if (g_config->index.onChange)
pipeline::index(path, {}, IndexMode::OnChange, true);
manager->onView(path);
pipeline::Index(path, {}, IndexMode::OnChange, true);
manager->OnView(path);
if (g_config->diagnostics.onChange >= 0)
manager->scheduleDiag(path, g_config->diagnostics.onChange);
manager->ScheduleDiag(path, g_config->diagnostics.onChange);
}
void MessageHandler::textDocument_didClose(TextDocumentParam &param) {
std::string path = param.textDocument.uri.getPath();
wfiles->onClose(path);
manager->onClose(path);
pipeline::removeCache(path);
std::string path = param.textDocument.uri.GetPath();
wfiles->OnClose(path);
manager->OnClose(path);
pipeline::RemoveCache(path);
}
void MessageHandler::textDocument_didOpen(DidOpenTextDocumentParam &param) {
std::string path = param.textDocument.uri.getPath();
WorkingFile *wf = wfiles->onOpen(param.textDocument);
if (std::optional<std::string> cached_file_contents = pipeline::loadIndexedContent(path))
wf->setIndexContent(*cached_file_contents);
std::string path = param.textDocument.uri.GetPath();
WorkingFile *wf = wfiles->OnOpen(param.textDocument);
if (std::optional<std::string> cached_file_contents =
pipeline::LoadIndexedContent(path))
wf->SetIndexContent(*cached_file_contents);
QueryFile *file = findFile(path);
QueryFile *file = FindFile(path);
if (file) {
emitSkippedRanges(wf, *file);
emitSemanticHighlight(db, wf, *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.
auto [lang, header] = lookupExtension(path);
if ((lang != LanguageId::Unknown && !header) || pipeline::stats.completed == pipeline::stats.enqueued)
pipeline::index(path, {}, IndexMode::Normal, false);
if ((lang != LanguageId::Unknown && !header) ||
!pipeline::pending_index_requests)
pipeline::Index(path, {}, IndexMode::Normal, false);
if (header)
project->indexRelated(path);
project->IndexRelated(path);
manager->onView(path);
// For the first few didOpen, sort indexer requests based on path similarity.
if (++pipeline::stats.opened >= 5)
return;
std::unordered_map<std::string, int> dir2prio;
{
std::lock_guard lock(wfiles->mutex);
for (auto &[f, wf] : wfiles->files) {
std::string cur = lowerPathIfInsensitive(f);
for (int pri = 1 << 20; !(cur = llvm::sys::path::parent_path(cur)).empty(); pri /= 2)
dir2prio[cur] += pri;
}
}
pipeline::indexerSort(dir2prio);
manager->OnView(path);
}
void MessageHandler::textDocument_didSave(TextDocumentParam &param) {
const std::string &path = param.textDocument.uri.getPath();
pipeline::index(path, {}, IndexMode::Normal, false);
manager->onSave(path);
const std::string &path = param.textDocument.uri.GetPath();
pipeline::Index(path, {}, IndexMode::Normal, false);
manager->OnSave(path);
}
} // namespace ccls

View File

@ -1,19 +1,24 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 <llvm/ADT/STLExtras.h>
#include <llvm/ADT/StringRef.h>
#include <llvm/Support/Path.h>
#include <algorithm>
using namespace llvm;
MAKE_HASHABLE(ccls::SymbolIdx, t.usr, t.kind);
namespace ccls {
@ -29,27 +34,33 @@ struct DocumentHighlight {
// ccls extension
Role role = Role::None;
bool operator<(const DocumentHighlight &o) const { return !(range == o.range) ? range < o.range : kind < o.kind; }
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 &param, ReplyOnce &reply) {
void MessageHandler::textDocument_documentHighlight(
TextDocumentPositionParam &param, ReplyOnce &reply) {
int file_id;
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply, &file_id);
if (!wf)
return;
std::vector<DocumentHighlight> result;
std::vector<SymbolRef> syms = findSymbolsAtLocation(wf, file, param.position, true);
std::vector<SymbolRef> 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; }))
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)) {
if (auto loc = GetLsLocation(db, wfiles, sym, file_id)) {
DocumentHighlight highlight;
highlight.range = loc->range;
if (sym.role & Role::Write)
@ -74,20 +85,22 @@ struct DocumentLink {
REFLECT_STRUCT(DocumentLink, range, target);
} // namespace
void MessageHandler::textDocument_documentLink(TextDocumentParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_documentLink(TextDocumentParam &param,
ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
std::vector<DocumentLink> result;
int column;
for (const IndexInclude &include : file->def->includes)
if (std::optional<int> bline = wf->getBufferPosFromIndexPos(include.line, &column, false)) {
if (std::optional<int> bline =
wf->GetBufferPosFromIndexPos(include.line, &column, 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)});
result.push_back({lsRange{{*bline, (int)start + 1}, {*bline, (int)end}},
DocumentUri::FromPath(include.resolved_path)});
}
reply(result);
} // namespace ccls
@ -95,12 +108,14 @@ void MessageHandler::textDocument_documentLink(TextDocumentParam &param, ReplyOn
namespace {
struct DocumentSymbolParam : TextDocumentParam {
// Include sym if `!(sym.role & excludeRole)`.
Role excludeRole = Role((int)Role::All - (int)Role::Definition - (int)Role::Declaration - (int)Role::Dynamic);
Role excludeRole = Role((int)Role::All - (int)Role::Definition -
(int)Role::Declaration - (int)Role::Dynamic);
// If >= 0, return Range[] instead of SymbolInformation[] to reduce output.
int startLine = -1;
int endLine = -1;
};
REFLECT_STRUCT(DocumentSymbolParam, textDocument, excludeRole, startLine, endLine);
REFLECT_STRUCT(DocumentSymbolParam, textDocument, excludeRole, startLine,
endLine);
struct DocumentSymbol {
std::string name;
@ -110,95 +125,157 @@ struct DocumentSymbol {
lsRange selectionRange;
std::vector<std::unique_ptr<DocumentSymbol>> children;
};
void reflect(JsonWriter &vis, std::unique_ptr<DocumentSymbol> &v);
REFLECT_STRUCT(DocumentSymbol, name, detail, kind, range, selectionRange, children);
void reflect(JsonWriter &vis, std::unique_ptr<DocumentSymbol> &v) { reflect(vis, *v); }
void Reflect(JsonWriter &vis, std::unique_ptr<DocumentSymbol> &v);
REFLECT_STRUCT(DocumentSymbol, name, detail, kind, range, selectionRange,
children);
void Reflect(JsonWriter &vis, std::unique_ptr<DocumentSymbol> &v) {
Reflect(vis, *v);
}
template <typename Def> 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(); }
template <typename Def>
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<std::unique_ptr<DocumentSymbol>> &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) {
void MessageHandler::textDocument_documentSymbol(JsonReader &reader,
ReplyOnce &reply) {
DocumentSymbolParam param;
reflect(reader, param);
Reflect(reader, param);
int file_id;
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply, &file_id, true);
if (!file)
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply, &file_id);
if (!wf)
return;
auto allows = [&](SymbolRef sym) { return !(sym.role & param.excludeRole); };
auto Allows = [&](SymbolRef sym) {
return !(sym.role & param.excludeRole);
};
if (param.startLine >= 0) {
std::vector<lsRange> result;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !allows(sym) ||
!(param.startLine <= sym.range.start.line && sym.range.start.line <= param.endLine))
if (refcnt <= 0 || !Allows(sym) ||
!(param.startLine <= sym.range.start.line &&
sym.range.start.line <= param.endLine))
continue;
if (auto loc = getLsLocation(db, wfiles, sym, file_id))
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::vector<ExtentRef> syms;
syms.reserve(file->symbol2refcnt.size());
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.extent.valid())
syms.push_back(sym);
// Global variables `int i, j, k;` have the same extent.start. Sort them by
// range.start instead. In case of a tie, prioritize the widest ExtentRef.
std::sort(syms.begin(), syms.end(), [](const ExtentRef &lhs, const ExtentRef &rhs) {
return std::tie(lhs.range.start, rhs.extent.end) < std::tie(rhs.range.start, lhs.extent.end);
});
std::vector<std::unique_ptr<DocumentSymbol>> res;
std::vector<DocumentSymbol *> scopes;
for (ExtentRef sym : syms) {
auto ds = std::make_unique<DocumentSymbol>();
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::unordered_map<SymbolIdx, std::unique_ptr<DocumentSymbol>> sym2ds;
std::vector<std::pair<std::vector<const void *>, 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});
auto &ds = r.first->second;
if (!ds || sym.role & Role::Definition) {
if (!ds)
ds = std::make_unique<DocumentSymbol>();
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;
}
}
withEntity(db, sym, [&](const auto &entity) {
const auto *def = entity.anyDef();
if (!r.second)
continue;
std::vector<const void *> def_ptrs;
SymbolKind kind = SymbolKind::Unknown;
WithEntity(db, sym, [&](const auto &entity) {
auto *def = entity.AnyDef();
if (!def)
return;
ds->name = def->name(false);
ds->name = def->Name(false);
ds->detail = def->detailed_name;
ds->kind = def->kind;
if (!ignore(def) && (ds->kind == SymbolKind::Namespace || allows(sym))) {
// Drop scopes which are before selectionRange.start. In
// `int i, j, k;`, the scope of i will be ended by j.
while (!scopes.empty() && scopes.back()->range.end <= ds->selectionRange.start)
scopes.pop_back();
auto *ds1 = ds.get();
if (scopes.empty())
res.push_back(std::move(ds));
else
scopes.back()->children.push_back(std::move(ds));
scopes.push_back(ds1);
}
for (auto &def : entity.def)
if (def.file_id == file_id && !Ignore(&def)) {
kind = ds->kind = def.kind;
def_ptrs.push_back(&def);
}
});
if (def_ptrs.empty() || !(kind == SymbolKind::Namespace || Allows(sym))) {
ds.reset();
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());
}
reply(res);
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<std::unique_ptr<DocumentSymbol>> result;
for (auto &[_, ds] : sym2ds)
if (ds) {
Uniquify(ds->children);
result.push_back(std::move(ds));
}
Uniquify(result);
reply(result);
} else {
std::vector<SymbolInformation> result;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !allows(sym))
if (refcnt <= 0 || !Allows(sym))
continue;
if (std::optional<SymbolInformation> info = getSymbolInfo(db, sym, false)) {
if ((sym.kind == Kind::Type && ignore(db->getType(sym).anyDef())) ||
(sym.kind == Kind::Var && ignore(db->getVar(sym).anyDef())))
if (std::optional<SymbolInformation> 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)) {
if (auto loc = GetLsLocation(db, wfiles, sym, file_id)) {
info->location = *loc;
result.push_back(*info);
}
@ -207,66 +284,4 @@ void MessageHandler::textDocument_documentSymbol(JsonReader &reader, ReplyOnce &
reply(result);
}
}
void MessageHandler::textDocument_switchSourceHeader(TextDocumentIdentifier &param, ReplyOnce &reply) {
QueryFile *file;
WorkingFile *wf;
std::tie(file, wf) = findOrFail(param.uri.getPath(), reply);
if (!wf)
return reply(JsonNull{});
int file_id = file->id;
DocumentUri result;
const std::string &path = wf->filename;
bool is_hdr = lookupExtension(path).second;
// Vote for each interesting symbol's definitions (for header) or declarations (for non-header).
// Select the file with the most votes.
// Ignore Type symbols to skip class forward declarations and namespaces.
std::unordered_map<int, int> file_id2cnt;
for (auto [sym, refcnt] : file->symbol2refcnt) {
if (refcnt <= 0 || !sym.extent.valid() || sym.kind == Kind::Type)
continue;
if (is_hdr) {
withEntity(db, sym, [&](const auto &entity) {
for (auto &def : entity.def)
if (def.spell && def.file_id != file_id)
++file_id2cnt[def.file_id];
});
} else {
for (DeclRef dr : getNonDefDeclarations(db, sym))
if (dr.file_id != file_id)
++file_id2cnt[dr.file_id];
}
}
if (file_id2cnt.size()) {
auto best = file_id2cnt.begin();
for (auto it = file_id2cnt.begin(); it != file_id2cnt.end(); ++it)
if (it->second > best->second || (it->second == best->second && it->first < best->first))
best = it;
if (auto &def = db->files[best->first].def)
return reply(DocumentUri::fromPath(def->path));
}
if (is_hdr) {
// Check if `path` is in a #include entry.
for (QueryFile &file1 : db->files) {
auto &def = file1.def;
if (!def || lookupExtension(def->path).second)
continue;
for (IndexInclude &include : def->includes)
if (path == include.resolved_path)
return reply(DocumentUri::fromPath(def->path));
}
return reply(JsonNull{});
}
// Otherwise, find the #include with the same stem.
StringRef stem = sys::path::stem(path);
for (IndexInclude &include : file->def->includes)
if (sys::path::stem(include.resolved_path) == stem)
return reply(DocumentUri::fromPath(std::string(include.resolved_path)));
reply(JsonNull{});
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -13,19 +25,22 @@ struct FoldingRange {
int startLine, startCharacter, endLine, endCharacter;
std::string kind = "region";
};
REFLECT_STRUCT(FoldingRange, startLine, startCharacter, endLine, endCharacter, kind);
REFLECT_STRUCT(FoldingRange, startLine, startCharacter, endLine, endCharacter,
kind);
} // namespace
void MessageHandler::textDocument_foldingRange(TextDocumentParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_foldingRange(TextDocumentParam &param,
ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
std::vector<FoldingRange> result;
std::optional<lsRange> 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))) {
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;

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -12,23 +24,29 @@ namespace ccls {
using namespace clang;
namespace {
llvm::Expected<tooling::Replacements> formatCode(StringRef code, StringRef file, tooling::Range range) {
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));
llvm::Expected<tooling::Replacements>
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<TextEdit> replacementsToEdits(std::string_view code, const tooling::Replacements &repls) {
std::vector<TextEdit> ReplacementsToEdits(std::string_view code,
const tooling::Replacements &Repls) {
std::vector<TextEdit> ret;
int i = 0, line = 0, col = 0;
auto move = [&](unsigned p) {
for (; i < (int)p; i++)
auto move = [&](int p) {
for (; i < p; i++)
if (code[i] == '\n')
line++, col = 0;
else {
@ -40,53 +58,57 @@ std::vector<TextEdit> replacementsToEdits(std::string_view code, const tooling::
col++;
}
};
for (const auto &r : repls) {
move(r.getOffset());
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()});
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) {
void Format(ReplyOnce &reply, WorkingFile *wfile, tooling::Range range) {
std::string_view code = wfile->buffer_content;
auto replsOrErr =
formatCode(StringRef(code.data(), code.size()), StringRef(wfile->filename.data(), wfile->filename.size()), range);
if (replsOrErr)
reply(replacementsToEdits(code, *replsOrErr));
auto ReplsOrErr = FormatCode(code, wfile->filename, range);
if (ReplsOrErr)
reply(ReplacementsToEdits(code, *ReplsOrErr));
else
reply.error(ErrorCode::UnknownErrorCode, llvm::toString(replsOrErr.takeError()));
reply.Error(ErrorCode::UnknownErrorCode,
llvm::toString(ReplsOrErr.takeError()));
}
} // namespace
void MessageHandler::textDocument_formatting(DocumentFormattingParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_formatting(DocumentFormattingParam &param,
ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
format(reply, wf, {0, (unsigned)wf->buffer_content.size()});
Format(reply, wf, {0, (unsigned)wf->buffer_content.size()});
}
void MessageHandler::textDocument_onTypeFormatting(DocumentOnTypeFormattingParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_onTypeFormatting(
DocumentOnTypeFormattingParam &param, ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf) {
return;
}
std::string_view code = wf->buffer_content;
int pos = getOffsetForPosition(param.position, code);
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)});
Format(reply, wf, {(unsigned)lbrace, unsigned(pos - lbrace)});
}
void MessageHandler::textDocument_rangeFormatting(DocumentRangeFormattingParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_rangeFormatting(
DocumentRangeFormattingParam &param, ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf) {
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)});
int begin = GetOffsetForPosition(param.range.start, code),
end = GetOffsetForPosition(param.range.end, code);
Format(reply, wf, {(unsigned)begin, unsigned(end - begin)});
}
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -15,47 +27,46 @@ struct Hover {
std::optional<lsRange> range;
};
void reflect(JsonWriter &vis, MarkedString &v) {
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();
vis.StartObject();
REFLECT_MEMBER(language);
REFLECT_MEMBER(value);
vis.endObject();
vis.EndObject();
} else {
reflect(vis, v.value);
Reflect(vis, v.value);
}
}
REFLECT_STRUCT(Hover, contents, range);
const char *languageIdentifier(LanguageId lang) {
const char *LanguageIdentifier(LanguageId lang) {
switch (lang) {
// clang-format off
// 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
// clang-format on
}
}
// Returns the hover or detailed name for `sym`, if any.
std::pair<std::optional<MarkedString>, std::optional<MarkedString>> getHover(DB *db, LanguageId lang, SymbolRef sym,
int file_id) {
std::pair<std::optional<MarkedString>, std::optional<MarkedString>>
GetHover(DB *db, LanguageId lang, SymbolRef sym, int file_id) {
const char *comments = nullptr;
std::optional<MarkedString> ls_comments, hover;
withEntity(db, sym, [&](const auto &entity) {
WithEntity(db, sym, [&](const auto &entity) {
for (auto &d : entity.def) {
if (!comments && d.comments[0])
comments = d.comments;
if (d.spell) {
if (d.comments[0])
comments = d.comments;
if (const char *s = d.hover[0] ? d.hover : d.detailed_name[0] ? d.detailed_name : nullptr) {
comments = d.comments[0] ? d.comments : nullptr;
if (const char *s =
d.hover[0] ? d.hover
: d.detailed_name[0] ? d.detailed_name : nullptr) {
if (!hover)
hover = {languageIdentifier(lang), s};
hover = {LanguageIdentifier(lang), s};
else if (strlen(s) > hover->value.size())
hover->value = s;
}
@ -65,7 +76,9 @@ std::pair<std::optional<MarkedString>, std::optional<MarkedString>> getHover(DB
}
if (!hover && entity.def.size()) {
auto &d = entity.def[0];
hover = {languageIdentifier(lang)};
if (d.comments[0])
comments = d.comments;
hover = {LanguageIdentifier(lang)};
if (d.hover[0])
hover->value = d.hover;
else if (d.detailed_name[0])
@ -78,18 +91,20 @@ std::pair<std::optional<MarkedString>, std::optional<MarkedString>> getHover(DB
}
} // namespace
void MessageHandler::textDocument_hover(TextDocumentPositionParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
void MessageHandler::textDocument_hover(TextDocumentPositionParam &param,
ReplyOnce &reply) {
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
Hover result;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
std::optional<lsRange> ls_range = getLsRange(wfiles->getFile(file->def->path), sym.range);
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) {
std::optional<lsRange> 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);
auto [hover, comments] = GetHover(db, file->def->language, sym, file->id);
if (comments || hover) {
result.range = *ls_range;
if (comments)

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -29,25 +41,27 @@ struct ReferenceParam : public TextDocumentPositionParam {
Role role = Role::None;
};
REFLECT_STRUCT(ReferenceParam::Context, includeDeclaration);
REFLECT_STRUCT(ReferenceParam, textDocument, position, context, folders, base, excludeRole, role);
REFLECT_STRUCT(ReferenceParam, textDocument, position, context, folders, base,
excludeRole, role);
} // namespace
void MessageHandler::textDocument_references(JsonReader &reader, ReplyOnce &reply) {
void MessageHandler::textDocument_references(JsonReader &reader,
ReplyOnce &reply) {
ReferenceParam param;
reflect(reader, param);
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
Reflect(reader, param);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
for (auto &folder : param.folders)
ensureEndsInSlash(folder);
std::vector<uint8_t> file_set = db->getFileSet(param.folders);
EnsureEndsInSlash(folder);
std::vector<uint8_t> file_set = db->GetFileSet(param.folders);
std::vector<Location> result;
std::unordered_set<Use> seen_uses;
int line = param.position.line;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) {
// Found symbol. Return references.
std::unordered_set<Usr> seen;
seen.insert(sym.usr);
@ -58,16 +72,17 @@ void MessageHandler::textDocument_references(JsonReader &reader, ReplyOnce &repl
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))
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) {
WithEntity(db, sym, [&](const auto &entity) {
SymbolKind parent_kind = SymbolKind::Unknown;
for (auto &def : entity.def)
if (def.spell) {
parent_kind = getSymbolKind(db, sym);
parent_kind = GetSymbolKind(db, sym);
if (param.base)
for (Usr usr : make_range(def.bases_begin(), def.bases_end()))
if (!seen.count(usr)) {
@ -109,7 +124,7 @@ void MessageHandler::textDocument_references(JsonReader &reader, ReplyOnce &repl
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.uri = DocumentUri::FromPath(file1.def->path);
loc.range.start.line = loc.range.end.line = include.line;
break;
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -12,17 +24,18 @@ using namespace clang;
namespace ccls {
namespace {
WorkspaceEdit buildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym, std::string_view old_text,
WorkspaceEdit BuildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym,
std::string_view old_text,
const std::string &new_text) {
std::unordered_map<int, std::pair<WorkingFile *, TextDocumentEdit>> path2edit;
std::unordered_map<int, std::unordered_set<Range>> edited;
eachOccurrence(db, sym, true, [&](Use use) {
EachOccurrence(db, sym, true, [&](Use use) {
int file_id = use.file_id;
QueryFile &file = db->files[file_id];
if (!file.def || !edited[file_id].insert(use.range).second)
return;
std::optional<Location> loc = getLsLocation(db, wfiles, use);
std::optional<Location> loc = GetLsLocation(db, wfiles, use);
if (!loc)
return;
@ -30,14 +43,14 @@ WorkspaceEdit buildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym, st
auto &edit = it->second.second;
if (inserted) {
const std::string &path = file.def->path;
edit.textDocument.uri = DocumentUri::fromPath(path);
if ((it->second.first = wfiles->getFile(path)))
edit.textDocument.uri = DocumentUri::FromPath(path);
if ((it->second.first = wfiles->GetFile(path)))
edit.textDocument.version = it->second.first->version;
}
// TODO LoadIndexedContent if wf is nullptr.
if (WorkingFile *wf = it->second.first) {
int start = getOffsetForPosition(loc->range.start, wf->buffer_content),
end = getOffsetForPosition(loc->range.end, wf->buffer_content);
int start = GetOffsetForPosition(loc->range.start, wf->buffer_content),
end = GetOffsetForPosition(loc->range.end, wf->buffer_content);
if (wf->buffer_content.compare(start, end - start, old_text))
return;
}
@ -52,14 +65,16 @@ WorkspaceEdit buildWorkspaceEdit(DB *db, WorkingFiles *wfiles, SymbolRef sym, st
} // namespace
void MessageHandler::textDocument_rename(RenameParam &param, ReplyOnce &reply) {
auto [file, wf] = findOrFail(param.textDocument.uri.getPath(), reply);
auto [file, wf] = FindOrFail(param.textDocument.uri.GetPath(), reply);
if (!wf)
return;
WorkspaceEdit result;
for (SymbolRef sym : findSymbolsAtLocation(wf, file, param.position)) {
result =
buildWorkspaceEdit(db, wfiles, sym, lexIdentifierAroundPos(param.position, wf->buffer_content), param.newName);
for (SymbolRef sym : FindSymbolsAtLocation(wf, file, param.position)) {
result = BuildWorkspaceEdit(
db, wfiles, sym,
LexIdentifierAroundPos(param.position, wf->buffer_content),
param.newName);
break;
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -28,11 +40,12 @@ REFLECT_STRUCT(ParameterInformation, label);
REFLECT_STRUCT(SignatureInformation, label, documentation, parameters);
REFLECT_STRUCT(SignatureHelp, signatures, activeSignature, activeParameter);
void buildOptional(const CodeCompletionString &ccs, std::string &label, std::vector<ParameterInformation> &ls_params) {
for (const auto &chunk : ccs) {
switch (chunk.Kind) {
void BuildOptional(const CodeCompletionString &CCS, std::string &label,
std::vector<ParameterInformation> &ls_params) {
for (const auto &Chunk : CCS) {
switch (Chunk.Kind) {
case CodeCompletionString::CK_Optional:
buildOptional(*chunk.Optional, label, ls_params);
BuildOptional(*Chunk.Optional, label, ls_params);
break;
case CodeCompletionString::CK_Placeholder:
// A string that acts as a placeholder for, e.g., a function call
@ -43,76 +56,74 @@ void buildOptional(const CodeCompletionString &ccs, std::string &label, std::vec
// the code-completion location within a function call, message send,
// macro invocation, etc.
int off = (int)label.size();
label += chunk.Text;
label += Chunk.Text;
ls_params.push_back({{off, (int)label.size()}});
break;
}
case CodeCompletionString::CK_VerticalSpace:
break;
default:
label += chunk.Text;
label += Chunk.Text;
break;
}
}
}
class SignatureHelpConsumer : public CodeCompleteConsumer {
std::shared_ptr<GlobalCodeCompletionAllocator> alloc;
CodeCompletionTUInfo cCTUInfo;
std::shared_ptr<GlobalCodeCompletionAllocator> Alloc;
CodeCompletionTUInfo CCTUInfo;
public:
bool from_cache;
SignatureHelp ls_sighelp;
SignatureHelpConsumer(const clang::CodeCompleteOptions &opts, bool from_cache)
: CodeCompleteConsumer(opts), alloc(std::make_shared<GlobalCodeCompletionAllocator>()), cCTUInfo(alloc),
from_cache(from_cache) {}
void ProcessOverloadCandidates(Sema &s, unsigned currentArg, OverloadCandidate *candidates, unsigned numCandidates,
SourceLocation openParLoc
#if LLVM_VERSION_MAJOR >= 14
SignatureHelpConsumer(const clang::CodeCompleteOptions &CCOpts,
bool from_cache)
: CodeCompleteConsumer(CCOpts, false),
Alloc(std::make_shared<GlobalCodeCompletionAllocator>()),
CCTUInfo(Alloc), from_cache(from_cache) {}
void ProcessOverloadCandidates(Sema &S, unsigned CurrentArg,
OverloadCandidate *Candidates,
unsigned NumCandidates
#if LLVM_VERSION_MAJOR >= 8
,
bool braced
SourceLocation OpenParLoc
#endif
) override {
ls_sighelp.activeParameter = (int)currentArg;
for (unsigned i = 0; i < numCandidates; i++) {
OverloadCandidate cand = candidates[i];
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<char>').
if (auto *func = cand.getFunction())
if (auto *pattern = func->getTemplateInstantiationPattern())
cand = OverloadCandidate(pattern);
if (auto *Func = Cand.getFunction())
if (auto *Pattern = Func->getTemplateInstantiationPattern())
Cand = OverloadCandidate(Pattern);
const auto *ccs =
#if LLVM_VERSION_MAJOR >= 14
cand.CreateSignatureString(currentArg, s, *alloc, cCTUInfo, true, braced);
#else
cand.CreateSignatureString(currentArg, s, *alloc, cCTUInfo, true);
#endif
const auto *CCS =
Cand.CreateSignatureString(CurrentArg, S, *Alloc, CCTUInfo, true);
const char *ret_type = nullptr;
SignatureInformation &ls_sig = ls_sighelp.signatures.emplace_back();
const RawComment *rc = getCompletionComment(s.getASTContext(), cand.getFunction());
ls_sig.documentation = rc ? rc->getBriefText(s.getASTContext()) : "";
for (const auto &chunk : *ccs)
switch (chunk.Kind) {
const RawComment *RC = getCompletionComment(S.getASTContext(), Cand.getFunction());
ls_sig.documentation = RC ? RC->getBriefText(S.getASTContext()) : "";
for (const auto &Chunk : *CCS)
switch (Chunk.Kind) {
case CodeCompletionString::CK_ResultType:
ret_type = chunk.Text;
ret_type = Chunk.Text;
break;
case CodeCompletionString::CK_Placeholder:
case CodeCompletionString::CK_CurrentParameter: {
int off = (int)ls_sig.label.size();
ls_sig.label += chunk.Text;
ls_sig.label += Chunk.Text;
ls_sig.parameters.push_back({{off, (int)ls_sig.label.size()}});
break;
}
case CodeCompletionString::CK_Optional:
buildOptional(*chunk.Optional, ls_sig.label, ls_sig.parameters);
BuildOptional(*Chunk.Optional, ls_sig.label, ls_sig.parameters);
break;
case CodeCompletionString::CK_VerticalSpace:
break;
default:
ls_sig.label += chunk.Text;
ls_sig.label += Chunk.Text;
break;
}
if (ret_type) {
@ -120,65 +131,66 @@ public:
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;
});
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; }
CodeCompletionAllocator &getAllocator() override { return *Alloc; }
CodeCompletionTUInfo &getCodeCompletionTUInfo() override { return CCTUInfo; }
};
} // namespace
void MessageHandler::textDocument_signatureHelp(TextDocumentPositionParam &param, ReplyOnce &reply) {
void MessageHandler::textDocument_signatureHelp(
TextDocumentPositionParam &param, ReplyOnce &reply) {
static CompleteConsumerCache<SignatureHelp> cache;
Position begin_pos = param.position;
std::string path = param.textDocument.uri.getPath();
WorkingFile *wf = wfiles->getFile(path);
std::string path = param.textDocument.uri.GetPath();
WorkingFile *wf = wfiles->GetFile(path);
if (!wf) {
reply.notOpened(path);
reply.NotOpened(path);
return;
}
std::string buffer_line;
if (param.position.line >= 0 && param.position.line < wf->buffer_lines.size())
buffer_line = wf->buffer_lines[param.position.line];
{
std::string filter;
begin_pos = wf->getCompletionPosition(param.position, &filter);
Position end_pos;
begin_pos = wf->GetCompletionPosition(param.position, &filter, &end_pos);
}
SemaManager::OnComplete callback = [reply, path, begin_pos, buffer_line](CodeCompleteConsumer *optConsumer) {
if (!optConsumer)
return;
auto *consumer = static_cast<SignatureHelpConsumer *>(optConsumer);
reply(consumer->ls_sighelp);
if (!consumer->from_cache) {
cache.withLock([&]() {
cache.path = path;
cache.line = buffer_line;
cache.position = begin_pos;
cache.result = consumer->ls_sighelp;
});
}
};
SemaManager::OnComplete callback =
[reply, path, begin_pos](CodeCompleteConsumer *OptConsumer) {
if (!OptConsumer)
return;
auto *Consumer = static_cast<SignatureHelpConsumer *>(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 = true;
if (cache.isCacheValid(path, buffer_line, begin_pos)) {
SignatureHelpConsumer consumer(ccOpts, true);
cache.withLock([&]() { consumer.ls_sighelp = cache.result; });
callback(&consumer);
CodeCompleteOptions CCOpts;
CCOpts.IncludeGlobals = false;
CCOpts.IncludeMacros = false;
CCOpts.IncludeBriefComments = true;
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<SemaManager::CompTask>(
reply.id, param.textDocument.uri.getPath(), param.position,
std::make_unique<SignatureHelpConsumer>(ccOpts, false), ccOpts, callback));
manager->comp_tasks.PushBack(std::make_unique<SemaManager::CompTask>(
reply.id, param.textDocument.uri.GetPath(), param.position,
std::make_unique<SignatureHelpConsumer>(CCOpts, false), CCOpts,
callback));
}
}
} // namespace ccls

View File

@ -1,13 +1,25 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "sema_manager.hh"
#include <llvm/ADT/STLExtras.h>
#include <llvm/ADT/StringRef.h>
@ -24,49 +36,54 @@ REFLECT_STRUCT(SymbolInformation, name, kind, location, containerName);
void MessageHandler::workspace_didChangeConfiguration(EmptyParam &) {
for (auto &[folder, _] : g_config->workspaceFolders)
project->load(folder);
project->index(wfiles, RequestId());
project->Load(folder);
project->Index(wfiles, RequestId());
manager->clear();
manager->Clear();
};
void MessageHandler::workspace_didChangeWatchedFiles(DidChangeWatchedFilesParam &param) {
void MessageHandler::workspace_didChangeWatchedFiles(
DidChangeWatchedFilesParam &param) {
for (auto &event : param.changes) {
std::string path = event.uri.getPath();
if ((g_config->cache.directory.size() && StringRef(path).startswith(g_config->cache.directory)) ||
std::string path = event.uri.GetPath();
if ((g_config->cache.directory.size() &&
StringRef(path).startswith(g_config->cache.directory)) ||
lookupExtension(path).first == LanguageId::Unknown)
return;
for (std::string cur = path; cur.size(); cur = sys::path::parent_path(cur))
if (cur[0] == '.')
return;
IndexMode mode =
wfiles->GetFile(path) ? IndexMode::Normal : IndexMode::Background;
switch (event.type) {
case FileChangeType::Created:
case FileChangeType::Changed: {
IndexMode mode = wfiles->getFile(path) ? IndexMode::Normal : IndexMode::Background;
pipeline::index(path, {}, mode, true);
pipeline::Index(path, {}, mode, true);
if (event.type == FileChangeType::Changed) {
if (mode == IndexMode::Normal)
manager->onSave(path);
manager->OnSave(path);
else
manager->onClose(path);
manager->OnClose(path);
}
break;
}
case FileChangeType::Deleted:
pipeline::index(path, {}, IndexMode::Delete, false);
manager->onClose(path);
pipeline::Index(path, {}, mode, false);
manager->OnClose(path);
break;
}
}
}
void MessageHandler::workspace_didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParam &param) {
void MessageHandler::workspace_didChangeWorkspaceFolders(
DidChangeWorkspaceFoldersParam &param) {
for (const WorkspaceFolder &wf : param.event.removed) {
std::string root = wf.uri.getPath();
ensureEndsInSlash(root);
std::string root = wf.uri.GetPath();
EnsureEndsInSlash(root);
LOG_S(INFO) << "delete workspace folder " << wf.name << ": " << root;
auto it = llvm::find_if(g_config->workspaceFolders, [&](auto &folder) { return folder.first == root; });
auto it = llvm::find_if(g_config->workspaceFolders,
[&](auto &folder) { return folder.first == root; });
if (it != g_config->workspaceFolders.end()) {
g_config->workspaceFolders.erase(it);
{
@ -78,37 +95,39 @@ void MessageHandler::workspace_didChangeWorkspaceFolders(DidChangeWorkspaceFolde
}
auto &workspaceFolders = g_config->workspaceFolders;
for (const WorkspaceFolder &wf : param.event.added) {
std::string folder = wf.uri.getPath();
ensureEndsInSlash(folder);
std::string real = realPath(folder) + '/';
std::string folder = wf.uri.GetPath();
EnsureEndsInSlash(folder);
std::string real = RealPath(folder) + '/';
if (folder == real)
real.clear();
LOG_S(INFO) << "add workspace folder " << wf.name << ": "
<< (real.empty() ? folder : (folder + " -> ").append(real));
<< (real.empty() ? folder : folder + " -> " + real);
workspaceFolders.emplace_back();
auto it = workspaceFolders.end() - 1;
for (; it != workspaceFolders.begin() && folder < it[-1].first; --it)
*it = it[-1];
*it = {folder, real};
project->load(folder);
project->Load(folder);
}
project->index(wfiles, RequestId());
project->Index(wfiles, RequestId());
manager->clear();
manager->Clear();
}
namespace {
// Lookup |symbol| in |db| and insert the value into |result|.
bool addSymbol(DB *db, WorkingFiles *wfiles, const std::vector<uint8_t> &file_set, SymbolIdx sym, bool use_detailed,
std::vector<std::tuple<SymbolInformation, int, SymbolIdx>> *result) {
std::optional<SymbolInformation> info = getSymbolInfo(db, sym, true);
bool AddSymbol(
DB *db, WorkingFiles *wfiles, const std::vector<uint8_t> &file_set,
SymbolIdx sym, bool use_detailed,
std::vector<std::tuple<SymbolInformation, int, SymbolIdx>> *result) {
std::optional<SymbolInformation> info = GetSymbolInfo(db, sym, true);
if (!info)
return false;
Maybe<DeclRef> dr;
bool in_folder = false;
withEntity(db, sym, [&](const auto &entity) {
WithEntity(db, sym, [&](const auto &entity) {
for (auto &def : entity.def)
if (def.spell) {
dr = def.spell;
@ -117,7 +136,7 @@ bool addSymbol(DB *db, WorkingFiles *wfiles, const std::vector<uint8_t> &file_se
}
});
if (!dr) {
auto &decls = getNonDefDeclarations(db, sym);
auto &decls = GetNonDefDeclarations(db, sym);
for (auto &dr1 : decls) {
dr = dr1;
if (!in_folder && (in_folder = file_set[dr1.file_id]))
@ -127,7 +146,7 @@ bool addSymbol(DB *db, WorkingFiles *wfiles, const std::vector<uint8_t> &file_se
if (!in_folder)
return false;
std::optional<Location> ls_location = getLsLocation(db, wfiles, *dr);
std::optional<Location> ls_location = GetLsLocation(db, wfiles, *dr);
if (!ls_location)
return false;
info->location = *ls_location;
@ -136,12 +155,13 @@ bool addSymbol(DB *db, WorkingFiles *wfiles, const std::vector<uint8_t> &file_se
}
} // namespace
void MessageHandler::workspace_symbol(WorkspaceSymbolParam &param, ReplyOnce &reply) {
void MessageHandler::workspace_symbol(WorkspaceSymbolParam &param,
ReplyOnce &reply) {
std::vector<SymbolInformation> result;
const std::string &query = param.query;
for (auto &folder : param.folders)
ensureEndsInSlash(folder);
std::vector<uint8_t> file_set = db->getFileSet(param.folders);
EnsureEndsInSlash(folder);
std::vector<uint8_t> file_set = db->GetFileSet(param.folders);
// {symbol info, matching detailed_name or short_name, index}
std::vector<std::tuple<SymbolInformation, int, SymbolIdx>> cands;
@ -154,21 +174,23 @@ void MessageHandler::workspace_symbol(WorkspaceSymbolParam &param, ReplyOnce &re
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);
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) &&
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}))
if (Add({func.usr, Kind::Func}))
goto done_add;
for (auto &type : db->types)
if (add({type.usr, Kind::Type}))
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}))
if (var.def.size() && !var.def[0].is_local() && Add({var.usr, Kind::Var}))
goto done_add;
done_add:
@ -176,11 +198,15 @@ done_add:
// 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()));
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)), false);
std::sort(cands.begin(), cands.end(), [](const auto &l, const auto &r) { return std::get<1>(l) > std::get<1>(r); });
std::get<1>(cand) = fuzzy.Match(
db->GetSymbolName(std::get<2>(cand), std::get<1>(cand)), false);
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.

View File

@ -1,9 +1,22 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -21,7 +34,6 @@
#include <llvm/Support/Threading.h>
#include <chrono>
#include <inttypes.h>
#include <mutex>
#include <shared_mutex>
#include <thread>
@ -38,25 +50,19 @@ struct PublishDiagnosticParam {
std::vector<Diagnostic> diagnostics;
};
REFLECT_STRUCT(PublishDiagnosticParam, uri, diagnostics);
constexpr char index_progress_token[] = "index";
struct WorkDoneProgressCreateParam {
const char *token = index_progress_token;
};
REFLECT_STRUCT(WorkDoneProgressCreateParam, token);
} // namespace
void VFS::clear() {
void VFS::Clear() {
std::lock_guard lock(mutex);
state.clear();
}
int VFS::loaded(const std::string &path) {
int 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) {
bool VFS::Stamp(const std::string &path, int64_t ts, int step) {
std::lock_guard<std::mutex> lock(mutex);
State &st = state[path];
if (st.timestamp < ts || (st.timestamp == ts && st.step < step)) {
@ -68,13 +74,12 @@ bool VFS::stamp(const std::string &path, int64_t ts, int step) {
}
struct MessageHandler;
void standaloneInitialize(MessageHandler &, const std::string &root);
void StandaloneInitialize(MessageHandler &, const std::string &root);
namespace pipeline {
std::atomic<bool> g_quit;
std::atomic<int64_t> loaded_ts{0}, request_id{0};
IndexStats stats;
std::atomic<bool> quit;
std::atomic<int64_t> loaded_ts{0}, pending_index_requests{0}, request_id{0};
int64_t tick = 0;
namespace {
@ -86,7 +91,6 @@ struct IndexRequest {
bool must_exist = false;
RequestId id;
int64_t ts = tick++;
int prio = 0; // For didOpen sorting
};
std::mutex thread_mtx;
@ -108,34 +112,31 @@ struct InMemoryIndexFile {
std::shared_mutex g_index_mutex;
std::unordered_map<std::string, InMemoryIndexFile> g_index;
bool cacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path, const std::vector<const char *> &args,
bool CacheInvalid(VFS *vfs, IndexFile *prev, const std::string &path,
const std::vector<const char *> &args,
const std::optional<std::string> &from) {
{
std::lock_guard<std::mutex> lock(vfs->mutex);
if (prev->mtime < vfs->state[path].timestamp) {
LOG_V(1) << "timestamp changed for " << path << (from ? " (via " + *from + ")" : std::string());
LOG_V(1) << "timestamp changed for " << path
<< (from ? " (via " + *from + ")" : std::string());
return true;
}
}
// For inferred files, allow -o a a.cc -> -o b b.cc
StringRef stem = sys::path::stem(path);
int changed = -1, size = std::min(prev->args.size(), args.size());
for (int i = 0; i < size; i++)
if (strcmp(prev->args[i], args[i]) && sys::path::stem(args[i]) != stem) {
changed = i;
break;
}
if (changed < 0 && prev->args.size() != args.size())
changed = size;
if (changed >= 0)
LOG_V(1) << "args changed for " << path << (from ? " (via " + *from + ")" : std::string())
<< "; old: " << (changed < prev->args.size() ? prev->args[changed] : "")
<< "; new: " << (changed < size ? args[changed] : "");
return changed >= 0;
std::string stem = sys::path::stem(path);
bool changed = prev->args.size() != args.size();
for (size_t i = 0; !changed && i < args.size(); i++)
if (strcmp(prev->args[i], args[i]) && sys::path::stem(args[i]) != stem)
changed = true;
if (changed)
LOG_V(1) << "args changed for " << path
<< (from ? " (via " + *from + ")" : std::string());
return changed;
};
std::string appendSerializationFormat(const std::string &base) {
std::string AppendSerializationFormat(const std::string &base) {
switch (g_config->cache.format) {
case SerializeFormat::Binary:
return base + ".blob";
@ -144,26 +145,29 @@ std::string appendSerializationFormat(const std::string &base) {
}
}
std::string getCachePath(std::string src) {
std::string GetCachePath(std::string src) {
if (g_config->cache.hierarchicalPath) {
std::string ret = src[0] == '/' ? src.substr(1) : src;
std::string ret =
g_config->cache.directory + (src[0] == '/' ? src.substr(1) : src);
#ifdef _WIN32
std::replace(ret.begin(), ret.end(), ':', '@');
#endif
return g_config->cache.directory + ret;
return ret;
}
for (auto &[root, _] : g_config->workspaceFolders)
if (StringRef(src).startswith(root)) {
auto len = root.size();
return g_config->cache.directory + escapeFileName(root.substr(0, len - 1)) + '/' +
escapeFileName(src.substr(len));
return g_config->cache.directory +
EscapeFileName(root.substr(0, len - 1)) + '/' +
EscapeFileName(src.substr(len));
}
return g_config->cache.directory + '@' +
escapeFileName(g_config->fallbackFolder.substr(0, g_config->fallbackFolder.size() - 1)) + '/' +
escapeFileName(src);
EscapeFileName(g_config->fallbackFolder.substr(
0, g_config->fallbackFolder.size() - 1)) +
'/' + EscapeFileName(src);
}
std::unique_ptr<IndexFile> rawCacheLoad(const std::string &path) {
std::unique_ptr<IndexFile> RawCacheLoad(const std::string &path) {
if (g_config->cache.retainInMemory) {
std::shared_lock lock(g_index_mutex);
auto it = g_index.find(path);
@ -173,47 +177,51 @@ std::unique_ptr<IndexFile> rawCacheLoad(const std::string &path) {
return nullptr;
}
std::string cache_path = getCachePath(path);
std::optional<std::string> file_content = readContent(cache_path);
std::optional<std::string> serialized_indexed_content = readContent(appendSerializationFormat(cache_path));
std::string cache_path = GetCachePath(path);
std::optional<std::string> file_content = ReadContent(cache_path);
std::optional<std::string> serialized_indexed_content =
ReadContent(AppendSerializationFormat(cache_path));
if (!file_content || !serialized_indexed_content)
return nullptr;
return ccls::deserialize(g_config->cache.format, path, *serialized_indexed_content, *file_content,
return ccls::Deserialize(g_config->cache.format, 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<std::string>()(path) % n_MUTEXES];
std::mutex &GetFileMutex(const std::string &path) {
const int N_MUTEXES = 256;
static std::mutex mutexes[N_MUTEXES];
return mutexes[std::hash<std::string>()(path) % N_MUTEXES];
}
bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *project, VFS *vfs,
const GroupMatch &matcher) {
std::optional<IndexRequest> opt_request = index_request->tryPopFront();
bool Indexer_Parse(SemaManager *completion, WorkingFiles *wfiles,
Project *project, VFS *vfs, const GroupMatch &matcher) {
std::optional<IndexRequest> 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);
on_indexed->PushBack(std::move(dummy), false);
return false;
}
struct RAII {
~RAII() { stats.completed++; }
} raii;
if (!matcher.matches(request.path)) {
if (!matcher.Matches(request.path)) {
LOG_IF_S(INFO, loud) << "skip " << request.path;
return false;
}
Project::Entry entry = project->findEntry(request.path, true, request.must_exist);
// must_exist is currently unused.
Project::Entry entry =
project->FindEntry(request.path, true, request.must_exist);
if (request.must_exist && entry.filename.empty())
return true;
if (request.args.size())
@ -221,27 +229,25 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
std::string path_to_index = entry.filename;
std::unique_ptr<IndexFile> prev;
bool deleted = request.mode == IndexMode::Delete,
no_linkage = g_config->index.initialNoLinkage || request.mode != IndexMode::Background;
bool deleted = false, no_linkage = g_config->index.initialNoLinkage ||
request.mode != IndexMode::Background;
int reparse = 0;
if (deleted)
reparse = 2;
else if (!(g_config->index.onChange && wfiles->getFile(path_to_index))) {
std::optional<int64_t> write_time = lastWriteTime(path_to_index);
if (!write_time) {
deleted = true;
} else {
if (vfs->stamp(path_to_index, *write_time, no_linkage ? 2 : 0))
reparse = 1;
if (request.path != path_to_index) {
std::optional<int64_t> mtime1 = lastWriteTime(request.path);
if (!mtime1)
deleted = true;
else if (vfs->stamp(request.path, *mtime1, no_linkage ? 2 : 0))
reparse = 2;
}
std::optional<int64_t> write_time = LastWriteTime(path_to_index);
if (!write_time) {
deleted = true;
} else {
if (vfs->Stamp(path_to_index, *write_time, no_linkage ? 2 : 0))
reparse = no_linkage ? 2 : 1;
if (request.path != path_to_index) {
std::optional<int64_t> mtime1 = LastWriteTime(request.path);
if (!mtime1)
deleted = true;
else if (vfs->Stamp(request.path, *mtime1, no_linkage ? 2 : 0))
reparse = 2;
}
}
if (deleted)
reparse = 2;
if (g_config->index.onChange) {
reparse = 2;
@ -250,28 +256,31 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
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);
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 || prev->no_linkage < no_linkage ||
cacheInvalid(vfs, prev.get(), path_to_index, entry.args, std::nullopt))
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 (auto mtime1 = LastWriteTime(dep.first.val().str())) {
if (dep.second < *mtime1) {
reparse = 2;
LOG_V(1) << "timestamp changed for " << path_to_index << " via " << dep.first.val().str();
LOG_V(1) << "timestamp changed for " << path_to_index << " via "
<< dep.first.val().str();
break;
}
} else {
reparse = 2;
LOG_V(1) << "timestamp changed for " << path_to_index << " via " << dep.first.val().str();
LOG_V(1) << "timestamp changed for " << path_to_index << " via "
<< dep.first.val().str();
break;
}
}
@ -280,12 +289,13 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
if (reparse == 2)
break;
if (vfs->loaded(path_to_index))
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::Background);
IndexUpdate update = IndexUpdate::CreateDelta(nullptr, prev.get());
on_indexed->PushBack(std::move(update),
request.mode != IndexMode::Background);
{
std::lock_guard lock1(vfs->mutex);
VFS::State &st = vfs->state[path_to_index];
@ -297,10 +307,10 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
for (const auto &dep : dependencies) {
std::string path = dep.first.val().str();
if (!vfs->stamp(path, dep.second, 1))
if (!vfs->Stamp(path, dep.second, 1))
continue;
std::lock_guard lock1(getFileMutex(path));
prev = rawCacheLoad(path);
std::lock_guard lock1(GetFileMutex(path));
prev = RawCacheLoad(path);
if (!prev)
continue;
{
@ -313,8 +323,9 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
if (prev->no_linkage)
st.step = 3;
}
IndexUpdate update = IndexUpdate::createDelta(nullptr, prev.get());
on_indexed->pushBack(std::move(update), request.mode != IndexMode::Background);
IndexUpdate update = IndexUpdate::CreateDelta(nullptr, prev.get());
on_indexed->PushBack(std::move(update),
request.mode != IndexMode::Background);
if (entry.id >= 0) {
std::lock_guard lock2(project->mtx);
project->root2folder[entry.root].path2entry_index[path] = entry.id;
@ -323,9 +334,17 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
return true;
} while (0);
if (loud) {
std::string line;
if (LOG_V_ENABLED(1)) {
line = "\n ";
for (auto &arg : entry.args)
(line += ' ') += arg;
}
LOG_S(INFO) << (deleted ? "delete " : "parse ") << path_to_index << line;
}
std::vector<std::unique_ptr<IndexFile>> indexes;
int n_errs = 0;
std::string first_error;
if (deleted) {
indexes.push_back(std::make_unique<IndexFile>(request.path, "", false));
if (request.path != path_to_index)
@ -333,77 +352,65 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
} else {
std::vector<std::pair<std::string, std::string>> remapped;
if (g_config->index.onChange) {
std::string content = wfiles->getContent(path_to_index);
std::string content = wfiles->GetContent(path_to_index);
if (content.size())
remapped.emplace_back(path_to_index, content);
}
bool ok;
auto result =
idx::index(completion, wfiles, vfs, entry.directory, path_to_index, entry.args, remapped, no_linkage, ok);
indexes = std::move(result.indexes);
n_errs = result.n_errs;
first_error = std::move(result.first_error);
indexes = idx::Index(completion, wfiles, vfs, entry.directory,
path_to_index, entry.args, remapped, no_linkage, ok);
if (!ok) {
if (request.id.valid()) {
if (request.id.Valid()) {
ResponseError err;
err.code = ErrorCode::InternalError;
err.message = "failed to index " + path_to_index;
pipeline::replyError(request.id, err);
pipeline::ReplyError(request.id, err);
}
return true;
}
}
if (loud || n_errs) {
std::string line;
SmallString<64> tmp;
SmallString<256> msg;
(Twine(deleted ? "delete " : "parse ") + path_to_index).toVector(msg);
if (n_errs)
msg += " error:" + std::to_string(n_errs) + ' ' + first_error;
if (LOG_V_ENABLED(1)) {
msg += "\n ";
for (const char *arg : entry.args)
(msg += ' ') += arg;
}
LOG_S(INFO) << std::string_view(msg.data(), msg.size());
}
for (std::unique_ptr<IndexFile> &curr : indexes) {
std::string path = curr->path;
if (!matcher.matches(path)) {
if (!matcher.Matches(path)) {
LOG_IF_S(INFO, loud) << "skip index for " << path;
continue;
}
if (!deleted)
LOG_IF_S(INFO, loud) << "store index for " << path << " (delta: " << !!prev << ")";
LOG_IF_S(INFO, loud) << "store index for " << path
<< " (delta: " << !!prev << ")";
{
std::lock_guard lock(getFileMutex(path));
int loaded = vfs->loaded(path), retain = g_config->cache.retainInMemory;
std::lock_guard lock(GetFileMutex(path));
int loaded = vfs->Loaded(path), retain = g_config->cache.retainInMemory;
if (loaded)
prev = rawCacheLoad(path);
prev = RawCacheLoad(path);
else
prev.reset();
if (retain > 0 && retain <= loaded + 1) {
std::lock_guard lock(g_index_mutex);
auto it = g_index.insert_or_assign(path, InMemoryIndexFile{curr->file_contents, *curr});
auto it = g_index.insert_or_assign(
path, InMemoryIndexFile{curr->file_contents, *curr});
std::string().swap(it.first->second.index.file_contents);
}
if (g_config->cache.directory.size()) {
std::string cache_path = getCachePath(path);
std::string cache_path = GetCachePath(path);
if (deleted) {
(void)sys::fs::remove(cache_path);
(void)sys::fs::remove(appendSerializationFormat(cache_path));
(void)sys::fs::remove(AppendSerializationFormat(cache_path));
} else {
if (g_config->cache.hierarchicalPath)
sys::fs::create_directories(sys::path::parent_path(cache_path, sys::path::Style::posix), true);
writeToFile(cache_path, curr->file_contents);
writeToFile(appendSerializationFormat(cache_path), serialize(g_config->cache.format, *curr));
sys::fs::create_directories(
sys::path::parent_path(cache_path, sys::path::Style::posix),
true);
WriteToFile(cache_path, curr->file_contents);
WriteToFile(AppendSerializationFormat(cache_path),
Serialize(g_config->cache.format, *curr));
}
}
on_indexed->pushBack(IndexUpdate::createDelta(prev.get(), curr.get()), request.mode != IndexMode::Background);
on_indexed->PushBack(IndexUpdate::CreateDelta(prev.get(), curr.get()),
request.mode != IndexMode::Background);
{
std::lock_guard lock1(vfs->mutex);
vfs->state[path].loaded++;
@ -420,17 +427,13 @@ bool indexer_Parse(SemaManager *completion, WorkingFiles *wfiles, Project *proje
return true;
}
void quit(SemaManager &manager) {
g_quit.store(true, std::memory_order_relaxed);
manager.quit();
void Quit(SemaManager &manager) {
quit.store(true, std::memory_order_relaxed);
manager.Quit();
{
std::lock_guard lock(index_request->mutex_);
}
{ std::lock_guard lock(index_request->mutex_); }
indexer_waiter->cv.notify_all();
{
std::lock_guard lock(for_stdout->mutex_);
}
{ std::lock_guard lock(for_stdout->mutex_); }
stdout_waiter->cv.notify_one();
std::unique_lock lock(thread_mtx);
no_active_threads.wait(lock, [] { return !active_threads; });
@ -438,18 +441,18 @@ void quit(SemaManager &manager) {
} // namespace
void threadEnter() {
void ThreadEnter() {
std::lock_guard lock(thread_mtx);
active_threads++;
}
void threadLeave() {
void ThreadLeave() {
std::lock_guard lock(thread_mtx);
if (!--active_threads)
no_active_threads.notify_one();
}
void init() {
void Init() {
main_waiter = new MultiQueueWaiter;
on_request = new ThreadedQueue<InMessage>(main_waiter);
on_indexed = new ThreadedQueue<IndexUpdate>(main_waiter);
@ -461,73 +464,49 @@ void init() {
for_stdout = new ThreadedQueue<std::string>(stdout_waiter);
}
void indexer_Main(SemaManager *manager, VFS *vfs, Project *project, WorkingFiles *wfiles) {
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))
if (indexer_waiter->wait(g_quit, index_request))
if (!Indexer_Parse(manager, wfiles, project, vfs, matcher))
if (indexer_waiter->Wait(quit, index_request))
break;
}
void indexerSort(const std::unordered_map<std::string, int> &dir2prio) {
index_request->apply([&](std::deque<IndexRequest> &q) {
for (IndexRequest &request : q) {
std::string cur = lowerPathIfInsensitive(request.path);
while (!(cur = llvm::sys::path::parent_path(cur)).empty()) {
auto it = dir2prio.find(cur);
if (it != dir2prio.end()) {
request.prio = it->second;
LOG_V(3) << "set priority " << request.prio << " to " << request.path;
break;
}
}
}
std::stable_sort(q.begin(), q.end(), [](auto &l, auto &r) { return l.prio > r.prio; });
});
}
void main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) {
void Main_OnIndexed(DB *db, WorkingFiles *wfiles, IndexUpdate *update) {
if (update->refresh) {
LOG_S(INFO) << "loaded project. Refresh semantic highlight for all working file.";
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);
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);
}
if (g_config->client.semanticTokensRefresh) {
std::optional<bool> param;
request("workspace/semanticTokens/refresh", param);
EmitSemanticHighlight(db, wf.get(), file);
}
return;
}
db->applyIndexUpdate(update);
db->ApplyIndexUpdate(update);
// 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)) {
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);
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);
if (g_config->client.semanticTokensRefresh) {
// Return filename, even if the spec indicates params is none.
TextDocumentIdentifier param;
param.uri = DocumentUri::fromPath(wfile->filename);
request("workspace/semanticTokens/refresh", param);
}
EmitSkippedRanges(wfile, file);
EmitSemanticHighlight(db, wfile, file);
}
}
}
void launchStdin() {
threadEnter();
void LaunchStdin() {
ThreadEnter();
std::thread([]() {
set_thread_name("stdin");
std::string str;
@ -566,13 +545,14 @@ void launchStdin() {
assert(!document->HasParseError());
JsonReader reader{document.get()};
if (!reader.m->HasMember("jsonrpc") || std::string((*reader.m)["jsonrpc"].GetString()) != "2.0")
if (!reader.m->HasMember("jsonrpc") ||
std::string((*reader.m)["jsonrpc"].GetString()) != "2.0")
break;
RequestId id;
std::string method;
reflectMember(reader, "id", id);
reflectMember(reader, "method", method);
if (id.valid())
ReflectMember(reader, "id", id);
ReflectMember(reader, "method", method);
if (id.Valid())
LOG_V(2) << "receive RequestMessage: " << id.value << " " << method;
else
LOG_V(2) << "receive NotificationMessage " << method;
@ -580,9 +560,10 @@ void launchStdin() {
continue;
received_exit = method == "exit";
// g_config is not available before "initialize". Use 0 in that case.
on_request->pushBack(
on_request->PushBack(
{id, std::move(method), std::move(message), std::move(document),
chrono::steady_clock::now() + chrono::milliseconds(g_config ? g_config->request.timeout : 0)});
chrono::steady_clock::now() +
chrono::milliseconds(g_config ? g_config->request.timeout : 0)});
if (received_exit)
break;
@ -595,53 +576,55 @@ void launchStdin() {
std::copy(str.begin(), str.end(), message.get());
auto document = std::make_unique<rapidjson::Document>();
document->Parse(message.get(), str.size());
on_request->pushBack(
{RequestId(), std::string("exit"), std::move(message), std::move(document), chrono::steady_clock::now()});
on_request->PushBack({RequestId(), std::string("exit"),
std::move(message), std::move(document),
chrono::steady_clock::now()});
}
threadLeave();
ThreadLeave();
}).detach();
}
void launchStdout() {
threadEnter();
void LaunchStdout() {
ThreadEnter();
std::thread([]() {
set_thread_name("stdout");
while (true) {
std::vector<std::string> messages = for_stdout->dequeueAll();
std::vector<std::string> messages = for_stdout->DequeueAll();
for (auto &s : messages) {
llvm::outs() << "Content-Length: " << s.size() << "\r\n\r\n" << s;
llvm::outs().flush();
}
if (stdout_waiter->wait(g_quit, for_stdout))
if (stdout_waiter->Wait(quit, for_stdout))
break;
}
threadLeave();
ThreadLeave();
}).detach();
}
void mainLoop() {
void MainLoop() {
Project project;
WorkingFiles wfiles;
VFS vfs;
SemaManager manager(
&project, &wfiles,
[](const std::string &path, std::vector<Diagnostic> diagnostics) {
[&](std::string path, std::vector<Diagnostic> diagnostics) {
PublishDiagnosticParam params;
params.uri = DocumentUri::fromPath(path);
params.diagnostics = std::move(diagnostics);
notify("textDocument/publishDiagnostics", params);
params.uri = DocumentUri::FromPath(path);
params.diagnostics = diagnostics;
Notify("textDocument/publishDiagnostics", params);
},
[](const RequestId &id) {
if (id.valid()) {
[](RequestId id) {
if (id.Valid()) {
ResponseError err;
err.code = ErrorCode::InternalError;
err.message = "drop older completion request";
replyError(id, err);
ReplyError(id, err);
}
});
IncludeComplete include_complete(&project);
DB db;
// Setup shared references.
@ -651,10 +634,9 @@ void mainLoop() {
handler.vfs = &vfs;
handler.wfiles = &wfiles;
handler.manager = &manager;
handler.include_complete = &include_complete;
bool work_done_created = false, in_progress = false;
bool has_indexed = false;
int64_t last_completed = 0;
std::deque<InMessage> backlog;
StringMap<std::deque<InMessage *>> path2backlog;
while (true) {
@ -665,7 +647,7 @@ void mainLoop() {
if (backlog[0].backlog_path.size()) {
if (now < backlog[0].deadline)
break;
handler.run(backlog[0]);
handler.Run(backlog[0]);
path2backlog[backlog[0].backlog_path].pop_front();
}
backlog.pop_front();
@ -673,38 +655,30 @@ void mainLoop() {
handler.overdue = false;
}
std::vector<InMessage> messages = on_request->dequeueAll();
std::vector<InMessage> messages = on_request->DequeueAll();
bool did_work = messages.size();
for (InMessage &message : messages)
try {
handler.run(message);
handler.Run(message);
} catch (NotIndexed &ex) {
backlog.push_back(std::move(message));
backlog.back().backlog_path = ex.path;
path2backlog[ex.path].push_back(&backlog.back());
}
// If the "exit" notification has been received, clear all index requests
// to make indexers stop in time.
if (g_quit.load(std::memory_order_relaxed)) {
index_request->apply([&](std::deque<IndexRequest> &q) {
q.clear();
});
}
bool indexed = false;
for (int i = 20; i--;) {
std::optional<IndexUpdate> update = on_indexed->tryPopFront();
std::optional<IndexUpdate> update = on_indexed->TryPopFront();
if (!update)
break;
did_work = true;
indexed = true;
main_OnIndexed(&db, &wfiles, &*update);
Main_OnIndexed(&db, &wfiles, &*update);
if (update->files_def_update) {
auto it = path2backlog.find(update->files_def_update->first.path);
if (it != path2backlog.end()) {
for (auto &message : it->second) {
handler.run(*message);
handler.Run(*message);
message->backlog_path.clear();
}
path2backlog.erase(it);
@ -712,114 +686,81 @@ void mainLoop() {
}
}
int64_t completed = stats.completed.load(std::memory_order_relaxed);
if (completed != last_completed) {
if (!work_done_created) {
WorkDoneProgressCreateParam param;
request("window/workDoneProgress/create", param);
work_done_created = true;
}
int64_t enqueued = stats.enqueued.load(std::memory_order_relaxed);
if (completed != enqueued) {
if (!in_progress) {
WorkDoneProgressParam param;
param.token = index_progress_token;
param.value.kind = "begin";
param.value.title = "indexing";
notify("$/progress", param);
in_progress = true;
}
int64_t last_idle = stats.last_idle.load(std::memory_order_relaxed);
WorkDoneProgressParam param;
param.token = index_progress_token;
param.value.kind = "report";
param.value.message = (Twine(completed - last_idle) + "/" + Twine(enqueued - last_idle)).str();
param.value.percentage = 100 * (completed - last_idle) / (enqueued - last_idle);
notify("$/progress", param);
} else if (in_progress) {
stats.last_idle.store(enqueued, std::memory_order_relaxed);
WorkDoneProgressParam param;
param.token = index_progress_token;
param.value.kind = "end";
notify("$/progress", param);
in_progress = false;
}
last_completed = completed;
}
if (did_work) {
has_indexed |= indexed;
if (g_quit.load(std::memory_order_relaxed))
if (quit.load(std::memory_order_relaxed))
break;
} else {
if (has_indexed) {
freeUnusedMemory();
FreeUnusedMemory();
has_indexed = false;
}
if (backlog.empty())
main_waiter->wait(g_quit, on_indexed, on_request);
main_waiter->Wait(quit, on_indexed, on_request);
else
main_waiter->waitUntil(backlog[0].deadline, on_indexed, on_request);
main_waiter->WaitUntil(backlog[0].deadline, on_indexed, on_request);
}
}
quit(manager);
Quit(manager);
}
void standalone(const std::string &root) {
void Standalone(const std::string &root) {
Project project;
WorkingFiles wfiles;
VFS vfs;
SemaManager manager(
nullptr, nullptr, [](const std::string &, const std::vector<Diagnostic> &) {}, [](const RequestId &id) {});
nullptr, nullptr, [&](std::string, std::vector<Diagnostic>) {},
[](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);
StandaloneInitialize(handler, root);
bool tty = sys::Process::StandardOutIsDisplayed();
if (tty) {
int entries = 0;
for (auto &[_, folder] : project.root2folder)
entries += folder.entries.size();
printf("entries: %4d\n", entries);
printf("entries: %5d\n", entries);
}
while (1) {
(void)on_indexed->dequeueAll();
int64_t enqueued = stats.enqueued, completed = stats.completed;
(void)on_indexed->DequeueAll();
int pending = pending_index_requests;
if (tty) {
printf("\rcompleted: %4" PRId64 "/%" PRId64, completed, enqueued);
printf("\rpending: %5d", pending);
fflush(stdout);
}
if (completed == enqueued)
if (!pending)
break;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (tty)
puts("");
quit(manager);
Quit(manager);
}
void index(const std::string &path, const std::vector<const char *> &args, IndexMode mode, bool must_exist,
RequestId id) {
if (!path.empty())
stats.enqueued++;
index_request->pushBack({path, args, mode, must_exist, std::move(id)}, mode != IndexMode::Background);
void Index(const std::string &path, const std::vector<const char *> &args,
IndexMode mode, bool must_exist, RequestId id) {
pending_index_requests++;
index_request->PushBack({path, args, mode, must_exist, id},
mode != IndexMode::Background);
}
void removeCache(const std::string &path) {
void RemoveCache(const std::string &path) {
if (g_config->cache.directory.size()) {
std::lock_guard lock(g_index_mutex);
g_index.erase(path);
}
}
std::optional<std::string> loadIndexedContent(const std::string &path) {
std::optional<std::string> LoadIndexedContent(const std::string &path) {
if (g_config->cache.directory.empty()) {
std::shared_lock lock(g_index_mutex);
auto it = g_index.find(path);
@ -827,10 +768,11 @@ std::optional<std::string> loadIndexedContent(const std::string &path) {
return {};
return it->second.content;
}
return readContent(getCachePath(path));
return ReadContent(GetCachePath(path));
}
void notifyOrRequest(const char *method, bool request, const std::function<void(JsonWriter &)> &fn) {
void NotifyOrRequest(const char *method, bool request,
const std::function<void(JsonWriter &)> &fn) {
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> w(output);
w.StartObject();
@ -847,10 +789,11 @@ void notifyOrRequest(const char *method, bool request, const std::function<void(
fn(writer);
w.EndObject();
LOG_V(2) << (request ? "RequestMessage: " : "NotificationMessage: ") << method;
for_stdout->pushBack(output.GetString());
for_stdout->PushBack(output.GetString());
}
static void reply(const RequestId &id, const char *key, const std::function<void(JsonWriter &)> &fn) {
static void Reply(RequestId id, const char *key,
const std::function<void(JsonWriter &)> &fn) {
rapidjson::StringBuffer output;
rapidjson::Writer<rapidjson::StringBuffer> w(output);
w.StartObject();
@ -862,23 +805,28 @@ static void reply(const RequestId &id, const char *key, const std::function<void
w.Null();
break;
case RequestId::kInt:
w.Int64(atoll(id.value.c_str()));
w.Int(id.value);
break;
case RequestId::kString:
w.String(id.value.c_str(), id.value.size());
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();
if (id.valid())
if (id.Valid())
LOG_V(2) << "respond to RequestMessage: " << id.value;
for_stdout->pushBack(output.GetString());
for_stdout->PushBack(output.GetString());
}
void reply(const RequestId &id, const std::function<void(JsonWriter &)> &fn) { reply(id, "result", fn); }
void Reply(RequestId id, const std::function<void(JsonWriter &)> &fn) {
Reply(id, "result", fn);
}
void replyError(const RequestId &id, const std::function<void(JsonWriter &)> &fn) { reply(id, "error", fn); }
void ReplyError(RequestId id, const std::function<void(JsonWriter &)> &fn) {
Reply(id, "error", fn);
}
} // namespace pipeline
} // namespace ccls

View File

@ -1,6 +1,3 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
#pragma once
#include "lsp.hh"
@ -27,56 +24,51 @@ struct VFS {
std::unordered_map<std::string, State> state;
std::mutex mutex;
void clear();
int loaded(const std::string &path);
bool stamp(const std::string &path, int64_t ts, int step);
void Clear();
int Loaded(const std::string &path);
bool Stamp(const std::string &path, int64_t ts, int step);
};
enum class IndexMode {
Delete,
Background,
OnChange,
Normal,
};
struct IndexStats {
std::atomic<int64_t> last_idle, completed, enqueued, opened;
};
namespace pipeline {
extern std::atomic<bool> g_quit;
extern std::atomic<int64_t> loaded_ts;
extern IndexStats stats;
extern std::atomic<bool> quit;
extern std::atomic<int64_t> loaded_ts, pending_index_requests;
extern int64_t tick;
void threadEnter();
void threadLeave();
void init();
void launchStdin();
void launchStdout();
void indexer_Main(SemaManager *manager, VFS *vfs, Project *project, WorkingFiles *wfiles);
void indexerSort(const std::unordered_map<std::string, int> &dir2prio);
void mainLoop();
void standalone(const std::string &root);
void ThreadEnter();
void ThreadLeave();
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<const char *> &args, IndexMode mode, bool must_exist,
RequestId id = {});
void removeCache(const std::string &path);
std::optional<std::string> loadIndexedContent(const std::string &path);
void Index(const std::string &path, const std::vector<const char *> &args,
IndexMode mode, bool must_exist, RequestId id = {});
void RemoveCache(const std::string &path);
std::optional<std::string> LoadIndexedContent(const std::string& path);
void notifyOrRequest(const char *method, bool request, const std::function<void(JsonWriter &)> &fn);
template <typename T> void notify(const char *method, T &result) {
notifyOrRequest(method, false, [&](JsonWriter &w) { reflect(w, result); });
void NotifyOrRequest(const char *method, bool request,
const std::function<void(JsonWriter &)> &fn);
template <typename T> void Notify(const char *method, T &result) {
NotifyOrRequest(method, false, [&](JsonWriter &w) { Reflect(w, result); });
}
template <typename T> void request(const char *method, T &result) {
notifyOrRequest(method, true, [&](JsonWriter &w) { reflect(w, result); });
template <typename T> void Request(const char *method, T &result) {
NotifyOrRequest(method, true, [&](JsonWriter &w) { Reflect(w, result); });
}
void reply(const RequestId &id, const std::function<void(JsonWriter &)> &fn);
void Reply(RequestId id, const std::function<void(JsonWriter &)> &fn);
void replyError(const RequestId &id, const std::function<void(JsonWriter &)> &fn);
template <typename T> void replyError(const RequestId &id, T &result) {
replyError(id, [&](JsonWriter &w) { reflect(w, result); });
void ReplyError(RequestId id, const std::function<void(JsonWriter &)> &fn);
template <typename T> void ReplyError(RequestId id, T &result) {
ReplyError(id, [&](JsonWriter &w) { Reflect(w, result); });
}
} // namespace pipeline
} // namespace ccls

View File

@ -1,20 +1,32 @@
// Copyright 2017-2020 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 <llvm/ADT/StringRef.h>
#include <string>
#include <string_view>
#include <vector>
namespace ccls {
std::string normalizePath(llvm::StringRef path);
std::string NormalizePath(const std::string &path);
// Free any unused memory and return it to the system.
void freeUnusedMemory();
void FreeUnusedMemory();
// Stop self and wait for SIGCONT.
void traceMe();
void TraceMe();
void spawnThread(void *(*fn)(void *), void *arg);
void SpawnThread(void *(*fn)(void *), void *arg);
} // namespace ccls

View File

@ -1,7 +1,19 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* Copyright 2017-2018 ccls Authors
#if defined(__unix__) || defined(__APPLE__) || defined(__HAIKU__)
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.
==============================================================================*/
#if defined(__unix__) || defined(__APPLE__)
#include "platform.hh"
#include "utils.hh"
@ -37,22 +49,22 @@
namespace ccls {
namespace pipeline {
void threadEnter();
void ThreadEnter();
}
std::string normalizePath(llvm::StringRef path) {
llvm::SmallString<256> p(path);
llvm::sys::path::remove_dots(p, true);
return {p.data(), p.size()};
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() {
void FreeUnusedMemory() {
#ifdef __GLIBC__
malloc_trim(4 * 1024 * 1024);
#endif
}
void traceMe() {
void TraceMe() {
// If the environment variable is defined, wait for a debugger.
// In gdb, you need to invoke `signal SIGCONT` if you want ccls to continue
// after detaching.
@ -61,7 +73,7 @@ void traceMe() {
raise(traceme[0] == 's' ? SIGSTOP : SIGTSTP);
}
void spawnThread(void *(*fn)(void *), void *arg) {
void SpawnThread(void *(*fn)(void *), void *arg) {
pthread_t thd;
pthread_attr_t attr;
struct rlimit rlim;
@ -71,7 +83,7 @@ void spawnThread(void *(*fn)(void *), void *arg) {
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_attr_setstacksize(&attr, stack_size);
pipeline::threadEnter();
pipeline::ThreadEnter();
pthread_create(&thd, &attr, fn, arg);
pthread_attr_destroy(&attr);
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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.
==============================================================================*/
#if defined(_WIN32)
#include "platform.hh"
@ -19,27 +31,35 @@
#include <thread>
namespace ccls {
std::string normalizePath(llvm::StringRef path) {
std::string NormalizePath(const std::string &path) {
DWORD retval = 0;
TCHAR buffer[MAX_PATH] = TEXT("");
TCHAR **lpp_part = {NULL};
std::string result(path);
if (GetFullPathName(result.c_str(), MAX_PATH, buffer, lpp_part) != 0)
std::string result;
retval = GetFullPathName(path.c_str(), MAX_PATH, buffer, lpp_part);
// fail, return original
if (retval == 0)
result = path;
else
result = buffer;
std::replace(result.begin(), result.end(), '\\', '/');
// Normalize drive letter.
if (result.size() > 1 && result[0] >= 'a' && result[0] <= 'z' && result[1] == ':')
if (result.size() > 1 && result[0] >= 'a' && result[0] <= 'z' &&
result[1] == ':')
result[0] = toupper(result[0]);
return result;
}
void freeUnusedMemory() {}
void FreeUnusedMemory() {}
// TODO Wait for debugger to attach
void traceMe() {}
void TraceMe() {}
void spawnThread(void *(*fn)(void *), void *arg) { std::thread(fn, arg).detach(); }
} // namespace ccls
void SpawnThread(void *(*fn)(void *), void *arg) {
std::thread(fn, arg).detach();
}
}
#endif

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "position.hh"
@ -14,7 +26,7 @@
#include <stdlib.h>
namespace ccls {
Pos Pos::fromString(const std::string &encoded) {
Pos Pos::FromString(const std::string &encoded) {
char *p = const_cast<char *>(encoded.c_str());
uint16_t line = uint16_t(strtoul(p, &p, 10) - 1);
assert(*p == ':');
@ -23,13 +35,13 @@ Pos Pos::fromString(const std::string &encoded) {
return {line, column};
}
std::string Pos::toString() {
std::string Pos::ToString() {
char buf[99];
snprintf(buf, sizeof buf, "%d:%d", line + 1, column + 1);
return buf;
}
Range Range::fromString(const std::string &encoded) {
Range Range::FromString(const std::string &encoded) {
Pos start, end;
char *p = const_cast<char *>(encoded.c_str());
start.line = uint16_t(strtoul(p, &p, 10) - 1);
@ -46,50 +58,53 @@ Range Range::fromString(const std::string &encoded) {
return {start, end};
}
bool Range::contains(int line, int column) const {
if (line > UINT16_MAX)
bool Range::Contains(int line, int column) const {
if (line > INT16_MAX)
return false;
Pos p{(uint16_t)line, (int16_t)std::min<int>(column, INT16_MAX)};
return !(p < start) && p < end;
}
std::string Range::toString() {
std::string Range::ToString() {
char buf[99];
snprintf(buf, sizeof buf, "%d:%d-%d:%d", start.line + 1, start.column + 1, end.line + 1, end.column + 1);
snprintf(buf, sizeof buf, "%d:%d-%d:%d", start.line + 1, start.column + 1,
end.line + 1, end.column + 1);
return buf;
}
void reflect(JsonReader &vis, Pos &v) { v = Pos::fromString(vis.getString()); }
void reflect(JsonReader &vis, Range &v) { v = Range::fromString(vis.getString()); }
void reflect(JsonWriter &vis, Pos &v) {
std::string output = v.toString();
vis.string(output.c_str(), output.size());
}
void reflect(JsonWriter &vis, Range &v) {
std::string output = v.toString();
vis.string(output.c_str(), output.size());
void Reflect(JsonReader &vis, Pos &v) { v = Pos::FromString(vis.GetString()); }
void Reflect(JsonReader &vis, Range &v) {
v = Range::FromString(vis.GetString());
}
void reflect(BinaryReader &visitor, Pos &value) {
reflect(visitor, value.line);
reflect(visitor, value.column);
void Reflect(JsonWriter &vis, Pos &v) {
std::string output = v.ToString();
vis.String(output.c_str(), output.size());
}
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);
void Reflect(JsonWriter &vis, Range &v) {
std::string output = v.ToString();
vis.String(output.c_str(), output.size());
}
void reflect(BinaryWriter &vis, Pos &v) {
reflect(vis, v.line);
reflect(vis, v.column);
void Reflect(BinaryReader &visitor, Pos &value) {
Reflect(visitor, value.line);
Reflect(visitor, value.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);
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);
}
void Reflect(BinaryWriter &vis, Pos &v) {
Reflect(vis, v.line);
Reflect(vis, v.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

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -13,14 +25,16 @@ struct Pos {
uint16_t line = 0;
int16_t column = -1;
static Pos fromString(const std::string &encoded);
static Pos FromString(const std::string &encoded);
bool valid() const { return column >= 0; }
std::string toString();
bool Valid() const { return column >= 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 {
return line == o.line && column == o.column;
}
bool operator<(const Pos &o) const {
if (line != o.line)
return line < o.line;
@ -33,31 +47,35 @@ struct Range {
Pos start;
Pos end;
static Range fromString(const std::string &encoded);
static Range FromString(const std::string &encoded);
bool valid() const { return start.valid(); }
bool contains(int line, int column) const;
bool Valid() const { return start.Valid(); }
bool Contains(int line, int column) const;
std::string toString();
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; }
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
// 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);
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 {

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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"
@ -25,9 +37,9 @@
#include <rapidjson/writer.h>
#ifdef _WIN32
#include <Windows.h>
# include <Windows.h>
#else
#include <unistd.h>
# include <unistd.h>
#endif
#include <array>
@ -41,15 +53,17 @@ using namespace llvm;
namespace ccls {
std::pair<LanguageId, bool> 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);
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 = types::isCuda(i) ? LanguageId::Cuda : objc ? LanguageId::ObjCpp : LanguageId::Cpp;
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)
else if (I == types::TY_C || I == types::TY_CHeader)
ret = LanguageId::C;
else
ret = LanguageId::Unknown;
@ -81,107 +95,186 @@ struct ProjectProcessor {
LOG_S(WARNING) << toString(glob_or_err.takeError());
}
bool excludesArg(StringRef arg, int &i) {
if (arg.startswith("-M")) {
if (arg == "-MF" || arg == "-MT" || arg == "-MQ")
i++;
return true;
}
if (arg == "-Xclang") {
i++;
return true;
}
return exclude_args.count(arg) || any_of(exclude_globs, [&](const GlobPattern &glob) { return glob.match(arg); });
bool ExcludesArg(StringRef arg) {
return exclude_args.count(arg) || any_of(exclude_globs,
[&](const GlobPattern &glob) { return glob.match(arg); });
}
// Expand %c %cpp ... in .ccls
void process(Project::Entry &entry) {
std::vector<const char *> args(entry.args.begin(), entry.args.begin() + entry.compdb_size);
void Process(Project::Entry &entry) {
std::vector<const char *> args(entry.args.begin(),
entry.args.begin() + entry.compdb_size);
auto [lang, header] = lookupExtension(entry.filename);
for (int i = entry.compdb_size; i < entry.args.size(); i++) {
const char *arg = entry.args[i];
StringRef a(arg);
if (a[0] == '%') {
StringRef A(arg);
if (A[0] == '%') {
bool ok = false;
for (;;) {
if (a.consume_front("%c "))
if (A.consume_front("%c "))
ok |= lang == LanguageId::C;
else if (a.consume_front("%h "))
else if (A.consume_front("%h "))
ok |= lang == LanguageId::C && header;
else if (a.consume_front("%cpp "))
else if (A.consume_front("%cpp "))
ok |= lang == LanguageId::Cpp;
else if (a.consume_front("%cu "))
ok |= lang == LanguageId::Cuda;
else if (a.consume_front("%hpp "))
else if (A.consume_front("%hpp "))
ok |= lang == LanguageId::Cpp && header;
else if (a.consume_front("%objective-c "))
else if (A.consume_front("%objective-c "))
ok |= lang == LanguageId::ObjC;
else if (a.consume_front("%objective-cpp "))
else if (A.consume_front("%objective-cpp "))
ok |= lang == LanguageId::ObjCpp;
else
break;
}
if (ok)
args.push_back(a.data());
} else if (!excludesArg(a, i)) {
args.push_back(A.data());
} else if (!ExcludesArg(A)) {
args.push_back(arg);
}
}
entry.args = args;
GetSearchDirs(entry);
}
void GetSearchDirs(Project::Entry &entry) {
#if LLVM_VERSION_MAJOR < 8
const std::string base_name = sys::path::filename(entry.filename);
size_t hash = std::hash<std::string>{}(entry.directory);
bool OPT_o = false;
for (auto &arg : entry.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;
}
}
hash_combine(hash, std::hash<std::string_view>{}(arg));
}
if (!command_set.insert(hash).second)
return;
auto args = entry.args;
args.push_back("-fsyntax-only");
for (const std::string &arg : g_config->clang.extraArgs)
args.push_back(Intern(arg));
args.push_back(Intern("-working-directory=" + entry.directory));
args.push_back(Intern("-resource-dir=" + g_config->clang.resourceDir));
// a weird C++ deduction guide heap-use-after-free causes libclang to crash.
IgnoringDiagConsumer DiagC;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(new DiagnosticOptions());
DiagnosticsEngine Diags(
IntrusiveRefCntPtr<DiagnosticIDs>(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);
std::unique_ptr<driver::Compilation> 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>();
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));
EnsureEndsInSlash(path);
switch (E.Group) {
default:
folder.search_dir2kind[path] |= 2;
break;
case frontend::Quoted:
folder.search_dir2kind[path] |= 1;
break;
case frontend::Angled:
folder.search_dir2kind[path] |= 3;
break;
}
}
#endif
}
};
std::vector<const char *> readCompilerArgumentsFromFile(const std::string &path) {
auto mbOrErr = MemoryBuffer::getFile(path);
if (!mbOrErr)
std::vector<const char *>
ReadCompilerArgumentsFromFile(const std::string &path) {
auto MBOrErr = MemoryBuffer::getFile(path);
if (!MBOrErr)
return {};
std::vector<const char *> args;
for (line_iterator i(*mbOrErr.get(), true, '#'), e; i != e; ++i) {
std::string line(*i);
doPathMapping(line);
args.push_back(intern(line));
for (line_iterator I(*MBOrErr.get(), true, '#'), E; I != E; ++I) {
std::string line = *I;
DoPathMapping(line);
args.push_back(Intern(line));
}
return args;
}
bool appendToCDB(const std::vector<const char *> &args) {
bool AppendToCDB(const std::vector<const char *> &args) {
return args.size() && StringRef("%compile_commands.json") == args[0];
}
std::vector<const char *> getFallback(const std::string &path) {
std::vector<const char *> GetFallback(const std::string path) {
std::vector<const char *> argv{"clang"};
if (sys::path::extension(path) == ".h")
argv.push_back("-xobjective-c++-header");
argv.push_back(intern(path));
for (const std::string &arg : g_config->clang.extraArgs)
argv.push_back(Intern(arg));
argv.push_back(Intern(path));
return argv;
}
void loadDirectoryListing(ProjectProcessor &proc, const std::string &root, const StringSet<> &seen) {
void LoadDirectoryListing(ProjectProcessor &proc, const std::string &root,
const StringSet<> &Seen) {
Project::Folder &folder = proc.folder;
std::vector<std::string> files;
auto getDotCcls = [&root, &folder](std::string cur) {
auto GetDotCcls = [&root, &folder](std::string cur) {
while (!(cur = sys::path::parent_path(cur)).empty()) {
auto it = folder.dot_ccls.find(cur + '/');
auto it = folder.dot_ccls.find(cur);
if (it != folder.dot_ccls.end())
return it->second;
std::string normalized = normalizePath(cur);
std::string normalized = NormalizePath(cur);
// Break if outside of the project root.
if (normalized.size() <= root.size() || normalized.compare(0, root.size(), root) != 0)
if (normalized.size() <= root.size() ||
normalized.compare(0, root.size(), root) != 0)
break;
}
return folder.dot_ccls[root];
};
getFilesInFolder(root, true /*recursive*/, true /*add_folder_to_path*/,
[&folder, &files, &seen](const std::string &path) {
GetFilesInFolder(root, true /*recursive*/, true /*add_folder_to_path*/,
[&folder, &files, &Seen](const std::string &path) {
std::pair<LanguageId, bool> lang = lookupExtension(path);
if (lang.first != LanguageId::Unknown && !lang.second) {
if (!seen.count(path))
if (!Seen.count(path))
files.push_back(path);
} else if (sys::path::filename(path) == ".ccls") {
std::vector<const char *> args = readCompilerArgumentsFromFile(path);
folder.dot_ccls.emplace(sys::path::parent_path(path).str() + '/', args);
std::vector<const char *> args = ReadCompilerArgumentsFromFile(path);
folder.dot_ccls.emplace(sys::path::parent_path(path),
args);
std::string l;
for (size_t i = 0; i < args.size(); i++) {
if (i)
@ -194,187 +287,176 @@ void loadDirectoryListing(ProjectProcessor &proc, const std::string &root, const
// If the first line of .ccls is %compile_commands.json, append extra flags.
for (auto &e : folder.entries)
if (const auto &args = getDotCcls(e.filename); appendToCDB(args)) {
if (const auto &args = GetDotCcls(e.filename); AppendToCDB(args)) {
if (args.size())
e.args.insert(e.args.end(), args.begin() + 1, args.end());
proc.process(e);
proc.Process(e);
}
// Set flags for files not in compile_commands.json
for (const std::string &file : files)
if (const auto &args = getDotCcls(file); !appendToCDB(args)) {
if (const auto &args = GetDotCcls(file); !AppendToCDB(args)) {
Project::Entry e;
e.root = e.directory = root;
e.filename = file;
if (args.empty()) {
e.args = getFallback(e.filename);
e.args = GetFallback(e.filename);
} else {
e.args = args;
e.args.push_back(intern(e.filename));
e.args.push_back(Intern(e.filename));
}
proc.process(e);
proc.Process(e);
folder.entries.push_back(e);
}
}
// Computes a score based on how well |a| and |b| match. This is used for
// argument guessing.
int computeGuessScore(std::string_view a, std::string_view b) {
int score = 0;
unsigned h = 0;
llvm::SmallDenseMap<unsigned, int> m;
for (uint8_t c : a)
if (c == '/') {
score -= 9;
if (h)
m[h]++;
h = 0;
} else {
h = h * 33 + c;
}
h = 0;
for (uint8_t c : b)
if (c == '/') {
score -= 9;
auto it = m.find(h);
if (it != m.end() && it->second > 0) {
it->second--;
score += 31;
}
h = 0;
} else {
h = h * 33 + c;
}
uint8_t c;
int d[127] = {};
for (int i = a.size(); i-- && (c = a[i]) != '/';)
if (c < 127)
d[c]++;
for (int i = b.size(); i-- && (c = b[i]) != '/';)
if (c < 127 && d[c])
d[c]--, score++;
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 < a.size())
score -= 100 * (std::count(a.begin() + i, a.end() - j, '/') +
std::count(b.begin() + i, b.end() - j, '/'));
return score;
}
} // namespace
void Project::loadDirectory(const std::string &root, Project::Folder &folder) {
SmallString<256> cdbDir, path, stdinPath;
void Project::LoadDirectory(const std::string &root, Project::Folder &folder) {
SmallString<256> CDBDir, Path, StdinPath;
std::string err_msg;
folder.entries.clear();
if (g_config->compilationDatabaseCommand.empty()) {
cdbDir = root;
if (g_config->compilationDatabaseDirectory.size()) {
if (sys::path::is_absolute(g_config->compilationDatabaseDirectory))
cdbDir = g_config->compilationDatabaseDirectory;
else
sys::path::append(cdbDir, g_config->compilationDatabaseDirectory);
}
sys::path::append(path, cdbDir, "compile_commands.json");
CDBDir = root;
if (g_config->compilationDatabaseDirectory.size())
sys::path::append(CDBDir, g_config->compilationDatabaseDirectory);
sys::path::append(Path, CDBDir, "compile_commands.json");
} else {
// If `compilationDatabaseCommand` is specified, execute it to get the
// compdb.
#ifdef _WIN32
char tmpdir[L_tmpnam];
tmpnam_s(tmpdir, L_tmpnam);
cdbDir = tmpdir;
CDBDir = tmpdir;
if (sys::fs::create_directory(tmpdir, false))
return;
#else
char tmpdir[] = "/tmp/ccls-compdb-XXXXXX";
if (!mkdtemp(tmpdir))
return;
cdbDir = tmpdir;
CDBDir = tmpdir;
#endif
sys::path::append(path, cdbDir, "compile_commands.json");
sys::path::append(stdinPath, cdbDir, "stdin");
sys::path::append(Path, CDBDir, "compile_commands.json");
sys::path::append(StdinPath, CDBDir, "stdin");
{
rapidjson::StringBuffer sb;
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
JsonWriter json_writer(&writer);
reflect(json_writer, *g_config);
Reflect(json_writer, *g_config);
std::string input = sb.GetString();
FILE *fout = fopen(stdinPath.c_str(), "wb");
FILE *fout = fopen(StdinPath.c_str(), "wb");
fwrite(input.c_str(), input.size(), 1, fout);
fclose(fout);
}
#if LLVM_VERSION_MAJOR >= 16 // llvmorg-16-init-12589-ge748db0f7f09
std::array<std::optional<StringRef>, 3>
#else
std::array<Optional<StringRef>, 3>
#endif
redir{StringRef(stdinPath), StringRef(path), StringRef()};
std::array<Optional<StringRef>, 3> Redir{StringRef(StdinPath),
StringRef(Path), StringRef()};
std::vector<StringRef> args{g_config->compilationDatabaseCommand, root};
if (sys::ExecuteAndWait(args[0], args, {}, redir, 0, 0, &err_msg) < 0) {
LOG_S(ERROR) << "failed to execute " << args[0].str() << " " << args[1].str() << ": " << err_msg;
if (sys::ExecuteAndWait(args[0], args, llvm::None, Redir, 0, 0, &err_msg) <
0) {
LOG_S(ERROR) << "failed to execute " << args[0].str() << " "
<< args[1].str() << ": " << err_msg;
return;
}
}
std::unique_ptr<tooling::CompilationDatabase> cdb = tooling::CompilationDatabase::loadFromDirectory(cdbDir, err_msg);
std::unique_ptr<tooling::CompilationDatabase> CDB =
tooling::CompilationDatabase::loadFromDirectory(CDBDir, err_msg);
if (!g_config->compilationDatabaseCommand.empty()) {
#ifdef _WIN32
DeleteFileA(stdinPath.c_str());
DeleteFileA(path.c_str());
RemoveDirectoryA(cdbDir.c_str());
DeleteFileA(StdinPath.c_str());
DeleteFileA(Path.c_str());
RemoveDirectoryA(CDBDir.c_str());
#else
unlink(stdinPath.c_str());
unlink(path.c_str());
rmdir(cdbDir.c_str());
unlink(StdinPath.c_str());
unlink(Path.c_str());
rmdir(CDBDir.c_str());
#endif
}
ProjectProcessor proc(folder);
StringSet<> seen;
StringSet<> Seen;
std::vector<Project::Entry> result;
if (!cdb) {
if (g_config->compilationDatabaseCommand.size() || sys::fs::exists(path))
LOG_S(ERROR) << "failed to load " << path.c_str();
if (!CDB) {
if (g_config->compilationDatabaseCommand.size() || sys::fs::exists(Path))
LOG_S(ERROR) << "failed to load " << Path.c_str();
} else {
LOG_S(INFO) << "loaded " << path.c_str();
for (tooling::CompileCommand &cmd : cdb->getAllCompileCommands()) {
LOG_S(INFO) << "loaded " << Path.c_str();
for (tooling::CompileCommand &Cmd : CDB->getAllCompileCommands()) {
static bool once;
Project::Entry entry;
entry.root = root;
doPathMapping(entry.root);
DoPathMapping(entry.root);
// If workspace folder is real/ but entries use symlink/, convert to
// real/.
entry.directory = realPath(resolveIfRelative(root, cmd.Directory));
entry.directory.push_back('/');
normalizeFolder(entry.directory);
entry.directory.pop_back();
doPathMapping(entry.directory);
entry.filename = realPath(resolveIfRelative(entry.directory, cmd.Filename));
normalizeFolder(entry.filename);
doPathMapping(entry.filename);
entry.directory = RealPath(Cmd.Directory);
NormalizeFolder(entry.directory);
DoPathMapping(entry.directory);
entry.filename =
RealPath(ResolveIfRelative(entry.directory, Cmd.Filename));
NormalizeFolder(entry.filename);
DoPathMapping(entry.filename);
std::vector<std::string> args = std::move(cmd.CommandLine);
std::vector<std::string> args = std::move(Cmd.CommandLine);
entry.args.reserve(args.size());
for (int i = 0; i < args.size(); i++) {
doPathMapping(args[i]);
if (!proc.excludesArg(args[i], i))
entry.args.push_back(intern(args[i]));
for (std::string &arg : args) {
DoPathMapping(arg);
if (!proc.ExcludesArg(arg))
entry.args.push_back(Intern(arg));
}
entry.compdb_size = entry.args.size();
if (seen.insert(entry.filename).second)
// 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.GetSearchDirs(entry);
if (Seen.insert(entry.filename).second)
folder.entries.push_back(entry);
}
}
// Use directory listing if .ccls exists or compile_commands.json does not
// exist.
path.clear();
sys::path::append(path, root, ".ccls");
if (sys::fs::exists(path))
loadDirectoryListing(proc, root, seen);
Path.clear();
sys::path::append(Path, root, ".ccls");
if (sys::fs::exists(Path))
LoadDirectoryListing(proc, root, Seen);
std::vector<const char *> extra_args;
for (const std::string &arg : g_config->clang.extraArgs)
extra_args.push_back(Intern(arg));
for (auto &e : folder.entries) {
e.args.insert(e.args.end(), extra_args.begin(), extra_args.end());
e.args.push_back(Intern("-working-directory=" + e.directory));
}
}
void Project::load(const std::string &root) {
void Project::Load(const std::string &root) {
assert(root.back() == '/');
std::lock_guard lock(mtx);
Folder &folder = root2folder[root];
loadDirectory(root, folder);
LoadDirectory(root, folder);
for (auto &[path, kind] : folder.search_dir2kind)
LOG_S(INFO) << "search directory: " << path << ' ' << " \"< "[kind];
@ -386,133 +468,119 @@ void Project::load(const std::string &root) {
}
}
Project::Entry Project::findEntry(const std::string &path, bool can_redirect, bool must_exist) {
std::string best_dot_ccls_root;
Project::Folder *best_dot_ccls_folder = nullptr;
std::string best_dot_ccls_dir;
const std::vector<const char *> *best_dot_ccls_args = nullptr;
bool match = false, exact_match = false;
Project::Entry Project::FindEntry(const std::string &path, bool can_redirect,
bool must_exist) {
Project::Folder *best_folder = nullptr;
const Entry *best = nullptr;
Project::Folder *best_compdb_folder = nullptr;
Project::Entry ret;
std::lock_guard lock(mtx);
for (auto &[root, folder] : root2folder)
if (StringRef(path).startswith(root)) {
// Find the best-fit .ccls
for (auto &[dir, args] : folder.dot_ccls)
if (StringRef(path).startswith(dir) && dir.length() > best_dot_ccls_dir.length()) {
best_dot_ccls_root = root;
best_dot_ccls_folder = &folder;
best_dot_ccls_dir = dir;
best_dot_ccls_args = &args;
}
if (!match) {
auto it = folder.path2entry_index.find(path);
if (it != folder.path2entry_index.end()) {
Project::Entry &entry = folder.entries[it->second];
// best->compdb_size is >0 for an explicit compile_commands.json
// entry, 0 for an implicit entry.
exact_match = entry.compdb_size > 0 && entry.filename == path;
if ((match = exact_match || can_redirect) && entry.compdb_size) {
best_compdb_folder = &folder;
best = &entry;
}
}
for (auto &[root, folder] : root2folder) {
// The entry may have different filename but it doesn't matter when building
// CompilerInvocation. The main filename is specified separately.
auto it = folder.path2entry_index.find(path);
if (it != folder.path2entry_index.end()) {
Project::Entry &entry = folder.entries[it->second];
if (can_redirect || entry.filename == path)
return entry;
if (entry.compdb_size) {
best_folder = &folder;
best = &entry;
}
break;
}
bool append = false;
if (best_dot_ccls_args && !(append = appendToCDB(*best_dot_ccls_args)) && !exact_match) {
// If the first line is not %compile_commands.json, override the compdb
// match if it is not an exact match.
ret.root = ret.directory = best_dot_ccls_root;
ret.filename = path;
if (best_dot_ccls_args->empty()) {
ret.args = getFallback(path);
} else {
ret.args = *best_dot_ccls_args;
ret.args.push_back(intern(path));
}
} else {
// If the first line is %compile_commands.json, find the matching compdb
// entry and append .ccls args.
if (must_exist && !match && !(best_dot_ccls_args && !append))
return ret;
if (!best) {
// Infer args from a similar path.
int best_score = INT_MIN;
auto [lang, header] = lookupExtension(path);
for (auto &[root, folder] : root2folder)
if (StringRef(path).startswith(root))
for (const Entry &e : folder.entries)
if (e.compdb_size) {
int score = computeGuessScore(path, e.filename);
// Decrease score if .c is matched against .hh
auto [lang1, header1] = lookupExtension(e.filename);
if (lang != lang1 && !(lang == LanguageId::C && header))
score -= 30;
if (score > best_score) {
best_score = score;
best_compdb_folder = &folder;
best = &e;
}
}
ret.is_inferred = true;
}
if (!best) {
ret.root = ret.directory = g_config->fallbackFolder;
ret.args = getFallback(path);
} else {
// The entry may have different filename but it doesn't matter when
// building CompilerInvocation. The main filename is specified
// separately.
ret.root = best->root;
ret.directory = best->directory;
ret.args = best->args;
if (best->compdb_size) // delete trailing .ccls options if exist
ret.args.resize(best->compdb_size);
else
best_dot_ccls_args = nullptr;
}
ret.filename = path;
}
if (best_dot_ccls_args && append && best_dot_ccls_args->size())
ret.args.insert(ret.args.end(), best_dot_ccls_args->begin() + 1, best_dot_ccls_args->end());
if (best_compdb_folder)
ProjectProcessor(*best_compdb_folder).process(ret);
else if (best_dot_ccls_folder)
ProjectProcessor(*best_dot_ccls_folder).process(ret);
for (const std::string &arg : g_config->clang.extraArgs)
ret.args.push_back(intern(arg));
ret.args.push_back(intern("-working-directory=" + ret.directory));
bool exists = false;
std::string dir;
const std::vector<const char *> *extra = nullptr;
Project::Entry ret;
for (auto &[root, folder] : root2folder)
if (StringRef(path).startswith(root))
for (auto &[dir1, args] : folder.dot_ccls)
if (StringRef(path).startswith(dir1)) {
dir = dir1;
extra = &args;
if (AppendToCDB(args))
goto out;
exists = true;
ret.root = ret.directory = root;
ret.filename = path;
if (args.empty()) {
ret.args = GetFallback(path);
} else {
ret.args = args;
ret.args.push_back(Intern(path));
}
ProjectProcessor(folder).Process(ret);
for (const std::string &arg : g_config->clang.extraArgs)
ret.args.push_back(Intern(arg));
ret.args.push_back(Intern("-working-directory=" + ret.directory));
return ret;
}
out:
if (must_exist && !exists)
return ret;
if (!best) {
int best_score = INT_MIN;
for (auto &[root, folder] : root2folder) {
if (dir.size() && !StringRef(path).startswith(dir))
continue;
for (const Entry &e : folder.entries)
if (e.compdb_size) {
int score = ComputeGuessScore(path, e.filename);
if (score > best_score) {
best_score = score;
best = &e;
best_folder = &folder;
}
}
}
}
ret.is_inferred = true;
ret.filename = path;
if (!best) {
if (ret.root.empty())
ret.root = g_config->fallbackFolder;
ret.directory = ret.root;
ret.args = GetFallback(path);
} else {
ret.root = best->root;
ret.directory = best->directory;
ret.args = best->args;
ret.args.resize(best->compdb_size);
if (extra && extra->size())
ret.args.insert(ret.args.end(), extra->begin() + 1, extra->end());
ProjectProcessor(*best_folder).Process(ret);
for (const std::string &arg : g_config->clang.extraArgs)
ret.args.push_back(Intern(arg));
ret.args.push_back(Intern("-working-directory=" + ret.directory));
}
return ret;
}
void Project::index(WorkingFiles *wfiles, const RequestId &id) {
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::vector<const char *> args, extra_args;
for (const std::string &arg : g_config->clang.extraArgs)
extra_args.push_back(intern(arg));
GroupMatch match(gi.whitelist, gi.blacklist),
match_i(gi.initialWhitelist, gi.initialBlacklist);
{
std::lock_guard lock(mtx);
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;
args = entry.args;
args.insert(args.end(), extra_args.begin(), extra_args.end());
args.push_back(intern("-working-directory=" + entry.directory));
pipeline::index(entry.filename, args, interactive ? IndexMode::Normal : IndexMode::Background, false, id);
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::Background,
false, id);
} else {
LOG_V(1) << "[" << i << "/" << folder.entries.size() << "]: " << reason << "; skip " << entry.filename;
LOG_V(1) << "[" << i << "/" << folder.entries.size() << "]: " << reason
<< "; skip " << entry.filename;
}
i++;
}
@ -522,26 +590,22 @@ void Project::index(WorkingFiles *wfiles, const RequestId &id) {
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::Background, false);
pipeline::Index("", {}, IndexMode::Background, false);
}
void Project::indexRelated(const std::string &path) {
void Project::IndexRelated(const std::string &path) {
auto &gi = g_config->index;
GroupMatch match(gi.whitelist, gi.blacklist);
StringRef stem = sys::path::stem(path);
std::vector<const char *> args, extra_args;
for (const std::string &arg : g_config->clang.extraArgs)
extra_args.push_back(intern(arg));
std::string stem = sys::path::stem(path);
std::lock_guard lock(mtx);
for (auto &[root, folder] : root2folder)
if (StringRef(path).startswith(root)) {
for (const Project::Entry &entry : folder.entries) {
std::string reason;
args = entry.args;
args.insert(args.end(), extra_args.begin(), extra_args.end());
args.push_back(intern("-working-directory=" + entry.directory));
if (sys::path::stem(entry.filename) == stem && entry.filename != path && match.matches(entry.filename, &reason))
pipeline::index(entry.filename, args, IndexMode::Background, true);
if (sys::path::stem(entry.filename) == stem && entry.filename != path &&
match.Matches(entry.filename, &reason))
pipeline::Index(entry.filename, entry.args, IndexMode::Background,
true);
}
break;
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -28,7 +40,6 @@ struct Project {
// 0 unless coming from a compile_commands.json entry.
int compdb_size = 0;
int id = -1;
int prio = 0;
};
struct Folder {
@ -52,19 +63,20 @@ struct Project {
// 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);
void loadDirectory(const std::string &root, Folder &folder);
void Load(const std::string &root);
void LoadDirectory(const std::string &root, Folder &folder);
// 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_redirect, bool must_exist);
Entry FindEntry(const std::string &path, bool can_redirect, bool must_exist);
// 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<const char *> &args, const std::string &path);
void SetArgsForFile(const std::vector<const char *> &args,
const std::string &path);
void index(WorkingFiles *wfiles, const RequestId &id);
void indexRelated(const std::string &path);
void Index(WorkingFiles *wfiles, RequestId id);
void IndexRelated(const std::string &path);
};
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "query.hh"
@ -9,39 +21,41 @@
#include <rapidjson/document.h>
#include <llvm/ADT/STLExtras.h>
#include <assert.h>
#include <functional>
#include <limits.h>
#include <optional>
#include <stdint.h>
#include <limits.h>
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace ccls {
namespace {
void assignFileId(const Lid2file_id &lid2file_id, int file_id, Use &use) {
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 <typename T> void addRange(std::vector<T> &into, const std::vector<T> &from) {
template <typename T>
void AddRange(std::vector<T> &into, const std::vector<T> &from) {
into.insert(into.end(), from.begin(), from.end());
}
template <typename T> void removeRange(std::vector<T> &from, const std::vector<T> &to_remove) {
template <typename T>
void RemoveRange(std::vector<T> &from, const std::vector<T> &to_remove) {
if (to_remove.size()) {
std::unordered_set<T> 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());
from.erase(
std::remove_if(from.begin(), from.end(),
[&](const T &t) { return to_remove_set.count(t) > 0; }),
from.end());
}
}
QueryFile::DefUpdate buildFileDefUpdate(IndexFile &&indexed) {
QueryFile::DefUpdate BuildFileDefUpdate(IndexFile &&indexed) {
QueryFile::Def def;
def.path = std::move(indexed.path);
def.args = std::move(indexed.args);
@ -55,7 +69,8 @@ QueryFile::DefUpdate buildFileDefUpdate(IndexFile &&indexed) {
}
// Returns true if an element with the same file is found.
template <typename Q> bool tryReplaceDef(llvm::SmallVectorImpl<Q> &def_list, Q &&def) {
template <typename Q>
bool TryReplaceDef(llvm::SmallVectorImpl<Q> &def_list, Q &&def) {
for (auto &def1 : def_list)
if (def1.file_id == def.file_id) {
def1 = std::move(def);
@ -66,21 +81,21 @@ template <typename Q> bool tryReplaceDef(llvm::SmallVectorImpl<Q> &def_list, Q &
} // namespace
template <typename T> Vec<T> convert(const std::vector<T> &o) {
template <typename T> Vec<T> Convert(const std::vector<T> &o) {
Vec<T> r{std::make_unique<T[]>(o.size()), (int)o.size()};
std::copy(o.begin(), o.end(), r.begin());
return r;
}
QueryFunc::Def convert(const IndexFunc::Def &o) {
QueryFunc::Def Convert(const IndexFunc::Def &o) {
QueryFunc::Def r;
r.detailed_name = o.detailed_name;
r.hover = o.hover;
r.comments = o.comments;
r.spell = o.spell;
r.bases = convert(o.bases);
r.vars = convert(o.vars);
r.callees = convert(o.callees);
r.bases = Convert(o.bases);
r.vars = Convert(o.vars);
r.callees = Convert(o.callees);
// no file_id
r.qual_name_offset = o.qual_name_offset;
r.short_name_offset = o.short_name_offset;
@ -91,16 +106,16 @@ QueryFunc::Def convert(const IndexFunc::Def &o) {
return r;
}
QueryType::Def convert(const IndexType::Def &o) {
QueryType::Def Convert(const IndexType::Def &o) {
QueryType::Def r;
r.detailed_name = o.detailed_name;
r.hover = o.hover;
r.comments = o.comments;
r.spell = o.spell;
r.bases = convert(o.bases);
r.funcs = convert(o.funcs);
r.types = convert(o.types);
r.vars = convert(o.vars);
r.bases = Convert(o.bases);
r.funcs = Convert(o.funcs);
r.types = Convert(o.types);
r.vars = Convert(o.vars);
r.alias_of = o.alias_of;
// no file_id
r.qual_name_offset = o.qual_name_offset;
@ -111,7 +126,7 @@ QueryType::Def convert(const IndexType::Def &o) {
return r;
}
IndexUpdate IndexUpdate::createDelta(IndexFile *previous, IndexFile *current) {
IndexUpdate IndexUpdate::CreateDelta(IndexFile *previous, IndexFile *current) {
IndexUpdate r;
static IndexFile empty(current->path, "<empty>", false);
if (previous)
@ -120,11 +135,11 @@ IndexUpdate IndexUpdate::createDelta(IndexFile *previous, IndexFile *current) {
previous = &empty;
r.lid2path = std::move(current->lid2path);
r.funcs_hint = int(current->usr2func.size() - previous->usr2func.size());
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, convert(func.def));
r.funcs_removed.emplace_back(func.usr, Convert(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);
@ -132,17 +147,17 @@ IndexUpdate IndexUpdate::createDelta(IndexFile *previous, IndexFile *current) {
for (auto &it : current->usr2func) {
auto &func = it.second;
if (func.def.detailed_name[0])
r.funcs_def_update.emplace_back(it.first, convert(func.def));
r.funcs_def_update.emplace_back(it.first, Convert(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.types_hint = int(current->usr2type.size() - previous->usr2type.size());
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, convert(type.def));
r.types_removed.emplace_back(type.usr, Convert(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);
@ -151,14 +166,14 @@ IndexUpdate IndexUpdate::createDelta(IndexFile *previous, IndexFile *current) {
for (auto &it : current->usr2type) {
auto &type = it.second;
if (type.def.detailed_name[0])
r.types_def_update.emplace_back(it.first, convert(type.def));
r.types_def_update.emplace_back(it.first, Convert(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);
};
r.vars_hint = int(current->usr2var.size() - previous->usr2var.size());
r.vars_hint = current->usr2var.size() - previous->usr2var.size();
for (auto &it : previous->usr2var) {
auto &var = it.second;
if (var.def.detailed_name[0])
@ -174,7 +189,7 @@ IndexUpdate IndexUpdate::createDelta(IndexFile *previous, IndexFile *current) {
r.vars_uses[var.usr].second = std::move(var.uses);
}
r.files_def_update = buildFileDefUpdate(std::move(*current));
r.files_def_update = BuildFileDefUpdate(std::move(*current));
return r;
}
@ -189,15 +204,19 @@ void DB::clear() {
vars.clear();
}
template <typename Def> void DB::removeUsrs(Kind kind, int file_id, const std::vector<std::pair<Usr, Def>> &to_remove) {
template <typename Def>
void DB::RemoveUsrs(Kind kind, int file_id,
const std::vector<std::pair<Usr, Def>> &to_remove) {
switch (kind) {
case Kind::Func: {
for (auto &[usr, _] : to_remove) {
// FIXME
if (!hasFunc(usr))
if (!HasFunc(usr))
continue;
QueryFunc &func = getFunc(usr);
auto it = llvm::find_if(func.def, [=](const QueryFunc::Def &def) { return def.file_id == file_id; });
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);
}
@ -206,10 +225,12 @@ template <typename Def> void DB::removeUsrs(Kind kind, int file_id, const std::v
case Kind::Type: {
for (auto &[usr, _] : to_remove) {
// FIXME
if (!hasType(usr))
if (!HasType(usr))
continue;
QueryType &type = getType(usr);
auto it = llvm::find_if(type.def, [=](const QueryType::Def &def) { return def.file_id == file_id; });
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);
}
@ -218,10 +239,12 @@ template <typename Def> void DB::removeUsrs(Kind kind, int file_id, const std::v
case Kind::Var: {
for (auto &[usr, _] : to_remove) {
// FIXME
if (!hasVar(usr))
if (!HasVar(usr))
continue;
QueryVar &var = getVar(usr);
auto it = llvm::find_if(var.def, [=](const QueryVar::Def &def) { return def.file_id == file_id; });
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);
}
@ -232,24 +255,24 @@ template <typename Def> void DB::removeUsrs(Kind kind, int file_id, const std::v
}
}
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(); \
C##s.back().usr = it.first; \
} \
auto &entity = C##s[r.first->second]; \
removeRange(entity.F, it.second.first); \
addRange(entity.F, it.second.second); \
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(); \
C##s.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<int, int> prev_lid2file_id, lid2file_id;
for (auto &[lid, path] : u->prev_lid2path)
prev_lid2file_id[lid] = getFileId(path);
prev_lid2file_id[lid] = GetFileId(path);
for (auto &[lid, path] : u->lid2path) {
int file_id = getFileId(path);
int file_id = GetFileId(path);
lid2file_id[lid] = file_id;
if (!files[file_id].def) {
files[file_id].def = QueryFile::Def();
@ -258,8 +281,10 @@ void DB::applyIndexUpdate(IndexUpdate *u) {
}
// References (Use &use) in this function are important to update file_id.
auto ref = [&](std::unordered_map<int, int> &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;
auto Ref = [&](std::unordered_map<int, int> &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;
ExtentRef sym{{use.range, usr, kind, use.role}};
int &v = files[use.file_id].symbol2refcnt[sym];
v += delta;
@ -267,8 +292,10 @@ void DB::applyIndexUpdate(IndexUpdate *u) {
if (!v)
files[use.file_id].symbol2refcnt.erase(sym);
};
auto refDecl = [&](std::unordered_map<int, int> &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;
auto RefDecl = [&](std::unordered_map<int, int> &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;
@ -277,41 +304,45 @@ void DB::applyIndexUpdate(IndexUpdate *u) {
files[dr.file_id].symbol2refcnt.erase(sym);
};
auto updateUses = [&](Usr usr, Kind kind, llvm::DenseMap<Usr, int, DenseMapInfoForUsr> &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();
entities.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);
};
auto UpdateUses =
[&](Usr usr, Kind kind,
llvm::DenseMap<Usr, int, DenseMapInfoForUsr> &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();
entities.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;
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;
@ -323,19 +354,19 @@ void DB::applyIndexUpdate(IndexUpdate *u) {
}
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) {
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);
RefDecl(prev_lid2file_id, usr, Kind::Func, dr, -1);
for (DeclRef &dr : del_add.second)
refDecl(lid2file_id, usr, Kind::Func, dr, 1);
RefDecl(lid2file_id, usr, Kind::Func, dr, 1);
}
REMOVE_ADD(func, declarations);
REMOVE_ADD(func, derived);
for (auto &[usr, p] : u->funcs_uses)
updateUses(usr, Kind::Func, func_usr, funcs, p, true);
UpdateUses(usr, Kind::Func, func_usr, funcs, p, true);
if ((t = types.size() + u->types_hint) > types.capacity()) {
t = size_t(t * grow);
@ -344,20 +375,20 @@ void DB::applyIndexUpdate(IndexUpdate *u) {
}
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) {
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);
RefDecl(prev_lid2file_id, usr, Kind::Type, dr, -1);
for (DeclRef &dr : del_add.second)
refDecl(lid2file_id, usr, Kind::Type, dr, 1);
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);
UpdateUses(usr, Kind::Type, type_usr, types, p, false);
if ((t = vars.size() + u->vars_hint) > vars.capacity()) {
t = size_t(t * grow);
@ -366,24 +397,24 @@ void DB::applyIndexUpdate(IndexUpdate *u) {
}
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) {
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);
RefDecl(prev_lid2file_id, usr, Kind::Var, dr, -1);
for (DeclRef &dr : del_add.second)
refDecl(lid2file_id, usr, Kind::Var, dr, 1);
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);
UpdateUses(usr, Kind::Var, var_usr, vars, p, false);
#undef REMOVE_ADD
}
int DB::getFileId(const std::string &path) {
auto it = name2file_id.try_emplace(lowerPathIfInsensitive(path));
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;
@ -391,74 +422,80 @@ int DB::getFileId(const std::string &path) {
return it.first->second;
}
int DB::update(QueryFile::DefUpdate &&u) {
int file_id = getFileId(u.first.path);
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<std::pair<Usr, QueryFunc::Def>> &&us) {
void DB::Update(const Lid2file_id &lid2file_id, int file_id,
std::vector<std::pair<Usr, QueryFunc::Def>> &&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}]++;
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}]++;
}
auto r = func_usr.try_emplace({u.first}, func_usr.size());
if (r.second)
auto R = func_usr.try_emplace({u.first}, func_usr.size());
if (R.second)
funcs.emplace_back();
QueryFunc &existing = funcs[r.first->second];
QueryFunc &existing = funcs[R.first->second];
existing.usr = u.first;
if (!tryReplaceDef(existing.def, std::move(def)))
if (!TryReplaceDef(existing.def, std::move(def)))
existing.def.push_back(std::move(def));
}
}
void DB::update(const Lid2file_id &lid2file_id, int file_id, std::vector<std::pair<Usr, QueryType::Def>> &&us) {
void DB::Update(const Lid2file_id &lid2file_id, int file_id,
std::vector<std::pair<Usr, QueryType::Def>> &&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}]++;
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)
auto R = type_usr.try_emplace({u.first}, type_usr.size());
if (R.second)
types.emplace_back();
QueryType &existing = types[r.first->second];
QueryType &existing = types[R.first->second];
existing.usr = u.first;
if (!tryReplaceDef(existing.def, std::move(def)))
if (!TryReplaceDef(existing.def, std::move(def)))
existing.def.push_back(std::move(def));
}
}
void DB::update(const Lid2file_id &lid2file_id, int file_id, std::vector<std::pair<Usr, QueryVar::Def>> &&us) {
void DB::Update(const Lid2file_id &lid2file_id, int file_id,
std::vector<std::pair<Usr, QueryVar::Def>> &&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}]++;
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)
auto R = var_usr.try_emplace({u.first}, var_usr.size());
if (R.second)
vars.emplace_back();
QueryVar &existing = vars[r.first->second];
QueryVar &existing = vars[R.first->second];
existing.usr = u.first;
if (!tryReplaceDef(existing.def, std::move(def)))
if (!TryReplaceDef(existing.def, std::move(def)))
existing.def.push_back(std::move(def));
}
}
std::string_view DB::getSymbolName(SymbolIdx sym, bool qualified) {
std::string_view DB::GetSymbolName(SymbolIdx sym, bool qualified) {
Usr usr = sym.usr;
switch (sym.kind) {
default:
@ -468,22 +505,22 @@ std::string_view DB::getSymbolName(SymbolIdx sym, bool qualified) {
return files[usr].def->path;
break;
case Kind::Func:
if (const auto *def = getFunc(usr).anyDef())
return def->name(qualified);
if (const auto *def = Func(usr).AnyDef())
return def->Name(qualified);
break;
case Kind::Type:
if (const auto *def = getType(usr).anyDef())
return def->name(qualified);
if (const auto *def = Type(usr).AnyDef())
return def->Name(qualified);
break;
case Kind::Var:
if (const auto *def = getVar(usr).anyDef())
return def->name(qualified);
if (const auto *def = Var(usr).AnyDef())
return def->Name(qualified);
break;
}
return "";
}
std::vector<uint8_t> DB::getFileSet(const std::vector<std::string> &folders) {
std::vector<uint8_t> DB::GetFileSet(const std::vector<std::string> &folders) {
if (folders.empty())
return std::vector<uint8_t>(files.size(), 1);
std::vector<uint8_t> file_set(files.size());
@ -503,15 +540,16 @@ std::vector<uint8_t> DB::getFileSet(const std::vector<std::string> &folders) {
namespace {
// Computes roughly how long |range| is.
int computeRangeSize(const Range &range) {
int ComputeRangeSize(const Range &range) {
if (range.start.line != range.end.line)
return INT_MAX;
return range.end.column - range.start.column;
}
template <typename Q, typename C>
std::vector<Use> getDeclarations(llvm::DenseMap<Usr, int, DenseMapInfoForUsr> &entity_usr,
llvm::SmallVectorImpl<Q> &entities, const C &usrs) {
std::vector<Use>
GetDeclarations(llvm::DenseMap<Usr, int, DenseMapInfoForUsr> &entity_usr,
llvm::SmallVectorImpl<Q> &entities, const C &usrs) {
std::vector<Use> ret;
ret.reserve(usrs.size());
for (Usr usr : usrs) {
@ -528,28 +566,29 @@ std::vector<Use> getDeclarations(llvm::DenseMap<Usr, int, DenseMapInfoForUsr> &e
}
return ret;
}
} // namespace
}
Maybe<DeclRef> getDefinitionSpell(DB *db, SymbolIdx sym) {
Maybe<DeclRef> GetDefinitionSpell(DB *db, SymbolIdx sym) {
Maybe<DeclRef> ret;
eachEntityDef(db, sym, [&](const auto &def) { return !(ret = def.spell); });
EachEntityDef(db, sym, [&](const auto &def) { return !(ret = def.spell); });
return ret;
}
std::vector<Use> getFuncDeclarations(DB *db, const std::vector<Usr> &usrs) {
return getDeclarations(db->func_usr, db->funcs, usrs);
std::vector<Use> GetFuncDeclarations(DB *db, const std::vector<Usr> &usrs) {
return GetDeclarations(db->func_usr, db->funcs, usrs);
}
std::vector<Use> getFuncDeclarations(DB *db, const Vec<Usr> &usrs) {
return getDeclarations(db->func_usr, db->funcs, usrs);
std::vector<Use> GetFuncDeclarations(DB *db, const Vec<Usr> &usrs) {
return GetDeclarations(db->func_usr, db->funcs, usrs);
}
std::vector<Use> getTypeDeclarations(DB *db, const std::vector<Usr> &usrs) {
return getDeclarations(db->type_usr, db->types, usrs);
std::vector<Use> GetTypeDeclarations(DB *db, const std::vector<Usr> &usrs) {
return GetDeclarations(db->type_usr, db->types, usrs);
}
std::vector<DeclRef> getVarDeclarations(DB *db, const std::vector<Usr> &usrs, unsigned kind) {
std::vector<DeclRef> GetVarDeclarations(DB *db, const std::vector<Usr> &usrs,
unsigned kind) {
std::vector<DeclRef> ret;
ret.reserve(usrs.size());
for (Usr usr : usrs) {
QueryVar &var = db->getVar(usr);
QueryVar &var = db->Var(usr);
bool has_def = false;
for (auto &def : var.def)
if (def.spell) {
@ -574,22 +613,22 @@ std::vector<DeclRef> getVarDeclarations(DB *db, const std::vector<Usr> &usrs, un
return ret;
}
std::vector<DeclRef> &getNonDefDeclarations(DB *db, SymbolIdx sym) {
std::vector<DeclRef> &GetNonDefDeclarations(DB *db, SymbolIdx sym) {
static std::vector<DeclRef> empty;
switch (sym.kind) {
case Kind::Func:
return db->getFunc(sym).declarations;
return db->GetFunc(sym).declarations;
case Kind::Type:
return db->getType(sym).declarations;
return db->GetType(sym).declarations;
case Kind::Var:
return db->getVar(sym).declarations;
return db->GetVar(sym).declarations;
default:
break;
}
return empty;
}
std::vector<Use> getUsesForAllBases(DB *db, QueryFunc &root) {
std::vector<Use> GetUsesForAllBases(DB *db, QueryFunc &root) {
std::vector<Use> ret;
std::vector<QueryFunc *> stack{&root};
std::unordered_set<Usr> seen;
@ -597,8 +636,8 @@ std::vector<Use> getUsesForAllBases(DB *db, QueryFunc &root) {
while (!stack.empty()) {
QueryFunc &func = *stack.back();
stack.pop_back();
if (auto *def = func.anyDef()) {
eachDefinedFunc(db, def->bases, [&](QueryFunc &func1) {
if (auto *def = func.AnyDef()) {
EachDefinedFunc(db, def->bases, [&](QueryFunc &func1) {
if (!seen.count(func1.usr)) {
seen.insert(func1.usr);
stack.push_back(&func1);
@ -611,7 +650,7 @@ std::vector<Use> getUsesForAllBases(DB *db, QueryFunc &root) {
return ret;
}
std::vector<Use> getUsesForAllDerived(DB *db, QueryFunc &root) {
std::vector<Use> GetUsesForAllDerived(DB *db, QueryFunc &root) {
std::vector<Use> ret;
std::vector<QueryFunc *> stack{&root};
std::unordered_set<Usr> seen;
@ -619,7 +658,7 @@ std::vector<Use> getUsesForAllDerived(DB *db, QueryFunc &root) {
while (!stack.empty()) {
QueryFunc &func = *stack.back();
stack.pop_back();
eachDefinedFunc(db, func.derived, [&](QueryFunc &func1) {
EachDefinedFunc(db, func.derived, [&](QueryFunc &func1) {
if (!seen.count(func1.usr)) {
seen.insert(func1.usr);
stack.push_back(&func1);
@ -631,14 +670,17 @@ std::vector<Use> getUsesForAllDerived(DB *db, QueryFunc &root) {
return ret;
}
std::optional<lsRange> getLsRange(WorkingFile *wfile, const Range &location) {
std::optional<lsRange> 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}};
int start_column = location.start.column, end_column = location.end.column;
std::optional<int> start = wfile->getBufferPosFromIndexPos(location.start.line, &start_column, false);
std::optional<int> end = wfile->getBufferPosFromIndexPos(location.end.line, &end_column, true);
std::optional<int> start = wfile->GetBufferPosFromIndexPos(
location.start.line, &start_column, false);
std::optional<int> end = wfile->GetBufferPosFromIndexPos(
location.end.line, &end_column, true);
if (!start || !end)
return std::nullopt;
@ -656,60 +698,61 @@ std::optional<lsRange> getLsRange(WorkingFile *wfile, const Range &location) {
return lsRange{Position{*start, start_column}, Position{*end, end_column}};
}
DocumentUri getLsDocumentUri(DB *db, int file_id, std::string *path) {
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);
return DocumentUri::FromPath(*path);
} else {
*path = "";
return DocumentUri::fromPath("");
return DocumentUri::FromPath("");
}
}
DocumentUri getLsDocumentUri(DB *db, int file_id) {
DocumentUri GetLsDocumentUri(DB *db, int file_id) {
QueryFile &file = db->files[file_id];
if (file.def) {
return DocumentUri::fromPath(file.def->path);
return DocumentUri::FromPath(file.def->path);
} else {
return DocumentUri::fromPath("");
return DocumentUri::FromPath("");
}
}
std::optional<Location> getLsLocation(DB *db, WorkingFiles *wfiles, Use use) {
std::optional<Location> GetLsLocation(DB *db, WorkingFiles *wfiles, Use use) {
std::string path;
DocumentUri uri = getLsDocumentUri(db, use.file_id, &path);
std::optional<lsRange> range = getLsRange(wfiles->getFile(path), use.range);
DocumentUri uri = GetLsDocumentUri(db, use.file_id, &path);
std::optional<lsRange> range = GetLsRange(wfiles->GetFile(path), use.range);
if (!range)
return std::nullopt;
return Location{uri, *range};
}
std::optional<Location> getLsLocation(DB *db, WorkingFiles *wfiles, SymbolRef sym, int file_id) {
return getLsLocation(db, wfiles, Use{{sym.range, sym.role}, file_id});
std::optional<Location> GetLsLocation(DB *db, WorkingFiles *wfiles,
SymbolRef sym, int file_id) {
return GetLsLocation(db, wfiles, Use{{sym.range, sym.role}, file_id});
}
LocationLink getLocationLink(DB *db, WorkingFiles *wfiles, DeclRef dr) {
LocationLink GetLocationLink(DB *db, WorkingFiles *wfiles, DeclRef dr) {
std::string path;
DocumentUri uri = getLsDocumentUri(db, dr.file_id, &path);
if (auto range = getLsRange(wfiles->getFile(path), dr.range))
if (auto extent = getLsRange(wfiles->getFile(path), dr.extent)) {
DocumentUri uri = GetLsDocumentUri(db, dr.file_id, &path);
if (auto range = GetLsRange(wfiles->GetFile(path), dr.range))
if (auto extent = GetLsRange(wfiles->GetFile(path), dr.extent)) {
LocationLink ret;
ret.targetUri = uri.raw_uri;
ret.targetSelectionRange = *range;
ret.targetRange = extent->includes(*range) ? *extent : *range;
ret.targetRange = extent->Includes(*range) ? *extent : *range;
return ret;
}
return {};
}
SymbolKind getSymbolKind(DB *db, SymbolIdx sym) {
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) {
WithEntity(db, sym, [&](const auto &entity) {
for (auto &def : entity.def) {
ret = def.kind;
break;
@ -719,12 +762,13 @@ SymbolKind getSymbolKind(DB *db, SymbolIdx sym) {
return ret;
}
std::optional<SymbolInformation> getSymbolInfo(DB *db, SymbolIdx sym, bool detailed) {
std::optional<SymbolInformation> GetSymbolInfo(DB *db, SymbolIdx sym,
bool detailed) {
switch (sym.kind) {
case Kind::Invalid:
break;
case Kind::File: {
QueryFile &file = db->getFile(sym);
QueryFile &file = db->GetFile(sym);
if (!file.def)
break;
@ -735,11 +779,11 @@ std::optional<SymbolInformation> getSymbolInfo(DB *db, SymbolIdx sym, bool detai
}
default: {
SymbolInformation info;
eachEntityDef(db, sym, [&](const auto &def) {
EachEntityDef(db, sym, [&](const auto &def) {
if (detailed)
info.name = def.detailed_name;
else
info.name = def.name(true);
info.name = def.Name(true);
info.kind = def.kind;
return false;
});
@ -750,11 +794,14 @@ std::optional<SymbolInformation> getSymbolInfo(DB *db, SymbolIdx sym, bool detai
return std::nullopt;
}
std::vector<SymbolRef> findSymbolsAtLocation(WorkingFile *wfile, QueryFile *file, Position &ls_pos, bool smallest) {
std::vector<SymbolRef> FindSymbolsAtLocation(WorkingFile *wfile,
QueryFile *file, Position &ls_pos,
bool smallest) {
std::vector<SymbolRef> 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)) {
if (auto line = wfile->GetIndexPosFromBufferPos(
ls_pos.line, &ls_pos.character, false)) {
ls_pos.line = *line;
} else {
ls_pos.line = -1;
@ -763,7 +810,7 @@ std::vector<SymbolRef> findSymbolsAtLocation(WorkingFile *wfile, QueryFile *file
}
for (auto [sym, refcnt] : file->symbol2refcnt)
if (refcnt > 0 && sym.range.contains(ls_pos.line, ls_pos.character))
if (refcnt > 0 && sym.range.Contains(ls_pos.line, ls_pos.character))
symbols.push_back(sym);
// Order shorter ranges first, since they are more detailed/precise. This is
@ -777,21 +824,23 @@ std::vector<SymbolRef> findSymbolsAtLocation(WorkingFile *wfile, QueryFile *file
//
// 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<int>(a.kind) - static_cast<int>(b.kind);
if (t)
return t > 0;
return a.usr < b.usr;
});
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<int>(a.kind) - static_cast<int>(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++)

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -9,13 +21,16 @@
#include <llvm/ADT/DenseMap.h>
#include <llvm/ADT/SmallVector.h>
#include <llvm/ADT/StringMap.h>
namespace llvm {
template <> struct DenseMapInfo<ccls::ExtentRef> {
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<ccls::ExtentRef>()(sym); }
static inline ccls::ExtentRef getTombstoneKey() {
return {{ccls::Range(), ccls::Usr(-1)}};
}
static unsigned getHashValue(ccls::ExtentRef sym) {
return std::hash<ccls::ExtentRef>()(sym);
}
static bool isEqual(ccls::ExtentRef l, ccls::ExtentRef r) { return l == r; }
};
} // namespace llvm
@ -44,7 +59,7 @@ struct QueryFile {
template <typename Q, typename QDef> struct QueryEntity {
using Def = QDef;
Def *anyDef() {
Def *AnyDef() {
Def *ret = nullptr;
for (auto &i : static_cast<Q *>(this)->def) {
ret = &i;
@ -53,10 +68,14 @@ template <typename Q, typename QDef> struct QueryEntity {
}
return ret;
}
const Def *anyDef() const { return const_cast<QueryEntity *>(this)->anyDef(); }
const Def *AnyDef() const {
return const_cast<QueryEntity *>(this)->AnyDef();
}
};
template <typename T> using Update = std::unordered_map<Usr, std::pair<std::vector<T>, std::vector<T>>>;
template <typename T>
using Update =
std::unordered_map<Usr, std::pair<std::vector<T>, std::vector<T>>>;
struct QueryFunc : QueryEntity<QueryFunc, FuncDef<Vec>> {
Usr usr;
@ -85,7 +104,7 @@ struct QueryVar : QueryEntity<QueryVar, VarDef> {
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);
static IndexUpdate CreateDelta(IndexFile *previous, IndexFile *current);
int file_id;
@ -136,7 +155,7 @@ using Lid2file_id = std::unordered_map<int, int>;
// The query database is heavily optimized for fast queries. It is stored
// in-memory.
struct DB {
llvm::SmallVector<QueryFile, 0> files;
std::vector<QueryFile> files;
llvm::StringMap<int> name2file_id;
llvm::DenseMap<Usr, int, DenseMapInfoForUsr> func_usr, type_usr, var_usr;
llvm::SmallVector<QueryFunc, 0> funcs;
@ -145,86 +164,97 @@ struct DB {
void clear();
template <typename Def> void removeUsrs(Kind kind, int file_id, const std::vector<std::pair<Usr, Def>> &to_remove);
template <typename Def>
void RemoveUsrs(Kind kind, int file_id,
const std::vector<std::pair<Usr, Def>> &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<std::pair<Usr, QueryType::Def>> &&us);
void update(const Lid2file_id &, int file_id, std::vector<std::pair<Usr, QueryFunc::Def>> &&us);
void update(const Lid2file_id &, int file_id, std::vector<std::pair<Usr, QueryVar::Def>> &&us);
std::string_view getSymbolName(SymbolIdx sym, bool qualified);
std::vector<uint8_t> getFileSet(const std::vector<std::string> &folders);
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<std::pair<Usr, QueryType::Def>> &&us);
void Update(const Lid2file_id &, int file_id,
std::vector<std::pair<Usr, QueryFunc::Def>> &&us);
void Update(const Lid2file_id &, int file_id,
std::vector<std::pair<Usr, QueryVar::Def>> &&us);
std::string_view GetSymbolName(SymbolIdx sym, bool qualified);
std::vector<uint8_t> GetFileSet(const std::vector<std::string> &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); }
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 &getFunc(Usr usr) { return funcs[func_usr[usr]]; }
QueryType &getType(Usr usr) { return types[type_usr[usr]]; }
QueryVar &getVar(Usr usr) { return vars[var_usr[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 getFunc(ref.usr); }
QueryType &getType(SymbolIdx ref) { return getType(ref.usr); }
QueryVar &getVar(SymbolIdx ref) { return getVar(ref.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<DeclRef> getDefinitionSpell(DB *db, SymbolIdx sym);
Maybe<DeclRef> GetDefinitionSpell(DB *db, SymbolIdx sym);
// Get defining declaration (if exists) or an arbitrary declaration (otherwise)
// for each id.
std::vector<Use> getFuncDeclarations(DB *, const std::vector<Usr> &);
std::vector<Use> getFuncDeclarations(DB *, const Vec<Usr> &);
std::vector<Use> getTypeDeclarations(DB *, const std::vector<Usr> &);
std::vector<DeclRef> getVarDeclarations(DB *, const std::vector<Usr> &, unsigned);
std::vector<Use> GetFuncDeclarations(DB *, const std::vector<Usr> &);
std::vector<Use> GetFuncDeclarations(DB *, const Vec<Usr> &);
std::vector<Use> GetTypeDeclarations(DB *, const std::vector<Usr> &);
std::vector<DeclRef> GetVarDeclarations(DB *, const std::vector<Usr> &, unsigned);
// Get non-defining declarations.
std::vector<DeclRef> &getNonDefDeclarations(DB *db, SymbolIdx sym);
std::vector<DeclRef> &GetNonDefDeclarations(DB *db, SymbolIdx sym);
std::vector<Use> getUsesForAllBases(DB *db, QueryFunc &root);
std::vector<Use> getUsesForAllDerived(DB *db, QueryFunc &root);
std::optional<lsRange> 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::vector<Use> GetUsesForAllBases(DB *db, QueryFunc &root);
std::vector<Use> GetUsesForAllDerived(DB *db, QueryFunc &root);
std::optional<lsRange> 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<Location> getLsLocation(DB *db, WorkingFiles *wfiles, Use use);
std::optional<Location> getLsLocation(DB *db, WorkingFiles *wfiles, SymbolRef sym, int file_id);
LocationLink getLocationLink(DB *db, WorkingFiles *wfiles, DeclRef dr);
std::optional<Location> GetLsLocation(DB *db, WorkingFiles *wfiles, Use use);
std::optional<Location> GetLsLocation(DB *db, WorkingFiles *wfiles,
SymbolRef sym, int file_id);
LocationLink GetLocationLink(DB *db, WorkingFiles *wfiles, DeclRef dr);
// Returns a symbol. The symbol will *NOT* have a location assigned.
std::optional<SymbolInformation> getSymbolInfo(DB *db, SymbolIdx sym, bool detailed);
std::optional<SymbolInformation> GetSymbolInfo(DB *db, SymbolIdx sym,
bool detailed);
std::vector<SymbolRef> findSymbolsAtLocation(WorkingFile *working_file, QueryFile *file, Position &ls_pos,
std::vector<SymbolRef> FindSymbolsAtLocation(WorkingFile *working_file,
QueryFile *file,
Position &ls_pos,
bool smallest = false);
template <typename Fn> void withEntity(DB *db, SymbolIdx sym, Fn &&fn) {
template <typename Fn> 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));
fn(db->GetFunc(sym));
break;
case Kind::Type:
fn(db->getType(sym));
fn(db->GetType(sym));
break;
case Kind::Var:
fn(db->getVar(sym));
fn(db->GetVar(sym));
break;
}
}
template <typename Fn> void eachEntityDef(DB *db, SymbolIdx sym, Fn &&fn) {
withEntity(db, sym, [&](const auto &entity) {
template <typename Fn> void EachEntityDef(DB *db, SymbolIdx sym, Fn &&fn) {
WithEntity(db, sym, [&](const auto &entity) {
for (auto &def : entity.def)
if (!fn(def))
break;
});
}
template <typename Fn> void eachOccurrence(DB *db, SymbolIdx sym, bool include_decl, Fn &&fn) {
withEntity(db, sym, [&](const auto &entity) {
template <typename Fn>
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) {
@ -237,11 +267,12 @@ template <typename Fn> void eachOccurrence(DB *db, SymbolIdx sym, bool include_d
});
}
SymbolKind getSymbolKind(DB *db, SymbolIdx sym);
SymbolKind GetSymbolKind(DB *db, SymbolIdx sym);
template <typename C, typename Fn> void eachDefinedFunc(DB *db, const C &usrs, Fn &&fn) {
template <typename C, typename Fn>
void EachDefinedFunc(DB *db, const C &usrs, Fn &&fn) {
for (Usr usr : usrs) {
auto &obj = db->getFunc(usr);
auto &obj = db->Func(usr);
if (!obj.def.empty())
fn(obj);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -37,10 +49,12 @@ struct Diag : DiagBase {
std::vector<TextEdit> edits;
};
TextEdit toTextEdit(const clang::SourceManager &SM, const clang::LangOptions &L, const clang::FixItHint &FixIt);
TextEdit ToTextEdit(const clang::SourceManager &SM,
const clang::LangOptions &L,
const clang::FixItHint &FixIt);
template <typename K, typename V> struct LruCache {
std::shared_ptr<V> get(const K &key) {
std::shared_ptr<V> Get(const K &key) {
for (auto it = items.begin(); it != items.end(); ++it)
if (it->first == key) {
auto x = std::move(*it);
@ -50,7 +64,7 @@ template <typename K, typename V> struct LruCache {
}
return nullptr;
}
std::shared_ptr<V> take(const K &key) {
std::shared_ptr<V> Take(const K &key) {
for (auto it = items.begin(); it != items.end(); ++it)
if (it->first == key) {
auto x = std::move(it->second);
@ -59,13 +73,13 @@ template <typename K, typename V> struct LruCache {
}
return nullptr;
}
void insert(const K &key, std::shared_ptr<V> value) {
void Insert(const K &key, std::shared_ptr<V> 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; }
void Clear() { items.clear(); }
void SetCapacity(int cap) { capacity = cap; }
private:
std::vector<std::pair<K, std::shared_ptr<V>>> items;
@ -81,33 +95,38 @@ struct Session {
bool inferred = false;
// TODO share
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs = llvm::vfs::getRealFileSystem();
std::shared_ptr<clang::PCHContainerOperations> pch;
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS =
llvm::vfs::getRealFileSystem();
std::shared_ptr<clang::PCHContainerOperations> PCH;
Session(const Project::Entry &file, WorkingFiles *wfiles, std::shared_ptr<clang::PCHContainerOperations> pch)
: file(file), wfiles(wfiles), pch(pch) {}
Session(const Project::Entry &file, WorkingFiles *wfiles,
std::shared_ptr<clang::PCHContainerOperations> PCH)
: file(file), wfiles(wfiles), PCH(PCH) {}
std::shared_ptr<PreambleData> getPreamble();
std::shared_ptr<PreambleData> GetPreamble();
};
struct SemaManager {
using OnDiagnostic = std::function<void(std::string path, std::vector<Diagnostic> diagnostics)>;
using OnDiagnostic = std::function<void(
std::string path, std::vector<Diagnostic> diagnostics)>;
// If OptConsumer is nullptr, the request has been cancelled.
using OnComplete = std::function<void(clang::CodeCompleteConsumer *OptConsumer)>;
using OnComplete =
std::function<void(clang::CodeCompleteConsumer *OptConsumer)>;
using OnDropped = std::function<void(RequestId request_id)>;
struct CompTask {
CompTask(const RequestId &id, const std::string &path, const Position &position,
std::unique_ptr<clang::CodeCompleteConsumer> Consumer, clang::CodeCompleteOptions CCOpts,
const OnComplete &on_complete)
: id(id), path(path), position(position), consumer(std::move(Consumer)), cc_opts(CCOpts),
on_complete(on_complete) {}
CompTask(const RequestId &id, const std::string &path,
const Position &position,
std::unique_ptr<clang::CodeCompleteConsumer> 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<clang::CodeCompleteConsumer> consumer;
clang::CodeCompleteOptions cc_opts;
std::unique_ptr<clang::CodeCompleteConsumer> Consumer;
clang::CodeCompleteOptions CCOpts;
OnComplete on_complete;
};
struct DiagTask {
@ -121,15 +140,17 @@ struct SemaManager {
bool from_diag = false;
};
SemaManager(Project *project, WorkingFiles *wfiles, OnDiagnostic on_diagnostic, OnDropped on_dropped);
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<ccls::Session> ensureSession(const std::string &path, bool *created = nullptr);
void clear();
void quit();
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<ccls::Session> EnsureSession(const std::string &path,
bool *created = nullptr);
void Clear();
void Quit();
// Global state.
Project *project_;
@ -147,27 +168,26 @@ struct SemaManager {
ThreadedQueue<DiagTask> diag_tasks;
ThreadedQueue<PreambleTask> preamble_tasks;
std::shared_ptr<clang::PCHContainerOperations> pch;
std::shared_ptr<clang::PCHContainerOperations> 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 <typename T> struct CompleteConsumerCache {
template <typename T>
struct CompleteConsumerCache {
std::mutex mutex;
std::string path;
std::string line;
Position position;
T result;
template <typename Fn> void withLock(Fn &&fn) {
template <typename Fn> void WithLock(Fn &&fn) {
std::lock_guard lock(mutex);
fn();
}
bool isCacheValid(const std::string &path, const std::string &line, Position position) {
bool IsCacheValid(const std::string path, Position position) {
std::lock_guard lock(mutex);
return this->position == position && this->path == path &&
this->line.compare(0, position.character, line, 0, position.character) == 0;
return this->path == path && this->position == position;
}
};
} // namespace ccls

View File

@ -1,5 +1,17 @@
// Copyright 2017-2020 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "serializer.hh"
@ -13,8 +25,6 @@
#include <llvm/ADT/CachedHashString.h>
#include <llvm/ADT/DenseSet.h>
#include <llvm/ADT/STLExtras.h>
#include <llvm/Support/Allocator.h>
#include <mutex>
#include <stdexcept>
@ -25,7 +35,7 @@ bool gTestOutputMode = false;
namespace ccls {
void JsonReader::iterArray(llvm::function_ref<void()> fn) {
void JsonReader::IterArray(std::function<void()> fn) {
if (!m->IsArray())
throw std::invalid_argument("array");
// Use "0" to indicate any element for now.
@ -38,7 +48,7 @@ void JsonReader::iterArray(llvm::function_ref<void()> fn) {
}
path_.pop_back();
}
void JsonReader::member(const char *name, llvm::function_ref<void()> fn) {
void JsonReader::Member(const char *name, std::function<void()> fn) {
path_.push_back(name);
auto it = m->FindMember(name);
if (it != m->MemberEnd()) {
@ -49,9 +59,9 @@ void JsonReader::member(const char *name, llvm::function_ref<void()> fn) {
}
path_.pop_back();
}
bool JsonReader::isNull() { return m->IsNull(); }
std::string JsonReader::getString() { return m->GetString(); }
std::string JsonReader::getPath() const {
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_)
if (t[0] == '0') {
@ -65,222 +75,233 @@ std::string JsonReader::getPath() const {
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::int64(int64_t v) { m->Int64(v); }
void JsonWriter::string(const char *s) { m->String(s); }
void JsonWriter::string(const char *s, size_t len) { m->String(s, len); }
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("string"); v = intern(vis.getString()); }
void reflect(JsonReader &vis, std::string &v ) { if (!vis.m->IsString()) throw std::invalid_argument("string"); v = vis.getString(); }
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("string"); v = Intern(vis.GetString()); }
void Reflect(JsonReader &vis, std::string &v ) { if (!vis.m->IsString()) throw std::invalid_argument("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(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<bool>(); }
void reflect(BinaryReader &vis, unsigned char &v ) { v = vis.get<unsigned char>(); }
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<double>(); }
void reflect(BinaryReader &vis, const char *&v ) { v = intern(vis.getString()); }
void reflect(BinaryReader &vis, std::string &v ) { v = vis.getString(); }
void Reflect(BinaryReader &vis, bool &v ) { v = vis.Get<bool>(); }
void Reflect(BinaryReader &vis, unsigned char &v ) { v = vis.Get<unsigned char>(); }
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<double>(); }
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()); }
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) {
void Reflect(JsonWriter &vis, std::string_view &data) {
if (data.empty())
vis.string("");
vis.String("");
else
vis.string(&data[0], (rapidjson::SizeType)data.size());
vis.String(&data[0], (rapidjson::SizeType)data.size());
}
void reflect(JsonReader &vis, JsonNull &v) {}
void reflect(JsonWriter &vis, JsonNull &v) { vis.m->Null(); }
void Reflect(JsonReader &vis, JsonNull &v) {}
void Reflect(JsonWriter &vis, JsonNull &v) { vis.m->Null(); }
template <typename V> void reflect(JsonReader &vis, std::unordered_map<Usr, V> &v) {
vis.iterArray([&]() {
template <typename V>
void Reflect(JsonReader &vis, std::unordered_map<Usr, V> &v) {
vis.IterArray([&]() {
V val;
reflect(vis, val);
Reflect(vis, val);
v[val.usr] = std::move(val);
});
}
template <typename V> void reflect(JsonWriter &vis, std::unordered_map<Usr, V> &v) {
template <typename V>
void Reflect(JsonWriter &vis, std::unordered_map<Usr, V> &v) {
// Determinism
std::vector<std::pair<uint64_t, V>> xs(v.begin(), v.end());
std::sort(xs.begin(), xs.end(), [](const auto &a, const auto &b) { return a.first < b.first; });
vis.startArray();
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();
Reflect(vis, it.second);
vis.EndArray();
}
template <typename V> void reflect(BinaryReader &vis, std::unordered_map<Usr, V> &v) {
for (auto n = vis.varUInt(); n; n--) {
template <typename V>
void Reflect(BinaryReader &vis, std::unordered_map<Usr, V> &v) {
for (auto n = vis.VarUInt(); n; n--) {
V val;
reflect(vis, val);
Reflect(vis, val);
v[val.usr] = std::move(val);
}
}
template <typename V> void reflect(BinaryWriter &vis, std::unordered_map<Usr, V> &v) {
vis.varUInt(v.size());
template <typename V>
void Reflect(BinaryWriter &vis, std::unordered_map<Usr, V> &v) {
vis.VarUInt(v.size());
for (auto &it : v)
reflect(vis, it.second);
Reflect(vis, it.second);
}
// Used by IndexFile::dependencies.
void reflect(JsonReader &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
void Reflect(JsonReader &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
std::string name;
for (auto it = vis.m->MemberBegin(); it != vis.m->MemberEnd(); ++it)
v[internH(it->name.GetString())] = it->value.GetInt64();
v[InternH(it->name.GetString())] = it->value.GetInt64();
}
void reflect(JsonWriter &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
vis.startObject();
void Reflect(JsonWriter &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
vis.StartObject();
for (auto &it : v) {
vis.m->Key(it.first.val().data()); // llvm 8 -> data()
vis.m->Int64(it.second);
}
vis.endObject();
vis.EndObject();
}
void reflect(BinaryReader &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
void Reflect(BinaryReader &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
std::string name;
for (auto n = vis.varUInt(); n; n--) {
reflect(vis, name);
reflect(vis, v[internH(name)]);
for (auto n = vis.VarUInt(); n; n--) {
Reflect(vis, name);
Reflect(vis, v[InternH(name)]);
}
}
void reflect(BinaryWriter &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
void Reflect(BinaryWriter &vis, DenseMap<CachedHashStringRef, int64_t> &v) {
std::string key;
vis.varUInt(v.size());
vis.VarUInt(v.size());
for (auto &it : v) {
key = it.first.val().str();
reflect(vis, key);
reflect(vis, it.second);
Reflect(vis, key);
Reflect(vis, it.second);
}
}
template <typename Vis> void reflect(Vis &vis, IndexInclude &v) {
reflectMemberStart(vis);
template <typename Vis> void Reflect(Vis &vis, IndexInclude &v) {
ReflectMemberStart(vis);
REFLECT_MEMBER(line);
REFLECT_MEMBER(resolved_path);
reflectMemberEnd(vis);
ReflectMemberEnd(vis);
}
void reflect(JsonWriter &vis, IndexInclude &v) {
reflectMemberStart(vis);
void Reflect(JsonWriter &vis, IndexInclude &v) {
ReflectMemberStart(vis);
REFLECT_MEMBER(line);
if (gTestOutputMode) {
std::string basename(llvm::sys::path::filename(v.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);
}
reflectMemberEnd(vis);
ReflectMemberEnd(vis);
}
template <typename Def> void reflectHoverAndComments(JsonReader &vis, Def &def) {
reflectMember(vis, "hover", def.hover);
reflectMember(vis, "comments", def.comments);
template <typename Def>
void ReflectHoverAndComments(JsonReader &vis, Def &def) {
ReflectMember(vis, "hover", def.hover);
ReflectMember(vis, "comments", def.comments);
}
template <typename Def> void reflectHoverAndComments(JsonWriter &vis, Def &def) {
template <typename Def>
void ReflectHoverAndComments(JsonWriter &vis, Def &def) {
// Don't emit empty hover and comments in JSON test mode.
if (!gTestOutputMode || def.hover[0])
reflectMember(vis, "hover", def.hover);
ReflectMember(vis, "hover", def.hover);
if (!gTestOutputMode || def.comments[0])
reflectMember(vis, "comments", def.comments);
ReflectMember(vis, "comments", def.comments);
}
template <typename Def> void reflectHoverAndComments(BinaryReader &vis, Def &def) {
reflect(vis, def.hover);
reflect(vis, def.comments);
template <typename Def>
void ReflectHoverAndComments(BinaryReader &vis, Def &def) {
Reflect(vis, def.hover);
Reflect(vis, def.comments);
}
template <typename Def> void reflectHoverAndComments(BinaryWriter &vis, Def &def) {
reflect(vis, def.hover);
reflect(vis, def.comments);
template <typename Def>
void ReflectHoverAndComments(BinaryWriter &vis, Def &def) {
Reflect(vis, def.hover);
Reflect(vis, def.comments);
}
template <typename Def> void reflectShortName(JsonReader &vis, Def &def) {
template <typename Def> void ReflectShortName(JsonReader &vis, Def &def) {
if (gTestOutputMode) {
std::string short_name;
reflectMember(vis, "short_name", short_name);
def.short_name_offset = std::string_view(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(vis, "short_name_offset", def.short_name_offset);
reflectMember(vis, "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 <typename Def> void reflectShortName(JsonWriter &vis, Def &def) {
template <typename Def> void ReflectShortName(JsonWriter &vis, Def &def) {
if (gTestOutputMode) {
std::string_view short_name(def.detailed_name + def.short_name_offset, def.short_name_size);
reflectMember(vis, "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(vis, "short_name_offset", def.short_name_offset);
reflectMember(vis, "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 <typename Def> void reflectShortName(BinaryReader &vis, Def &def) {
reflect(vis, def.short_name_offset);
reflect(vis, def.short_name_size);
template <typename Def> void ReflectShortName(BinaryReader &vis, Def &def) {
Reflect(vis, def.short_name_offset);
Reflect(vis, def.short_name_size);
}
template <typename Def> void reflectShortName(BinaryWriter &vis, Def &def) {
reflect(vis, def.short_name_offset);
reflect(vis, def.short_name_size);
template <typename Def> void ReflectShortName(BinaryWriter &vis, Def &def) {
Reflect(vis, def.short_name_offset);
Reflect(vis, def.short_name_size);
}
template <typename TVisitor> void reflect1(TVisitor &vis, IndexFunc &v) {
reflectMemberStart(vis);
template <typename TVisitor> 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);
ReflectShortName(vis, v.def);
REFLECT_MEMBER2("spell", v.def.spell);
reflectHoverAndComments(vis, v.def);
ReflectHoverAndComments(vis, v.def);
REFLECT_MEMBER2("bases", v.def.bases);
REFLECT_MEMBER2("vars", v.def.vars);
REFLECT_MEMBER2("callees", v.def.callees);
@ -291,20 +312,20 @@ template <typename TVisitor> void reflect1(TVisitor &vis, IndexFunc &v) {
REFLECT_MEMBER2("declarations", v.declarations);
REFLECT_MEMBER2("derived", v.derived);
REFLECT_MEMBER2("uses", v.uses);
reflectMemberEnd(vis);
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); }
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 <typename Vis> void reflect1(Vis &vis, IndexType &v) {
reflectMemberStart(vis);
template <typename TVisitor> 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);
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);
@ -318,20 +339,20 @@ template <typename Vis> void reflect1(Vis &vis, IndexType &v) {
REFLECT_MEMBER2("derived", v.derived);
REFLECT_MEMBER2("instances", v.instances);
REFLECT_MEMBER2("uses", v.uses);
reflectMemberEnd(vis);
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); }
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 <typename TVisitor> void reflect1(TVisitor &vis, IndexVar &v) {
reflectMemberStart(vis);
template <typename TVisitor> 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);
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);
@ -340,16 +361,16 @@ template <typename TVisitor> void reflect1(TVisitor &vis, IndexVar &v) {
REFLECT_MEMBER2("declarations", v.declarations);
REFLECT_MEMBER2("uses", v.uses);
reflectMemberEnd(vis);
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); }
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
template <typename TVisitor> void reflect1(TVisitor &vis, IndexFile &v) {
reflectMemberStart(vis);
template <typename TVisitor> void Reflect1(TVisitor &vis, IndexFile &v) {
ReflectMemberStart(vis);
if (!gTestOutputMode) {
REFLECT_MEMBER(mtime);
REFLECT_MEMBER(language);
@ -364,69 +385,68 @@ template <typename TVisitor> void reflect1(TVisitor &vis, IndexFile &v) {
REFLECT_MEMBER(usr2func);
REFLECT_MEMBER(usr2type);
REFLECT_MEMBER(usr2var);
reflectMemberEnd(vis);
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 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(JsonReader &vis, SerializeFormat &v) {
v = vis.GetString()[0] == 'j' ? SerializeFormat::Json
: SerializeFormat::Binary;
}
void reflect(JsonWriter &vis, SerializeFormat &v) {
void Reflect(JsonWriter &vis, SerializeFormat &v) {
switch (v) {
case SerializeFormat::Binary:
vis.string("binary");
vis.String("binary");
break;
case SerializeFormat::Json:
vis.string("json");
vis.String("json");
break;
}
}
void reflectMemberStart(JsonReader &vis) {
if (!vis.m->IsObject())
throw std::invalid_argument("object");
}
static BumpPtrAllocator Alloc;
static DenseSet<CachedHashStringRef> Strings;
static std::mutex AllocMutex;
static BumpPtrAllocator alloc;
static DenseSet<CachedHashStringRef> 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<char>(s.size() + 1);
memcpy(p, s.data(), s.size());
p[s.size()] = '\0';
*r.first = CachedHashStringRef(StringRef(p, s.size()), hs.hash());
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<char>(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;
return *R.first;
}
const char *intern(StringRef s) { return internH(s).val().data(); }
const char *Intern(StringRef S) {
return InternH(S).val().data();
}
std::string serialize(SerializeFormat format, IndexFile &file) {
std::string Serialize(SerializeFormat format, IndexFile &file) {
switch (format) {
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();
Reflect(writer, major);
Reflect(writer, minor);
ReflectFile(writer, file);
return writer.Take();
}
case SerializeFormat::Json: {
rapidjson::StringBuffer output;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(output);
writer.SetFormatOptions(rapidjson::PrettyFormatOptions::kFormatSingleLineArray);
writer.SetFormatOptions(
rapidjson::PrettyFormatOptions::kFormatSingleLineArray);
writer.SetIndent(' ', 2);
JsonWriter json_writer(&writer);
if (!gTestOutputMode) {
@ -435,16 +455,18 @@ std::string serialize(SerializeFormat format, IndexFile &file) {
output.Put(c);
output.Put('\n');
}
reflectFile(json_writer, file);
ReflectFile(json_writer, file);
return output.GetString();
}
}
return "";
}
std::unique_ptr<IndexFile> deserialize(SerializeFormat format, const std::string &path,
const std::string &serialized_index_content, const std::string &file_content,
std::optional<int> expected_version) {
std::unique_ptr<IndexFile>
Deserialize(SerializeFormat format, const std::string &path,
const std::string &serialized_index_content,
const std::string &file_content,
std::optional<int> expected_version) {
if (serialized_index_content.empty())
return nullptr;
@ -456,12 +478,13 @@ std::unique_ptr<IndexFile> deserialize(SerializeFormat format, const std::string
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)
Reflect(reader, major);
Reflect(reader, minor);
if (major != IndexFile::kMajorVersion ||
minor != IndexFile::kMinorVersion)
throw std::invalid_argument("Invalid version");
file = std::make_unique<IndexFile>(path, file_content, false);
reflectFile(reader, *file);
ReflectFile(reader, *file);
} catch (std::invalid_argument &e) {
LOG_S(INFO) << "failed to deserialize '" << path << "': " << e.what();
return nullptr;
@ -486,9 +509,10 @@ std::unique_ptr<IndexFile> deserialize(SerializeFormat format, const std::string
file = std::make_unique<IndexFile>(path, file_content, false);
JsonReader json_reader{&reader};
try {
reflectFile(json_reader, *file);
ReflectFile(json_reader, *file);
} catch (std::invalid_argument &e) {
LOG_S(INFO) << "'" << path << "': failed to deserialize " << json_reader.getPath() << "." << e.what();
LOG_S(INFO) << "'" << path << "': failed to deserialize "
<< json_reader.GetPath() << "." << e.what();
return nullptr;
}
break;
@ -498,26 +522,26 @@ std::unique_ptr<IndexFile> deserialize(SerializeFormat format, const std::string
// Restore non-serialized state.
file->path = path;
if (g_config->clang.pathMappings.size()) {
doPathMapping(file->import_file);
DoPathMapping(file->import_file);
std::vector<const char *> args;
for (const char *arg : file->args) {
std::string s(arg);
doPathMapping(s);
args.push_back(intern(s));
DoPathMapping(s);
args.push_back(Intern(s));
}
file->args = std::move(args);
for (auto &[_, path] : file->lid2path)
doPathMapping(path);
DoPathMapping(path);
for (auto &include : file->includes) {
std::string p(include.resolved_path);
doPathMapping(p);
include.resolved_path = intern(p);
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;
DoPathMapping(path);
dependencies[InternH(path)] = it.second;
}
file->dependencies = std::move(dependencies);
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -22,8 +34,7 @@
namespace llvm {
class CachedHashStringRef;
class StringRef;
template <typename Fn> class function_ref;
} // namespace llvm
}
namespace ccls {
enum class SerializeFormat { Binary, Json };
@ -35,58 +46,59 @@ struct JsonReader {
std::vector<const char *> path_;
JsonReader(rapidjson::Value *m) : m(m) {}
void startObject() {}
void endObject() {}
void iterArray(llvm::function_ref<void()> fn);
void member(const char *name, llvm::function_ref<void()> fn);
bool isNull();
std::string getString();
std::string getPath() const;
void StartObject() {}
void EndObject() {}
void IterArray(std::function<void()> fn);
void Member(const char *name, std::function<void()> fn);
bool IsNull();
std::string GetString();
std::string GetPath() const;
};
struct JsonWriter {
using W = rapidjson::Writer<rapidjson::StringBuffer, rapidjson::UTF8<char>, rapidjson::UTF8<char>,
rapidjson::CrtAllocator, 0>;
using W =
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::UTF8<char>,
rapidjson::UTF8<char>, 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 int64(int64_t v);
void string(const char *s);
void string(const char *s, size_t len);
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 <typename T> T get() {
template <typename T> T Get() {
T ret;
memcpy(&ret, p_, sizeof(T));
p_ += sizeof(T);
return ret;
}
uint64_t varUInt() {
uint64_t VarUInt() {
auto x = *reinterpret_cast<const uint8_t *>(p_++);
if (x < 253)
return x;
if (x == 253)
return get<uint16_t>();
return Get<uint16_t>();
if (x == 254)
return get<uint32_t>();
return get<uint64_t>();
return Get<uint32_t>();
return Get<uint64_t>();
}
int64_t varInt() {
uint64_t x = varUInt();
int64_t VarInt() {
uint64_t x = VarUInt();
return int64_t(x >> 1 ^ -(x & 1));
}
const char *getString() {
const char *GetString() {
const char *ret = p_;
while (*p_)
p_++;
@ -98,31 +110,31 @@ struct BinaryReader {
struct BinaryWriter {
std::string buf_;
template <typename T> void pack(T x) {
template <typename T> 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) {
void VarUInt(uint64_t n) {
if (n < 253)
pack<uint8_t>(n);
Pack<uint8_t>(n);
else if (n < 65536) {
pack<uint8_t>(253);
pack<uint16_t>(n);
Pack<uint8_t>(253);
Pack<uint16_t>(n);
} else if (n < 4294967296) {
pack<uint8_t>(254);
pack<uint32_t>(n);
Pack<uint8_t>(254);
Pack<uint32_t>(n);
} else {
pack<uint8_t>(255);
pack<uint64_t>(n);
Pack<uint8_t>(255);
Pack<uint64_t>(n);
}
}
void varInt(int64_t n) { varUInt(uint64_t(n) << 1 ^ n >> 63); }
std::string take() { return std::move(buf_); }
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) {
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);
@ -131,253 +143,270 @@ struct BinaryWriter {
struct IndexFile;
#define REFLECT_MEMBER(name) reflectMember(vis, #name, v.name)
#define REFLECT_MEMBER2(name, v) reflectMember(vis, name, v)
#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<T> v0; \
::ccls::reflect(vis, v0); \
v = static_cast<T>(v0); \
} \
LLVM_ATTRIBUTE_UNUSED inline void reflect(JsonWriter &vis, T &v) { \
auto v0 = static_cast<std::underlying_type_t<T>>(v); \
::ccls::reflect(vis, v0); \
#define REFLECT_UNDERLYING(T) \
LLVM_ATTRIBUTE_UNUSED inline void Reflect(JsonReader &vis, T &v) { \
std::underlying_type_t<T> v0; \
::ccls::Reflect(vis, v0); \
v = static_cast<T>(v0); \
} \
LLVM_ATTRIBUTE_UNUSED inline void Reflect(JsonWriter &vis, T &v) { \
auto v0 = static_cast<std::underlying_type_t<T>>(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<T> v0; \
::ccls::reflect(vis, v0); \
v = static_cast<T>(v0); \
} \
LLVM_ATTRIBUTE_UNUSED inline void reflect(BinaryWriter &vis, T &v) { \
auto v0 = static_cast<std::underlying_type_t<T>>(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<T> v0; \
::ccls::Reflect(vis, v0); \
v = static_cast<T>(v0); \
} \
LLVM_ATTRIBUTE_UNUSED inline void Reflect(BinaryWriter &vis, T &v) { \
auto v0 = static_cast<std::underlying_type_t<T>>(v); \
::ccls::Reflect(vis, v0); \
}
#define _MAPPABLE_REFLECT_MEMBER(name) REFLECT_MEMBER(name);
#define REFLECT_STRUCT(type, ...) \
template <typename Vis> void reflect(Vis &vis, type &v) { \
reflectMemberStart(vis); \
MACRO_MAP(_MAPPABLE_REFLECT_MEMBER, __VA_ARGS__) \
reflectMemberEnd(vis); \
#define REFLECT_STRUCT(type, ...) \
template <typename Vis> 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);
#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(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(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(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(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, JsonNull &v);
void Reflect(JsonWriter &vis, JsonNull &v);
void reflect(JsonReader &vis, SerializeFormat &v);
void reflect(JsonWriter &vis, SerializeFormat &v);
void Reflect(JsonReader &vis, SerializeFormat &v);
void Reflect(JsonWriter &vis, SerializeFormat &v);
void reflect(JsonWriter &vis, std::string_view &v);
void Reflect(JsonWriter &vis, std::string_view &v);
//// Type constructors
// reflectMember std::optional<T> is used to represent TypeScript optional
// properties (in `key: value` context). reflect std::optional<T> is used for a
// ReflectMember std::optional<T> is used to represent TypeScript optional
// properties (in `key: value` context). Reflect std::optional<T> is used for a
// different purpose, whether an object is nullable (possibly in `value`
// context).
template <typename T> void reflect(JsonReader &vis, std::optional<T> &v) {
if (!vis.isNull()) {
template <typename T> void Reflect(JsonReader &vis, std::optional<T> &v) {
if (!vis.IsNull()) {
v.emplace();
reflect(vis, *v);
Reflect(vis, *v);
}
}
template <typename T> void reflect(JsonWriter &vis, std::optional<T> &v) {
template <typename T> void Reflect(JsonWriter &vis, std::optional<T> &v) {
if (v)
reflect(vis, *v);
Reflect(vis, *v);
else
vis.null_();
vis.Null();
}
template <typename T> void reflect(BinaryReader &vis, std::optional<T> &v) {
template <typename T> void Reflect(BinaryReader &vis, std::optional<T> &v) {
if (*vis.p_++) {
v.emplace();
reflect(vis, *v);
Reflect(vis, *v);
}
}
template <typename T> void reflect(BinaryWriter &vis, std::optional<T> &v) {
template <typename T> void Reflect(BinaryWriter &vis, std::optional<T> &v) {
if (v) {
vis.pack<unsigned char>(1);
reflect(vis, *v);
vis.Pack<unsigned char>(1);
Reflect(vis, *v);
} else {
vis.pack<unsigned char>(0);
vis.Pack<unsigned char>(0);
}
}
// The same as std::optional
template <typename T> void reflect(JsonReader &vis, Maybe<T> &v) {
if (!vis.isNull())
reflect(vis, *v);
template <typename T> void Reflect(JsonReader &vis, Maybe<T> &v) {
if (!vis.IsNull())
Reflect(vis, *v);
}
template <typename T> void reflect(JsonWriter &vis, Maybe<T> &v) {
template <typename T> void Reflect(JsonWriter &vis, Maybe<T> &v) {
if (v)
reflect(vis, *v);
Reflect(vis, *v);
else
vis.null_();
vis.Null();
}
template <typename T> void reflect(BinaryReader &vis, Maybe<T> &v) {
template <typename T> void Reflect(BinaryReader &vis, Maybe<T> &v) {
if (*vis.p_++)
reflect(vis, *v);
Reflect(vis, *v);
}
template <typename T> void reflect(BinaryWriter &vis, Maybe<T> &v) {
template <typename T> void Reflect(BinaryWriter &vis, Maybe<T> &v) {
if (v) {
vis.pack<unsigned char>(1);
reflect(vis, *v);
vis.Pack<unsigned char>(1);
Reflect(vis, *v);
} else {
vis.pack<unsigned char>(0);
vis.Pack<unsigned char>(0);
}
}
template <typename T> void reflectMember(JsonWriter &vis, const char *name, std::optional<T> &v) {
template <typename T>
void ReflectMember(JsonWriter &vis, const char *name, std::optional<T> &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);
vis.Key(name);
Reflect(vis, *v);
}
}
template <typename T> void reflectMember(BinaryWriter &vis, const char *, std::optional<T> &v) { reflect(vis, v); }
template <typename T>
void ReflectMember(BinaryWriter &vis, const char *, std::optional<T> &v) {
Reflect(vis, v);
}
// The same as std::optional
template <typename T> void reflectMember(JsonWriter &vis, const char *name, Maybe<T> &v) {
if (v.valid()) {
vis.key(name);
reflect(vis, v);
template <typename T>
void ReflectMember(JsonWriter &vis, const char *name, Maybe<T> &v) {
if (v.Valid()) {
vis.Key(name);
Reflect(vis, v);
}
}
template <typename T> void reflectMember(BinaryWriter &vis, const char *, Maybe<T> &v) { reflect(vis, v); }
template <typename T>
void ReflectMember(BinaryWriter &vis, const char *, Maybe<T> &v) {
Reflect(vis, v);
}
template <typename L, typename R> void reflect(JsonReader &vis, std::pair<L, R> &v) {
vis.member("L", [&]() { reflect(vis, v.first); });
vis.member("R", [&]() { reflect(vis, v.second); });
template <typename L, typename R>
void Reflect(JsonReader &vis, std::pair<L, R> &v) {
vis.Member("L", [&]() { Reflect(vis, v.first); });
vis.Member("R", [&]() { Reflect(vis, v.second); });
}
template <typename L, typename R> void reflect(JsonWriter &vis, std::pair<L, R> &v) {
vis.startObject();
reflectMember(vis, "L", v.first);
reflectMember(vis, "R", v.second);
vis.endObject();
template <typename L, typename R>
void Reflect(JsonWriter &vis, std::pair<L, R> &v) {
vis.StartObject();
ReflectMember(vis, "L", v.first);
ReflectMember(vis, "R", v.second);
vis.EndObject();
}
template <typename L, typename R> void reflect(BinaryReader &vis, std::pair<L, R> &v) {
reflect(vis, v.first);
reflect(vis, v.second);
template <typename L, typename R>
void Reflect(BinaryReader &vis, std::pair<L, R> &v) {
Reflect(vis, v.first);
Reflect(vis, v.second);
}
template <typename L, typename R> void reflect(BinaryWriter &vis, std::pair<L, R> &v) {
reflect(vis, v.first);
reflect(vis, v.second);
template <typename L, typename R>
void Reflect(BinaryWriter &vis, std::pair<L, R> &v) {
Reflect(vis, v.first);
Reflect(vis, v.second);
}
// std::vector
template <typename T> void reflect(JsonReader &vis, std::vector<T> &v) {
vis.iterArray([&]() {
template <typename T> void Reflect(JsonReader &vis, std::vector<T> &v) {
vis.IterArray([&]() {
v.emplace_back();
reflect(vis, v.back());
Reflect(vis, v.back());
});
}
template <typename T> void reflect(JsonWriter &vis, std::vector<T> &v) {
vis.startArray();
template <typename T> void Reflect(JsonWriter &vis, std::vector<T> &v) {
vis.StartArray();
for (auto &it : v)
reflect(vis, it);
vis.endArray();
Reflect(vis, it);
vis.EndArray();
}
template <typename T> void reflect(BinaryReader &vis, std::vector<T> &v) {
for (auto n = vis.varUInt(); n; n--) {
template <typename T> void Reflect(BinaryReader &vis, std::vector<T> &v) {
for (auto n = vis.VarUInt(); n; n--) {
v.emplace_back();
reflect(vis, v.back());
Reflect(vis, v.back());
}
}
template <typename T> void reflect(BinaryWriter &vis, std::vector<T> &v) {
vis.varUInt(v.size());
template <typename T> void Reflect(BinaryWriter &vis, std::vector<T> &v) {
vis.VarUInt(v.size());
for (auto &it : v)
reflect(vis, it);
Reflect(vis, it);
}
// reflectMember
// ReflectMember
void reflectMemberStart(JsonReader &);
template <typename T> void reflectMemberStart(T &) {}
inline void reflectMemberStart(JsonWriter &vis) { vis.startObject(); }
template <typename T> void ReflectMemberStart(T &) {}
inline void ReflectMemberStart(JsonWriter &vis) { vis.StartObject(); }
template <typename T> void reflectMemberEnd(T &) {}
inline void reflectMemberEnd(JsonWriter &vis) { vis.endObject(); }
template <typename T> void ReflectMemberEnd(T &) {}
inline void ReflectMemberEnd(JsonWriter &vis) { vis.EndObject(); }
template <typename T> void reflectMember(JsonReader &vis, const char *name, T &v) {
vis.member(name, [&]() { reflect(vis, v); });
template <typename T> void ReflectMember(JsonReader &vis, const char *name, T &v) {
vis.Member(name, [&]() { Reflect(vis, v); });
}
template <typename T> void reflectMember(JsonWriter &vis, const char *name, T &v) {
vis.key(name);
reflect(vis, v);
template <typename T> void ReflectMember(JsonWriter &vis, const char *name, T &v) {
vis.Key(name);
Reflect(vis, v);
}
template <typename T> void ReflectMember(BinaryReader &vis, const char *, T &v) {
Reflect(vis, v);
}
template <typename T> void ReflectMember(BinaryWriter &vis, const char *, T &v) {
Reflect(vis, v);
}
template <typename T> void reflectMember(BinaryReader &vis, const char *, T &v) { reflect(vis, v); }
template <typename T> 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<IndexFile> deserialize(SerializeFormat format, const std::string &path,
const std::string &serialized_index_content, const std::string &file_content,
std::optional<int> expected_version);
const char *Intern(llvm::StringRef str);
llvm::CachedHashStringRef InternH(llvm::StringRef str);
std::string Serialize(SerializeFormat format, IndexFile &file);
std::unique_ptr<IndexFile>
Deserialize(SerializeFormat format, const std::string &path,
const std::string &serialized_index_content,
const std::string &file_content,
std::optional<int> expected_version);
} // namespace ccls

View File

@ -1,18 +1,30 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "test.hh"
#include "sema_manager.hh"
#include "filesystem.hh"
#include "indexer.hh"
#include "pipeline.hh"
#include "platform.hh"
#include "sema_manager.hh"
#include "serializer.hh"
#include "utils.hh"
#include <llvm/ADT/StringRef.h>
#include <llvm/Config/llvm-config.h>
#include <llvm/ADT/StringRef.h>
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
@ -34,10 +46,11 @@ using namespace llvm;
extern bool gTestOutputMode;
namespace ccls {
std::string toString(const rapidjson::Document &document) {
std::string ToString(const rapidjson::Document &document) {
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
writer.SetFormatOptions(rapidjson::PrettyFormatOptions::kFormatSingleLineArray);
writer.SetFormatOptions(
rapidjson::PrettyFormatOptions::kFormatSingleLineArray);
writer.SetIndent(' ', 2);
buffer.Clear();
@ -53,7 +66,7 @@ struct TextReplacer {
std::vector<Replacement> replacements;
std::string apply(const std::string &content) {
std::string Apply(const std::string &content) {
std::string result = content;
for (const Replacement &replacement : replacements) {
@ -62,7 +75,9 @@ struct TextReplacer {
if (idx == std::string::npos)
break;
result.replace(result.begin() + idx, result.begin() + idx + replacement.from.size(), replacement.to);
result.replace(result.begin() + idx,
result.begin() + idx + replacement.from.size(),
replacement.to);
}
}
@ -70,13 +85,14 @@ struct TextReplacer {
}
};
void trimInPlace(std::string &s) {
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());
}
std::vector<std::string> splitString(const std::string &str, const std::string &delimiter) {
std::vector<std::string> SplitString(const std::string &str,
const std::string &delimiter) {
// http://stackoverflow.com/a/13172514
std::vector<std::string> strings;
@ -93,9 +109,11 @@ std::vector<std::string> splitString(const std::string &str, const std::string &
return strings;
}
void parseTestExpectation(const std::string &filename, const std::vector<std::string> &lines_with_endings,
TextReplacer *replacer, std::vector<std::string> *flags,
std::unordered_map<std::string, std::string> *output_sections) {
void ParseTestExpectation(
const std::string &filename,
const std::vector<std::string> &lines_with_endings, TextReplacer *replacer,
std::vector<std::string> *flags,
std::unordered_map<std::string, std::string> *output_sections) {
// Scan for EXTRA_FLAGS:
{
bool in_output = false;
@ -155,11 +173,14 @@ void parseTestExpectation(const std::string &filename, const std::vector<std::st
}
}
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;
str.assign(std::istreambuf_iterator<char>(in), std::istreambuf_iterator<char>());
str.assign(std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>());
in.close();
// Replace expectation
@ -168,13 +189,13 @@ void updateTestExpectation(const std::string &filename, const std::string &expec
str.replace(it, expectation.size(), actual);
// Write it back out.
writeToFile(filename, str);
WriteToFile(filename, str);
}
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);
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);
printf("[FAILED] %s (section %s)\n", path.c_str(), path_section.c_str());
#if _POSIX_C_SOURCE >= 200809L
@ -200,28 +221,33 @@ void diffDocuments(std::string path, std::string path_section, rapidjson::Docume
return;
}
#endif
std::vector<std::string> actual_output = splitString(joined_actual_output, "\n");
std::vector<std::string> expected_output = splitString(joined_expected_output, "\n");
std::vector<std::string> actual_output =
SplitString(joined_actual_output, "\n");
std::vector<std::string> expected_output =
SplitString(joined_expected_output, "\n");
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());
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) {
std::string expected = file->toString();
std::string serialized = ccls::serialize(SerializeFormat::Json, *file);
void VerifySerializeToFrom(IndexFile *file) {
std::string expected = file->ToString();
std::string serialized = ccls::Serialize(SerializeFormat::Json, *file);
std::unique_ptr<IndexFile> result =
ccls::deserialize(SerializeFormat::Json, "--.cc", serialized, "<empty>", std::nullopt /*expected_version*/);
std::string actual = result->toString();
ccls::Deserialize(SerializeFormat::Json, "--.cc", serialized, "<empty>",
std::nullopt /*expected_version*/);
std::string actual = result->ToString();
if (expected != actual) {
fprintf(stderr, "Serialization failure\n");
// assert(false);
}
}
std::string findExpectedOutputForFilename(std::string filename,
const std::unordered_map<std::string, std::string> &expected) {
std::string FindExpectedOutputForFilename(
std::string filename,
const std::unordered_map<std::string, std::string> &expected) {
for (const auto &entry : expected) {
if (StringRef(entry.first).endswith(filename))
return entry.second;
@ -233,7 +259,9 @@ std::string findExpectedOutputForFilename(std::string filename,
return "{}";
}
IndexFile *findDbForPathEnding(const std::string &path, const std::vector<std::unique_ptr<IndexFile>> &dbs) {
IndexFile *
FindDbForPathEnding(const std::string &path,
const std::vector<std::unique_ptr<IndexFile>> &dbs) {
for (auto &db : dbs) {
if (StringRef(db->path).endswith(path))
return db.get();
@ -241,13 +269,14 @@ IndexFile *findDbForPathEnding(const std::string &path, const std::vector<std::u
return nullptr;
}
bool runIndexTests(const std::string &filter_path, bool enable_update) {
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 const char kRequiredClangVersion[] = "6.0.0";
if (version != kRequiredClangVersion && version.find("svn") == std::string::npos) {
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",
@ -259,91 +288,98 @@ bool runIndexTests(const std::string &filter_path, bool enable_update) {
bool update_all = false;
// FIXME: show diagnostics in STL/headers when running tests. At the moment
// this can be done by conRequestIdex index(1, 1);
SemaManager completion(nullptr, nullptr, [&](std::string, std::vector<Diagnostic>) {}, [](RequestId id) {});
getFilesInFolder("index_tests", true /*recursive*/, true /*add_folder_to_path*/, [&](const std::string &path) {
bool is_fail_allowed = false;
SemaManager completion(
nullptr, nullptr, [&](std::string, std::vector<Diagnostic>) {},
[](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 (path.find(filter_path) == std::string::npos)
return;
if (!filter_path.empty())
printf("Running %s\n", path.c_str());
if (!filter_path.empty())
printf("Running %s\n", path.c_str());
// Parse expected output from the test, parse it into JSON document.
std::vector<std::string> lines_with_endings;
{
std::ifstream fin(path);
for (std::string line; std::getline(fin, line);)
lines_with_endings.push_back(line);
}
TextReplacer text_replacer;
std::vector<std::string> flags;
std::unordered_map<std::string, std::string> all_expected_output;
parseTestExpectation(path, lines_with_endings, &text_replacer, &flags, &all_expected_output);
// Parse expected output from the test, parse it into JSON document.
std::vector<std::string> lines_with_endings;
{
std::ifstream fin(path);
for (std::string line; std::getline(fin, line);)
lines_with_endings.push_back(line);
}
TextReplacer text_replacer;
std::vector<std::string> flags;
std::unordered_map<std::string, std::string> 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);
// Build flags.
flags.push_back("-resource-dir=" + GetDefaultResourceDirectory());
flags.push_back(path);
// Run test.
g_config = new Config;
VFS vfs;
WorkingFiles wfiles;
std::vector<const char *> cargs;
for (auto &arg : flags)
cargs.push_back(arg.c_str());
bool ok;
auto result = ccls::idx::index(&completion, &wfiles, &vfs, "", path, cargs, {}, true, ok);
// Run test.
g_config = new Config;
VFS vfs;
WorkingFiles wfiles;
std::vector<const char *> cargs;
for (auto &arg : flags)
cargs.push_back(arg.c_str());
bool ok;
auto dbs = ccls::idx::Index(&completion, &wfiles, &vfs, "", path, cargs,
{}, true, ok);
for (const auto &entry : all_expected_output) {
const std::string &expected_path = entry.first;
std::string expected_output = text_replacer.apply(entry.second);
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, result.indexes);
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);
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();
// Get output from index operation.
IndexFile *db = FindDbForPathEnding(expected_path, dbs);
std::string actual_output = "{}";
if (db) {
VerifySerializeToFrom(db);
actual_output = db->ToString();
}
actual_output = text_replacer.Apply(actual_output);
if (c == 'a')
update_all = true;
// 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 (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");
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;
}

View File

@ -1,10 +1,22 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 <string>
namespace ccls {
bool runIndexTests(const std::string &filter_path, bool enable_update);
bool RunIndexTests(const std::string &filter_path, bool enable_update);
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -22,7 +34,7 @@ template <typename Lockable> void lock(Lockable &l) { l.lock(); }
namespace ccls {
struct BaseThreadQueue {
virtual bool isEmpty() = 0;
virtual bool IsEmpty() = 0;
virtual ~BaseThreadQueue() = default;
};
@ -33,9 +45,13 @@ template <typename... Queue> struct MultiQueueLock {
void unlock() { unlock_impl(typename std::index_sequence_for<Queue...>{}); }
private:
template <size_t... Is> void lock_impl(std::index_sequence<Is...>) { std::lock(std::get<Is>(tuple_)->mutex_...); }
template <size_t... Is> void lock_impl(std::index_sequence<Is...>) {
std::lock(std::get<Is>(tuple_)->mutex_...);
}
template <size_t... Is> void unlock_impl(std::index_sequence<Is...>) { (std::get<Is>(tuple_)->mutex_.unlock(), ...); }
template <size_t... Is> void unlock_impl(std::index_sequence<Is...>) {
(std::get<Is>(tuple_)->mutex_.unlock(), ...);
}
std::tuple<Queue...> tuple_;
};
@ -43,18 +59,19 @@ private:
struct MultiQueueWaiter {
std::condition_variable_any cv;
static bool hasState(std::initializer_list<BaseThreadQueue *> queues) {
static bool HasState(std::initializer_list<BaseThreadQueue *> queues) {
for (BaseThreadQueue *queue : queues) {
if (!queue->isEmpty())
if (!queue->IsEmpty())
return true;
}
return false;
}
template <typename... BaseThreadQueue> bool wait(std::atomic<bool> &quit, BaseThreadQueue... queues) {
template <typename... BaseThreadQueue>
bool Wait(std::atomic<bool> &quit, BaseThreadQueue... queues) {
MultiQueueLock<BaseThreadQueue...> l(queues...);
while (!quit.load(std::memory_order_relaxed)) {
if (hasState({queues...}))
if (HasState({queues...}))
return false;
cv.wait(l);
}
@ -62,9 +79,10 @@ struct MultiQueueWaiter {
}
template <typename... BaseThreadQueue>
void waitUntil(std::chrono::steady_clock::time_point t, BaseThreadQueue... queues) {
void WaitUntil(std::chrono::steady_clock::time_point t,
BaseThreadQueue... queues) {
MultiQueueLock<BaseThreadQueue...> l(queues...);
if (!hasState({queues...}))
if (!HasState({queues...}))
cv.wait_until(l, t);
}
};
@ -79,23 +97,25 @@ public:
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_; }
size_t Size() const { return total_count_; }
// Add an element to the queue.
template <void (std::deque<T>::*Push)(T &&)> void push(T &&t, bool priority) {
template <void (std::deque<T>::*push)(T &&)> void Push(T &&t, bool priority) {
std::lock_guard<std::mutex> lock(mutex_);
if (priority)
(priority_.*Push)(std::move(t));
(priority_.*push)(std::move(t));
else
(queue_.*Push)(std::move(t));
(queue_.*push)(std::move(t));
++total_count_;
waiter_->cv.notify_one();
}
void pushBack(T &&t, bool priority = false) { push<&std::deque<T>::push_back>(std::move(t), priority); }
void PushBack(T &&t, bool priority = false) {
Push<&std::deque<T>::push_back>(std::move(t), priority);
}
// Return all elements in the queue.
std::vector<T> dequeueAll() {
std::vector<T> DequeueAll() {
std::lock_guard<std::mutex> lock(mutex_);
total_count_ = 0;
@ -115,20 +135,19 @@ public:
}
// Returns true if the queue is empty. This is lock-free.
bool isEmpty() { return total_count_ == 0; }
bool IsEmpty() { return total_count_ == 0; }
// Get the first element from the queue. Blocks until one is available.
T dequeue(int keep_only_latest = 0) {
T Dequeue() {
std::unique_lock<std::mutex> lock(mutex_);
waiter_->cv.wait(lock, [&]() { return !priority_.empty() || !queue_.empty(); });
waiter_->cv.wait(lock,
[&]() { return !priority_.empty() || !queue_.empty(); });
auto execute = [&](std::deque<T> *q) {
if (keep_only_latest > 0 && q->size() > keep_only_latest)
q->erase(q->begin(), q->end() - keep_only_latest);
auto val = std::move(q->front());
q->pop_front();
--total_count_;
return val;
return std::move(val);
};
if (!priority_.empty())
return execute(&priority_);
@ -137,13 +156,13 @@ public:
// Get the first element from the queue without blocking. Returns a null
// value if the queue is empty.
std::optional<T> tryPopFront() {
std::optional<T> TryPopFront() {
std::lock_guard<std::mutex> lock(mutex_);
auto execute = [&](std::deque<T> *q) {
auto val = std::move(q->front());
q->pop_front();
--total_count_;
return val;
return std::move(val);
};
if (priority_.size())
return execute(&priority_);
@ -152,9 +171,12 @@ public:
return std::nullopt;
}
template <typename Fn> void apply(Fn fn) {
template <typename Fn> void Iterate(Fn fn) {
std::lock_guard<std::mutex> lock(mutex_);
fn(queue_);
for (auto &entry : priority_)
fn(entry);
for (auto &entry : queue_)
fn(entry);
}
mutable std::mutex mutex_;

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "utils.hh"
@ -30,23 +42,27 @@ struct Matcher::Impl {
std::regex regex;
};
Matcher::Matcher(const std::string &pattern) : impl(std::make_unique<Impl>()), pattern(pattern) {
impl->regex = std::regex(pattern, std::regex_constants::ECMAScript | std::regex_constants::icase |
Matcher::Matcher(const std::string &pattern)
: impl(std::make_unique<Impl>()), pattern(pattern) {
impl->regex = std::regex(pattern, std::regex_constants::ECMAScript |
std::regex_constants::icase |
std::regex_constants::optimize);
}
Matcher::~Matcher() {}
bool Matcher::matches(const std::string &text) const {
bool Matcher::Matches(const std::string &text) const {
return std::regex_search(text, impl->regex, std::regex_constants::match_any);
}
GroupMatch::GroupMatch(const std::vector<std::string> &whitelist, const std::vector<std::string> &blacklist) {
GroupMatch::GroupMatch(const std::vector<std::string> &whitelist,
const std::vector<std::string> &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);
params.message =
"failed to parse EMCAScript regex " + pattern + " : " + what;
pipeline::Notify(window_showMessage, params);
};
for (const std::string &pattern : whitelist) {
try {
@ -64,12 +80,13 @@ GroupMatch::GroupMatch(const std::vector<std::string> &whitelist, const std::vec
}
}
bool GroupMatch::matches(const std::string &text, std::string *blacklist_pattern) const {
bool GroupMatch::Matches(const std::string &text,
std::string *blacklist_pattern) const {
for (const Matcher &m : whitelist)
if (m.matches(text))
if (m.Matches(text))
return true;
for (const Matcher &m : blacklist)
if (m.matches(text)) {
if (m.Matches(text)) {
if (blacklist_pattern)
*blacklist_pattern = m.pattern;
return false;
@ -77,7 +94,7 @@ bool GroupMatch::matches(const std::string &text, std::string *blacklist_pattern
return true;
}
uint64_t hashUsr(llvm::StringRef s) {
uint64_t HashUsr(llvm::StringRef s) {
union {
uint64_t ret;
uint8_t out[8];
@ -85,11 +102,12 @@ uint64_t hashUsr(llvm::StringRef s) {
// 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<const uint8_t *>(s.data()), s.size(), k, out, 8);
(void)siphash(reinterpret_cast<const uint8_t *>(s.data()), s.size(), k, out,
8);
return ret;
}
std::string lowerPathIfInsensitive(const std::string &path) {
std::string LowerPathIfInsensitive(const std::string &path) {
#if defined(_WIN32)
std::string ret = path;
for (char &c : ret)
@ -100,12 +118,12 @@ std::string lowerPathIfInsensitive(const std::string &path) {
#endif
}
void ensureEndsInSlash(std::string &path) {
void EnsureEndsInSlash(std::string &path) {
if (path.empty() || path[path.size() - 1] != '/')
path += '/';
}
std::string escapeFileName(std::string path) {
std::string EscapeFileName(std::string path) {
bool slash = path.size() && path.back() == '/';
#ifdef _WIN32
std::replace(path.begin(), path.end(), ':', '@');
@ -116,41 +134,38 @@ std::string escapeFileName(std::string path) {
return path;
}
std::string resolveIfRelative(const std::string &directory, const std::string &path) {
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());
SmallString<256> Ret;
sys::path::append(Ret, directory, path);
return NormalizePath(Ret.str());
}
std::string realPath(const std::string &path) {
std::string RealPath(const std::string &path) {
SmallString<256> buf;
sys::fs::real_path(path, buf);
return buf.empty() ? path : llvm::sys::path::convert_to_slash(buf);
}
bool normalizeFolder(std::string &path) {
for (auto &[root, real] : g_config->workspaceFolders) {
StringRef p(path);
if (p.startswith(root))
return true;
if (p.startswith(real)) {
bool NormalizeFolder(std::string &path) {
for (auto &[root, real] : g_config->workspaceFolders)
if (real.size() && llvm::StringRef(path).startswith(real)) {
path = root + path.substr(real.size());
return true;
}
}
return false;
}
std::optional<int64_t> lastWriteTime(const std::string &path) {
sys::fs::file_status status;
if (sys::fs::status(path, status))
std::optional<int64_t> LastWriteTime(const std::string &path) {
sys::fs::file_status Status;
if (sys::fs::status(path, Status))
return {};
return sys::toTimeT(status.getLastModificationTime());
return sys::toTimeT(Status.getLastModificationTime());
}
std::optional<std::string> readContent(const std::string &filename) {
std::optional<std::string> ReadContent(const std::string &filename) {
char buf[4096];
std::string ret;
FILE *f = fopen(filename.c_str(), "rb");
@ -163,9 +178,10 @@ std::optional<std::string> readContent(const std::string &filename) {
return ret;
}
void writeToFile(const std::string &filename, const std::string &content) {
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)) {
if (!f ||
(content.size() && fwrite(content.c_str(), content.size(), 1, f) != 1)) {
LOG_S(ERROR) << "failed to write to " << filename << ' ' << strerror(errno);
return;
}
@ -174,17 +190,20 @@ void writeToFile(const std::string &filename, const std::string &content) {
// 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) {
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)
if ((case_sensitivity ? text[i] == pat[j - 1]
: tolower(text[i]) == tolower(pat[j - 1])) &&
!--j)
return i;
return -1;
}
std::string getDefaultResourceDirectory() { return CLANG_RESOURCE_DIRECTORY; }
std::string GetDefaultResourceDirectory() { return CLANG_RESOURCE_DIRECTORY; }
} // namespace ccls

View File

@ -1,12 +1,23 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 <optional>
#include <string_view>
#include <cstdint>
#include <iterator>
#include <memory>
#include <string>
@ -24,38 +35,42 @@ struct Matcher {
std::string pattern;
Matcher(const std::string &pattern); // throw
Matcher(Matcher &&) = default;
Matcher(Matcher&&) = default;
~Matcher();
bool matches(const std::string &text) const;
bool Matches(const std::string &text) const;
};
struct GroupMatch {
std::vector<Matcher> whitelist, blacklist;
GroupMatch(const std::vector<std::string> &whitelist, const std::vector<std::string> &blacklist);
bool matches(const std::string &text, std::string *blacklist_pattern = nullptr) const;
GroupMatch(const std::vector<std::string> &whitelist,
const std::vector<std::string> &blacklist);
bool Matches(const std::string &text,
std::string *blacklist_pattern = nullptr) const;
};
uint64_t hashUsr(llvm::StringRef s);
uint64_t HashUsr(llvm::StringRef s);
std::string lowerPathIfInsensitive(const std::string &path);
std::string LowerPathIfInsensitive(const std::string &path);
// Ensures that |path| ends in a slash.
void ensureEndsInSlash(std::string &path);
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 EscapeFileName(std::string path);
std::string resolveIfRelative(const std::string &directory, const std::string &path);
std::string realPath(const std::string &path);
bool normalizeFolder(std::string &path);
std::string ResolveIfRelative(const std::string &directory,
const std::string &path);
std::string RealPath(const std::string &path);
bool NormalizeFolder(std::string &path);
std::optional<int64_t> lastWriteTime(const std::string &path);
std::optional<std::string> readContent(const std::string &filename);
void writeToFile(const std::string &filename, const std::string &content);
std::optional<int64_t> LastWriteTime(const std::string &path);
std::optional<std::string> 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);
int ReverseSubseqMatch(std::string_view pat, std::string_view text,
int case_sensitivity);
// http://stackoverflow.com/a/38140932
//
@ -68,27 +83,28 @@ int reverseSubseqMatch(std::string_view pat, std::string_view text, int case_sen
inline void hash_combine(std::size_t &seed) {}
template <typename T, typename... Rest> inline void hash_combine(std::size_t &seed, const T &v, Rest... rest) {
template <typename T, typename... Rest>
inline void hash_combine(std::size_t &seed, const T &v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
hash_combine(seed, rest...);
}
#define MAKE_HASHABLE(type, ...) \
namespace std { \
template <> struct hash<type> { \
std::size_t operator()(const type &t) const { \
std::size_t ret = 0; \
ccls::hash_combine(ret, __VA_ARGS__); \
return ret; \
} \
}; \
#define MAKE_HASHABLE(type, ...) \
namespace std { \
template <> struct hash<type> { \
std::size_t operator()(const type &t) const { \
std::size_t ret = 0; \
ccls::hash_combine(ret, __VA_ARGS__); \
return ret; \
} \
}; \
}
std::string getDefaultResourceDirectory();
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()`.
// empty state. T should define a function `bool T::Valid()`.
template <typename T> class Maybe {
T storage;
@ -110,10 +126,10 @@ public:
const T &operator*() const { return storage; }
T &operator*() { return storage; }
bool valid() const { return storage.valid(); }
explicit operator bool() const { return valid(); }
bool Valid() const { return storage.Valid(); }
explicit operator bool() const { return Valid(); }
operator std::optional<T>() const {
if (valid())
if (Valid())
return storage;
return std::nullopt;
}
@ -128,11 +144,13 @@ public:
template <typename T> struct Vec {
std::unique_ptr<T[]> a;
int s = 0;
#if !(__clang__ || __GNUC__ > 7 || __GNUC__ == 7 && __GNUC_MINOR__ >= 4) || defined(_WIN32)
#if !(__clang__ || __GNUC__ > 7 || __GNUC__ == 7 && __GNUC_MINOR__ >= 4)
// Work around a bug in GCC<7.4 that optional<IndexUpdate> would not be
// construtible.
Vec() = default;
Vec(const Vec &o) : a(std::make_unique<T[]>(o.s)), s(o.s) { std::copy(o.a.get(), o.a.get() + o.s, a.get()); }
Vec(const Vec &o) : a(std::make_unique<T[]>(o.s)), s(o.s) {
std::copy(o.a.get(), o.a.get() + o.s, a.get());
}
Vec(Vec &&) = default;
Vec &operator=(Vec &&) = default;
Vec(std::unique_ptr<T[]> a, int s) : a(std::move(a)), s(s) {}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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 "working_files.hh"
@ -27,7 +39,7 @@ constexpr int kMaxDiff = 20;
// |kMaxColumnAlignSize|.
constexpr int kMaxColumnAlignSize = 200;
Position getPositionForOffset(const std::string &content, int offset) {
Position GetPositionForOffset(const std::string &content, int offset) {
if (offset >= content.size())
offset = (int)content.size() - 1;
@ -42,7 +54,7 @@ Position getPositionForOffset(const std::string &content, int offset) {
return {line, col};
}
std::vector<std::string> toLines(const std::string &c) {
std::vector<std::string> ToLines(const std::string &c) {
std::vector<std::string> ret;
int last = 0, e = c.size();
for (int i = 0; i < e; i++)
@ -59,7 +71,7 @@ std::vector<std::string> toLines(const std::string &c) {
// 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;
@ -78,9 +90,12 @@ int myersDiff(const char *a, int la, const char *b, int lb, int threshold) {
int *v = v_static + lb;
v[1] = 0;
for (int di = 0; di <= threshold; di++) {
int low = -di + 2 * std::max(0, di - lb), high = di - 2 * std::max(0, di - la);
int low = -di + 2 * std::max(0, di - lb),
high = di - 2 * std::max(0, di - la);
for (int i = low; i <= high; i += 2) {
int x = i == -di || (i != di && v[i - 1] < v[i + 1]) ? v[i + 1] : v[i - 1] + 1, y = x - i;
int x = i == -di || (i != di && v[i - 1] < v[i + 1]) ? v[i + 1]
: v[i - 1] + 1,
y = x - i;
while (x < la && y < lb && a[x] == b[y])
x++, y++;
v[i] = x;
@ -91,8 +106,8 @@ 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) {
return myersDiff(a.data(), a.size(), b.data(), b.size(), threshold);
int MyersDiff(const std::string &a, const std::string &b, int threshold) {
return MyersDiff(a.data(), a.size(), b.data(), b.size(), threshold);
}
// Computes edit distance with O(N*M) Needleman-Wunsch algorithm
@ -101,7 +116,7 @@ int myersDiff(const std::string &a, const std::string &b, int threshold) {
// Myers' diff algorithm is used to find best matching line while this one is
// used to align a single column because Myers' needs some twiddling to return
// distance vector.
std::vector<int> editDistanceVector(std::string a, std::string b) {
std::vector<int> EditDistanceVector(std::string a, std::string b) {
std::vector<int> d(b.size() + 1);
std::iota(d.begin(), d.end(), 0);
for (int i = 0; i < (int)a.size(); i++) {
@ -118,16 +133,17 @@ std::vector<int> 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++;
while (tail < (int)a.size() && tail < (int)b.size() && a[a.size() - 1 - tail] == b[b.size() - 1 - tail])
while (tail < (int)a.size() && tail < (int)b.size() &&
a[a.size() - 1 - tail] == b[b.size() - 1 - tail])
tail++;
if (column < head)
return column;
if ((int)a.size() - tail < column)
return column + (int)b.size() - (int)a.size();
return column + b.size() - a.size();
if (std::max(a.size(), b.size()) - head - tail >= kMaxColumnAlignSize)
return std::min(column, (int)b.size());
@ -135,14 +151,14 @@ int alignColumn(const std::string &a, int column, std::string b, bool is_end) {
b = b.substr(head, b.size() - tail - head);
// left[i] = cost of aligning a[head, column) to b[head, head + i)
std::vector<int> left = editDistanceVector(a.substr(head, column - head), b);
std::vector<int> left = EditDistanceVector(a.substr(head, column - head), b);
// right[i] = cost of aligning a[column, a.size() - tail) to b[head + i,
// b.size() - tail)
std::string a_rev = a.substr(column, a.size() - tail - column);
std::reverse(a_rev.begin(), a_rev.end());
std::reverse(b.begin(), b.end());
std::vector<int> right = editDistanceVector(a_rev, b);
std::vector<int> right = EditDistanceVector(a_rev, b);
std::reverse(right.begin(), right.end());
int best = 0, best_cost = INT_MAX;
@ -159,14 +175,16 @@ 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.
std::optional<int> findMatchingLine(const std::vector<std::string> &index_lines,
const std::vector<int> &index_to_buffer, int line, int *column,
const std::vector<std::string> &buffer_lines, bool is_end) {
std::optional<int>
FindMatchingLine(const std::vector<std::string> &index_lines,
const std::vector<int> &index_to_buffer, int line, int *column,
const std::vector<std::string> &buffer_lines, bool is_end) {
// If this is a confident mapping, returns.
if (index_to_buffer[line] >= 0) {
int ret = index_to_buffer[line];
if (column)
*column = alignColumn(index_lines[line], *column, buffer_lines[ret], is_end);
*column =
AlignColumn(index_lines[line], *column, buffer_lines[ret], is_end);
return ret;
}
@ -177,7 +195,8 @@ std::optional<int> findMatchingLine(const std::vector<std::string> &index_lines,
while (++down < int(index_to_buffer.size()) && index_to_buffer[down] < 0) {
}
up = up < 0 ? 0 : index_to_buffer[up];
down = down >= int(index_to_buffer.size()) ? int(buffer_lines.size()) - 1 : index_to_buffer[down];
down = down >= int(index_to_buffer.size()) ? int(buffer_lines.size()) - 1
: index_to_buffer[down];
if (up > down)
return std::nullopt;
@ -186,35 +205,37 @@ std::optional<int> findMatchingLine(const std::vector<std::string> &index_lines,
int best = up, best_dist = kMaxDiff + 1;
const std::string &needle = index_lines[line];
for (int i = up; i <= down; i++) {
int dist = myersDiff(needle, buffer_lines[i], kMaxDiff);
int dist = MyersDiff(needle, buffer_lines[i], kMaxDiff);
if (dist < best_dist) {
best_dist = dist;
best = i;
}
}
if (column)
*column = alignColumn(index_lines[line], *column, buffer_lines[best], is_end);
*column =
AlignColumn(index_lines[line], *column, buffer_lines[best], is_end);
return best;
}
} // namespace
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();
OnBufferContentUpdated();
// setIndexContent gets called when the file is opened.
// SetIndexContent gets called when the file is opened.
}
void WorkingFile::setIndexContent(const std::string &index_content) {
index_lines = toLines(index_content);
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);
void WorkingFile::OnBufferContentUpdated() {
buffer_lines = ToLines(buffer_content);
index_to_buffer.clear();
buffer_to_index.clear();
@ -226,18 +247,19 @@ void WorkingFile::onBufferContentUpdated() {
// we are confident that the line appeared in index maps to the one appeared in
// buffer. And then using them as start points to extend upwards and downwards
// to align other identical lines (but not unique).
void WorkingFile::computeLineMapping() {
void WorkingFile::ComputeLineMapping() {
std::unordered_map<uint64_t, int> hash_to_unique;
std::vector<uint64_t> index_hashes(index_lines.size());
std::vector<uint64_t> buffer_hashes(buffer_lines.size());
index_to_buffer.resize(index_lines.size());
buffer_to_index.resize(buffer_lines.size());
hash_to_unique.reserve(std::max(index_to_buffer.size(), buffer_to_index.size()));
hash_to_unique.reserve(
std::max(index_to_buffer.size(), buffer_to_index.size()));
// For index line i, set index_to_buffer[i] to -1 if line i is duplicated.
int i = 0;
for (StringRef line : index_lines) {
uint64_t h = hashUsr(line);
uint64_t h = HashUsr(line);
auto it = hash_to_unique.find(h);
if (it == hash_to_unique.end()) {
hash_to_unique[h] = i;
@ -254,7 +276,7 @@ void WorkingFile::computeLineMapping() {
i = 0;
hash_to_unique.clear();
for (StringRef line : buffer_lines) {
uint64_t h = hashUsr(line);
uint64_t h = HashUsr(line);
auto it = hash_to_unique.find(h);
if (it == hash_to_unique.end()) {
hash_to_unique[h] = i;
@ -273,7 +295,8 @@ void WorkingFile::computeLineMapping() {
for (auto h : index_hashes) {
if (index_to_buffer[i] >= 0) {
auto it = hash_to_unique.find(h);
if (it != hash_to_unique.end() && it->second >= 0 && buffer_to_index[it->second] >= 0)
if (it != hash_to_unique.end() && it->second >= 0 &&
buffer_to_index[it->second] >= 0)
index_to_buffer[i] = it->second;
else
index_to_buffer[i] = -1;
@ -284,7 +307,8 @@ void WorkingFile::computeLineMapping() {
// Starting at unique lines, extend upwards and downwards.
for (i = 0; i < (int)index_hashes.size() - 1; i++) {
int j = index_to_buffer[i];
if (0 <= j && j + 1 < buffer_hashes.size() && index_hashes[i + 1] == buffer_hashes[j + 1])
if (0 <= j && j + 1 < buffer_hashes.size() &&
index_hashes[i + 1] == buffer_hashes[j + 1])
index_to_buffer[i + 1] = j + 1;
}
for (i = (int)index_hashes.size(); --i > 0;) {
@ -300,85 +324,95 @@ void WorkingFile::computeLineMapping() {
buffer_to_index[index_to_buffer[i]] = i;
}
std::optional<int> WorkingFile::getBufferPosFromIndexPos(int line, int *column, bool is_end) {
std::optional<int> 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()) {
LOG_S(WARNING) << "bad index_line (got " << line << ", expected [0, " << index_lines.size() << ")) in " << filename;
LOG_S(WARNING) << "bad index_line (got " << line << ", expected [0, "
<< index_lines.size() << ")) in " << filename;
return std::nullopt;
}
if (index_to_buffer.empty())
computeLineMapping();
return findMatchingLine(index_lines, index_to_buffer, line, column, buffer_lines, is_end);
ComputeLineMapping();
return FindMatchingLine(index_lines, index_to_buffer, line, column,
buffer_lines, is_end);
}
std::optional<int> WorkingFile::getIndexPosFromBufferPos(int line, int *column, bool is_end) {
std::optional<int> WorkingFile::GetIndexPosFromBufferPos(int line, int *column,
bool is_end) {
if (line < 0 || line >= (int)buffer_lines.size())
return std::nullopt;
if (buffer_to_index.empty())
computeLineMapping();
return findMatchingLine(buffer_lines, buffer_to_index, line, column, index_lines, is_end);
ComputeLineMapping();
return FindMatchingLine(buffer_lines, buffer_to_index, line, column,
index_lines, is_end);
}
Position WorkingFile::getCompletionPosition(Position pos, std::string *filter) const {
int start = getOffsetForPosition(pos, buffer_content);
Position WorkingFile::GetCompletionPosition(Position pos, std::string *filter,
Position *replace_end_pos) const {
int start = GetOffsetForPosition(pos, buffer_content);
int i = start;
#if LLVM_VERSION_MAJOR < 14 // llvmorg-14-init-3863-g601102d282d5
#define isAsciiIdentifierContinue isIdentifierBody
#endif
while (i > 0 && isAsciiIdentifierContinue(buffer_content[i - 1]))
while (i > 0 && isIdentifierBody(buffer_content[i - 1]))
--i;
*replace_end_pos = pos;
for (int i = start;
i < buffer_content.size() && isIdentifierBody(buffer_content[i]); i++)
replace_end_pos->character++;
*filter = buffer_content.substr(i, start - i);
return getPositionForOffset(buffer_content, i);
return GetPositionForOffset(buffer_content, i);
}
WorkingFile *WorkingFiles::getFile(const std::string &path) {
WorkingFile *WorkingFiles::GetFile(const std::string &path) {
std::lock_guard lock(mutex);
return getFileUnlocked(path);
return GetFileUnlocked(path);
}
WorkingFile *WorkingFiles::getFileUnlocked(const std::string &path) {
WorkingFile *WorkingFiles::GetFileUnlocked(const std::string &path) {
auto it = files.find(path);
return it != files.end() ? it->second.get() : nullptr;
}
std::string WorkingFiles::getContent(const std::string &path) {
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 TextDocumentItem &open) {
WorkingFile *WorkingFiles::OnOpen(const TextDocumentItem &open) {
std::lock_guard lock(mutex);
std::string path = open.uri.getPath();
std::string path = open.uri.GetPath();
std::string content = open.text;
auto &wf = files[path];
if (wf) {
wf->version = open.version;
wf->buffer_content = content;
wf->onBufferContentUpdated();
wf->OnBufferContentUpdated();
} else {
wf = std::make_unique<WorkingFile>(path, content);
}
return wf.get();
}
void WorkingFiles::onChange(const TextDocumentDidChangeParam &change) {
void WorkingFiles::OnChange(const TextDocumentDidChangeParam &change) {
std::lock_guard lock(mutex);
std::string path = change.textDocument.uri.getPath();
WorkingFile *file = getFileUnlocked(path);
std::string path = change.textDocument.uri.GetPath();
WorkingFile *file = GetFileUnlocked(path);
if (!file) {
LOG_S(WARNING) << "Could not change " << path << " because it was not open";
return;
}
file->timestamp =
chrono::duration_cast<chrono::seconds>(chrono::high_resolution_clock::now().time_since_epoch()).count();
file->timestamp = chrono::duration_cast<chrono::seconds>(
chrono::high_resolution_clock::now().time_since_epoch())
.count();
// version: number | null
if (change.textDocument.version)
@ -389,20 +423,23 @@ void WorkingFiles::onChange(const TextDocumentDidChangeParam &change) {
// See https://github.com/Microsoft/language-server-protocol/issues/9.
if (!diff.range) {
file->buffer_content = diff.text;
file->onBufferContentUpdated();
file->OnBufferContentUpdated();
} else {
int start_offset = getOffsetForPosition(diff.range->start, file->buffer_content);
int start_offset =
GetOffsetForPosition(diff.range->start, file->buffer_content);
// Ignore TextDocumentContentChangeEvent.rangeLength which causes trouble
// when UTF-16 surrogate pairs are used.
int end_offset = getOffsetForPosition(diff.range->end, file->buffer_content);
int end_offset =
GetOffsetForPosition(diff.range->end, file->buffer_content);
file->buffer_content.replace(file->buffer_content.begin() + start_offset,
file->buffer_content.begin() + end_offset, diff.text);
file->onBufferContentUpdated();
file->buffer_content.begin() + end_offset,
diff.text);
file->OnBufferContentUpdated();
}
}
}
void WorkingFiles::onClose(const std::string &path) {
void WorkingFiles::OnClose(const std::string &path) {
std::lock_guard lock(mutex);
files.erase(path);
}
@ -411,22 +448,25 @@ void WorkingFiles::onClose(const std::string &path) {
// 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) {
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--)
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)
while (i < content.size() && uint8_t(content[i]) >= 128 &&
uint8_t(content[i]) < 192)
i++;
}
return int(i);
}
std::string_view lexIdentifierAroundPos(Position position, std::string_view content) {
int start = getOffsetForPosition(position, content), end = start + 1;
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.
@ -434,12 +474,12 @@ std::string_view lexIdentifierAroundPos(Position position, std::string_view cont
c = content[start - 1];
if (c == ':' && start > 1 && content[start - 2] == ':')
start--;
else if (!isAsciiIdentifierContinue(c))
else if (!isIdentifierBody(c))
break;
}
for (; end < content.size() && isAsciiIdentifierContinue(content[end]); end++)
for (; end < content.size() && isIdentifierBody(content[end]); end++)
;
return content.substr(start, end - start);
}
} // namespace ccls
}

View File

@ -1,5 +1,17 @@
// Copyright 2017-2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0
/* 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
@ -36,46 +48,50 @@ struct WorkingFile {
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);
void SetIndexContent(const std::string &index_content);
// This should be called whenever |buffer_content| has changed.
void onBufferContentUpdated();
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<int> getBufferPosFromIndexPos(int line, int *column, bool is_end);
std::optional<int> 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<int> getIndexPosFromBufferPos(int line, int *column, bool is_end);
std::optional<int> GetIndexPosFromBufferPos(int line, int *column,
bool is_end);
// Returns the stable completion position (it jumps back until there is a
// non-alphanumeric character).
Position getCompletionPosition(Position pos, std::string *filter) const;
Position GetCompletionPosition(Position pos, std::string *filter,
Position *replace_end_pos) const;
private:
// Compute index_to_buffer and buffer_to_index.
void computeLineMapping();
void ComputeLineMapping();
};
struct WorkingFiles {
WorkingFile *getFile(const std::string &path);
WorkingFile *getFileUnlocked(const std::string &path);
std::string getContent(const std::string &path);
WorkingFile *GetFile(const std::string &path);
WorkingFile *GetFileUnlocked(const std::string &path);
std::string GetContent(const std::string &path);
template <typename Fn> void withLock(Fn &&fn) {
template <typename Fn> 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);
WorkingFile *OnOpen(const TextDocumentItem &open);
void OnChange(const TextDocumentDidChangeParam &change);
void OnClose(const std::string &close);
std::mutex mutex;
std::unordered_map<std::string, std::unique_ptr<WorkingFile>> files;
};
int getOffsetForPosition(Position pos, std::string_view content);
int GetOffsetForPosition(Position pos, std::string_view content);
std::string_view lexIdentifierAroundPos(Position position, std::string_view content);
std::string_view LexIdentifierAroundPos(Position position,
std::string_view content);
} // namespace ccls