2017-03-25 20:15:00 +00:00
// TODO: cleanup includes
2017-04-09 02:24:32 +00:00
# include "cache.h"
2017-05-27 04:21:00 +00:00
# include "clang_complete.h"
2017-04-08 22:54:36 +00:00
# include "file_consumer.h"
2017-05-21 19:51:15 +00:00
# include "match.h"
2017-05-27 04:21:00 +00:00
# include "include_complete.h"
2017-05-22 05:54:27 +00:00
# include "ipc_manager.h"
2017-02-25 23:59:09 +00:00
# include "indexer.h"
# include "query.h"
2017-06-15 05:32:23 +00:00
# include "query_utils.h"
2017-03-05 02:16:23 +00:00
# include "language_server_api.h"
2017-06-15 05:32:23 +00:00
# include "lex_utils.h"
2017-04-09 02:24:32 +00:00
# include "options.h"
2017-03-26 21:40:34 +00:00
# include "project.h"
2017-03-25 20:27:28 +00:00
# include "platform.h"
2017-05-21 07:37:53 +00:00
# include "standard_includes.h"
2017-03-10 07:06:01 +00:00
# include "test.h"
2017-03-25 20:15:00 +00:00
# include "timer.h"
# include "threaded_queue.h"
2017-03-26 21:40:34 +00:00
# include "working_files.h"
2017-03-05 02:16:23 +00:00
2017-03-25 20:27:28 +00:00
# include <doctest/doctest.h>
# include <rapidjson/istreamwrapper.h>
# include <rapidjson/ostreamwrapper.h>
2017-06-10 01:02:48 +00:00
# include <climits>
2017-03-31 05:30:50 +00:00
# include <fstream>
2017-04-17 01:22:59 +00:00
# include <functional>
2017-03-25 20:27:28 +00:00
# include <iostream>
2017-05-21 07:37:53 +00:00
# include <iterator>
2017-03-25 20:27:28 +00:00
# include <string>
2017-05-17 07:08:45 +00:00
# include <sstream>
2017-03-25 20:27:28 +00:00
# include <unordered_map>
# include <thread>
# include <vector>
2017-02-25 23:59:09 +00:00
2017-04-11 07:29:36 +00:00
// TODO: provide a feature like 'https://github.com/goldsborough/clang-expand',
// ie, a fully linear view of a function with inline function calls expanded.
// We can probably use vscode decorators to achieve it.
2017-05-20 19:31:07 +00:00
// TODO: implement ThreadPool type which monitors CPU usage / number of work items
// per second completed and scales up/down number of running threads.
2017-03-25 20:27:28 +00:00
namespace {
2017-04-09 02:24:32 +00:00
2017-04-21 04:06:15 +00:00
std : : vector < std : : string > kEmptyArgs ;
2017-04-16 19:02:29 +00:00
2017-05-20 19:31:07 +00:00
// Expected client version. We show an error if this doesn't match.
2017-06-14 07:13:26 +00:00
const int kExpectedClientVersion = 3 ;
2017-04-16 19:02:29 +00:00
2017-05-20 08:07:29 +00:00
// Cached completion information, so we can give fast completion results when
// the user erases a character. vscode will resend the completion request if
// that happens.
struct CodeCompleteCache {
2017-06-30 06:51:22 +00:00
// NOTE: Make sure to access these variables under |WithLock|.
optional < std : : string > cached_path_ ;
optional < lsPosition > cached_completion_position_ ;
NonElidedVector < lsCompletionItem > cached_results_ ;
2017-05-21 23:48:21 +00:00
2017-06-30 06:51:22 +00:00
std : : mutex mutex_ ;
void WithLock ( std : : function < void ( ) > action ) {
std : : lock_guard < std : : mutex > lock ( mutex_ ) ;
action ( ) ;
}
bool IsCacheValid ( lsTextDocumentPositionParams position ) {
std : : lock_guard < std : : mutex > lock ( mutex_ ) ;
return cached_path_ = = position . textDocument . uri . GetPath ( ) & &
cached_completion_position_ = = position . position ;
2017-05-21 23:48:21 +00:00
}
2017-05-20 08:07:29 +00:00
} ;
2017-04-16 19:02:29 +00:00
2017-05-20 08:07:29 +00:00
// This function returns true if e2e timing should be displayed for the given IpcId.
bool ShouldDisplayIpcTiming ( IpcId id ) {
switch ( id ) {
case IpcId : : TextDocumentPublishDiagnostics :
2017-05-20 21:45:46 +00:00
case IpcId : : CqueryPublishInactiveRegions :
2017-05-20 08:07:29 +00:00
return false ;
default :
return true ;
}
}
2017-04-16 19:02:29 +00:00
2017-05-21 07:37:53 +00:00
2017-04-16 19:02:29 +00:00
2017-04-09 02:24:32 +00:00
2017-04-12 06:41:19 +00:00
void PushBack ( NonElidedVector < lsLocation > * result , optional < lsLocation > location ) {
if ( location )
result - > push_back ( * location ) ;
}
2017-05-27 07:10:21 +00:00
bool FindFileOrFail ( QueryDatabase * db , lsRequestId id , const std : : string & absolute_path , QueryFile * * out_query_file , QueryFileId * out_file_id = nullptr ) {
auto it = db - > usr_to_file . find ( LowerPathIfCaseInsensitive ( absolute_path ) ) ;
2017-05-09 04:20:28 +00:00
if ( it ! = db - > usr_to_file . end ( ) ) {
optional < QueryFile > & file = db - > files [ it - > second . id ] ;
2017-04-22 07:32:29 +00:00
if ( file ) {
2017-05-27 07:10:21 +00:00
* out_query_file = & file . value ( ) ;
if ( out_file_id )
* out_file_id = QueryFileId ( it - > second . id ) ;
return true ;
2017-04-22 07:32:29 +00:00
}
2017-04-11 05:43:01 +00:00
}
2017-05-27 07:10:21 +00:00
if ( out_file_id )
* out_file_id = QueryFileId ( ( size_t ) - 1 ) ;
std : : cerr < < " Unable to find file " < < absolute_path < < std : : endl ;
Out_Error out ;
out . id = id ;
out . error . code = lsErrorCodes : : InternalError ;
out . error . message = " Unable to find file " + absolute_path ;
IpcManager : : instance ( ) - > SendOutMessageToClient ( IpcId : : Cout , out ) ;
return false ;
2017-04-11 05:43:01 +00:00
}
2017-05-27 07:10:21 +00:00
QueryFile * FindFile ( QueryDatabase * db , const std : : string & absolute_path ) {
auto it = db - > usr_to_file . find ( LowerPathIfCaseInsensitive ( absolute_path ) ) ;
2017-05-09 04:20:28 +00:00
if ( it ! = db - > usr_to_file . end ( ) ) {
optional < QueryFile > & file = db - > files [ it - > second . id ] ;
2017-04-22 07:32:29 +00:00
if ( file )
return & file . value ( ) ;
}
2017-04-09 02:24:32 +00:00
return nullptr ;
}
2017-04-15 05:14:05 +00:00
2017-05-20 21:45:46 +00:00
void PublishInactiveLines ( WorkingFile * working_file , const std : : vector < Range > & inactive ) {
Out_CquerySetInactiveRegion out ;
out . params . uri = lsDocumentUri : : FromPath ( working_file - > filename ) ;
for ( Range skipped : inactive ) {
optional < lsRange > ls_skipped = GetLsRange ( working_file , skipped ) ;
if ( ls_skipped )
out . params . inactiveRegions . push_back ( * ls_skipped ) ;
}
IpcManager : : instance ( ) - > SendOutMessageToClient ( IpcId : : CqueryPublishInactiveRegions , out ) ;
}
2017-04-15 05:14:05 +00:00
2017-05-29 23:57:19 +00:00
optional < int > FindIncludeLine ( const std : : vector < std : : string > & lines , const std : : string & full_include_line ) {
//
// This returns an include line. For example,
//
// #include <a> // 0
// #include <c> // 1
//
// Given #include <b>, this will return '1', which means that the
// #include <b> text should be inserted at the start of line 1. Inserting
// at the start of a line allows insertion at both the top and bottom of the
// document.
//
// If the include line is already in the document this returns nullopt.
//
optional < int > last_include_line ;
optional < int > best_include_line ;
// 1 => include line is gt content (ie, it should go after)
// -1 => include line is lt content (ie, it should go before)
int last_line_compare = 1 ;
for ( int line = 0 ; line < ( int ) lines . size ( ) ; + + line ) {
if ( ! StartsWith ( lines [ line ] , " #include " ) ) {
last_line_compare = 1 ;
continue ;
}
last_include_line = line ;
int current_line_compare = full_include_line . compare ( lines [ line ] ) ;
if ( current_line_compare = = 0 )
return nullopt ;
if ( last_line_compare = = 1 & & current_line_compare = = - 1 )
best_include_line = line ;
last_line_compare = current_line_compare ;
}
if ( best_include_line )
return * best_include_line ;
// If |best_include_line| didn't match that means we likely didn't find an
// include which was lt the new one, so put it at the end of the last include
// list.
if ( last_include_line )
return * last_include_line + 1 ;
// No includes, use top of document.
return 0 ;
}
2017-06-29 04:40:30 +00:00
optional < QueryFileId > GetImplementationFile ( QueryDatabase * db , QueryFileId file_id , QueryFile * file ) {
2017-05-29 21:18:35 +00:00
for ( SymbolRef sym : file - > def . outline ) {
switch ( sym . idx . kind ) {
case SymbolKind : : Func : {
optional < QueryFunc > & func = db - > funcs [ sym . idx . idx ] ;
2017-06-29 04:40:30 +00:00
// Note: we ignore the definition if it is in the same file (ie, possibly a header).
if ( func & & func - > def . definition_extent & & func - > def . definition_extent - > path ! = file_id )
2017-05-29 21:18:35 +00:00
return func - > def . definition_extent - > path ;
break ;
}
case SymbolKind : : Var : {
optional < QueryVar > & var = db - > vars [ sym . idx . idx ] ;
2017-06-29 04:40:30 +00:00
// Note: we ignore the definition if it is in the same file (ie, possibly a header).
if ( var & & var - > def . definition_extent & & var - > def . definition_extent - > path ! = file_id )
2017-05-29 21:18:35 +00:00
return db - > vars [ sym . idx . idx ] - > def . definition_extent - > path ;
break ;
}
default :
break ;
}
}
// No associated definition, scan the project for a file in the same
// directory with the same base-name.
std : : string original_path = LowerPathIfCaseInsensitive ( file - > def . path ) ;
std : : string target_path = original_path ;
size_t last = target_path . find_last_of ( ' . ' ) ;
if ( last ! = std : : string : : npos ) {
target_path = target_path . substr ( 0 , last ) ;
}
2017-06-29 04:40:30 +00:00
std : : cerr < < " !! Looking for impl file that starts with " < < target_path < < std : : endl ;
2017-05-29 21:18:35 +00:00
for ( auto & entry : db - > usr_to_file ) {
Usr path = entry . first ;
// Do not consider header files for implementation files.
// TODO: make file extensions configurable.
if ( EndsWith ( path , " .h " ) | | EndsWith ( path , " .hpp " ) )
continue ;
if ( StartsWith ( path , target_path ) & & path ! = original_path ) {
return entry . second ;
}
}
return nullopt ;
}
void EnsureImplFile ( QueryDatabase * db , QueryFileId file_id , optional < lsDocumentUri > & impl_uri , optional < QueryFileId > & impl_file_id ) {
if ( ! impl_uri . has_value ( ) ) {
optional < QueryFile > & file = db - > files [ file_id . id ] ;
assert ( file ) ;
2017-06-29 04:40:30 +00:00
impl_file_id = GetImplementationFile ( db , file_id , & file . value ( ) ) ;
2017-05-29 21:18:35 +00:00
if ( ! impl_file_id . has_value ( ) )
impl_file_id = file_id ;
optional < QueryFile > & impl_file = db - > files [ impl_file_id - > id ] ;
if ( impl_file )
impl_uri = lsDocumentUri : : FromPath ( impl_file - > def . path ) ;
else
impl_uri = lsDocumentUri : : FromPath ( file - > def . path ) ;
}
}
optional < lsTextEdit > BuildAutoImplementForFunction ( QueryDatabase * db , WorkingFiles * working_files , WorkingFile * working_file , int default_line , QueryFileId decl_file_id , QueryFileId impl_file_id , QueryFunc & func ) {
for ( const QueryLocation & decl : func . declarations ) {
if ( decl . path ! = decl_file_id )
continue ;
optional < lsRange > ls_decl = GetLsRange ( working_file , decl . range ) ;
if ( ! ls_decl )
continue ;
optional < std : : string > type_name ;
optional < lsPosition > same_file_insert_end ;
if ( func . def . declaring_type ) {
optional < QueryType > & declaring_type = db - > types [ func . def . declaring_type - > id ] ;
if ( declaring_type ) {
type_name = declaring_type - > def . short_name ;
optional < lsRange > ls_type_def_extent = GetLsRange ( working_file , declaring_type - > def . definition_extent - > range ) ;
if ( ls_type_def_extent ) {
same_file_insert_end = ls_type_def_extent - > end ;
same_file_insert_end - > character + = 1 ; // move past semicolon.
}
}
}
std : : string insert_text ;
int newlines_after_name = 0 ;
LexFunctionDeclaration ( working_file - > buffer_content , ls_decl - > start , type_name , & insert_text , & newlines_after_name ) ;
if ( ! same_file_insert_end ) {
same_file_insert_end = ls_decl - > end ;
same_file_insert_end - > line + = newlines_after_name ;
same_file_insert_end - > character = 1000 ;
}
lsTextEdit edit ;
if ( decl_file_id = = impl_file_id ) {
edit . range . start = * same_file_insert_end ;
edit . range . end = * same_file_insert_end ;
edit . newText = " \n \n " + insert_text ;
}
else {
lsPosition best_pos ;
best_pos . line = default_line ;
int best_dist = INT_MAX ;
optional < QueryFile > & file = db - > files [ impl_file_id . id ] ;
assert ( file ) ;
for ( SymbolRef sym : file - > def . outline ) {
switch ( sym . idx . kind ) {
case SymbolKind : : Func : {
optional < QueryFunc > & sym_func = db - > funcs [ sym . idx . idx ] ;
if ( ! sym_func | | ! sym_func - > def . definition_extent )
break ;
for ( QueryLocation & func_decl : sym_func - > declarations ) {
if ( func_decl . path = = decl_file_id ) {
int dist = func_decl . range . start . line - decl . range . start . line ;
if ( abs ( dist ) < abs ( best_dist ) ) {
optional < lsLocation > def_loc = GetLsLocation ( db , working_files , * sym_func - > def . definition_extent ) ;
if ( ! def_loc )
continue ;
best_dist = dist ;
if ( dist > 0 )
best_pos = def_loc - > range . start ;
else
best_pos = def_loc - > range . end ;
}
}
}
break ;
}
case SymbolKind : : Var : {
// TODO: handle vars.
//optional<QueryVar>& var = db->vars[sym.idx.idx];
//if (!var || !var->def.definition_extent)
// continue;
break ;
}
2017-06-17 05:40:03 +00:00
case SymbolKind : : Invalid :
case SymbolKind : : File :
case SymbolKind : : Type :
std : : cerr < < " Unexpected SymbolKind "
< < static_cast < int > ( sym . idx . kind ) < < std : : endl ;
break ;
2017-05-29 21:18:35 +00:00
}
}
edit . range . start = best_pos ;
edit . range . end = best_pos ;
if ( best_dist < 0 )
edit . newText = " \n \n " + insert_text ;
else
edit . newText = insert_text + " \n \n " ;
}
return edit ;
}
return nullopt ;
}
2017-06-10 04:13:16 +00:00
void EmitDiagnostics ( WorkingFiles * working_files , std : : string path , NonElidedVector < lsDiagnostic > diagnostics ) {
// Emit diagnostics.
Out_TextDocumentPublishDiagnostics diagnostic_response ;
diagnostic_response . params . uri = lsDocumentUri : : FromPath ( path ) ;
diagnostic_response . params . diagnostics = diagnostics ;
IpcManager : : instance ( ) - > SendOutMessageToClient ( IpcId : : TextDocumentPublishDiagnostics , diagnostic_response ) ;
// Cache diagnostics so we can show fixits.
2017-06-14 06:29:41 +00:00
working_files - > DoActionOnFile ( path , [ & ] ( WorkingFile * working_file ) {
if ( working_file )
working_file - > diagnostics_ = diagnostics ;
} ) ;
2017-06-10 04:13:16 +00:00
}
2017-04-15 05:14:05 +00:00
2017-06-16 02:28:49 +00:00
// 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 FilterCompletionResponse ( Out_TextDocumentComplete * complete_response ,
const std : : string & complete_text ) {
// Used to inject more completions.
# if false
const size_t kNumIterations = 250 ;
size_t size = complete_response - > result . items . size ( ) ;
complete_response - > result . items . reserve ( size * ( kNumIterations + 1 ) ) ;
for ( size_t iteration = 0 ; iteration < kNumIterations ; + + iteration ) {
for ( size_t i = 0 ; i < size ; + + i ) {
auto item = complete_response - > result . items [ i ] ;
item . label + = " # " + std : : to_string ( iteration ) ;
complete_response - > result . items . push_back ( item ) ;
}
}
# endif
2017-04-15 05:14:05 +00:00
2017-06-16 02:28:49 +00:00
const size_t kMaxResultSize = 100u ;
if ( complete_response - > result . items . size ( ) > kMaxResultSize ) {
//std::cerr << "!!! Filtering " << complete_response->result.items.size() << " results using " << complete_text << std::endl;
2017-04-15 05:14:05 +00:00
2017-06-16 02:28:49 +00:00
complete_response - > result . isIncomplete = true ;
2017-04-15 05:14:05 +00:00
2017-06-16 02:28:49 +00:00
if ( complete_text . empty ( ) ) {
complete_response - > result . items . resize ( kMaxResultSize ) ;
}
else {
NonElidedVector < lsCompletionItem > filtered_result ;
filtered_result . reserve ( kMaxResultSize ) ;
2017-06-29 04:33:52 +00:00
2017-06-29 06:59:38 +00:00
std : : unordered_set < std : : string > inserted ;
inserted . reserve ( kMaxResultSize ) ;
2017-06-16 02:28:49 +00:00
for ( const lsCompletionItem & item : complete_response - > result . items ) {
2017-06-29 04:33:52 +00:00
if ( item . label . find ( complete_text ) ! = std : : string : : npos ) {
2017-06-29 06:59:38 +00:00
// Don't insert the same completion entry.
if ( ! inserted . insert ( item . InsertedContent ( ) ) . second ) {
std : : cerr < < " foo " < < item . InsertedContent ( ) ;
continue ;
}
2017-06-16 02:28:49 +00:00
filtered_result . push_back ( item ) ;
if ( filtered_result . size ( ) > = kMaxResultSize )
break ;
}
}
2017-04-16 19:02:29 +00:00
2017-06-29 04:33:52 +00:00
if ( filtered_result . size ( ) < kMaxResultSize ) {
for ( const lsCompletionItem & item : complete_response - > result . items ) {
if ( SubstringMatch ( complete_text , item . label ) ) {
2017-06-29 06:59:38 +00:00
// Don't insert the same completion entry.
if ( ! inserted . insert ( item . InsertedContent ( ) ) . second )
continue ;
2017-06-29 04:33:52 +00:00
filtered_result . push_back ( item ) ;
if ( filtered_result . size ( ) > = kMaxResultSize )
break ;
}
}
}
2017-06-16 02:28:49 +00:00
complete_response - > result . items = filtered_result ;
}
//std::cerr << "!! Filtering resulted in " << complete_response->result.items.size() << " entries" << std::endl;
}
}
2017-04-16 19:02:29 +00:00
2017-04-15 05:14:05 +00:00
2017-04-09 19:38:52 +00:00
2017-04-08 06:45:28 +00:00
struct Index_DoIndex {
2017-04-03 01:34:15 +00:00
enum class Type {
2017-04-23 21:24:06 +00:00
// ImportOnly is used internally for loading dependency caches. The main cc
// file is loaded with ImportThenParse, which will call ImportOnly on all
// of the dependencies. The main cc will then be parsed, which will include
// updates to all dependencies.
ImportThenParse ,
2017-04-21 04:06:15 +00:00
Parse ,
2017-04-21 04:50:31 +00:00
Freshen ,
2017-04-03 01:34:15 +00:00
} ;
2017-05-19 07:02:01 +00:00
Index_DoIndex ( Type type , const Project : : Entry & entry , optional < std : : string > content , bool is_interactive )
: type ( type ) , entry ( entry ) , content ( content ) , is_interactive ( is_interactive ) { }
2017-04-21 04:09:54 +00:00
2017-05-19 05:44:23 +00:00
// Type of index operation.
2017-04-21 04:09:54 +00:00
Type type ;
2017-05-19 05:44:23 +00:00
// Project entry for file path and file arguments.
2017-05-07 05:36:29 +00:00
Project : : Entry entry ;
2017-05-19 05:44:23 +00:00
// File contents that should be indexed.
2017-05-16 07:26:26 +00:00
optional < std : : string > content ;
2017-05-19 07:02:01 +00:00
// If this index request is in response to an interactive user session, for
// example, the user saving a file they are actively editing. We report
// additional information for interactive indexes such as the IndexUpdate
// delta as well as the diagnostics.
bool is_interactive ;
2017-03-25 19:18:25 +00:00
} ;
2017-04-08 06:45:28 +00:00
struct Index_DoIdMap {
2017-05-12 06:08:15 +00:00
std : : unique_ptr < IndexFile > previous ;
std : : unique_ptr < IndexFile > current ;
2017-05-16 07:26:26 +00:00
optional < std : : string > indexed_content ;
2017-05-17 07:08:45 +00:00
PerformanceImportFile perf ;
2017-05-19 07:02:01 +00:00
bool is_interactive ;
2017-04-08 06:45:28 +00:00
2017-05-16 07:26:26 +00:00
explicit Index_DoIdMap ( std : : unique_ptr < IndexFile > current ,
2017-05-17 07:08:45 +00:00
optional < std : : string > indexed_content ,
2017-05-19 07:02:01 +00:00
PerformanceImportFile perf ,
bool is_interactive )
2017-05-16 07:26:26 +00:00
: current ( std : : move ( current ) ) ,
2017-05-17 07:08:45 +00:00
indexed_content ( indexed_content ) ,
2017-05-19 07:02:01 +00:00
perf ( perf ) ,
is_interactive ( is_interactive ) { }
2017-04-24 01:01:51 +00:00
2017-05-12 06:08:15 +00:00
explicit Index_DoIdMap ( std : : unique_ptr < IndexFile > previous ,
2017-05-16 07:26:26 +00:00
std : : unique_ptr < IndexFile > current ,
2017-05-17 07:08:45 +00:00
optional < std : : string > indexed_content ,
2017-05-19 07:02:01 +00:00
PerformanceImportFile perf ,
bool is_interactive )
2017-04-08 06:45:28 +00:00
: previous ( std : : move ( previous ) ) ,
2017-05-16 07:26:26 +00:00
current ( std : : move ( current ) ) ,
2017-05-17 07:08:45 +00:00
indexed_content ( indexed_content ) ,
2017-05-19 07:02:01 +00:00
perf ( perf ) ,
is_interactive ( is_interactive ) { }
2017-04-08 06:45:28 +00:00
} ;
struct Index_OnIdMapped {
2017-05-12 06:08:15 +00:00
std : : unique_ptr < IndexFile > previous_index ;
std : : unique_ptr < IndexFile > current_index ;
2017-04-08 06:45:28 +00:00
std : : unique_ptr < IdMap > previous_id_map ;
std : : unique_ptr < IdMap > current_id_map ;
2017-05-16 07:26:26 +00:00
optional < std : : string > indexed_content ;
2017-05-17 07:08:45 +00:00
PerformanceImportFile perf ;
2017-05-19 07:02:01 +00:00
bool is_interactive ;
2017-05-16 07:26:26 +00:00
2017-05-17 07:08:45 +00:00
Index_OnIdMapped ( const optional < std : : string > & indexed_content ,
2017-05-19 07:02:01 +00:00
PerformanceImportFile perf ,
bool is_interactive )
2017-05-17 07:08:45 +00:00
: indexed_content ( indexed_content ) ,
2017-05-19 07:02:01 +00:00
perf ( perf ) ,
is_interactive ( is_interactive ) { }
2017-04-08 06:45:28 +00:00
} ;
struct Index_OnIndexed {
2017-03-25 19:18:25 +00:00
IndexUpdate update ;
2017-05-16 07:26:26 +00:00
// Map is file path to file content.
std : : unordered_map < std : : string , std : : string > indexed_content ;
2017-05-17 07:08:45 +00:00
PerformanceImportFile perf ;
2017-05-16 07:26:26 +00:00
2017-05-17 07:08:45 +00:00
explicit Index_OnIndexed (
IndexUpdate & update ,
const optional < std : : string > & indexed_content ,
PerformanceImportFile perf )
: update ( update ) , perf ( perf ) {
2017-05-16 07:26:26 +00:00
if ( indexed_content ) {
assert ( update . files_def_update . size ( ) = = 1 ) ;
this - > indexed_content [ update . files_def_update [ 0 ] . path ] = * indexed_content ;
}
}
2017-03-25 19:18:25 +00:00
} ;
2017-04-08 20:00:08 +00:00
using Index_DoIndexQueue = ThreadedQueue < Index_DoIndex > ;
using Index_DoIdMapQueue = ThreadedQueue < Index_DoIdMap > ;
using Index_OnIdMappedQueue = ThreadedQueue < Index_OnIdMapped > ;
using Index_OnIndexedQueue = ThreadedQueue < Index_OnIndexed > ;
2017-03-25 19:18:25 +00:00
void RegisterMessageTypes ( ) {
2017-03-25 21:02:45 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_CancelRequest > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_InitializeRequest > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_InitializedNotification > ( ) ;
2017-05-03 06:45:10 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_Exit > ( ) ;
2017-03-26 06:47:59 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDidOpen > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDidChange > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDidClose > ( ) ;
2017-04-10 00:08:54 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDidSave > ( ) ;
2017-04-14 08:21:03 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentRename > ( ) ;
2017-03-26 06:47:59 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentComplete > ( ) ;
2017-05-15 07:28:53 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentSignatureHelp > ( ) ;
2017-04-03 02:21:21 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDefinition > ( ) ;
2017-04-14 06:43:50 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDocumentHighlight > ( ) ;
2017-04-14 05:18:02 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentHover > ( ) ;
2017-04-10 05:34:06 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentReferences > ( ) ;
2017-03-25 21:02:45 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDocumentSymbol > ( ) ;
2017-05-21 04:30:59 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentDocumentLink > ( ) ;
2017-05-20 19:31:07 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentCodeAction > ( ) ;
2017-03-25 21:02:45 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_TextDocumentCodeLens > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_CodeLensResolve > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_WorkspaceSymbol > ( ) ;
2017-04-21 04:50:31 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryFreshenIndex > ( ) ;
2017-05-24 07:17:29 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryTypeHierarchyTree > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryCallTreeInitial > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryCallTreeExpand > ( ) ;
2017-05-07 06:56:04 +00:00
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryVars > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryCallers > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryBase > ( ) ;
MessageRegistry : : instance ( ) - > Register < Ipc_CqueryDerived > ( ) ;
2017-03-12 00:36:00 +00:00
}
2017-03-02 09:28:07 +00:00
2017-04-16 19:02:29 +00:00
2017-06-16 05:56:07 +00:00
void PriorityEnqueueFileForIndex ( QueryDatabase * db , Project * project , Index_DoIndexQueue * queue_do_index , WorkingFile * working_file , const std : : string & path ) {
// Only do a delta update (Type::Parse) if we've already imported the
// file. If the user saves a file not loaded by the project we don't
// want the initial import to be a delta-update.
Index_DoIndex : : Type index_type = Index_DoIndex : : Type : : Parse ;
QueryFile * file = FindFile ( db , path ) ;
if ( ! file )
index_type = Index_DoIndex : : Type : : ImportThenParse ;
queue_do_index - > PriorityEnqueue ( Index_DoIndex ( index_type , project - > FindCompilationEntryForFile ( path ) , working_file - > buffer_content , true /*is_interactive*/ ) ) ;
}
2017-04-23 20:02:41 +00:00
2017-06-17 02:42:14 +00:00
void InsertSymbolIntoResult ( QueryDatabase * db , WorkingFiles * working_files , SymbolIdx symbol , std : : vector < lsSymbolInformation > * result ) {
optional < lsSymbolInformation > info = GetSymbolInfo ( db , working_files , symbol ) ;
if ( ! info )
return ;
optional < QueryLocation > location = GetDefinitionExtentOfSymbol ( db , symbol ) ;
if ( ! location ) {
auto decls = GetDeclarationsOfSymbolForGotoDefinition ( db , symbol ) ;
if ( decls . empty ( ) )
return ;
location = decls [ 0 ] ;
}
2017-04-23 20:02:41 +00:00
2017-06-17 02:42:14 +00:00
optional < lsLocation > ls_location = GetLsLocation ( db , working_files , * location ) ;
if ( ! ls_location )
return ;
info - > location = * ls_location ;
result - > push_back ( * info ) ;
}
2017-04-23 20:02:41 +00:00
} // namespace
2017-04-16 19:02:29 +00:00
2017-05-21 19:51:15 +00:00
bool ImportCachedIndex ( Config * config ,
2017-04-24 01:01:51 +00:00
FileConsumer : : SharedState * file_consumer_shared ,
2017-04-21 04:06:15 +00:00
Index_DoIdMapQueue * queue_do_id_map ,
2017-05-16 07:26:26 +00:00
const std : : string & tu_path ,
const optional < std : : string > & indexed_content ) {
2017-04-24 01:01:51 +00:00
// TODO: only load cache if command line arguments are the same.
2017-05-17 07:08:45 +00:00
PerformanceImportFile tu_perf ;
2017-04-08 06:45:28 +00:00
Timer time ;
2017-05-21 23:22:00 +00:00
std : : unique_ptr < IndexFile > tu_cache = LoadCachedIndex ( config , tu_path ) ;
2017-05-17 07:08:45 +00:00
tu_perf . index_load_cached = time . ElapsedMicrosecondsAndReset ( ) ;
2017-05-21 23:22:00 +00:00
if ( ! tu_cache )
2017-04-24 01:01:51 +00:00
return true ;
2017-04-20 07:53:33 +00:00
2017-04-24 01:01:51 +00:00
bool needs_reparse = false ;
2017-04-11 05:26:27 +00:00
2017-04-24 01:01:51 +00:00
// Import all dependencies.
2017-05-21 23:22:00 +00:00
for ( auto & dependency_path : tu_cache - > dependencies ) {
2017-05-17 07:08:45 +00:00
//std::cerr << "- Got dependency " << dependency_path << std::endl;
PerformanceImportFile perf ;
time . Reset ( ) ;
2017-05-12 06:08:15 +00:00
std : : unique_ptr < IndexFile > cache = LoadCachedIndex ( config , dependency_path ) ;
2017-05-17 07:08:45 +00:00
perf . index_load_cached = time . ElapsedMicrosecondsAndReset ( ) ;
2017-05-09 01:21:21 +00:00
if ( cache & & GetLastModificationTime ( cache - > path ) = = cache - > last_modification_time )
2017-04-24 01:01:51 +00:00
file_consumer_shared - > Mark ( cache - > path ) ;
else
needs_reparse = true ;
2017-05-09 01:21:21 +00:00
if ( cache )
2017-05-19 07:02:01 +00:00
queue_do_id_map - > Enqueue ( Index_DoIdMap ( std : : move ( cache ) , nullopt , perf , false /*is_interactive*/ ) ) ;
2017-04-24 01:01:51 +00:00
}
// Import primary file.
2017-05-21 23:22:00 +00:00
if ( GetLastModificationTime ( tu_path ) = = tu_cache - > last_modification_time )
2017-04-24 01:01:51 +00:00
file_consumer_shared - > Mark ( tu_path ) ;
else
needs_reparse = true ;
2017-05-21 23:22:00 +00:00
queue_do_id_map - > Enqueue ( Index_DoIdMap ( std : : move ( tu_cache ) , indexed_content , tu_perf , false /*is_interactive*/ ) ) ;
2017-04-24 01:01:51 +00:00
return needs_reparse ;
2017-04-21 04:06:15 +00:00
}
2017-04-08 20:00:08 +00:00
2017-05-21 19:51:15 +00:00
void ParseFile ( Config * config ,
2017-05-19 05:44:23 +00:00
WorkingFiles * working_files ,
2017-04-21 04:06:15 +00:00
FileConsumer : : SharedState * file_consumer_shared ,
2017-05-25 02:04:19 +00:00
clang : : Index * index ,
2017-04-21 04:06:15 +00:00
Index_DoIdMapQueue * queue_do_id_map ,
2017-05-16 07:26:26 +00:00
const Project : : Entry & entry ,
2017-05-19 05:44:23 +00:00
const optional < std : : string > & indexed_content ,
2017-05-19 07:02:01 +00:00
bool is_interactive ) {
2017-04-08 06:45:28 +00:00
2017-05-12 06:08:15 +00:00
std : : unique_ptr < IndexFile > cache_for_args = LoadCachedIndex ( config , entry . filename ) ;
2017-04-20 05:46:10 +00:00
2017-05-07 05:36:29 +00:00
std : : string tu_path = cache_for_args ? cache_for_args - > import_file : entry . filename ;
const std : : vector < std : : string > & tu_args = entry . args ;
2017-04-20 05:46:10 +00:00
2017-05-17 07:08:45 +00:00
PerformanceImportFile perf ;
2017-05-12 06:08:15 +00:00
std : : vector < std : : unique_ptr < IndexFile > > indexes = Parse (
2017-04-21 04:06:15 +00:00
config , file_consumer_shared ,
2017-05-16 07:38:15 +00:00
tu_path , tu_args ,
2017-05-17 07:08:45 +00:00
entry . filename , indexed_content ,
2017-05-25 02:04:19 +00:00
& perf , index ) ;
2017-04-21 04:06:15 +00:00
2017-05-12 06:08:15 +00:00
for ( std : : unique_ptr < IndexFile > & new_index : indexes ) {
2017-05-17 07:08:45 +00:00
Timer time ;
2017-04-21 04:06:15 +00:00
// Load the cached index.
2017-05-12 06:08:15 +00:00
std : : unique_ptr < IndexFile > cached_index ;
2017-04-24 01:01:51 +00:00
if ( cache_for_args & & new_index - > path = = cache_for_args - > path )
cached_index = std : : move ( cache_for_args ) ;
2017-04-21 04:06:15 +00:00
else
2017-04-21 06:32:18 +00:00
cached_index = LoadCachedIndex ( config , new_index - > path ) ;
2017-04-24 01:01:51 +00:00
// TODO: Enable this assert when we are no longer forcibly indexing the primary file.
//assert(!cached_index || GetLastModificationTime(new_index->path) != cached_index->last_modification_time);
2017-05-17 07:08:45 +00:00
// Note: we are reusing the parent perf.
perf . index_load_cached = time . ElapsedMicrosecondsAndReset ( ) ;
2017-03-25 19:18:25 +00:00
2017-06-10 04:13:16 +00:00
// Publish lines skipped by the preprocessor if this is an interactive
// index.
if ( is_interactive ) {
2017-05-20 19:31:07 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( new_index - > path ) ;
2017-05-20 21:45:46 +00:00
if ( working_file ) {
// Publish source ranges disabled by preprocessor.
2017-06-10 04:13:16 +00:00
// TODO: We shouldn't be updating actual indexed content here, but we
// need to use the latest indexed content for the remapping.
// TODO: We should also remap diagnostics.
if ( indexed_content )
working_file - > SetIndexContent ( * indexed_content ) ;
PublishInactiveLines ( working_file , new_index - > skipped_by_preprocessor ) ;
2017-05-20 21:45:46 +00:00
}
2017-05-11 01:44:39 +00:00
}
2017-06-20 01:52:25 +00:00
// Publish diagnostics for non-interactive index.
else {
EmitDiagnostics ( working_files , new_index - > path , new_index - > diagnostics_ ) ;
}
2017-04-24 01:01:51 +00:00
// Any any existing dependencies to |new_index| that were there before,
// because we will not reparse them if they haven't changed.
// TODO: indexer should always include dependencies. This doesn't let us remove old dependencies.
if ( cached_index ) {
for ( auto & dep : cached_index - > dependencies ) {
if ( std : : find ( new_index - > dependencies . begin ( ) , new_index - > dependencies . end ( ) , dep ) = = new_index - > dependencies . end ( ) )
new_index - > dependencies . push_back ( dep ) ;
}
}
2017-04-08 22:54:36 +00:00
2017-05-16 07:26:26 +00:00
// Forward file content, but only for the primary file.
optional < std : : string > content ;
if ( new_index - > path = = entry . filename )
content = indexed_content ;
2017-05-17 06:01:51 +00:00
// Cache the newly indexed file. This replaces the existing cache.
// TODO: Run this as another import pipeline stage.
2017-05-17 07:08:45 +00:00
time . Reset ( ) ;
2017-05-17 06:01:51 +00:00
WriteToCache ( config , new_index - > path , * new_index , content ) ;
2017-05-17 07:08:45 +00:00
perf . index_save_to_disk = time . ElapsedMicrosecondsAndReset ( ) ;
2017-05-17 06:01:51 +00:00
2017-04-21 04:06:15 +00:00
// Dispatch IdMap creation request, which will happen on querydb thread.
2017-05-19 07:02:01 +00:00
Index_DoIdMap response ( std : : move ( cached_index ) , std : : move ( new_index ) , content , perf , is_interactive ) ;
2017-04-21 04:06:15 +00:00
queue_do_id_map - > Enqueue ( std : : move ( response ) ) ;
}
}
2017-05-21 19:51:15 +00:00
bool ResetStaleFiles ( Config * config ,
2017-04-24 01:01:51 +00:00
FileConsumer : : SharedState * file_consumer_shared ,
const std : : string & tu_path ) {
Timer time ;
2017-05-21 23:22:00 +00:00
std : : unique_ptr < IndexFile > tu_cache = LoadCachedIndex ( config , tu_path ) ;
2017-05-17 07:08:45 +00:00
2017-05-21 23:22:00 +00:00
if ( ! tu_cache ) {
2017-04-24 01:01:51 +00:00
std : : cerr < < " [indexer] Unable to load existing index from file when freshening (dependences will not be freshened) " < < std : : endl ;
file_consumer_shared - > Mark ( tu_path ) ;
return true ;
}
bool needs_reparse = false ;
// Check dependencies
2017-05-21 23:22:00 +00:00
for ( auto & dependency_path : tu_cache - > dependencies ) {
2017-05-12 06:08:15 +00:00
std : : unique_ptr < IndexFile > cache = LoadCachedIndex ( config , dependency_path ) ;
2017-04-24 01:01:51 +00:00
if ( GetLastModificationTime ( cache - > path ) ! = cache - > last_modification_time ) {
needs_reparse = true ;
file_consumer_shared - > Reset ( cache - > path ) ;
}
}
// Check primary file
2017-05-21 23:22:00 +00:00
if ( GetLastModificationTime ( tu_path ) ! = tu_cache - > last_modification_time ) {
2017-04-24 01:01:51 +00:00
needs_reparse = true ;
file_consumer_shared - > Mark ( tu_path ) ;
}
return needs_reparse ;
}
2017-05-21 19:51:15 +00:00
bool IndexMain_DoIndex ( Config * config ,
2017-04-21 04:06:15 +00:00
FileConsumer : : SharedState * file_consumer_shared ,
Project * project ,
2017-05-19 05:44:23 +00:00
WorkingFiles * working_files ,
2017-05-25 02:04:19 +00:00
clang : : Index * index ,
2017-04-21 04:06:15 +00:00
Index_DoIndexQueue * queue_do_index ,
Index_DoIdMapQueue * queue_do_id_map ) {
optional < Index_DoIndex > index_request = queue_do_index - > TryDequeue ( ) ;
if ( ! index_request )
return false ;
2017-04-08 20:00:08 +00:00
2017-04-21 04:06:15 +00:00
Timer time ;
2017-04-20 08:05:19 +00:00
2017-04-21 04:06:15 +00:00
switch ( index_request - > type ) {
2017-04-23 21:24:06 +00:00
case Index_DoIndex : : Type : : ImportThenParse : {
2017-04-24 01:01:51 +00:00
// This assumes index_request->path is a cc or translation unit file (ie,
// it is in compile_commands.json).
2017-05-16 07:26:26 +00:00
bool needs_reparse = ImportCachedIndex ( config , file_consumer_shared , queue_do_id_map , index_request - > entry . filename , index_request - > content ) ;
2017-04-21 04:06:15 +00:00
// If the file has been updated, we need to reparse it.
2017-04-24 01:01:51 +00:00
if ( needs_reparse ) {
2017-04-23 21:24:06 +00:00
// Instead of parsing the file immediately, we push the request to the
2017-04-21 04:06:15 +00:00
// back of the queue so we will finish all of the Import requests
2017-04-23 21:24:06 +00:00
// before starting to run actual index jobs. This gives the user a
2017-04-21 04:06:15 +00:00
// partially-correct index potentially much sooner.
index_request - > type = Index_DoIndex : : Type : : Parse ;
queue_do_index - > Enqueue ( std : : move ( * index_request ) ) ;
}
break ;
}
2017-04-08 20:00:08 +00:00
2017-04-21 04:06:15 +00:00
case Index_DoIndex : : Type : : Parse : {
2017-04-24 01:01:51 +00:00
// index_request->path can be a cc/tu or a dependency path.
2017-05-07 05:36:29 +00:00
file_consumer_shared - > Reset ( index_request - > entry . filename ) ;
2017-05-25 02:04:19 +00:00
ParseFile ( config , working_files , file_consumer_shared , index , queue_do_id_map , index_request - > entry , index_request - > content , index_request - > is_interactive ) ;
2017-04-21 04:50:31 +00:00
break ;
}
case Index_DoIndex : : Type : : Freshen : {
2017-04-24 01:01:51 +00:00
// This assumes index_request->path is a cc or translation unit file (ie,
// it is in compile_commands.json).
2017-05-07 05:36:29 +00:00
bool needs_reparse = ResetStaleFiles ( config , file_consumer_shared , index_request - > entry . filename ) ;
2017-04-24 01:01:51 +00:00
if ( needs_reparse )
2017-05-25 02:04:19 +00:00
ParseFile ( config , working_files , file_consumer_shared , index , queue_do_id_map , index_request - > entry , index_request - > content , index_request - > is_interactive ) ;
2017-04-21 04:06:15 +00:00
break ;
}
2017-03-14 08:33:39 +00:00
}
2017-04-08 06:45:28 +00:00
return true ;
}
2017-04-11 05:26:27 +00:00
bool IndexMain_DoCreateIndexUpdate (
Index_OnIdMappedQueue * queue_on_id_mapped ,
Index_OnIndexedQueue * queue_on_indexed ) {
2017-04-08 20:00:08 +00:00
optional < Index_OnIdMapped > response = queue_on_id_mapped - > TryDequeue ( ) ;
if ( ! response )
2017-04-08 06:45:28 +00:00
return false ;
Timer time ;
IndexUpdate update = IndexUpdate : : CreateDelta ( response - > previous_id_map . get ( ) , response - > current_id_map . get ( ) ,
2017-04-08 20:00:08 +00:00
response - > previous_index . get ( ) , response - > current_index . get ( ) ) ;
2017-05-17 07:08:45 +00:00
response - > perf . index_make_delta = time . ElapsedMicrosecondsAndReset ( ) ;
2017-05-21 19:51:15 +00:00
# if false
2017-05-17 07:08:45 +00:00
# define PRINT_SECTION(name) \
if ( response - > perf . name ) { \
total + = response - > perf . name ; \
long long milliseconds = response - > perf . name / 1000 ; \
long long remaining = response - > perf . name - milliseconds ; \
output < < " " < < # name < < " : " < < FormatMicroseconds ( response - > perf . name ) ; \
}
std : : stringstream output ;
long long total = 0 ;
output < < " [perf] " ;
PRINT_SECTION ( index_parse ) ;
PRINT_SECTION ( index_build ) ;
PRINT_SECTION ( index_save_to_disk ) ;
PRINT_SECTION ( index_load_cached ) ;
PRINT_SECTION ( querydb_id_map ) ;
PRINT_SECTION ( index_make_delta ) ;
output < < " \n total: " < < FormatMicroseconds ( total ) ;
output < < " path: " < < response - > current_index - > path ;
output < < std : : endl ;
std : : cerr < < output . rdbuf ( ) ;
# undef PRINT_SECTION
2017-05-19 07:02:01 +00:00
if ( response - > is_interactive )
std : : cerr < < " Applying IndexUpdate " < < std : : endl < < update . ToString ( ) < < std : : endl ;
2017-05-21 19:51:15 +00:00
# endif
2017-05-19 07:02:01 +00:00
2017-05-17 07:08:45 +00:00
Index_OnIndexed reply ( update , response - > indexed_content , response - > perf ) ;
2017-04-08 06:45:28 +00:00
queue_on_indexed - > Enqueue ( std : : move ( reply ) ) ;
return true ;
}
2017-04-23 22:45:40 +00:00
bool IndexMergeIndexUpdates ( Index_OnIndexedQueue * queue_on_indexed ) {
2017-04-11 05:26:27 +00:00
optional < Index_OnIndexed > root = queue_on_indexed - > TryDequeue ( ) ;
if ( ! root )
2017-04-23 22:45:40 +00:00
return false ;
2017-04-11 05:26:27 +00:00
2017-04-23 22:45:40 +00:00
bool did_merge = false ;
2017-04-11 05:26:27 +00:00
while ( true ) {
optional < Index_OnIndexed > to_join = queue_on_indexed - > TryDequeue ( ) ;
if ( ! to_join ) {
queue_on_indexed - > Enqueue ( std : : move ( * root ) ) ;
2017-04-23 22:45:40 +00:00
return did_merge ;
2017-04-11 05:26:27 +00:00
}
2017-04-23 22:45:40 +00:00
did_merge = true ;
2017-05-21 19:51:15 +00:00
//Timer time;
2017-04-11 05:26:27 +00:00
root - > update . Merge ( to_join - > update ) ;
2017-05-16 07:26:26 +00:00
for ( auto & & entry : to_join - > indexed_content )
root - > indexed_content . emplace ( entry ) ;
2017-05-21 19:51:15 +00:00
//time.ResetAndPrint("[indexer] Joining two querydb updates");
2017-04-11 05:26:27 +00:00
}
}
2017-04-08 22:54:36 +00:00
void IndexMain (
2017-05-21 19:51:15 +00:00
Config * config ,
2017-04-23 21:24:06 +00:00
FileConsumer : : SharedState * file_consumer_shared ,
Project * project ,
2017-05-19 05:44:23 +00:00
WorkingFiles * working_files ,
2017-04-23 22:45:40 +00:00
MultiQueueWaiter * waiter ,
2017-04-23 21:24:06 +00:00
Index_DoIndexQueue * queue_do_index ,
Index_DoIdMapQueue * queue_do_id_map ,
Index_OnIdMappedQueue * queue_on_id_mapped ,
Index_OnIndexedQueue * queue_on_indexed ) {
2017-04-08 22:54:36 +00:00
2017-04-19 00:05:14 +00:00
SetCurrentThreadName ( " indexer " ) ;
2017-05-25 02:04:19 +00:00
// TODO: dispose of index after it is not used for a while.
clang : : Index index ( 1 , 0 ) ;
2017-04-08 06:45:28 +00:00
while ( true ) {
// TODO: process all off IndexMain_DoIndex before calling IndexMain_DoCreateIndexUpdate for
// better icache behavior. We need to have some threads spinning on both though
// otherwise memory usage will get bad.
2017-04-19 07:32:59 +00:00
// We need to make sure to run both IndexMain_DoIndex and
// IndexMain_DoCreateIndexUpdate so we don't starve querydb from doing any
// work. Running both also lets the user query the partially constructed
// index.
2017-05-25 02:04:19 +00:00
bool did_index = IndexMain_DoIndex ( config , file_consumer_shared , project , working_files , & index , queue_do_index , queue_do_id_map ) ;
2017-04-19 07:32:59 +00:00
bool did_create_update = IndexMain_DoCreateIndexUpdate ( queue_on_id_mapped , queue_on_indexed ) ;
2017-04-23 22:45:40 +00:00
bool did_merge = false ;
// Nothing to index and no index updates to create, so join some already
// created index updates to reduce work on querydb thread.
if ( ! did_index & & ! did_create_update )
did_merge = IndexMergeIndexUpdates ( queue_on_indexed ) ;
// We didn't do any work, so wait for a notification.
if ( ! did_index & & ! did_create_update & & ! did_merge )
waiter - > Wait ( {
queue_do_index ,
queue_on_id_mapped ,
queue_on_indexed
} ) ;
2017-04-08 06:45:28 +00:00
}
2017-03-14 08:33:39 +00:00
}
2017-03-05 19:48:05 +00:00
2017-04-08 07:52:57 +00:00
2017-03-15 04:59:05 +00:00
2017-04-08 07:52:57 +00:00
2017-04-16 19:02:29 +00:00
2017-04-08 07:52:57 +00:00
2017-03-16 07:36:49 +00:00
2017-04-23 22:45:40 +00:00
bool QueryDbMainLoop (
2017-05-21 19:51:15 +00:00
Config * config ,
2017-04-23 21:24:06 +00:00
QueryDatabase * db ,
2017-04-23 22:45:40 +00:00
MultiQueueWaiter * waiter ,
2017-04-23 21:24:06 +00:00
Index_DoIndexQueue * queue_do_index ,
Index_DoIdMapQueue * queue_do_id_map ,
Index_OnIdMappedQueue * queue_on_id_mapped ,
Index_OnIndexedQueue * queue_on_indexed ,
Project * project ,
FileConsumer : : SharedState * file_consumer_shared ,
WorkingFiles * working_files ,
2017-05-27 04:21:00 +00:00
ClangCompleteManager * clang_complete ,
IncludeComplete * include_complete ,
2017-05-26 07:10:55 +00:00
CodeCompleteCache * global_code_complete_cache ,
CodeCompleteCache * non_global_code_complete_cache ,
2017-05-20 08:20:37 +00:00
CodeCompleteCache * signature_cache ) {
2017-04-16 21:49:48 +00:00
IpcManager * ipc = IpcManager : : instance ( ) ;
2017-03-26 21:40:34 +00:00
2017-04-23 22:45:40 +00:00
bool did_work = false ;
2017-04-16 21:49:48 +00:00
std : : vector < std : : unique_ptr < BaseIpcMessage > > messages = ipc - > GetMessages ( IpcManager : : Destination : : Server ) ;
2017-03-05 19:48:05 +00:00
for ( auto & message : messages ) {
2017-04-23 22:45:40 +00:00
did_work = true ;
2017-05-19 05:44:23 +00:00
//std::cerr << "[querydb] Processing message " << IpcIdToString(message->method_id) << std::endl;
2017-03-05 19:48:05 +00:00
2017-03-25 19:18:25 +00:00
switch ( message - > method_id ) {
2017-04-23 20:19:09 +00:00
case IpcId : : Initialize : {
auto request = static_cast < Ipc_InitializeRequest * > ( message . get ( ) ) ;
2017-04-26 01:32:45 +00:00
// Log initialization parameters.
rapidjson : : StringBuffer output ;
Writer writer ( output ) ;
Reflect ( writer , request - > params . initializationOptions ) ;
std : : cerr < < output . GetString ( ) < < std : : endl ;
2017-04-23 20:19:09 +00:00
if ( request - > params . rootUri ) {
std : : string project_path = request - > params . rootUri - > GetPath ( ) ;
2017-04-24 01:24:09 +00:00
std : : cerr < < " [querydb] Initialize in directory " < < project_path
2017-04-23 20:19:09 +00:00
< < " with uri " < < request - > params . rootUri - > raw_uri
< < std : : endl ;
if ( ! request - > params . initializationOptions ) {
std : : cerr < < " Initialization parameters (particularily cacheDirectory) are required " < < std : : endl ;
exit ( 1 ) ;
}
2017-03-05 19:48:05 +00:00
2017-04-23 20:19:09 +00:00
* config = * request - > params . initializationOptions ;
2017-03-15 07:14:44 +00:00
2017-05-20 19:31:07 +00:00
// Check client version.
if ( config - > clientVersion ! = kExpectedClientVersion ) {
Out_ShowLogMessage out ;
out . display_type = Out_ShowLogMessage : : DisplayType : : Show ;
out . params . type = lsMessageType : : Error ;
out . params . message = " cquery client (v " + std : : to_string ( config - > clientVersion ) + " ) and server (v " + std : : to_string ( kExpectedClientVersion ) + " ) version mismatch. Please update " ;
if ( config - > clientVersion > kExpectedClientVersion )
out . params . message + = " the cquery binary. " ;
else
2017-06-14 07:13:26 +00:00
out . params . message + = " your extension client (VSIX file). Make sure to uninstall the cquery extension and restart vscode before reinstalling. " ;
2017-05-20 19:31:07 +00:00
out . Write ( std : : cout ) ;
}
2017-04-23 20:19:09 +00:00
// Make sure cache directory is valid.
if ( config - > cacheDirectory . empty ( ) ) {
2017-05-21 19:51:15 +00:00
std : : cerr < < " [fatal] No cache directory " < < std : : endl ;
2017-04-23 20:19:09 +00:00
exit ( 1 ) ;
}
config - > cacheDirectory = NormalizePath ( config - > cacheDirectory ) ;
2017-05-21 19:51:15 +00:00
EnsureEndsInSlash ( config - > cacheDirectory ) ;
2017-04-23 20:19:09 +00:00
MakeDirectoryRecursive ( config - > cacheDirectory ) ;
2017-05-21 07:37:53 +00:00
// Set project root.
config - > projectRoot = NormalizePath ( request - > params . rootUri - > GetPath ( ) ) ;
2017-05-21 19:51:15 +00:00
EnsureEndsInSlash ( config - > projectRoot ) ;
2017-05-21 07:37:53 +00:00
2017-04-23 20:19:09 +00:00
// Start indexer threads.
int indexer_count = std : : max < int > ( std : : thread : : hardware_concurrency ( ) , 2 ) - 1 ;
if ( config - > indexerCount > 0 )
indexer_count = config - > indexerCount ;
std : : cerr < < " [querydb] Starting " < < indexer_count < < " indexers " < < std : : endl ;
for ( int i = 0 ; i < indexer_count ; + + i ) {
new std : : thread ( [ & ] ( ) {
2017-05-19 05:44:23 +00:00
IndexMain ( config , file_consumer_shared , project , working_files , waiter , queue_do_index , queue_do_id_map , queue_on_id_mapped , queue_on_indexed ) ;
2017-04-23 20:19:09 +00:00
} ) ;
}
2017-03-25 19:18:25 +00:00
2017-05-21 19:51:15 +00:00
Timer time ;
2017-04-23 20:19:09 +00:00
// Open up / load the project.
2017-04-26 04:03:22 +00:00
project - > Load ( config - > extraClangArguments , project_path ) ;
2017-05-21 19:51:15 +00:00
time . ResetAndPrint ( " [perf] Loaded compilation entries ( " + std : : to_string ( project - > entries . size ( ) ) + " files) " ) ;
2017-03-26 21:40:34 +00:00
2017-05-21 19:51:15 +00:00
// Start scanning include directories before dispatching project files, because that takes a long time.
2017-05-27 04:21:00 +00:00
include_complete - > Rescan ( ) ;
2017-04-16 23:52:42 +00:00
2017-05-21 19:51:15 +00:00
time . Reset ( ) ;
project - > ForAllFilteredFiles ( config , [ & ] ( int i , const Project : : Entry & entry ) {
//std::cerr << "[" << i << "/" << (project->entries.size() - 1)
// << "] Dispatching index request for file " << entry.filename
// << std::endl;
2017-05-19 07:02:01 +00:00
queue_do_index - > Enqueue ( Index_DoIndex ( Index_DoIndex : : Type : : ImportThenParse , entry , nullopt , false /*is_interactive*/ ) ) ;
2017-04-23 20:19:09 +00:00
} ) ;
2017-05-21 19:51:15 +00:00
time . ResetAndPrint ( " [perf] Dispatched initial index requests " ) ;
2017-04-23 20:19:09 +00:00
}
2017-04-16 23:52:42 +00:00
2017-04-23 20:19:09 +00:00
// TODO: query request->params.capabilities.textDocument and support only things
// the client supports.
2017-04-16 23:52:42 +00:00
2017-04-23 20:19:09 +00:00
auto response = Out_InitializeResponse ( ) ;
response . id = request - > id ;
2017-03-15 07:14:44 +00:00
2017-04-23 20:19:09 +00:00
//response.result.capabilities.textDocumentSync = lsTextDocumentSyncOptions();
//response.result.capabilities.textDocumentSync->openClose = true;
//response.result.capabilities.textDocumentSync->change = lsTextDocumentSyncKind::Full;
//response.result.capabilities.textDocumentSync->willSave = true;
//response.result.capabilities.textDocumentSync->willSaveWaitUntil = true;
response . result . capabilities . textDocumentSync = lsTextDocumentSyncKind : : Incremental ;
2017-04-22 07:32:29 +00:00
2017-04-23 20:19:09 +00:00
response . result . capabilities . renameProvider = true ;
2017-04-21 06:32:18 +00:00
2017-04-23 20:19:09 +00:00
response . result . capabilities . completionProvider = lsCompletionOptions ( ) ;
response . result . capabilities . completionProvider - > resolveProvider = false ;
2017-04-24 01:52:38 +00:00
// vscode doesn't support trigger character sequences, so we use ':' for '::' and '>' for '->'.
// See https://github.com/Microsoft/language-server-protocol/issues/138.
2017-05-21 07:37:53 +00:00
response . result . capabilities . completionProvider - > triggerCharacters = { " . " , " : " , " > " , " # " } ;
2017-04-09 19:38:52 +00:00
2017-05-15 07:28:53 +00:00
response . result . capabilities . signatureHelpProvider = lsSignatureHelpOptions ( ) ;
// NOTE: If updating signature help tokens make sure to also update
// WorkingFile::FindClosestCallNameInBuffer.
response . result . capabilities . signatureHelpProvider - > triggerCharacters = { " ( " , " , " } ;
2017-04-23 20:19:09 +00:00
response . result . capabilities . codeLensProvider = lsCodeLensOptions ( ) ;
response . result . capabilities . codeLensProvider - > resolveProvider = false ;
2017-04-09 19:38:52 +00:00
2017-04-23 20:19:09 +00:00
response . result . capabilities . definitionProvider = true ;
response . result . capabilities . documentHighlightProvider = true ;
response . result . capabilities . hoverProvider = true ;
response . result . capabilities . referencesProvider = true ;
2017-04-21 04:09:54 +00:00
2017-05-20 19:31:07 +00:00
response . result . capabilities . codeActionProvider = true ;
2017-04-23 20:19:09 +00:00
response . result . capabilities . documentSymbolProvider = true ;
response . result . capabilities . workspaceSymbolProvider = true ;
2017-04-10 00:17:49 +00:00
2017-05-21 04:30:59 +00:00
response . result . capabilities . documentLinkProvider = lsDocumentLinkOptions ( ) ;
response . result . capabilities . documentLinkProvider - > resolveProvider = false ;
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : Initialize , response ) ;
break ;
}
2017-04-14 08:21:03 +00:00
2017-05-03 06:45:10 +00:00
case IpcId : : Exit : {
exit ( 0 ) ;
break ;
}
2017-04-23 20:19:09 +00:00
case IpcId : : CqueryFreshenIndex : {
std : : cerr < < " Freshening " < < project - > entries . size ( ) < < " files " < < std : : endl ;
project - > ForAllFilteredFiles ( config , [ & ] ( int i , const Project : : Entry & entry ) {
std : : cerr < < " [ " < < i < < " / " < < ( project - > entries . size ( ) - 1 )
< < " ] Dispatching index request for file " < < entry . filename
< < std : : endl ;
2017-05-19 07:02:01 +00:00
queue_do_index - > Enqueue ( Index_DoIndex ( Index_DoIndex : : Type : : Freshen , entry , nullopt , false /*is_interactive*/ ) ) ;
2017-04-23 20:19:09 +00:00
} ) ;
2017-04-14 08:21:03 +00:00
break ;
}
2017-04-19 07:52:48 +00:00
2017-05-24 07:17:29 +00:00
case IpcId : : CqueryTypeHierarchyTree : {
auto msg = static_cast < Ipc_CqueryTypeHierarchyTree * > ( message . get ( ) ) ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-05-24 07:17:29 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-05-24 07:17:29 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
Out_CqueryTypeHierarchyTree response ;
response . id = msg - > id ;
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
if ( ref . idx . kind = = SymbolKind : : Type ) {
response . result = BuildTypeHierarchy ( db , working_files , QueryTypeId ( ref . idx . idx ) ) ;
break ;
}
}
ipc - > SendOutMessageToClient ( IpcId : : CqueryTypeHierarchyTree , response ) ;
break ;
}
case IpcId : : CqueryCallTreeInitial : {
auto msg = static_cast < Ipc_CqueryCallTreeInitial * > ( message . get ( ) ) ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-05-24 07:17:29 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-05-24 07:17:29 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
Out_CqueryCallTree response ;
response . id = msg - > id ;
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
if ( ref . idx . kind = = SymbolKind : : Func ) {
response . result = BuildInitialCallTree ( db , working_files , QueryFuncId ( ref . idx . idx ) ) ;
break ;
}
}
ipc - > SendOutMessageToClient ( IpcId : : CqueryCallTreeInitial , response ) ;
break ;
}
case IpcId : : CqueryCallTreeExpand : {
auto msg = static_cast < Ipc_CqueryCallTreeExpand * > ( message . get ( ) ) ;
Out_CqueryCallTree response ;
response . id = msg - > id ;
auto func_id = db - > usr_to_func . find ( msg - > params . usr ) ;
if ( func_id ! = db - > usr_to_func . end ( ) )
response . result = BuildExpandCallTree ( db , working_files , func_id - > second ) ;
ipc - > SendOutMessageToClient ( IpcId : : CqueryCallTreeExpand , response ) ;
break ;
}
2017-05-07 06:56:04 +00:00
case IpcId : : CqueryVars : {
auto msg = static_cast < Ipc_CqueryVars * > ( message . get ( ) ) ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-05-07 06:56:04 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-05-07 06:56:04 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
Out_LocationList response ;
response . id = msg - > id ;
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
if ( ref . idx . kind = = SymbolKind : : Type ) {
optional < QueryType > & type = db - > types [ ref . idx . idx ] ;
if ( ! type ) continue ;
std : : vector < QueryLocation > locations = ToQueryLocation ( db , type - > instances ) ;
response . result = GetLsLocations ( db , working_files , locations ) ;
}
}
2017-05-24 07:17:29 +00:00
ipc - > SendOutMessageToClient ( IpcId : : CqueryVars , response ) ;
2017-05-07 06:56:04 +00:00
break ;
}
case IpcId : : CqueryCallers : {
auto msg = static_cast < Ipc_CqueryCallers * > ( message . get ( ) ) ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-05-07 06:56:04 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-05-07 06:56:04 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
Out_LocationList response ;
response . id = msg - > id ;
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
if ( ref . idx . kind = = SymbolKind : : Func ) {
optional < QueryFunc > & func = db - > funcs [ ref . idx . idx ] ;
if ( ! func ) continue ;
std : : vector < QueryLocation > locations = ToQueryLocation ( db , func - > callers ) ;
response . result = GetLsLocations ( db , working_files , locations ) ;
}
}
2017-05-24 07:17:29 +00:00
ipc - > SendOutMessageToClient ( IpcId : : CqueryCallers , response ) ;
2017-05-07 06:56:04 +00:00
break ;
}
case IpcId : : CqueryBase : {
auto msg = static_cast < Ipc_CqueryBase * > ( message . get ( ) ) ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-05-07 06:56:04 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-05-07 06:56:04 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
Out_LocationList response ;
response . id = msg - > id ;
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
if ( ref . idx . kind = = SymbolKind : : Type ) {
optional < QueryType > & type = db - > types [ ref . idx . idx ] ;
if ( ! type ) continue ;
std : : vector < QueryLocation > locations = ToQueryLocation ( db , type - > def . parents ) ;
response . result = GetLsLocations ( db , working_files , locations ) ;
}
else if ( ref . idx . kind = = SymbolKind : : Func ) {
optional < QueryFunc > & func = db - > funcs [ ref . idx . idx ] ;
if ( ! func ) continue ;
optional < QueryLocation > location = GetBaseDefinitionOrDeclarationSpelling ( db , * func ) ;
if ( ! location ) continue ;
optional < lsLocation > ls_loc = GetLsLocation ( db , working_files , * location ) ;
if ( ! ls_loc ) continue ;
response . result . push_back ( * ls_loc ) ;
}
}
2017-05-24 07:17:29 +00:00
ipc - > SendOutMessageToClient ( IpcId : : CqueryBase , response ) ;
2017-05-07 06:56:04 +00:00
break ;
}
case IpcId : : CqueryDerived : {
auto msg = static_cast < Ipc_CqueryDerived * > ( message . get ( ) ) ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-05-07 06:56:04 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-05-07 06:56:04 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
Out_LocationList response ;
response . id = msg - > id ;
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
if ( ref . idx . kind = = SymbolKind : : Type ) {
optional < QueryType > & type = db - > types [ ref . idx . idx ] ;
if ( ! type ) continue ;
std : : vector < QueryLocation > locations = ToQueryLocation ( db , type - > derived ) ;
response . result = GetLsLocations ( db , working_files , locations ) ;
}
else if ( ref . idx . kind = = SymbolKind : : Func ) {
optional < QueryFunc > & func = db - > funcs [ ref . idx . idx ] ;
if ( ! func ) continue ;
std : : vector < QueryLocation > locations = ToQueryLocation ( db , func - > derived ) ;
response . result = GetLsLocations ( db , working_files , locations ) ;
}
}
2017-05-24 07:17:29 +00:00
ipc - > SendOutMessageToClient ( IpcId : : CqueryDerived , response ) ;
2017-05-07 06:56:04 +00:00
break ;
}
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentDidOpen : {
// NOTE: This function blocks code lens. If it starts taking a long time
// we will need to find a way to unblock the code lens request.
Timer time ;
auto msg = static_cast < Ipc_TextDocumentDidOpen * > ( message . get ( ) ) ;
2017-05-26 06:40:38 +00:00
std : : string path = msg - > params . textDocument . uri . GetPath ( ) ;
2017-04-23 20:19:09 +00:00
WorkingFile * working_file = working_files - > OnOpen ( msg - > params ) ;
2017-05-26 06:40:38 +00:00
optional < std : : string > cached_file_contents = LoadCachedFileContents ( config , path ) ;
2017-04-23 20:19:09 +00:00
if ( cached_file_contents )
working_file - > SetIndexContent ( * cached_file_contents ) ;
else
working_file - > SetIndexContent ( working_file - > buffer_content ) ;
2017-05-20 21:45:46 +00:00
2017-05-26 06:40:38 +00:00
std : : unique_ptr < IndexFile > cache = LoadCachedIndex ( config , path ) ;
2017-05-20 21:45:46 +00:00
if ( cache & & ! cache - > skipped_by_preprocessor . empty ( ) )
PublishInactiveLines ( working_file , cache - > skipped_by_preprocessor ) ;
time . ResetAndPrint ( " [querydb] Loading cached index file for DidOpen (blocks CodeLens) " ) ;
2017-04-14 08:21:03 +00:00
2017-05-27 04:21:00 +00:00
include_complete - > AddFile ( working_file - > filename ) ;
clang_complete - > NotifyView ( path ) ;
2017-06-16 05:56:07 +00:00
PriorityEnqueueFileForIndex ( db , project , queue_do_index , working_file , path ) ;
2017-05-21 19:51:15 +00:00
2017-04-23 20:19:09 +00:00
break ;
}
2017-04-23 21:24:06 +00:00
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentDidChange : {
auto msg = static_cast < Ipc_TextDocumentDidChange * > ( message . get ( ) ) ;
2017-05-26 06:40:38 +00:00
std : : string path = msg - > params . textDocument . uri . GetPath ( ) ;
2017-04-23 20:19:09 +00:00
working_files - > OnChange ( msg - > params ) ;
2017-05-27 04:21:00 +00:00
clang_complete - > NotifyEdit ( path ) ;
2017-04-23 20:19:09 +00:00
break ;
}
2017-04-23 21:24:06 +00:00
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentDidClose : {
auto msg = static_cast < Ipc_TextDocumentDidClose * > ( message . get ( ) ) ;
2017-05-29 23:33:57 +00:00
// Clear any diagnostics for the file.
Out_TextDocumentPublishDiagnostics diag ;
diag . params . uri = msg - > params . textDocument . uri ;
IpcManager : : instance ( ) - > SendOutMessageToClient ( IpcId : : TextDocumentPublishDiagnostics , diag ) ;
// Remove internal state.
2017-04-23 20:19:09 +00:00
working_files - > OnClose ( msg - > params ) ;
2017-05-29 23:33:57 +00:00
2017-04-15 05:14:05 +00:00
break ;
2017-04-14 08:21:03 +00:00
}
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentDidSave : {
auto msg = static_cast < Ipc_TextDocumentDidSave * > ( message . get ( ) ) ;
2017-04-17 01:22:59 +00:00
2017-04-23 20:19:09 +00:00
std : : string path = msg - > params . textDocument . uri . GetPath ( ) ;
2017-05-09 05:09:57 +00:00
// Send out an index request, and copy the current buffer state so we
// can update the cached index contents when the index is done.
//
// We also do not index if there is already an index request.
//
// TODO: Cancel outgoing index request. Might be tricky to make
// efficient since we have to cancel.
// - we could have an |atomic<int> active_cancellations| variable
// that all of the indexers check before accepting an index. if
// zero we don't slow down fast-path. if non-zero we acquire
// mutex and check to see if we should skip the current request.
// if so, ignore that index response.
2017-04-23 20:19:09 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( path ) ;
2017-06-16 05:56:07 +00:00
if ( working_file )
PriorityEnqueueFileForIndex ( db , project , queue_do_index , working_file , path ) ;
2017-05-26 06:40:38 +00:00
2017-05-27 04:21:00 +00:00
clang_complete - > NotifySave ( path ) ;
2017-04-17 01:22:59 +00:00
2017-04-23 20:19:09 +00:00
break ;
}
2017-04-17 01:22:59 +00:00
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentRename : {
auto msg = static_cast < Ipc_TextDocumentRename * > ( message . get ( ) ) ;
2017-04-17 01:22:59 +00:00
2017-04-23 20:19:09 +00:00
QueryFileId file_id ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file , & file_id ) )
2017-04-23 20:19:09 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-04-23 20:19:09 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
2017-03-26 06:47:59 +00:00
2017-04-23 20:19:09 +00:00
Out_TextDocumentRename response ;
response . id = msg - > id ;
2017-03-26 06:47:59 +00:00
2017-04-23 20:19:09 +00:00
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
// Found symbol. Return references to rename.
std : : vector < QueryLocation > uses = GetUsesOfSymbol ( db , ref . idx ) ;
response . result = BuildWorkspaceEdit ( db , working_files , uses , msg - > params . newName ) ;
break ;
}
2017-04-03 02:21:21 +00:00
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentRename , response ) ;
2017-04-03 02:21:21 +00:00
break ;
}
2017-04-15 05:14:05 +00:00
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentCompletion : {
2017-06-29 04:59:16 +00:00
auto msg = std : : shared_ptr < Ipc_TextDocumentComplete > (
static_cast < Ipc_TextDocumentComplete * > ( message . release ( ) ) ) ;
2017-04-15 05:14:05 +00:00
2017-05-27 04:21:00 +00:00
std : : string path = msg - > params . textDocument . uri . GetPath ( ) ;
2017-05-26 07:10:55 +00:00
WorkingFile * file = working_files - > GetFileByFilename ( path ) ;
2017-05-20 08:07:29 +00:00
2017-05-27 06:51:32 +00:00
// It shouldn't be possible, but sometimes vscode will send queries out
// of order, ie, we get completion request before buffer content update.
std : : string buffer_line ;
if ( msg - > params . position . line > = 0 & & msg - > params . position . line < file - > all_buffer_lines . size ( ) )
buffer_line = file - > all_buffer_lines [ msg - > params . position . line ] ;
2017-05-21 07:37:53 +00:00
if ( ShouldRunIncludeCompletion ( buffer_line ) ) {
2017-05-20 06:35:14 +00:00
Out_TextDocumentComplete complete_response ;
complete_response . id = msg - > id ;
complete_response . result . isIncomplete = false ;
2017-05-21 21:01:52 +00:00
2017-05-21 19:51:15 +00:00
{
2017-05-27 04:21:00 +00:00
std : : unique_lock < std : : mutex > lock ( include_complete - > completion_items_mutex , std : : defer_lock ) ;
if ( include_complete - > is_scanning )
2017-05-21 19:51:15 +00:00
lock . lock ( ) ;
complete_response . result . items . assign (
2017-05-27 04:21:00 +00:00
include_complete - > completion_items . begin ( ) ,
include_complete - > completion_items . end ( ) ) ;
2017-05-21 19:51:15 +00:00
if ( lock )
lock . unlock ( ) ;
// Update textEdit params.
for ( lsCompletionItem & item : complete_response . result . items ) {
2017-05-27 04:21:00 +00:00
item . textEdit - > range . start . line = msg - > params . position . line ;
2017-05-21 19:51:15 +00:00
item . textEdit - > range . start . character = 0 ;
2017-05-27 04:21:00 +00:00
item . textEdit - > range . end . line = msg - > params . position . line ;
2017-05-21 19:51:15 +00:00
item . textEdit - > range . end . character = ( int ) buffer_line . size ( ) ;
2017-05-21 07:37:53 +00:00
}
}
2017-05-21 21:01:52 +00:00
std : : cerr < < " [complete] Returning " < < complete_response . result . items . size ( ) < < " include completions " < < std : : endl ;
2017-06-16 02:28:49 +00:00
FilterCompletionResponse ( & complete_response , buffer_line ) ;
2017-05-21 07:37:53 +00:00
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentCompletion , complete_response ) ;
2017-05-20 08:07:29 +00:00
}
else {
2017-05-26 07:10:55 +00:00
bool is_global_completion = false ;
2017-06-16 02:28:49 +00:00
std : : string existing_completion ;
if ( file ) {
msg - > params . position = file - > FindStableCompletionSource ( msg - > params . position , & is_global_completion , & existing_completion ) ;
}
std : : cerr < < " [complete] Got existing completion " < < existing_completion ;
2017-05-21 07:37:53 +00:00
2017-06-09 07:08:06 +00:00
ClangCompleteManager : : OnComplete callback = std : : bind (
2017-06-30 06:51:22 +00:00
[ working_files , global_code_complete_cache , non_global_code_complete_cache , is_global_completion , existing_completion , msg ]
( const NonElidedVector < lsCompletionItem > & results , bool is_cached_result ) {
2017-05-21 07:37:53 +00:00
Out_TextDocumentComplete complete_response ;
complete_response . id = msg - > id ;
complete_response . result . isIncomplete = false ;
complete_response . result . items = results ;
// Emit completion results.
2017-06-16 02:28:49 +00:00
FilterCompletionResponse ( & complete_response , existing_completion ) ;
2017-06-10 04:13:16 +00:00
IpcManager : : instance ( ) - > SendOutMessageToClient ( IpcId : : TextDocumentCompletion , complete_response ) ;
2017-05-21 07:37:53 +00:00
2017-05-26 07:10:55 +00:00
// Cache completion results.
2017-06-30 06:51:22 +00:00
if ( ! is_cached_result ) {
std : : string path = msg - > params . textDocument . uri . GetPath ( ) ;
if ( is_global_completion ) {
global_code_complete_cache - > WithLock ( [ & ] ( ) {
global_code_complete_cache - > cached_path_ = path ;
std : : cerr < < " [complete] Updating global_code_complete_cache->cached_results [0] " < < std : : endl ;
global_code_complete_cache - > cached_results_ = results ;
std : : cerr < < " [complete] DONE Updating global_code_complete_cache->cached_results [0] " < < std : : endl ;
} ) ;
}
else {
non_global_code_complete_cache - > WithLock ( [ & ] ( ) {
non_global_code_complete_cache - > cached_path_ = path ;
non_global_code_complete_cache - > cached_completion_position_ = msg - > params . position ;
std : : cerr < < " [complete] Updating non_global_code_complete_cache->cached_results [1] " < < std : : endl ;
non_global_code_complete_cache - > cached_results_ = results ;
std : : cerr < < " [complete] DONE Updating non_global_code_complete_cache->cached_results [1] " < < std : : endl ;
} ) ;
}
2017-05-26 07:10:55 +00:00
}
2017-06-30 06:51:22 +00:00
} , std : : placeholders : : _1 , std : : placeholders : : _2 ) ;
2017-05-21 07:37:53 +00:00
2017-06-30 06:51:22 +00:00
bool is_cache_match = false ;
global_code_complete_cache - > WithLock ( [ & ] ( ) {
is_cache_match = is_global_completion & & global_code_complete_cache - > cached_path_ = = path & & ! global_code_complete_cache - > cached_results_ . empty ( ) ;
} ) ;
if ( is_cache_match ) {
2017-05-27 04:21:00 +00:00
std : : cerr < < " [complete] Early-returning cached global completion results at " < < msg - > params . position . ToString ( ) < < std : : endl ;
2017-05-26 07:10:55 +00:00
2017-06-10 04:13:16 +00:00
ClangCompleteManager : : OnComplete freshen_global =
[ global_code_complete_cache ]
2017-06-30 06:51:22 +00:00
( NonElidedVector < lsCompletionItem > results , bool is_cached_result ) {
assert ( ! is_cached_result ) ;
2017-06-10 04:13:16 +00:00
2017-06-15 05:47:10 +00:00
std : : cerr < < " [complete] Updating global_code_complete_cache->cached_results [2] " < < std : : endl ;
2017-05-26 07:10:55 +00:00
// note: path is updated in the normal completion handler.
2017-06-30 06:51:22 +00:00
global_code_complete_cache - > WithLock ( [ & ] ( ) {
global_code_complete_cache - > cached_results_ = results ;
} ) ;
std : : cerr < < " [complete] DONE Updating global_code_complete_cache->cached_results [2] " < < std : : endl ;
2017-05-27 04:21:00 +00:00
} ;
2017-05-26 07:10:55 +00:00
2017-06-30 06:51:22 +00:00
global_code_complete_cache - > WithLock ( [ & ] ( ) {
callback ( global_code_complete_cache - > cached_results_ , true /*is_cached_result*/ ) ;
} ) ;
clang_complete - > CodeComplete ( msg - > params , freshen_global ) ;
2017-05-26 07:10:55 +00:00
}
2017-05-27 04:21:00 +00:00
else if ( non_global_code_complete_cache - > IsCacheValid ( msg - > params ) ) {
std : : cerr < < " [complete] Using cached completion results at " < < msg - > params . position . ToString ( ) < < std : : endl ;
2017-06-30 06:51:22 +00:00
non_global_code_complete_cache - > WithLock ( [ & ] ( ) {
callback ( non_global_code_complete_cache - > cached_results_ , true /*is_cached_result*/ ) ;
} ) ;
2017-05-21 07:37:53 +00:00
}
else {
2017-06-30 06:51:22 +00:00
clang_complete - > CodeComplete ( msg - > params , callback ) ;
2017-05-21 07:37:53 +00:00
}
2017-05-20 08:07:29 +00:00
}
2017-04-14 06:43:50 +00:00
break ;
}
2017-04-19 07:52:48 +00:00
2017-05-15 07:28:53 +00:00
case IpcId : : TextDocumentSignatureHelp : {
auto msg = static_cast < Ipc_TextDocumentSignatureHelp * > ( message . get ( ) ) ;
2017-05-20 08:20:37 +00:00
lsTextDocumentPositionParams & params = msg - > params ;
2017-05-15 07:28:53 +00:00
WorkingFile * file = working_files - > GetFileByFilename ( params . textDocument . uri . GetPath ( ) ) ;
std : : string search ;
int active_param = 0 ;
if ( file ) {
lsPosition completion_position ;
search = file - > FindClosestCallNameInBuffer ( params . position , & active_param , & completion_position ) ;
params . position = completion_position ;
}
2017-05-20 08:20:37 +00:00
//std::cerr << "[completion] Returning signatures for " << search << std::endl;
2017-05-15 07:28:53 +00:00
if ( search . empty ( ) )
break ;
2017-06-30 06:51:22 +00:00
ClangCompleteManager : : OnComplete callback = std : : bind (
[ signature_cache ]
( BaseIpcMessage * message , std : : string search , int active_param , const NonElidedVector < lsCompletionItem > & results , bool is_cached_result ) {
2017-05-15 07:28:53 +00:00
auto msg = static_cast < Ipc_TextDocumentSignatureHelp * > ( message ) ;
auto ipc = IpcManager : : instance ( ) ;
Out_TextDocumentSignatureHelp response ;
response . id = msg - > id ;
for ( auto & result : results ) {
if ( result . label ! = search )
continue ;
lsSignatureInformation signature ;
signature . label = result . detail ;
for ( auto & parameter : result . parameters_ ) {
lsParameterInformation ls_param ;
ls_param . label = parameter ;
signature . parameters . push_back ( ls_param ) ;
}
response . result . signatures . push_back ( signature ) ;
}
// Guess the signature the user wants based on available parameter
// count.
response . result . activeSignature = 0 ;
for ( size_t i = 0 ; i < response . result . signatures . size ( ) ; + + i ) {
if ( active_param < response . result . signatures . size ( ) ) {
2017-05-17 07:08:45 +00:00
response . result . activeSignature = ( int ) i ;
2017-05-15 07:28:53 +00:00
break ;
}
}
// Set signature to what we parsed from the working file.
response . result . activeParameter = active_param ;
Timer timer ;
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentSignatureHelp , response ) ;
timer . ResetAndPrint ( " [complete] Writing signature help results " ) ;
2017-06-30 06:51:22 +00:00
if ( ! is_cached_result ) {
signature_cache - > WithLock ( [ & ] ( ) {
signature_cache - > cached_path_ = msg - > params . textDocument . uri . GetPath ( ) ;
signature_cache - > cached_completion_position_ = msg - > params . position ;
std : : cerr < < " [complete] Updating signature_cache->cached_results [3] " < < std : : endl ;
signature_cache - > cached_results_ = results ;
} ) ;
}
2017-05-20 08:20:37 +00:00
2017-05-15 07:28:53 +00:00
delete message ;
2017-06-30 06:51:22 +00:00
} , message . release ( ) , search , active_param , std : : placeholders : : _1 , std : : placeholders : : _2 ) ;
2017-05-15 07:28:53 +00:00
2017-05-20 08:23:01 +00:00
if ( signature_cache - > IsCacheValid ( params ) ) {
2017-05-20 08:20:37 +00:00
std : : cerr < < " [complete] Using cached completion results at " < < params . position . ToString ( ) < < std : : endl ;
2017-06-30 06:51:22 +00:00
signature_cache - > WithLock ( [ & ] ( ) {
callback ( signature_cache - > cached_results_ , true /*is_cached_result*/ ) ;
} ) ;
2017-05-20 08:20:37 +00:00
}
else {
2017-05-27 04:21:00 +00:00
clang_complete - > CodeComplete ( params , std : : move ( callback ) ) ;
2017-05-20 08:20:37 +00:00
}
2017-05-15 07:28:53 +00:00
break ;
}
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentDefinition : {
auto msg = static_cast < Ipc_TextDocumentDefinition * > ( message . get ( ) ) ;
2017-04-14 06:43:50 +00:00
2017-04-23 20:19:09 +00:00
QueryFileId file_id ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file , & file_id ) )
2017-04-23 20:19:09 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-04-23 20:19:09 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
2017-04-15 05:14:05 +00:00
2017-04-23 20:19:09 +00:00
Out_TextDocumentDefinition response ;
response . id = msg - > id ;
2017-04-15 05:14:05 +00:00
2017-04-23 20:19:09 +00:00
int target_line = msg - > params . position . line + 1 ;
int target_column = msg - > params . position . character + 1 ;
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
// Found symbol. Return definition.
// Special cases which are handled:
// - symbol has declaration but no definition (ie, pure virtual)
// - start at spelling but end at extent for better mouse tooltip
// - goto declaration while in definition of recursive type
optional < QueryLocation > def_loc = GetDefinitionSpellingOfSymbol ( db , ref . idx ) ;
// We use spelling start and extent end because this causes vscode to
// highlight the entire definition when previewing / hoving with the
// mouse.
optional < QueryLocation > def_extent = GetDefinitionExtentOfSymbol ( db , ref . idx ) ;
if ( def_loc & & def_extent )
def_loc - > range . end = def_extent - > range . end ;
// If the cursor is currently at or in the definition we should goto
// the declaration if possible. We also want to use declarations if
// we're pointing to, ie, a pure virtual function which has no
// definition.
if ( ! def_loc | | ( def_loc - > path = = file_id & &
def_loc - > range . Contains ( target_line , target_column ) ) ) {
// Goto declaration.
std : : vector < QueryLocation > declarations = GetDeclarationsOfSymbolForGotoDefinition ( db , ref . idx ) ;
for ( auto declaration : declarations ) {
optional < lsLocation > ls_declaration = GetLsLocation ( db , working_files , declaration ) ;
if ( ls_declaration )
response . result . push_back ( * ls_declaration ) ;
}
// We found some declarations. Break so we don't add the definition location.
if ( ! response . result . empty ( ) )
break ;
}
2017-04-14 06:43:50 +00:00
2017-04-23 20:19:09 +00:00
if ( def_loc )
PushBack ( & response . result , GetLsLocation ( db , working_files , * def_loc ) ) ;
2017-04-14 06:43:50 +00:00
2017-04-23 20:19:09 +00:00
if ( ! response . result . empty ( ) )
break ;
}
2017-04-14 05:18:02 +00:00
2017-05-21 03:46:15 +00:00
// No symbols - check for includes.
if ( response . result . empty ( ) ) {
for ( const IndexInclude & include : file - > def . includes ) {
if ( include . line = = target_line ) {
lsLocation result ;
result . uri = lsDocumentUri : : FromPath ( include . resolved_path ) ;
response . result . push_back ( result ) ;
break ;
}
}
}
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentDefinition , response ) ;
2017-04-14 05:18:02 +00:00
break ;
}
2017-04-19 07:52:48 +00:00
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentDocumentHighlight : {
auto msg = static_cast < Ipc_TextDocumentDocumentHighlight * > ( message . get ( ) ) ;
2017-04-14 05:18:02 +00:00
2017-04-23 20:19:09 +00:00
QueryFileId file_id ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file , & file_id ) )
2017-04-23 20:19:09 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-04-23 20:19:09 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
2017-04-14 05:18:02 +00:00
2017-04-23 20:19:09 +00:00
Out_TextDocumentDocumentHighlight response ;
response . id = msg - > id ;
2017-04-14 05:18:02 +00:00
2017-04-23 20:19:09 +00:00
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
// Found symbol. Return references to highlight.
std : : vector < QueryLocation > uses = GetUsesOfSymbol ( db , ref . idx ) ;
response . result . reserve ( uses . size ( ) ) ;
for ( const QueryLocation & use : uses ) {
if ( use . path ! = file_id )
continue ;
optional < lsLocation > ls_location = GetLsLocation ( db , working_files , use ) ;
if ( ! ls_location )
continue ;
2017-04-14 05:18:02 +00:00
2017-04-23 20:19:09 +00:00
lsDocumentHighlight highlight ;
highlight . kind = lsDocumentHighlightKind : : Text ;
highlight . range = ls_location - > range ;
response . result . push_back ( highlight ) ;
}
break ;
}
2017-04-10 05:34:06 +00:00
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentDocumentHighlight , response ) ;
2017-04-10 05:34:06 +00:00
break ;
}
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentHover : {
auto msg = static_cast < Ipc_TextDocumentHover * > ( message . get ( ) ) ;
2017-04-10 05:34:06 +00:00
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-04-23 20:19:09 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-04-23 20:19:09 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
Out_TextDocumentHover response ;
response . id = msg - > id ;
2017-04-10 05:34:06 +00:00
2017-04-23 20:19:09 +00:00
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
// Found symbol. Return hover.
optional < lsRange > ls_range = GetLsRange ( working_files - > GetFileByFilename ( file - > def . path ) , ref . loc . range ) ;
if ( ! ls_range )
2017-04-15 05:14:05 +00:00
continue ;
2017-04-10 05:34:06 +00:00
2017-04-23 20:19:09 +00:00
response . result . contents = GetHoverForSymbol ( db , ref . idx ) ;
response . result . range = * ls_range ;
break ;
2017-04-10 05:34:06 +00:00
}
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentHover , response ) ;
2017-04-15 05:14:05 +00:00
break ;
2017-04-10 05:34:06 +00:00
}
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentReferences : {
auto msg = static_cast < Ipc_TextDocumentReferences * > ( message . get ( ) ) ;
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-04-23 20:19:09 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-04-23 20:19:09 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
2017-04-10 05:34:06 +00:00
2017-04-23 20:19:09 +00:00
Out_TextDocumentReferences response ;
response . id = msg - > id ;
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
for ( const SymbolRef & ref : FindSymbolsAtLocation ( working_file , file , msg - > params . position ) ) {
optional < QueryLocation > excluded_declaration ;
if ( ! msg - > params . context . includeDeclaration ) {
std : : cerr < < " Excluding declaration in references " < < std : : endl ;
excluded_declaration = GetDefinitionSpellingOfSymbol ( db , ref . idx ) ;
}
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
// Found symbol. Return references.
std : : vector < QueryLocation > uses = GetUsesOfSymbol ( db , ref . idx ) ;
response . result . reserve ( uses . size ( ) ) ;
for ( const QueryLocation & use : uses ) {
if ( excluded_declaration . has_value ( ) & & use = = * excluded_declaration )
continue ;
optional < lsLocation > ls_location = GetLsLocation ( db , working_files , use ) ;
if ( ls_location )
response . result . push_back ( * ls_location ) ;
}
break ;
}
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentReferences , response ) ;
2017-03-29 06:33:38 +00:00
break ;
}
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentDocumentSymbol : {
auto msg = static_cast < Ipc_TextDocumentDocumentSymbol * > ( message . get ( ) ) ;
2017-03-06 08:48:51 +00:00
2017-04-23 20:19:09 +00:00
Out_TextDocumentDocumentSymbol response ;
response . id = msg - > id ;
2017-03-25 19:18:25 +00:00
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-04-23 20:19:09 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
for ( SymbolRef ref : file - > def . outline ) {
optional < lsSymbolInformation > info = GetSymbolInfo ( db , working_files , ref . idx ) ;
if ( ! info )
continue ;
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
optional < lsLocation > location = GetLsLocation ( db , working_files , ref . loc ) ;
if ( ! location )
continue ;
info - > location = * location ;
response . result . push_back ( * info ) ;
}
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentDocumentSymbol , response ) ;
2017-03-29 06:33:38 +00:00
break ;
}
2017-04-23 20:19:09 +00:00
2017-05-21 04:30:59 +00:00
case IpcId : : TextDocumentDocumentLink : {
auto msg = static_cast < Ipc_TextDocumentDocumentLink * > ( message . get ( ) ) ;
Out_TextDocumentDocumentLink response ;
response . id = msg - > id ;
if ( config - > showDocumentLinksOnIncludes ) {
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-05-21 04:30:59 +00:00
break ;
WorkingFile * working_file = working_files - > GetFileByFilename ( msg - > params . textDocument . uri . GetPath ( ) ) ;
if ( ! working_file ) {
std : : cerr < < " Unable to find working file " < < msg - > params . textDocument . uri . GetPath ( ) < < std : : endl ;
break ;
}
for ( const IndexInclude & include : file - > def . includes ) {
optional < int > buffer_line ;
optional < std : : string > buffer_line_content = working_file - > GetBufferLineContentFromIndexLine ( include . line , & buffer_line ) ;
if ( ! buffer_line | | ! buffer_line_content )
continue ;
2017-05-26 04:26:35 +00:00
// Subtract 1 from line because querydb stores 1-based lines but
// vscode expects 0-based lines.
optional < lsRange > between_quotes = ExtractQuotedRange ( * buffer_line - 1 , * buffer_line_content ) ;
if ( ! between_quotes )
2017-05-21 04:30:59 +00:00
continue ;
lsDocumentLink link ;
link . target = lsDocumentUri : : FromPath ( include . resolved_path ) ;
2017-05-26 04:26:35 +00:00
link . range = * between_quotes ;
2017-05-21 04:30:59 +00:00
response . result . push_back ( link ) ;
}
}
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentDocumentLink , response ) ;
break ;
}
2017-05-20 19:31:07 +00:00
case IpcId : : TextDocumentCodeAction : {
// NOTE: This code snippet will generate some FixIts for testing:
//
// struct origin { int x, int y };
// void foo() {
// point origin = {
// x: 0.0,
// y: 0.0
// };
// }
//
auto msg = static_cast < Ipc_TextDocumentCodeAction * > ( message . get ( ) ) ;
2017-05-29 21:18:35 +00:00
QueryFileId file_id ;
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file , & file_id ) )
break ;
2017-05-20 19:31:07 +00:00
WorkingFile * working_file = working_files - > GetFileByFilename ( msg - > params . textDocument . uri . GetPath ( ) ) ;
if ( ! working_file ) {
// TODO: send error response.
std : : cerr < < " [error] textDocument/codeAction could not find working file " < < std : : endl ;
break ;
}
Out_TextDocumentCodeAction response ;
response . id = msg - > id ;
2017-05-29 21:18:35 +00:00
// TODO: auto-insert namespace?
int default_line = ( int ) working_file - > all_buffer_lines . size ( ) ;
// Make sure to call EnsureImplFile before using these. We lazy load
// them because computing the values could involve an entire project
// scan.
optional < lsDocumentUri > impl_uri ;
optional < QueryFileId > impl_file_id ;
std : : vector < SymbolRef > syms = FindSymbolsAtLocation ( working_file , file , msg - > params . range . start ) ;
for ( SymbolRef sym : syms ) {
switch ( sym . idx . kind ) {
case SymbolKind : : Type : {
optional < QueryType > & type = db - > types [ sym . idx . idx ] ;
if ( ! type )
break ;
int num_edits = 0 ;
// Get implementation file.
Out_TextDocumentCodeAction : : Command command ;
for ( QueryFuncId func_id : type - > def . funcs ) {
optional < QueryFunc > & func_def = db - > funcs [ func_id . id ] ;
if ( ! func_def | | func_def - > def . definition_extent )
continue ;
EnsureImplFile ( db , file_id , impl_uri /*out*/ , impl_file_id /*out*/ ) ;
optional < lsTextEdit > edit = BuildAutoImplementForFunction ( db , working_files , working_file , default_line , file_id , * impl_file_id , * func_def ) ;
if ( ! edit )
continue ;
+ + num_edits ;
// Merge edits together if they are on the same line.
// TODO: be smarter about newline merging? ie, don't end up
// with foo()\n\n\n\nfoo(), we want foo()\n\nfoo()\n\n
//
if ( ! command . arguments . edits . empty ( ) & &
command . arguments . edits [ command . arguments . edits . size ( ) - 1 ] . range . end . line = = edit - > range . start . line ) {
command . arguments . edits [ command . arguments . edits . size ( ) - 1 ] . newText + = edit - > newText ;
}
else {
command . arguments . edits . push_back ( * edit ) ;
}
}
if ( command . arguments . edits . empty ( ) )
break ;
// If we're inserting at the end of the document, put a newline before the insertion.
if ( command . arguments . edits [ 0 ] . range . start . line > = default_line )
command . arguments . edits [ 0 ] . newText . insert ( 0 , " \n " ) ;
command . arguments . textDocumentUri = * impl_uri ;
command . title = " Auto-Implement " + std : : to_string ( num_edits ) + " methods on " + type - > def . short_name ;
command . command = " cquery._autoImplement " ;
response . result . push_back ( command ) ;
break ;
}
case SymbolKind : : Func : {
optional < QueryFunc > & func = db - > funcs [ sym . idx . idx ] ;
if ( ! func | | func - > def . definition_extent )
break ;
EnsureImplFile ( db , file_id , impl_uri /*out*/ , impl_file_id /*out*/ ) ;
// Get implementation file.
Out_TextDocumentCodeAction : : Command command ;
command . title = " Auto-Implement " + func - > def . short_name ;
command . command = " cquery._autoImplement " ;
command . arguments . textDocumentUri = * impl_uri ;
optional < lsTextEdit > edit = BuildAutoImplementForFunction ( db , working_files , working_file , default_line , file_id , * impl_file_id , * func ) ;
if ( ! edit )
break ;
// If we're inserting at the end of the document, put a newline before the insertion.
if ( edit - > range . start . line > = default_line )
edit - > newText . insert ( 0 , " \n " ) ;
command . arguments . edits . push_back ( * edit ) ;
response . result . push_back ( command ) ;
break ;
}
default :
break ;
}
// Only show one auto-impl section.
if ( ! response . result . empty ( ) )
break ;
}
2017-06-14 06:29:41 +00:00
std : : vector < lsDiagnostic > diagnostics ;
working_files - > DoAction ( [ & ] ( ) {
diagnostics = working_file - > diagnostics_ ;
} ) ;
for ( lsDiagnostic & diag : diagnostics ) {
2017-05-29 23:57:19 +00:00
if ( diag . range . start . line ! = msg - > params . range . start . line )
continue ;
// For error diagnostics, provide an action to resolve an include.
// TODO: find a way to index diagnostic contents so line numbers
// don't get mismatched when actively editing a file.
std : : string include_query = LexWordAroundPos ( diag . range . start , working_file - > buffer_content ) ;
if ( diag . severity = = lsDiagnosticSeverity : : Error & & ! include_query . empty ( ) ) {
const size_t kMaxResults = 20 ;
std : : unordered_set < std : : string > include_absolute_paths ;
// Find include candidate strings.
for ( int i = 0 ; i < db - > detailed_names . size ( ) ; + + i ) {
if ( include_absolute_paths . size ( ) > kMaxResults )
break ;
if ( db - > detailed_names [ i ] . find ( include_query ) = = std : : string : : npos )
continue ;
optional < QueryFileId > decl_file_id = GetDeclarationFileForSymbol ( db , db - > symbols [ i ] ) ;
if ( ! decl_file_id )
continue ;
optional < QueryFile > & decl_file = db - > files [ decl_file_id - > id ] ;
if ( ! decl_file )
continue ;
include_absolute_paths . insert ( decl_file - > def . path ) ;
}
// Build include strings.
2017-06-14 06:09:25 +00:00
std : : unordered_set < std : : string > include_insert_strings ;
2017-05-29 23:57:19 +00:00
include_insert_strings . reserve ( include_absolute_paths . size ( ) ) ;
for ( const std : : string & path : include_absolute_paths ) {
optional < lsCompletionItem > item = include_complete - > FindCompletionItemForAbsolutePath ( path ) ;
if ( ! item )
continue ;
if ( item - > textEdit )
2017-06-14 06:09:25 +00:00
include_insert_strings . insert ( item - > textEdit - > newText ) ;
2017-05-29 23:57:19 +00:00
else if ( ! item - > insertText . empty ( ) )
2017-06-14 06:09:25 +00:00
include_insert_strings . insert ( item - > insertText ) ;
2017-05-29 23:57:19 +00:00
else
assert ( false & & " unable to determine insert string for include completion item " ) ;
}
// Build code action.
if ( ! include_insert_strings . empty ( ) ) {
Out_TextDocumentCodeAction : : Command command ;
// Build edits.
for ( const std : : string & include_insert_string : include_insert_strings ) {
lsTextEdit edit ;
optional < int > include_line = FindIncludeLine ( working_file - > all_buffer_lines , include_insert_string ) ;
if ( ! include_line )
continue ;
edit . range . start . line = * include_line ;
edit . range . end . line = * include_line ;
edit . newText = include_insert_string + " \n " ;
command . arguments . edits . push_back ( edit ) ;
}
// Setup metadata and send to client.
if ( include_insert_strings . size ( ) = = 1 )
2017-06-14 06:09:25 +00:00
command . title = " Insert " + * include_insert_strings . begin ( ) ;
2017-05-29 23:57:19 +00:00
else
2017-06-29 04:40:30 +00:00
command . title = " Pick one of " + std : : to_string ( command . arguments . edits . size ( ) ) + " includes to insert " ;
2017-05-29 23:57:19 +00:00
command . command = " cquery._insertInclude " ;
command . arguments . textDocumentUri = msg - > params . textDocument . uri ;
response . result . push_back ( command ) ;
}
}
// clang does not provide accurate enough column reporting for
2017-05-20 19:31:07 +00:00
// diagnostics to do good column filtering, so report all
// diagnostics on the line.
2017-05-29 23:57:19 +00:00
if ( ! diag . fixits_ . empty ( ) ) {
2017-05-20 19:31:07 +00:00
Out_TextDocumentCodeAction : : Command command ;
command . title = " FixIt: " + diag . message ;
command . command = " cquery._applyFixIt " ;
command . arguments . textDocumentUri = msg - > params . textDocument . uri ;
command . arguments . edits = diag . fixits_ ;
response . result . push_back ( command ) ;
}
}
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentCodeAction , response ) ;
break ;
}
2017-04-23 20:19:09 +00:00
case IpcId : : TextDocumentCodeLens : {
auto msg = static_cast < Ipc_TextDocumentCodeLens * > ( message . get ( ) ) ;
Out_TextDocumentCodeLens response ;
response . id = msg - > id ;
lsDocumentUri file_as_uri = msg - > params . textDocument . uri ;
2017-05-26 06:40:38 +00:00
std : : string path = file_as_uri . GetPath ( ) ;
2017-05-27 04:21:00 +00:00
clang_complete - > NotifyView ( path ) ;
2017-04-23 20:19:09 +00:00
2017-05-27 07:10:21 +00:00
QueryFile * file ;
if ( ! FindFileOrFail ( db , msg - > id , msg - > params . textDocument . uri . GetPath ( ) , & file ) )
2017-03-29 06:33:38 +00:00
break ;
2017-05-27 07:10:21 +00:00
2017-04-23 20:19:09 +00:00
CommonCodeLensParams common ;
common . result = & response . result ;
common . db = db ;
common . working_files = working_files ;
common . working_file = working_files - > GetFileByFilename ( file - > def . path ) ;
for ( SymbolRef ref : file - > def . outline ) {
// NOTE: We OffsetColumn so that the code lens always show up in a
// predictable order. Otherwise, the client may randomize it.
SymbolIdx symbol = ref . idx ;
switch ( symbol . kind ) {
case SymbolKind : : Type : {
optional < QueryType > & type = db - > types [ symbol . idx ] ;
if ( ! type )
continue ;
2017-05-21 01:26:50 +00:00
AddCodeLens ( " ref " , " refs " , & common , ref . loc . OffsetStartColumn ( 0 ) , type - > uses , type - > def . definition_spelling , true /*force_display*/ ) ;
AddCodeLens ( " derived " , " derived " , & common , ref . loc . OffsetStartColumn ( 1 ) , ToQueryLocation ( db , type - > derived ) , nullopt , false /*force_display*/ ) ;
AddCodeLens ( " var " , " vars " , & common , ref . loc . OffsetStartColumn ( 2 ) , ToQueryLocation ( db , type - > instances ) , nullopt , false /*force_display*/ ) ;
2017-04-23 20:19:09 +00:00
break ;
}
case SymbolKind : : Func : {
optional < QueryFunc > & func = db - > funcs [ symbol . idx ] ;
if ( ! func )
continue ;
2017-04-11 07:29:36 +00:00
2017-05-21 23:22:00 +00:00
int16_t offset = 0 ;
2017-04-11 07:29:36 +00:00
2017-04-23 20:19:09 +00:00
std : : vector < QueryFuncRef > base_callers = GetCallersForAllBaseFunctions ( db , * func ) ;
std : : vector < QueryFuncRef > derived_callers = GetCallersForAllDerivedFunctions ( db , * func ) ;
if ( base_callers . empty ( ) & & derived_callers . empty ( ) ) {
2017-05-21 01:26:50 +00:00
AddCodeLens ( " call " , " calls " , & common , ref . loc . OffsetStartColumn ( offset + + ) , ToQueryLocation ( db , func - > callers ) , nullopt , true /*force_display*/ ) ;
2017-04-23 20:19:09 +00:00
}
else {
2017-05-21 01:26:50 +00:00
AddCodeLens ( " direct call " , " direct calls " , & common , ref . loc . OffsetStartColumn ( offset + + ) , ToQueryLocation ( db , func - > callers ) , nullopt , false /*force_display*/ ) ;
2017-04-23 20:19:09 +00:00
if ( ! base_callers . empty ( ) )
2017-05-21 01:26:50 +00:00
AddCodeLens ( " base call " , " base calls " , & common , ref . loc . OffsetStartColumn ( offset + + ) , ToQueryLocation ( db , base_callers ) , nullopt , false /*force_display*/ ) ;
2017-04-23 20:19:09 +00:00
if ( ! derived_callers . empty ( ) )
2017-05-21 01:26:50 +00:00
AddCodeLens ( " derived call " , " derived calls " , & common , ref . loc . OffsetStartColumn ( offset + + ) , ToQueryLocation ( db , derived_callers ) , nullopt , false /*force_display*/ ) ;
2017-04-23 20:19:09 +00:00
}
2017-04-11 07:29:36 +00:00
2017-05-21 01:26:50 +00:00
AddCodeLens ( " derived " , " derived " , & common , ref . loc . OffsetStartColumn ( offset + + ) , ToQueryLocation ( db , func - > derived ) , nullopt , false /*force_display*/ ) ;
2017-04-23 20:19:09 +00:00
// "Base"
optional < QueryLocation > base_loc = GetBaseDefinitionOrDeclarationSpelling ( db , * func ) ;
if ( base_loc ) {
optional < lsLocation > ls_base = GetLsLocation ( db , working_files , * base_loc ) ;
if ( ls_base ) {
optional < lsRange > range = GetLsRange ( common . working_file , ref . loc . range ) ;
if ( range ) {
TCodeLens code_lens ;
code_lens . range = * range ;
code_lens . range . start . character + = offset + + ;
code_lens . command = lsCommand < lsCodeLensCommandArguments > ( ) ;
code_lens . command - > title = " Base " ;
2017-05-07 06:56:04 +00:00
code_lens . command - > command = " cquery.goto " ;
2017-04-23 20:19:09 +00:00
code_lens . command - > arguments . uri = ls_base - > uri ;
code_lens . command - > arguments . position = ls_base - > range . start ;
response . result . push_back ( code_lens ) ;
}
2017-04-11 08:43:35 +00:00
}
}
2017-04-23 20:19:09 +00:00
break ;
2017-04-11 08:43:35 +00:00
}
2017-04-23 20:19:09 +00:00
case SymbolKind : : Var : {
optional < QueryVar > & var = db - > vars [ symbol . idx ] ;
if ( ! var )
continue ;
2017-04-11 08:43:35 +00:00
2017-05-21 01:26:50 +00:00
if ( var - > def . is_local & & ! config - > codeLensOnLocalVariables )
continue ;
2017-06-29 06:34:04 +00:00
bool force_display = true ;
// Do not show 0 refs on macro with no uses, as it is most likely a
// header guard.
if ( var - > def . is_macro )
force_display = false ;
AddCodeLens ( " ref " , " refs " , & common , ref . loc . OffsetStartColumn ( 0 ) , var - > uses , var - > def . definition_spelling , force_display ) ;
2017-04-23 20:19:09 +00:00
break ;
}
case SymbolKind : : File :
case SymbolKind : : Invalid : {
assert ( false & & " unexpected " ) ;
break ;
}
} ;
2017-03-29 06:33:38 +00:00
}
2017-04-22 07:32:29 +00:00
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : TextDocumentCodeLens , response ) ;
break ;
2017-03-17 07:58:41 +00:00
}
2017-03-06 08:48:51 +00:00
2017-04-23 20:19:09 +00:00
case IpcId : : WorkspaceSymbol : {
2017-05-21 19:51:15 +00:00
// TODO: implement fuzzy search, see https://github.com/junegunn/fzf/blob/master/src/matcher.go for inspiration
2017-04-23 20:19:09 +00:00
auto msg = static_cast < Ipc_WorkspaceSymbol * > ( message . get ( ) ) ;
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
Out_WorkspaceSymbol response ;
response . id = msg - > id ;
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
std : : cerr < < " [querydb] Considering " < < db - > detailed_names . size ( )
< < " candidates for query " < < msg - > params . query < < std : : endl ;
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
std : : string query = msg - > params . query ;
2017-06-29 06:59:38 +00:00
std : : unordered_set < std : : string > inserted_results ;
inserted_results . reserve ( config - > maxWorkspaceSearchResults ) ;
2017-04-23 20:19:09 +00:00
for ( int i = 0 ; i < db - > detailed_names . size ( ) ; + + i ) {
2017-06-17 02:42:14 +00:00
if ( db - > detailed_names [ i ] . find ( query ) ! = std : : string : : npos ) {
2017-06-29 06:59:38 +00:00
// Do not show the same entry twice.
if ( ! inserted_results . insert ( db - > detailed_names [ i ] ) . second )
continue ;
2017-06-17 02:42:14 +00:00
InsertSymbolIntoResult ( db , working_files , db - > symbols [ i ] , & response . result ) ;
if ( response . result . size ( ) > = config - > maxWorkspaceSearchResults )
break ;
2017-04-23 20:19:09 +00:00
}
2017-06-17 02:42:14 +00:00
}
2017-03-25 19:18:25 +00:00
2017-06-17 02:42:14 +00:00
if ( response . result . size ( ) < config - > maxWorkspaceSearchResults ) {
for ( int i = 0 ; i < db - > detailed_names . size ( ) ; + + i ) {
if ( SubstringMatch ( query , db - > detailed_names [ i ] ) ) {
2017-06-29 06:59:38 +00:00
// Do not show the same entry twice.
if ( ! inserted_results . insert ( db - > detailed_names [ i ] ) . second )
continue ;
2017-06-17 02:42:14 +00:00
InsertSymbolIntoResult ( db , working_files , db - > symbols [ i ] , & response . result ) ;
if ( response . result . size ( ) > = config - > maxWorkspaceSearchResults )
break ;
2017-04-23 20:19:09 +00:00
}
2017-04-12 07:19:49 +00:00
}
2017-04-09 22:16:06 +00:00
}
2017-03-25 19:18:25 +00:00
2017-04-24 01:24:09 +00:00
std : : cerr < < " [querydb] Found " < < response . result . size ( ) < < " results for query " < < query < < std : : endl ;
2017-04-23 20:19:09 +00:00
ipc - > SendOutMessageToClient ( IpcId : : WorkspaceSymbol , response ) ;
break ;
}
2017-03-25 19:18:25 +00:00
2017-04-23 20:19:09 +00:00
default : {
std : : cerr < < " [querydb] Unhandled IPC message " < < IpcIdToString ( message - > method_id ) < < std : : endl ;
exit ( 1 ) ;
}
2017-03-15 04:59:05 +00:00
}
}
2017-03-25 19:18:25 +00:00
// TODO: consider rate-limiting and checking for IPC messages so we don't block
// requests / we can serve partial requests.
2017-04-08 06:45:28 +00:00
2017-03-25 19:18:25 +00:00
while ( true ) {
2017-04-08 20:00:08 +00:00
optional < Index_DoIdMap > request = queue_do_id_map - > TryDequeue ( ) ;
if ( ! request )
2017-03-25 19:18:25 +00:00
break ;
2017-04-08 06:45:28 +00:00
2017-04-23 22:45:40 +00:00
did_work = true ;
2017-04-08 20:00:08 +00:00
2017-05-19 07:02:01 +00:00
Index_OnIdMapped response ( request - > indexed_content , request - > perf , request - > is_interactive ) ;
2017-04-08 06:45:28 +00:00
Timer time ;
2017-05-20 08:07:54 +00:00
2017-04-08 06:45:28 +00:00
if ( request - > previous ) {
2017-04-08 20:00:08 +00:00
response . previous_id_map = MakeUnique < IdMap > ( db , request - > previous - > id_cache ) ;
response . previous_index = std : : move ( request - > previous ) ;
2017-04-08 06:45:28 +00:00
}
2017-04-08 20:00:08 +00:00
assert ( request - > current ) ;
response . current_id_map = MakeUnique < IdMap > ( db , request - > current - > id_cache ) ;
response . current_index = std : : move ( request - > current ) ;
2017-05-17 07:08:45 +00:00
response . perf . querydb_id_map = time . ElapsedMicrosecondsAndReset ( ) ;
2017-04-08 06:45:28 +00:00
queue_on_id_mapped - > Enqueue ( std : : move ( response ) ) ;
}
while ( true ) {
2017-04-08 20:00:08 +00:00
optional < Index_OnIndexed > response = queue_on_indexed - > TryDequeue ( ) ;
if ( ! response )
2017-04-08 06:45:28 +00:00
break ;
2017-03-15 04:59:05 +00:00
2017-04-23 22:45:40 +00:00
did_work = true ;
2017-03-25 19:18:25 +00:00
Timer time ;
2017-04-21 06:32:18 +00:00
for ( auto & updated_file : response - > update . files_def_update ) {
// TODO: We're reading a file on querydb thread. This is slow!! If it is a
// problem in practice we need to create a file reader queue, dispatch the
// read to it, get a response, and apply the new index then.
WorkingFile * working_file = working_files - > GetFileByFilename ( updated_file . path ) ;
if ( working_file ) {
2017-05-16 07:26:26 +00:00
auto it = response - > indexed_content . find ( updated_file . path ) ;
if ( it ! = response - > indexed_content . end ( ) ) {
working_file - > SetIndexContent ( it - > second ) ;
2017-04-21 06:32:18 +00:00
time . ResetAndPrint ( " [querydb] Update WorkingFile index contents (via in-memory buffer) for " + updated_file . path ) ;
}
else {
optional < std : : string > cached_file_contents = LoadCachedFileContents ( config , updated_file . path ) ;
if ( cached_file_contents )
working_file - > SetIndexContent ( * cached_file_contents ) ;
else
working_file - > SetIndexContent ( working_file - > buffer_content ) ;
time . ResetAndPrint ( " [querydb] Update WorkingFile index contents (via disk load) for " + updated_file . path ) ;
}
}
}
2017-03-25 19:18:25 +00:00
db - > ApplyIndexUpdate ( & response - > update ) ;
2017-05-17 07:08:45 +00:00
//time.ResetAndPrint("[querydb] Applying index update");
2017-03-05 02:16:23 +00:00
}
2017-04-23 22:45:40 +00:00
return did_work ;
2017-03-05 02:16:23 +00:00
}
2017-03-15 04:59:05 +00:00
2017-05-21 19:51:15 +00:00
void QueryDbMain ( Config * config , MultiQueueWaiter * waiter ) {
2017-03-25 19:18:25 +00:00
// Create queues.
2017-04-23 22:45:40 +00:00
Index_DoIndexQueue queue_do_index ( waiter ) ;
Index_DoIdMapQueue queue_do_id_map ( waiter ) ;
Index_OnIdMappedQueue queue_on_id_mapped ( waiter ) ;
Index_OnIndexedQueue queue_on_indexed ( waiter ) ;
2017-04-08 06:45:28 +00:00
2017-03-26 21:40:34 +00:00
Project project ;
WorkingFiles working_files ;
2017-06-10 04:13:16 +00:00
ClangCompleteManager clang_complete (
config , & project , & working_files ,
std : : bind ( & EmitDiagnostics , & working_files , std : : placeholders : : _1 , std : : placeholders : : _2 ) ) ;
2017-05-27 04:21:00 +00:00
IncludeComplete include_complete ( config , & project ) ;
2017-06-30 06:51:22 +00:00
auto global_code_complete_cache = MakeUnique < CodeCompleteCache > ( ) ;
auto non_global_code_complete_cache = MakeUnique < CodeCompleteCache > ( ) ;
auto signature_cache = MakeUnique < CodeCompleteCache > ( ) ;
2017-04-08 22:54:36 +00:00
FileConsumer : : SharedState file_consumer_shared ;
2017-03-25 19:18:25 +00:00
// Run query db main loop.
2017-04-19 00:05:14 +00:00
SetCurrentThreadName ( " querydb " ) ;
2017-04-15 05:41:35 +00:00
QueryDatabase db ;
2017-03-15 04:59:05 +00:00
while ( true ) {
2017-05-21 19:51:15 +00:00
bool did_work = QueryDbMainLoop (
config , & db , waiter , & queue_do_index , & queue_do_id_map , & queue_on_id_mapped , & queue_on_indexed ,
& project , & file_consumer_shared , & working_files ,
2017-06-30 06:51:22 +00:00
& clang_complete , & include_complete , global_code_complete_cache . get ( ) , non_global_code_complete_cache . get ( ) , signature_cache . get ( ) ) ;
2017-04-23 22:45:40 +00:00
if ( ! did_work ) {
IpcManager * ipc = IpcManager : : instance ( ) ;
waiter - > Wait ( {
ipc - > threaded_queue_for_server_ . get ( ) ,
& queue_do_id_map ,
& queue_on_indexed
} ) ;
}
2017-03-15 04:59:05 +00:00
}
}
2017-04-16 19:02:29 +00:00
2017-03-05 19:48:05 +00:00
// TODO: global lock on stderr output.
// Separate thread whose only job is to read from stdin and
// dispatch read commands to the actual indexer program. This
// cannot be done on the main thread because reading from std::cin
// blocks.
//
// |ipc| is connected to a server.
2017-05-21 19:51:15 +00:00
void LanguageServerStdinLoop ( Config * config , std : : unordered_map < IpcId , Timer > * request_times ) {
2017-04-16 21:49:48 +00:00
IpcManager * ipc = IpcManager : : instance ( ) ;
2017-04-19 00:05:14 +00:00
SetCurrentThreadName ( " stdin " ) ;
2017-03-05 02:16:23 +00:00
while ( true ) {
2017-03-25 21:57:06 +00:00
std : : unique_ptr < BaseIpcMessage > message = MessageRegistry : : instance ( ) - > ReadMessageFromStdin ( ) ;
2017-03-05 02:16:23 +00:00
// Message parsing can fail if we don't recognize the method.
if ( ! message )
continue ;
2017-04-21 07:46:51 +00:00
( * request_times ) [ message - > method_id ] = Timer ( ) ;
2017-04-24 01:24:09 +00:00
//std::cerr << "[stdin] Got message " << IpcIdToString(message->method_id) << std::endl;
2017-03-05 02:16:23 +00:00
switch ( message - > method_id ) {
2017-03-26 06:47:59 +00:00
case IpcId : : Initialized : {
// TODO: don't send output until we get this notification
break ;
}
case IpcId : : CancelRequest : {
// TODO: support cancellation
break ;
}
2017-04-23 20:19:09 +00:00
case IpcId : : Initialize :
2017-05-03 06:45:10 +00:00
case IpcId : : Exit :
2017-03-26 21:40:34 +00:00
case IpcId : : TextDocumentDidOpen :
case IpcId : : TextDocumentDidChange :
2017-04-10 00:08:54 +00:00
case IpcId : : TextDocumentDidClose :
case IpcId : : TextDocumentDidSave :
2017-04-14 08:21:03 +00:00
case IpcId : : TextDocumentRename :
2017-03-26 06:47:59 +00:00
case IpcId : : TextDocumentCompletion :
2017-05-15 07:28:53 +00:00
case IpcId : : TextDocumentSignatureHelp :
2017-04-03 02:21:21 +00:00
case IpcId : : TextDocumentDefinition :
2017-04-14 06:43:50 +00:00
case IpcId : : TextDocumentDocumentHighlight :
2017-04-14 05:18:02 +00:00
case IpcId : : TextDocumentHover :
2017-04-10 05:34:06 +00:00
case IpcId : : TextDocumentReferences :
2017-03-25 21:57:06 +00:00
case IpcId : : TextDocumentDocumentSymbol :
2017-05-21 04:30:59 +00:00
case IpcId : : TextDocumentDocumentLink :
2017-05-20 19:31:07 +00:00
case IpcId : : TextDocumentCodeAction :
2017-03-25 21:57:06 +00:00
case IpcId : : TextDocumentCodeLens :
2017-04-21 04:50:31 +00:00
case IpcId : : WorkspaceSymbol :
2017-05-07 06:56:04 +00:00
case IpcId : : CqueryFreshenIndex :
2017-05-24 07:17:29 +00:00
case IpcId : : CqueryTypeHierarchyTree :
case IpcId : : CqueryCallTreeInitial :
case IpcId : : CqueryCallTreeExpand :
2017-05-07 06:56:04 +00:00
case IpcId : : CqueryVars :
case IpcId : : CqueryCallers :
case IpcId : : CqueryBase :
case IpcId : : CqueryDerived : {
2017-04-16 21:49:48 +00:00
ipc - > SendMessage ( IpcManager : : Destination : : Server , std : : move ( message ) ) ;
2017-03-25 19:18:25 +00:00
break ;
}
2017-03-26 06:47:59 +00:00
default : {
2017-04-16 21:49:48 +00:00
std : : cerr < < " [stdin] Unhandled IPC message " < < IpcIdToString ( message - > method_id ) < < std : : endl ;
2017-03-26 06:47:59 +00:00
exit ( 1 ) ;
}
2017-03-03 08:12:11 +00:00
}
}
}
2017-04-16 21:49:48 +00:00
2017-03-06 08:48:51 +00:00
2017-03-05 19:48:05 +00:00
2017-04-16 19:02:29 +00:00
2017-04-23 22:45:40 +00:00
void StdoutMain ( std : : unordered_map < IpcId , Timer > * request_times , MultiQueueWaiter * waiter ) {
SetCurrentThreadName ( " stdout " ) ;
2017-04-16 23:57:31 +00:00
IpcManager * ipc = IpcManager : : instance ( ) ;
2017-04-16 19:02:29 +00:00
2017-04-23 22:45:40 +00:00
while ( true ) {
std : : vector < std : : unique_ptr < BaseIpcMessage > > messages = ipc - > GetMessages ( IpcManager : : Destination : : Client ) ;
if ( messages . empty ( ) ) {
waiter - > Wait ( { ipc - > threaded_queue_for_client_ . get ( ) } ) ;
continue ;
}
2017-04-16 19:02:29 +00:00
2017-04-23 22:45:40 +00:00
for ( auto & message : messages ) {
2017-04-24 01:24:09 +00:00
//std::cerr << "[stdout] Processing message " << IpcIdToString(message->method_id) << std::endl;
2017-04-22 06:15:46 +00:00
2017-04-23 22:45:40 +00:00
switch ( message - > method_id ) {
case IpcId : : Cout : {
auto msg = static_cast < Ipc_Cout * > ( message . get ( ) ) ;
2017-04-21 07:46:51 +00:00
2017-06-14 06:59:40 +00:00
if ( ShouldDisplayIpcTiming ( msg - > original_ipc_id ) ) {
2017-05-20 08:07:29 +00:00
Timer time = ( * request_times ) [ msg - > original_ipc_id ] ;
time . ResetAndPrint ( " [e2e] Running " + std : : string ( IpcIdToString ( msg - > original_ipc_id ) ) ) ;
}
2017-04-16 19:02:29 +00:00
2017-04-23 22:45:40 +00:00
std : : cout < < msg - > content ;
std : : cout . flush ( ) ;
break ;
}
default : {
std : : cerr < < " [stdout] Unhandled IPC message " < < IpcIdToString ( message - > method_id ) < < std : : endl ;
exit ( 1 ) ;
}
2017-04-16 23:57:31 +00:00
}
}
}
}
2017-04-16 19:02:29 +00:00
2017-05-21 19:51:15 +00:00
void LanguageServerMain ( Config * config , MultiQueueWaiter * waiter ) {
2017-04-21 07:46:51 +00:00
std : : unordered_map < IpcId , Timer > request_times ;
2017-04-23 20:19:09 +00:00
// Start stdin reader. Reading from stdin is a blocking operation so this
// needs a dedicated thread.
2017-04-21 07:46:51 +00:00
new std : : thread ( [ & ] ( ) {
LanguageServerStdinLoop ( config , & request_times ) ;
2017-04-18 02:59:48 +00:00
} ) ;
2017-04-19 00:05:14 +00:00
2017-04-23 20:19:09 +00:00
// Start querydb thread. querydb will start indexer threads as needed.
2017-04-23 22:45:40 +00:00
new std : : thread ( [ & ] ( ) {
QueryDbMain ( config , waiter ) ;
2017-04-23 20:19:09 +00:00
} ) ;
// We run a dedicated thread for writing to stdout because there can be an
// unknown number of delays when output information.
2017-04-23 22:45:40 +00:00
StdoutMain ( & request_times , waiter ) ;
2017-03-16 07:36:49 +00:00
}
2017-03-03 08:12:11 +00:00
2017-04-16 19:02:29 +00:00
2017-03-25 01:28:09 +00:00
int main ( int argc , char * * argv ) {
2017-04-23 22:45:40 +00:00
MultiQueueWaiter waiter ;
IpcManager : : CreateInstance ( & waiter ) ;
2017-04-16 22:48:54 +00:00
2017-04-03 01:34:15 +00:00
//bool loop = true;
//while (loop)
// std::this_thread::sleep_for(std::chrono::milliseconds(10));
2017-06-09 07:08:06 +00:00
//std::this_thread::sleep_for(std::chrono::seconds(10));
2017-03-03 08:12:11 +00:00
2017-03-25 20:27:28 +00:00
PlatformInit ( ) ;
2017-04-16 21:51:47 +00:00
IndexInit ( ) ;
2017-03-25 19:18:25 +00:00
RegisterMessageTypes ( ) ;
2017-03-15 04:59:05 +00:00
2017-03-25 19:18:25 +00:00
std : : unordered_map < std : : string , std : : string > options =
ParseOptions ( argc , argv ) ;
2017-04-23 19:45:58 +00:00
if ( HasOption ( options , " --test " ) ) {
2017-03-17 23:45:10 +00:00
doctest : : Context context ;
context . applyCommandLine ( argc , argv ) ;
int res = context . run ( ) ;
if ( context . shouldExit ( ) )
2017-03-25 19:18:25 +00:00
return res ;
2017-03-17 23:45:10 +00:00
2017-05-25 03:06:05 +00:00
for ( int i = 0 ; i < 1 ; + + i )
2017-05-25 02:04:19 +00:00
RunTests ( ) ;
2017-05-27 07:47:03 +00:00
/*
2017-05-25 03:06:05 +00:00
for ( int i = 0 ; i < 1 ; + + i ) {
2017-05-25 02:04:19 +00:00
std : : this_thread : : sleep_for ( std : : chrono : : seconds ( 5 ) ) ;
2017-05-25 03:06:05 +00:00
std : : cerr < < " [POST] Memory usage: " < < GetProcessMemoryUsedInMb ( ) < < " mb " < < std : : endl ;
2017-05-25 02:04:19 +00:00
}
2017-05-27 07:47:03 +00:00
*/
2017-05-25 02:04:19 +00:00
std : : cerr < < std : : endl < < " [Enter] to exit " < < std : : endl ;
std : : cin . get ( ) ;
2017-03-15 04:59:05 +00:00
return 0 ;
}
2017-04-23 19:45:58 +00:00
else if ( HasOption ( options , " --language-server " ) ) {
//std::cerr << "Running language server" << std::endl;
2017-05-21 19:51:15 +00:00
Config config ;
2017-04-23 22:45:40 +00:00
LanguageServerMain ( & config , & waiter ) ;
2017-04-23 19:45:58 +00:00
return 0 ;
}
else {
std : : cout < < R " help(cquery help:
2017-03-25 19:18:25 +00:00
2017-04-23 19:45:58 +00:00
cquery is a low - latency C + + language server .
2017-03-25 19:18:25 +00:00
General :
- - help Print this help information .
- - language - server
2017-04-23 19:45:58 +00:00
Run as a language server . This implements the language
server spec over STDIN and STDOUT .
2017-03-25 19:18:25 +00:00
- - test Run tests . Does nothing if test support is not compiled in .
Configuration :
2017-04-23 19:45:58 +00:00
When opening up a directory , cquery will look for a compile_commands . json
file emitted by your preferred build system . If not present , cquery will
use a recursive directory listing instead . Command line flags can be
provided by adding a " clang_args " file in the top - level directory . Each
line in that file is a separate argument .
There are also a number of configuration options available when
initializing the language server - your editor should have tooling to
2017-05-21 19:51:15 +00:00
describe those options . See | Config | in this source code for a detailed
list of all currently supported options .
2017-03-25 19:18:25 +00:00
) help " ;
2017-03-05 19:48:05 +00:00
return 0 ;
2017-02-25 23:59:09 +00:00
}
}
2017-05-29 21:18:35 +00:00
TEST_SUITE ( " LexFunctionDeclaration " ) ;
TEST_CASE ( " simple " ) {
std : : string buffer_content = " void Foo(); " ;
lsPosition declaration = CharPos ( buffer_content , ' F ' ) ;
std : : string insert_text ;
int newlines_after_name = 0 ;
LexFunctionDeclaration ( buffer_content , declaration , nullopt , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " void Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
LexFunctionDeclaration ( buffer_content , declaration , std : : string ( " Type " ) , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " void Type::Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
}
TEST_CASE ( " ctor " ) {
std : : string buffer_content = " Foo(); " ;
lsPosition declaration = CharPos ( buffer_content , ' F ' ) ;
std : : string insert_text ;
int newlines_after_name = 0 ;
LexFunctionDeclaration ( buffer_content , declaration , std : : string ( " Foo " ) , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " Foo::Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
}
TEST_CASE ( " dtor " ) {
std : : string buffer_content = " ~Foo(); " ;
lsPosition declaration = CharPos ( buffer_content , ' ~ ' ) ;
std : : string insert_text ;
int newlines_after_name = 0 ;
LexFunctionDeclaration ( buffer_content , declaration , std : : string ( " Foo " ) , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " Foo::~Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
}
TEST_CASE ( " complex return type " ) {
std : : string buffer_content = " std::vector<int> Foo(); " ;
lsPosition declaration = CharPos ( buffer_content , ' F ' ) ;
std : : string insert_text ;
int newlines_after_name = 0 ;
LexFunctionDeclaration ( buffer_content , declaration , nullopt , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " std::vector<int> Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
LexFunctionDeclaration ( buffer_content , declaration , std : : string ( " Type " ) , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " std::vector<int> Type::Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
}
TEST_CASE ( " extra complex return type " ) {
std : : string buffer_content = " std::function < int() > \n Foo(); " ;
lsPosition declaration = CharPos ( buffer_content , ' F ' ) ;
std : : string insert_text ;
int newlines_after_name = 0 ;
LexFunctionDeclaration ( buffer_content , declaration , nullopt , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " std::function < int() > \n Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
LexFunctionDeclaration ( buffer_content , declaration , std : : string ( " Type " ) , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " std::function < int() > \n Type::Foo() { \n } " ) ;
REQUIRE ( newlines_after_name = = 0 ) ;
}
TEST_CASE ( " parameters " ) {
std : : string buffer_content = " void Foo(int a, \n \n int b); " ;
lsPosition declaration = CharPos ( buffer_content , ' F ' ) ;
std : : string insert_text ;
int newlines_after_name = 0 ;
LexFunctionDeclaration ( buffer_content , declaration , nullopt , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " void Foo(int a, \n \n int b) { \n } " ) ;
REQUIRE ( newlines_after_name = = 2 ) ;
LexFunctionDeclaration ( buffer_content , declaration , std : : string ( " Type " ) , & insert_text , & newlines_after_name ) ;
REQUIRE ( insert_text = = " void Type::Foo(int a, \n \n int b) { \n } " ) ;
REQUIRE ( newlines_after_name = = 2 ) ;
}
2017-05-29 23:57:19 +00:00
TEST_SUITE_END ( ) ;
TEST_SUITE ( " LexWordAroundPos " ) ;
TEST_CASE ( " edges " ) {
std : : string content = " Foobar " ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' F ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' o ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' b ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' a ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' r ' ) , content ) = = " Foobar " ) ;
}
TEST_CASE ( " simple " ) {
std : : string content = " Foobar " ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' F ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' o ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' b ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' a ' ) , content ) = = " Foobar " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' r ' ) , content ) = = " Foobar " ) ;
}
TEST_CASE ( " underscores and numbers " ) {
std : : string content = " _my_t5ype7 " ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' _ ' ) , content ) = = " _my_t5ype7 " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' 5 ' ) , content ) = = " _my_t5ype7 " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' e ' ) , content ) = = " _my_t5ype7 " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' 7 ' ) , content ) = = " _my_t5ype7 " ) ;
}
TEST_CASE ( " dot, dash, colon are skipped " ) {
std : : string content = " 1. 2- 3: " ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' 1 ' ) , content ) = = " 1 " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' 2 ' ) , content ) = = " 2 " ) ;
REQUIRE ( LexWordAroundPos ( CharPos ( content , ' 3 ' ) , content ) = = " 3 " ) ;
}
TEST_SUITE_END ( ) ;
TEST_SUITE ( " FindIncludeLine " ) ;
TEST_CASE ( " in document " ) {
std : : vector < std : : string > lines = {
" #include <bbb> " , // 0
" #include <ddd> " // 1
} ;
REQUIRE ( FindIncludeLine ( lines , " #include <bbb> " ) = = nullopt ) ;
}
TEST_CASE ( " insert before " ) {
std : : vector < std : : string > lines = {
" #include <bbb> " , // 0
" #include <ddd> " // 1
} ;
REQUIRE ( FindIncludeLine ( lines , " #include <aaa> " ) = = 0 ) ;
}
TEST_CASE ( " insert middle " ) {
std : : vector < std : : string > lines = {
" #include <bbb> " , // 0
" #include <ddd> " // 1
} ;
REQUIRE ( FindIncludeLine ( lines , " #include <ccc> " ) = = 1 ) ;
}
TEST_CASE ( " insert after " ) {
std : : vector < std : : string > lines = {
" #include <bbb> " , // 0
" #include <ddd> " , // 1
" " , // 2
} ;
REQUIRE ( FindIncludeLine ( lines , " #include <eee> " ) = = 2 ) ;
}
TEST_CASE ( " ignore header " ) {
std : : vector < std : : string > lines = {
" // FOOBAR " , // 0
" // FOOBAR " , // 1
" // FOOBAR " , // 2
" // FOOBAR " , // 3
" " , // 4
" #include <bbb> " , // 5
" #include <ddd> " , // 6
" " , // 7
} ;
REQUIRE ( FindIncludeLine ( lines , " #include <a> " ) = = 5 ) ;
REQUIRE ( FindIncludeLine ( lines , " #include <c> " ) = = 6 ) ;
REQUIRE ( FindIncludeLine ( lines , " #include <e> " ) = = 7 ) ;
}
TEST_SUITE_END ( ) ;