2022-07-17 11:00:29 +01:00
const Server = @This ( ) ;
2020-04-24 23:19:03 +01:00
const std = @import ( " std " ) ;
2021-10-07 12:34:14 +01:00
const zig_builtin = @import ( " builtin " ) ;
2022-11-16 22:58:37 +00:00
const build_options = @import ( " build_options " ) ;
2022-07-15 17:06:18 +01:00
const Config = @import ( " Config.zig " ) ;
const DocumentStore = @import ( " DocumentStore.zig " ) ;
const requests = @import ( " requests.zig " ) ;
const types = @import ( " types.zig " ) ;
const analysis = @import ( " analysis.zig " ) ;
const ast = @import ( " ast.zig " ) ;
const references = @import ( " references.zig " ) ;
const offsets = @import ( " offsets.zig " ) ;
const semantic_tokens = @import ( " semantic_tokens.zig " ) ;
2022-07-24 12:38:13 +01:00
const inlay_hints = @import ( " inlay_hints.zig " ) ;
2022-09-25 00:04:29 +01:00
const code_actions = @import ( " code_actions.zig " ) ;
2022-07-15 17:06:18 +01:00
const shared = @import ( " shared.zig " ) ;
2021-10-01 02:44:06 +01:00
const Ast = std . zig . Ast ;
2022-07-15 17:06:18 +01:00
const tracy = @import ( " tracy.zig " ) ;
const uri_utils = @import ( " uri.zig " ) ;
2022-08-11 03:47:33 +01:00
const diff = @import ( " diff.zig " ) ;
2022-10-29 06:46:22 +01:00
const ComptimeInterpreter = @import ( " ComptimeInterpreter.zig " ) ;
2021-10-02 06:37:11 +01:00
2022-09-25 01:45:02 +01:00
const data = @import ( " data/data.zig " ) ;
const snipped_data = @import ( " data/snippets.zig " ) ;
2022-09-11 22:48:15 +01:00
const log = std . log . scoped ( . server ) ;
2022-07-17 11:00:29 +01:00
// Server fields
2022-09-22 02:31:48 +01:00
config : * Config ,
2022-07-17 11:00:29 +01:00
allocator : std . mem . Allocator = undefined ,
2022-07-31 23:44:07 +01:00
arena : std . heap . ArenaAllocator = undefined ,
2022-07-17 11:00:29 +01:00
document_store : DocumentStore = undefined ,
2022-09-29 19:01:38 +01:00
builtin_completions : std . ArrayListUnmanaged ( types . CompletionItem ) ,
2022-07-17 11:00:29 +01:00
client_capabilities : ClientCapabilities = . { } ,
offset_encoding : offsets . Encoding = . utf16 ,
2022-09-30 05:04:27 +01:00
status : enum {
/// the server has not received a `initialize` request
uninitialized ,
/// the server has recieved a `initialize` request and is awaiting the `initialized` notification
initializing ,
/// the server has been initialized and is ready to received requests
initialized ,
/// the server has been shutdown and can't handle any more requests
shutdown ,
} ,
2020-06-26 12:29:59 +01:00
2022-07-17 11:00:29 +01:00
// Code was based off of https://github.com/andersfr/zig-lsp/blob/master/server.zig
2020-04-27 21:38:35 +01:00
2020-06-01 11:29:06 +01:00
const ClientCapabilities = struct {
supports_snippets : bool = false ,
2020-06-12 15:42:41 +01:00
supports_semantic_tokens : bool = false ,
2022-07-24 12:38:13 +01:00
supports_inlay_hints : bool = false ,
2022-11-25 21:32:08 +00:00
supports_will_save : bool = false ,
supports_will_save_wait_until : bool = false ,
2020-06-12 15:42:41 +01:00
hover_supports_md : bool = false ,
completion_doc_supports_md : bool = false ,
2022-06-23 15:44:22 +01:00
label_details_support : bool = false ,
2022-07-11 14:45:31 +01:00
supports_configuration : bool = false ,
2020-06-01 11:29:06 +01:00
} ;
2020-05-14 22:16:40 +01:00
const not_implemented_response =
\\,"error":{"code":-32601,"message":"NotImplemented"}}
2020-04-24 23:19:03 +01:00
;
2020-05-14 22:16:40 +01:00
const null_result_response =
\\,"result":null}
2020-04-24 23:19:03 +01:00
;
2020-05-14 22:16:40 +01:00
const empty_result_response =
\\,"result":{}}
2020-04-24 23:19:03 +01:00
;
2020-05-14 22:16:40 +01:00
const empty_array_response =
\\,"result":[]}
2020-04-24 23:19:03 +01:00
;
2020-05-14 22:16:40 +01:00
const edit_not_applied_response =
\\,"result":{"applied":false,"failureReason":"feature not implemented"}}
2020-04-24 23:19:03 +01:00
;
2020-05-14 22:16:40 +01:00
const no_completions_response =
\\,"result":{"isIncomplete":false,"items":[]}}
2020-04-24 23:19:03 +01:00
;
2021-04-01 12:14:49 +01:00
const no_signatures_response =
2021-04-02 18:49:01 +01:00
\\,"result":{"signatures":[]}}
2021-04-01 12:14:49 +01:00
;
2020-06-16 12:27:00 +01:00
const no_semantic_tokens_response =
\\,"result":{"data":[]}}
;
2020-04-24 23:19:03 +01:00
2020-04-27 21:38:35 +01:00
/// Sends a request or response
2022-07-31 22:38:27 +01:00
fn send ( writer : anytype , allocator : std . mem . Allocator , reqOrRes : anytype ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-08-23 11:44:26 +01:00
var arr = std . ArrayListUnmanaged ( u8 ) { } ;
defer arr . deinit ( allocator ) ;
2022-07-31 22:38:27 +01:00
2022-08-23 11:44:26 +01:00
try std . json . stringify ( reqOrRes , . { } , arr . writer ( allocator ) ) ;
2020-04-27 21:38:35 +01:00
2022-07-31 22:38:27 +01:00
try writer . print ( " Content-Length: {} \r \n \r \n " , . { arr . items . len } ) ;
try writer . writeAll ( arr . items ) ;
2020-04-24 23:19:03 +01:00
}
2022-09-30 05:04:27 +01:00
pub fn sendErrorResponse ( writer : anytype , allocator : std . mem . Allocator , code : types . ErrorCodes , message : [ ] const u8 ) ! void {
try send ( writer , allocator , . {
. @ " error " = types . ResponseError {
. code = @enumToInt ( code ) ,
. message = message ,
. data = . Null ,
} ,
} ) ;
}
2022-07-31 22:38:27 +01:00
fn respondGeneric ( writer : anytype , id : types . RequestId , response : [ ] const u8 ) ! void {
var buffered_writer = std . io . bufferedWriter ( writer ) ;
const buf_writer = buffered_writer . writer ( ) ;
2022-07-17 11:00:29 +01:00
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-06-06 13:40:33 +01:00
const id_len = switch ( id ) {
. Integer = > | id_val | blk : {
if ( id_val = = 0 ) break : blk 1 ;
var digits : usize = 1 ;
var value = @divTrunc ( id_val , 10 ) ;
while ( value ! = 0 ) : ( value = @divTrunc ( value , 10 ) ) {
digits + = 1 ;
}
break : blk digits ;
} ,
. String = > | str_val | str_val . len + 2 ,
2020-04-24 23:19:03 +01:00
} ;
2020-06-06 13:40:33 +01:00
// Numbers of character that will be printed from this string: len - 1 brackets
const json_fmt = " {{ \" jsonrpc \" : \" 2.0 \" , \" id \" : " ;
2020-05-17 18:26:02 +01:00
2022-07-31 22:38:27 +01:00
try buf_writer . print ( " Content-Length: {} \r \n \r \n " + + json_fmt , . { response . len + id_len + json_fmt . len - 1 } ) ;
2020-06-06 13:40:33 +01:00
switch ( id ) {
2022-07-31 22:38:27 +01:00
. Integer = > | int | try buf_writer . print ( " {} " , . { int } ) ,
. String = > | str | try buf_writer . print ( " \" {s} \" " , . { str } ) ,
2020-06-06 13:40:33 +01:00
}
2022-07-31 22:38:27 +01:00
try buf_writer . writeAll ( response ) ;
try buffered_writer . flush ( ) ;
2020-05-08 00:53:00 +01:00
}
2022-07-31 22:38:27 +01:00
fn showMessage ( server : * Server , writer : anytype , message_type : types . MessageType , message : [ ] const u8 ) ! void {
2022-09-29 00:30:26 +01:00
try send ( writer , server . arena . allocator ( ) , types . Notification {
2020-06-09 04:21:55 +01:00
. method = " window/showMessage " ,
. params = . {
2022-09-29 00:30:26 +01:00
. ShowMessage = . {
2021-06-24 11:38:01 +01:00
. type = message_type ,
2020-06-10 18:48:40 +01:00
. message = message ,
2020-06-09 04:21:55 +01:00
} ,
} ,
} ) ;
}
2022-10-05 12:23:38 +01:00
fn publishDiagnostics ( server : * Server , writer : anytype , handle : DocumentStore . Handle ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-05-24 17:00:21 +01:00
const tree = handle . tree ;
2020-04-24 23:19:03 +01:00
2022-08-23 11:44:26 +01:00
var allocator = server . arena . allocator ( ) ;
var diagnostics = std . ArrayListUnmanaged ( types . Diagnostic ) { } ;
2020-04-24 23:19:03 +01:00
2021-02-26 20:26:52 +00:00
for ( tree . errors ) | err | {
2020-05-09 02:02:29 +01:00
var mem_buffer : [ 256 ] u8 = undefined ;
var fbs = std . io . fixedBufferStream ( & mem_buffer ) ;
2021-01-10 07:12:11 +00:00
try tree . renderError ( err , fbs . writer ( ) ) ;
2020-05-09 02:02:29 +01:00
2022-08-23 11:44:26 +01:00
try diagnostics . append ( allocator , . {
2022-09-16 01:33:49 +01:00
. range = offsets . tokenToRange ( tree , err . token , server . offset_encoding ) ,
2020-05-09 02:02:29 +01:00
. severity = . Error ,
2021-02-26 20:26:52 +00:00
. code = @tagName ( err . tag ) ,
2020-05-09 02:02:29 +01:00
. source = " zls " ,
2022-07-31 23:44:07 +01:00
. message = try server . arena . allocator ( ) . dupe ( u8 , fbs . getWritten ( ) ) ,
2020-05-09 02:02:29 +01:00
// .relatedInformation = undefined
} ) ;
}
2022-10-10 18:16:23 +01:00
for ( handle . cimports . items ( . hash ) ) | hash , i | {
const result = server . document_store . cimports . get ( hash ) orelse continue ;
if ( result ! = . failure ) continue ;
const stderr = std . mem . trim ( u8 , result . failure , " " ) ;
2022-10-10 06:13:15 +01:00
var pos_and_diag_iterator = std . mem . split ( u8 , stderr , " : " ) ;
_ = pos_and_diag_iterator . next ( ) ; // skip file path
_ = pos_and_diag_iterator . next ( ) ; // skip line
_ = pos_and_diag_iterator . next ( ) ; // skip character
2022-10-10 18:16:23 +01:00
const node = handle . cimports . items ( . node ) [ i ] ;
2022-10-10 06:13:15 +01:00
try diagnostics . append ( allocator , . {
2022-10-10 18:16:23 +01:00
. range = offsets . nodeToRange ( handle . tree , node , server . offset_encoding ) ,
2022-10-10 06:13:15 +01:00
. severity = . Error ,
. code = " cImport " ,
. source = " zls " ,
. message = try allocator . dupe ( u8 , pos_and_diag_iterator . rest ( ) ) ,
} ) ;
}
2022-09-24 20:54:31 +01:00
if ( server . config . enable_ast_check_diagnostics and tree . errors . len = = 0 ) {
try getAstCheckDiagnostics ( server , handle , & diagnostics ) ;
2022-07-08 08:25:26 +01:00
}
2022-07-17 11:17:55 +01:00
if ( server . config . warn_style ) {
2022-07-08 23:00:27 +01:00
var node : u32 = 0 ;
while ( node < tree . nodes . len ) : ( node + = 1 ) {
if ( ast . isBuiltinCall ( tree , node ) ) {
const builtin_token = tree . nodes . items ( . main_token ) [ node ] ;
const call_name = tree . tokenSlice ( builtin_token ) ;
if ( ! std . mem . eql ( u8 , call_name , " @import " ) ) continue ;
2022-08-17 23:52:21 +01:00
var buffer : [ 2 ] Ast . Node . Index = undefined ;
const params = ast . builtinCallParams ( tree , node , & buffer ) . ? ;
2022-07-08 23:00:27 +01:00
if ( params . len ! = 1 ) continue ;
const import_str_token = tree . nodes . items ( . main_token ) [ params [ 0 ] ] ;
const import_str = tree . tokenSlice ( import_str_token ) ;
2022-08-26 00:13:58 +01:00
if ( std . mem . startsWith ( u8 , import_str , " \" ./ " ) ) {
2022-08-23 11:44:26 +01:00
try diagnostics . append ( allocator , . {
2022-09-16 01:33:49 +01:00
. range = offsets . tokenToRange ( tree , import_str_token , server . offset_encoding ) ,
2022-07-08 23:00:27 +01:00
. severity = . Hint ,
2022-08-26 00:13:58 +01:00
. code = " dot_slash_import " ,
2022-07-08 23:00:27 +01:00
. source = " zls " ,
2022-08-26 00:13:58 +01:00
. message = " A ./ is not needed in imports " ,
2022-07-08 23:00:27 +01:00
} ) ;
}
}
}
// TODO: style warnings for types, values and declarations below root scope
if ( tree . errors . len = = 0 ) {
for ( tree . rootDecls ( ) ) | decl_idx | {
const decl = tree . nodes . items ( . tag ) [ decl_idx ] ;
switch ( decl ) {
. fn_proto ,
. fn_proto_multi ,
. fn_proto_one ,
. fn_proto_simple ,
. fn_decl ,
= > blk : {
var buf : [ 1 ] Ast . Node . Index = undefined ;
const func = ast . fnProto ( tree , decl_idx , & buf ) . ? ;
if ( func . extern_export_inline_token ! = null ) break : blk ;
2020-05-08 16:01:34 +01:00
2021-02-26 20:26:52 +00:00
if ( func . name_token ) | name_token | {
2020-05-17 15:23:04 +01:00
const is_type_function = analysis . isTypeFunction ( tree , func ) ;
2020-05-15 20:10:53 +01:00
const func_name = tree . tokenSlice ( name_token ) ;
if ( ! is_type_function and ! analysis . isCamelCase ( func_name ) ) {
2022-08-23 11:44:26 +01:00
try diagnostics . append ( allocator , . {
2022-09-16 01:33:49 +01:00
. range = offsets . tokenToRange ( tree , name_token , server . offset_encoding ) ,
2022-07-08 23:00:27 +01:00
. severity = . Hint ,
. code = " bad_style " ,
2020-05-15 20:10:53 +01:00
. source = " zls " ,
. message = " Functions should be camelCase " ,
} ) ;
} else if ( is_type_function and ! analysis . isPascalCase ( func_name ) ) {
2022-08-23 11:44:26 +01:00
try diagnostics . append ( allocator , . {
2022-09-16 01:33:49 +01:00
. range = offsets . tokenToRange ( tree , name_token , server . offset_encoding ) ,
2022-07-08 23:00:27 +01:00
. severity = . Hint ,
. code = " bad_style " ,
2020-05-15 20:10:53 +01:00
. source = " zls " ,
. message = " Type functions should be PascalCase " ,
} ) ;
}
2020-05-04 03:17:19 +01:00
}
2022-07-08 23:00:27 +01:00
} ,
else = > { } ,
}
2020-05-04 03:17:19 +01:00
}
}
2020-04-24 23:19:03 +01:00
}
2022-10-05 12:23:38 +01:00
for ( handle . cimports . items ( . hash ) ) | hash , i | {
const result = server . document_store . cimports . get ( hash ) orelse continue ;
if ( result ! = . failure ) continue ;
const stderr = std . mem . trim ( u8 , result . failure , " " ) ;
2022-09-19 18:53:41 +01:00
var pos_and_diag_iterator = std . mem . split ( u8 , stderr , " : " ) ;
_ = pos_and_diag_iterator . next ( ) ; // skip file path
_ = pos_and_diag_iterator . next ( ) ; // skip line
_ = pos_and_diag_iterator . next ( ) ; // skip character
2022-10-05 12:23:38 +01:00
const node = handle . cimports . items ( . node ) [ i ] ;
2022-09-19 18:53:41 +01:00
try diagnostics . append ( allocator , . {
2022-10-05 12:23:38 +01:00
. range = offsets . nodeToRange ( handle . tree , node , server . offset_encoding ) ,
2022-09-19 18:53:41 +01:00
. severity = . Error ,
. code = " cImport " ,
. source = " zls " ,
. message = try allocator . dupe ( u8 , pos_and_diag_iterator . rest ( ) ) ,
} ) ;
2022-10-10 18:16:23 +01:00
}
2022-10-10 06:13:15 +01:00
if ( server . config . highlight_global_var_declarations ) {
const main_tokens = tree . nodes . items ( . main_token ) ;
const tags = tree . tokens . items ( . tag ) ;
for ( tree . rootDecls ( ) ) | decl | {
const decl_tag = tree . nodes . items ( . tag ) [ decl ] ;
const decl_main_token = tree . nodes . items ( . main_token ) [ decl ] ;
switch ( decl_tag ) {
. simple_var_decl ,
. aligned_var_decl ,
. local_var_decl ,
. global_var_decl ,
= > {
if ( tags [ main_tokens [ decl ] ] ! = . keyword_var ) continue ; // skip anything immutable
// uncomment this to get a list :)
//log.debug("possible global variable \"{s}\"", .{tree.tokenSlice(decl_main_token + 1)});
try diagnostics . append ( allocator , . {
. range = offsets . tokenToRange ( tree , decl_main_token , server . offset_encoding ) ,
. severity = . Hint ,
. code = " highlight_global_var_declarations " ,
. source = " zls " ,
. message = " Global var declaration " ,
} ) ;
} ,
else = > { } ,
}
}
2022-09-19 18:53:41 +01:00
}
2022-10-30 08:07:49 +00:00
if ( handle . interpreter ) | int | {
try diagnostics . ensureUnusedCapacity ( allocator , int . errors . count ( ) ) ;
var err_it = int . errors . iterator ( ) ;
while ( err_it . next ( ) ) | err | {
try diagnostics . append ( allocator , . {
. range = offsets . nodeToRange ( tree , err . key_ptr . * , server . offset_encoding ) ,
. severity = . Error ,
. code = err . value_ptr . code ,
. source = " zls " ,
. message = err . value_ptr . message ,
} ) ;
}
}
// try diagnostics.appendSlice(allocator, handle.interpreter.?.diagnostics.items);
2022-07-31 23:44:07 +01:00
try send ( writer , server . arena . allocator ( ) , types . Notification {
2020-04-27 21:38:35 +01:00
. method = " textDocument/publishDiagnostics " ,
2020-05-07 13:29:53 +01:00
. params = . {
2020-11-06 08:08:20 +00:00
. PublishDiagnostics = . {
2022-10-01 01:45:45 +01:00
. uri = handle . uri ,
2020-05-07 11:56:08 +01:00
. diagnostics = diagnostics . items ,
2020-05-07 13:29:53 +01:00
} ,
2020-05-07 14:23:13 +01:00
} ,
2020-04-27 21:38:35 +01:00
} ) ;
}
2020-04-24 23:19:03 +01:00
2022-09-24 20:54:31 +01:00
fn getAstCheckDiagnostics (
server : * Server ,
2022-10-05 12:23:38 +01:00
handle : DocumentStore . Handle ,
2022-09-24 20:54:31 +01:00
diagnostics : * std . ArrayListUnmanaged ( types . Diagnostic ) ,
) ! void {
var allocator = server . arena . allocator ( ) ;
const zig_exe_path = server . config . zig_exe_path orelse return ;
var process = std . ChildProcess . init ( & [ _ ] [ ] const u8 { zig_exe_path , " ast-check " , " --color " , " off " } , server . allocator ) ;
process . stdin_behavior = . Pipe ;
process . stderr_behavior = . Pipe ;
process . spawn ( ) catch | err | {
log . warn ( " Failed to spawn zig ast-check process, error: {} " , . { err } ) ;
return ;
} ;
2022-10-01 01:45:45 +01:00
try process . stdin . ? . writeAll ( handle . text ) ;
2022-09-24 20:54:31 +01:00
process . stdin . ? . close ( ) ;
process . stdin = null ;
const stderr_bytes = try process . stderr . ? . reader ( ) . readAllAlloc ( server . allocator , std . math . maxInt ( usize ) ) ;
defer server . allocator . free ( stderr_bytes ) ;
const term = process . wait ( ) catch | err | {
log . warn ( " Failed to await zig ast-check process, error: {} " , . { err } ) ;
return ;
} ;
if ( term ! = . Exited ) return ;
// NOTE: I believe that with color off it's one diag per line; is this correct?
var line_iterator = std . mem . split ( u8 , stderr_bytes , " \n " ) ;
while ( line_iterator . next ( ) ) | line | lin : {
2022-10-21 17:24:26 +01:00
if ( ! std . mem . startsWith ( u8 , line , " <stdin> " ) ) continue ;
2022-09-24 20:54:31 +01:00
var pos_and_diag_iterator = std . mem . split ( u8 , line , " : " ) ;
const maybe_first = pos_and_diag_iterator . next ( ) ;
if ( maybe_first ) | first | {
if ( first . len < = 1 ) break : lin ;
} else break ;
const utf8_position = types . Position {
. line = ( try std . fmt . parseInt ( u32 , pos_and_diag_iterator . next ( ) . ? , 10 ) ) - 1 ,
. character = ( try std . fmt . parseInt ( u32 , pos_and_diag_iterator . next ( ) . ? , 10 ) ) - 1 ,
} ;
// zig uses utf-8 encoding for character offsets
2022-10-01 01:45:45 +01:00
const position = offsets . convertPositionEncoding ( handle . text , utf8_position , . utf8 , server . offset_encoding ) ;
const range = offsets . tokenPositionToRange ( handle . text , position , server . offset_encoding ) ;
2022-09-24 20:54:31 +01:00
const msg = pos_and_diag_iterator . rest ( ) [ 1 . . ] ;
if ( std . mem . startsWith ( u8 , msg , " error: " ) ) {
try diagnostics . append ( allocator , . {
. range = range ,
. severity = . Error ,
. code = " ast_check " ,
. source = " zls " ,
. message = try server . arena . allocator ( ) . dupe ( u8 , msg [ " error: " . len . . ] ) ,
} ) ;
} else if ( std . mem . startsWith ( u8 , msg , " note: " ) ) {
var latestDiag = & diagnostics . items [ diagnostics . items . len - 1 ] ;
2022-09-25 00:01:31 +01:00
var fresh = if ( latestDiag . relatedInformation ) | related_information |
try server . arena . allocator ( ) . realloc ( @ptrCast ( [ ] types . DiagnosticRelatedInformation , related_information ) , related_information . len + 1 )
2022-09-24 20:54:31 +01:00
else
2022-09-25 00:01:31 +01:00
try server . arena . allocator ( ) . alloc ( types . DiagnosticRelatedInformation , 1 ) ;
2022-09-24 20:54:31 +01:00
const location = types . Location {
2022-10-01 01:45:45 +01:00
. uri = handle . uri ,
2022-09-24 20:54:31 +01:00
. range = range ,
} ;
fresh [ fresh . len - 1 ] = . {
. location = location ,
. message = try server . arena . allocator ( ) . dupe ( u8 , msg [ " note: " . len . . ] ) ,
} ;
latestDiag . relatedInformation = fresh ;
} else {
try diagnostics . append ( allocator , . {
. range = range ,
. severity = . Error ,
. code = " ast_check " ,
. source = " zls " ,
. message = try server . arena . allocator ( ) . dupe ( u8 , msg ) ,
} ) ;
}
}
}
2022-11-25 21:32:08 +00:00
/// caller owns returned memory.
fn autofix ( server : * Server , allocator : std . mem . Allocator , handle : * const DocumentStore . Handle ) ! std . ArrayListUnmanaged ( types . TextEdit ) {
var diagnostics = std . ArrayListUnmanaged ( types . Diagnostic ) { } ;
try getAstCheckDiagnostics ( server , handle . * , & diagnostics ) ;
2022-11-26 01:14:33 +00:00
2022-11-25 21:32:08 +00:00
var builder = code_actions . Builder {
. arena = & server . arena ,
. document_store = & server . document_store ,
. handle = handle ,
. offset_encoding = server . offset_encoding ,
} ;
var actions = std . ArrayListUnmanaged ( types . CodeAction ) { } ;
for ( diagnostics . items ) | diagnostic | {
try builder . generateCodeAction ( diagnostic , & actions ) ;
}
var text_edits = std . ArrayListUnmanaged ( types . TextEdit ) { } ;
for ( actions . items ) | action | {
if ( action . kind ! = . SourceFixAll ) continue ;
if ( action . edit . changes . size ! = 1 ) continue ;
const edits = action . edit . changes . get ( handle . uri ) orelse continue ;
try text_edits . appendSlice ( allocator , edits . items ) ;
}
return text_edits ;
}
2022-06-06 04:50:17 +01:00
fn typeToCompletion (
2022-07-17 11:00:29 +01:00
server : * Server ,
2022-08-23 11:44:26 +01:00
list : * std . ArrayListUnmanaged ( types . CompletionItem ) ,
2022-06-06 04:50:17 +01:00
field_access : analysis . FieldAccessReturn ,
2022-10-05 12:40:11 +01:00
orig_handle : * const DocumentStore . Handle ,
2022-06-06 04:50:17 +01:00
) error { OutOfMemory } ! void {
2022-08-23 11:44:26 +01:00
var allocator = server . arena . allocator ( ) ;
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-07-07 21:26:12 +01:00
const type_handle = field_access . original ;
2020-06-17 03:12:12 +01:00
switch ( type_handle . type . data ) {
. slice = > {
if ( ! type_handle . type . is_type_val ) {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-06-17 03:12:12 +01:00
. label = " len " ,
2022-06-23 15:44:22 +01:00
. detail = " const len: usize " ,
2020-06-17 03:12:12 +01:00
. kind = . Field ,
2021-03-28 15:02:48 +01:00
. insertText = " len " ,
. insertTextFormat = . PlainText ,
2020-06-17 03:12:12 +01:00
} ) ;
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-06-17 03:12:12 +01:00
. label = " ptr " ,
. kind = . Field ,
2021-03-28 15:02:48 +01:00
. insertText = " ptr " ,
. insertTextFormat = . PlainText ,
2020-06-17 03:12:12 +01:00
} ) ;
}
} ,
. error_union = > { } ,
2020-07-24 11:20:13 +01:00
. pointer = > | n | {
2022-07-17 11:00:29 +01:00
if ( server . config . operator_completions ) {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-07-24 11:20:13 +01:00
. label = " * " ,
. kind = . Operator ,
2021-03-28 15:02:48 +01:00
. insertText = " * " ,
. insertTextFormat = . PlainText ,
2020-07-24 11:20:13 +01:00
} ) ;
}
2022-07-17 11:00:29 +01:00
try server . nodeToCompletion (
2020-07-24 11:20:13 +01:00
list ,
. { . node = n , . handle = type_handle . handle } ,
null ,
orig_handle ,
type_handle . type . is_type_val ,
2021-04-03 16:54:26 +01:00
null ,
2020-07-24 11:20:13 +01:00
) ;
} ,
2022-07-17 11:00:29 +01:00
. other = > | n | try server . nodeToCompletion (
2020-06-17 03:12:12 +01:00
list ,
. { . node = n , . handle = type_handle . handle } ,
2020-07-07 21:26:12 +01:00
field_access . unwrapped ,
2020-06-17 03:12:12 +01:00
orig_handle ,
type_handle . type . is_type_val ,
2021-04-03 16:54:26 +01:00
null ,
2020-06-17 03:12:12 +01:00
) ,
2022-07-08 19:29:53 +01:00
. primitive , . array_index = > { } ,
2022-10-28 19:24:38 +01:00
. @ " comptime " = > | co | {
2022-10-29 22:28:44 +01:00
const ti = co . type . getTypeInfo ( ) ;
2022-10-28 19:24:38 +01:00
switch ( ti ) {
. @ " struct " = > | st | {
2022-10-30 08:07:49 +00:00
var fit = st . fields . iterator ( ) ;
while ( fit . next ( ) ) | entry | {
try list . append ( allocator , . {
. label = entry . key_ptr . * ,
. kind = . Field ,
. insertText = entry . key_ptr . * ,
. insertTextFormat = . PlainText ,
} ) ;
}
2022-10-28 19:24:38 +01:00
var it = st . scope . declarations . iterator ( ) ;
while ( it . next ( ) ) | entry | {
try list . append ( allocator , . {
. label = entry . key_ptr . * ,
2022-11-01 03:36:13 +00:00
. kind = if ( entry . value_ptr . isConstant ( ) ) . Constant else . Variable ,
2022-10-28 19:24:38 +01:00
. insertText = entry . key_ptr . * ,
. insertTextFormat = . PlainText ,
} ) ;
}
} ,
else = > { } ,
}
} ,
2020-06-17 03:12:12 +01:00
}
}
2022-06-06 04:50:17 +01:00
fn nodeToCompletion (
2022-07-17 11:00:29 +01:00
server : * Server ,
2022-08-23 11:44:26 +01:00
list : * std . ArrayListUnmanaged ( types . CompletionItem ) ,
2022-06-06 04:50:17 +01:00
node_handle : analysis . NodeWithHandle ,
unwrapped : ? analysis . TypeWithHandle ,
2022-10-05 12:40:11 +01:00
orig_handle : * const DocumentStore . Handle ,
2022-06-06 04:50:17 +01:00
is_type_val : bool ,
parent_is_type_val : ? bool ,
) error { OutOfMemory } ! void {
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-08-23 11:44:26 +01:00
var allocator = server . arena . allocator ( ) ;
2020-06-10 18:48:40 +01:00
const node = node_handle . node ;
const handle = node_handle . handle ;
2021-03-01 15:02:24 +00:00
const tree = handle . tree ;
const node_tags = tree . nodes . items ( . tag ) ;
const token_tags = tree . tokens . items ( . tag ) ;
2020-06-10 18:48:40 +01:00
2022-07-17 11:00:29 +01:00
const doc_kind : types . MarkupContent . Kind = if ( server . client_capabilities . completion_doc_supports_md )
2020-11-06 08:08:20 +00:00
. Markdown
else
. PlainText ;
2020-06-12 15:42:41 +01:00
const doc = if ( try analysis . getDocComments (
2022-08-23 11:44:26 +01:00
allocator ,
2020-06-12 15:42:41 +01:00
handle . tree ,
node ,
doc_kind ,
) ) | doc_comments |
2020-05-14 22:16:40 +01:00
types . MarkupContent {
2020-06-12 15:42:41 +01:00
. kind = doc_kind ,
2020-05-14 22:16:40 +01:00
. value = doc_comments ,
}
else
null ;
2020-05-13 18:30:57 +01:00
2021-10-03 00:39:24 +01:00
if ( ast . isContainer ( handle . tree , node ) ) {
2020-06-17 03:12:12 +01:00
const context = DeclToCompletionContext {
2022-07-17 11:17:55 +01:00
. server = server ,
2020-06-17 03:12:12 +01:00
. completions = list ,
. orig_handle = orig_handle ,
2021-04-03 16:54:26 +01:00
. parent_is_type_val = is_type_val ,
2020-06-17 03:12:12 +01:00
} ;
2021-04-03 16:54:26 +01:00
try analysis . iterateSymbolsContainer (
2022-07-17 11:00:29 +01:00
& server . document_store ,
2022-07-31 23:44:07 +01:00
& server . arena ,
2021-04-03 16:54:26 +01:00
node_handle ,
orig_handle ,
declToCompletion ,
context ,
! is_type_val ,
) ;
2020-06-17 03:12:12 +01:00
}
if ( is_type_val ) return ;
2021-03-01 15:02:24 +00:00
switch ( node_tags [ node ] ) {
2021-03-05 21:38:42 +00:00
. fn_proto ,
. fn_proto_multi ,
. fn_proto_one ,
. fn_proto_simple ,
. fn_decl ,
= > {
2021-10-01 02:44:06 +01:00
var buf : [ 1 ] Ast . Node . Index = undefined ;
2021-10-03 00:39:24 +01:00
const func = ast . fnProto ( tree , node , & buf ) . ? ;
2021-03-01 15:02:24 +00:00
if ( func . name_token ) | name_token | {
2022-07-17 11:00:29 +01:00
const use_snippets = server . config . enable_snippets and server . client_capabilities . supports_snippets ;
2020-06-06 00:44:43 +01:00
const insert_text = if ( use_snippets ) blk : {
2021-04-03 16:54:26 +01:00
const skip_self_param = ! ( parent_is_type_val orelse true ) and
2022-07-31 23:44:07 +01:00
try analysis . hasSelfParam ( & server . arena , & server . document_store , handle , func ) ;
break : blk try analysis . getFunctionSnippet ( server . arena . allocator ( ) , tree , func , skip_self_param ) ;
2021-03-28 15:02:48 +01:00
} else tree . tokenSlice ( func . name_token . ? ) ;
2020-05-13 18:30:57 +01:00
2020-06-10 19:24:17 +01:00
const is_type_function = analysis . isTypeFunction ( handle . tree , func ) ;
2020-05-17 15:23:04 +01:00
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-06-10 19:24:17 +01:00
. label = handle . tree . tokenSlice ( name_token ) ,
2020-05-17 15:23:04 +01:00
. kind = if ( is_type_function ) . Struct else . Function ,
2020-05-13 18:30:57 +01:00
. documentation = doc ,
2020-06-10 19:24:17 +01:00
. detail = analysis . getFunctionSignature ( handle . tree , func ) ,
2020-05-13 18:30:57 +01:00
. insertText = insert_text ,
2020-06-01 11:29:06 +01:00
. insertTextFormat = if ( use_snippets ) . Snippet else . PlainText ,
2020-05-17 15:23:04 +01:00
} ) ;
2020-05-13 18:30:57 +01:00
}
} ,
2021-03-06 19:55:59 +00:00
. global_var_decl ,
. local_var_decl ,
. aligned_var_decl ,
. simple_var_decl ,
= > {
2021-10-03 00:39:24 +01:00
const var_decl = ast . varDecl ( tree , node ) . ? ;
2021-03-01 15:02:24 +00:00
const is_const = token_tags [ var_decl . ast . mut_token ] = = . keyword_const ;
2020-05-19 05:12:05 +01:00
2022-07-31 23:44:07 +01:00
if ( try analysis . resolveVarDeclAlias ( & server . document_store , & server . arena , node_handle ) ) | result | {
2020-06-14 23:19:21 +01:00
const context = DeclToCompletionContext {
2022-07-17 11:17:55 +01:00
. server = server ,
2020-06-14 23:19:21 +01:00
. completions = list ,
. orig_handle = orig_handle ,
} ;
2022-07-17 11:17:55 +01:00
return try declToCompletion ( context , result ) ;
2020-05-19 05:12:05 +01:00
}
2020-05-25 23:51:50 +01:00
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2021-03-01 15:02:24 +00:00
. label = handle . tree . tokenSlice ( var_decl . ast . mut_token + 1 ) ,
2020-05-17 15:23:04 +01:00
. kind = if ( is_const ) . Constant else . Variable ,
2020-05-13 18:30:57 +01:00
. documentation = doc ,
2021-03-01 15:02:24 +00:00
. detail = analysis . getVariableSignature ( tree , var_decl ) ,
2021-03-28 15:02:48 +01:00
. insertText = tree . tokenSlice ( var_decl . ast . mut_token + 1 ) ,
. insertTextFormat = . PlainText ,
2020-05-17 15:23:04 +01:00
} ) ;
2020-05-13 18:30:57 +01:00
} ,
2021-03-06 19:55:59 +00:00
. container_field ,
. container_field_align ,
. container_field_init ,
= > {
2021-10-03 00:39:24 +01:00
const field = ast . containerField ( tree , node ) . ? ;
2022-11-26 17:22:16 +00:00
if ( ! field . ast . tuple_like ) {
try list . append ( allocator , . {
. label = handle . tree . tokenSlice ( field . ast . main_token ) ,
. kind = . Field ,
. documentation = doc ,
. detail = analysis . getContainerFieldSignature ( handle . tree , field ) ,
. insertText = tree . tokenSlice ( field . ast . main_token ) ,
. insertTextFormat = . PlainText ,
} ) ;
}
2020-06-10 23:00:13 +01:00
} ,
2021-03-06 19:55:59 +00:00
. array_type ,
. array_type_sentinel ,
= > {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-07-24 11:20:13 +01:00
. label = " len " ,
2022-06-23 15:44:22 +01:00
. detail = " const len: usize " ,
2020-07-24 11:20:13 +01:00
. kind = . Field ,
2021-03-28 15:02:48 +01:00
. insertText = " len " ,
. insertTextFormat = . PlainText ,
2020-07-24 11:20:13 +01:00
} ) ;
} ,
2021-03-06 19:55:59 +00:00
. ptr_type ,
. ptr_type_aligned ,
. ptr_type_bit_range ,
. ptr_type_sentinel ,
= > {
2021-10-03 00:39:24 +01:00
const ptr_type = ast . ptrType ( tree , node ) . ? ;
2021-03-01 15:02:24 +00:00
switch ( ptr_type . size ) {
2022-07-17 11:00:29 +01:00
. One , . C , . Many = > if ( server . config . operator_completions ) {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2021-03-01 15:02:24 +00:00
. label = " * " ,
. kind = . Operator ,
2021-03-28 15:02:48 +01:00
. insertText = " * " ,
. insertTextFormat = . PlainText ,
2021-03-01 15:02:24 +00:00
} ) ;
} ,
2021-03-11 12:59:09 +00:00
. Slice = > {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2021-03-28 15:02:48 +01:00
. label = " ptr " ,
. kind = . Field ,
. insertText = " ptr " ,
. insertTextFormat = . PlainText ,
} ) ;
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2021-03-28 15:02:48 +01:00
. label = " len " ,
2022-06-23 15:44:22 +01:00
. detail = " const len: usize " ,
2021-03-28 15:02:48 +01:00
. kind = . Field ,
. insertText = " len " ,
. insertTextFormat = . PlainText ,
} ) ;
2021-03-11 12:59:09 +00:00
return ;
} ,
2020-07-16 17:04:23 +01:00
}
2020-07-07 21:26:12 +01:00
2021-03-01 15:02:24 +00:00
if ( unwrapped ) | actual_type | {
2022-07-31 23:44:07 +01:00
try server . typeToCompletion ( list , . { . original = actual_type } , orig_handle ) ;
2020-05-27 16:49:11 +01:00
}
2020-07-16 17:04:23 +01:00
return ;
} ,
2021-03-01 15:02:24 +00:00
. optional_type = > {
2022-07-17 11:00:29 +01:00
if ( server . config . operator_completions ) {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-07-16 17:04:23 +01:00
. label = " ? " ,
. kind = . Operator ,
2021-03-28 15:02:48 +01:00
. insertText = " ? " ,
. insertTextFormat = . PlainText ,
2020-07-16 17:04:23 +01:00
} ) ;
}
return ;
2020-05-13 18:30:57 +01:00
} ,
2021-03-01 15:02:24 +00:00
. string_literal = > {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-05-17 15:23:04 +01:00
. label = " len " ,
2022-06-23 15:44:22 +01:00
. detail = " const len: usize " ,
2020-05-17 15:23:04 +01:00
. kind = . Field ,
2021-03-28 15:02:48 +01:00
. insertText = " len " ,
. insertTextFormat = . PlainText ,
2020-05-17 15:23:04 +01:00
} ) ;
} ,
2021-03-06 19:55:59 +00:00
else = > if ( analysis . nodeToString ( tree , node ) ) | string | {
2022-08-23 11:44:26 +01:00
try list . append ( allocator , . {
2020-05-13 18:30:57 +01:00
. label = string ,
. kind = . Field ,
. documentation = doc ,
2022-09-16 01:33:49 +01:00
. detail = offsets . nodeToSlice ( tree , node ) ,
2021-03-28 15:02:48 +01:00
. insertText = string ,
. insertTextFormat = . PlainText ,
2020-05-17 15:23:04 +01:00
} ) ;
2020-05-14 22:16:40 +01:00
} ,
2020-05-13 18:30:57 +01:00
}
}
2021-04-02 18:49:01 +01:00
pub fn identifierFromPosition ( pos_index : usize , handle : DocumentStore . Handle ) [ ] const u8 {
2022-10-01 01:45:45 +01:00
if ( pos_index + 1 > = handle . text . len ) return " " ;
2020-05-18 21:19:23 +01:00
var start_idx = pos_index ;
2020-05-21 12:36:14 +01:00
2022-10-01 01:45:45 +01:00
while ( start_idx > 0 and isSymbolChar ( handle . text [ start_idx - 1 ] ) ) {
2021-05-02 18:15:31 +01:00
start_idx - = 1 ;
}
2020-05-18 21:19:23 +01:00
var end_idx = pos_index ;
2022-10-01 01:45:45 +01:00
while ( end_idx < handle . text . len and isSymbolChar ( handle . text [ end_idx ] ) ) {
2021-05-02 18:15:31 +01:00
end_idx + = 1 ;
}
2020-05-18 21:19:23 +01:00
2021-10-01 02:47:48 +01:00
if ( end_idx < = start_idx ) return " " ;
2022-10-01 01:45:45 +01:00
return handle . text [ start_idx . . end_idx ] ;
2021-05-02 18:15:31 +01:00
}
2021-10-01 01:53:14 +01:00
2021-05-02 18:15:31 +01:00
fn isSymbolChar ( char : u8 ) bool {
return std . ascii . isAlNum ( char ) or char = = '_' ;
2020-05-18 21:19:23 +01:00
}
2022-07-17 11:00:29 +01:00
fn gotoDefinitionSymbol (
server : * Server ,
decl_handle : analysis . DeclWithHandle ,
resolve_alias : bool ,
2022-09-22 19:09:16 +01:00
) error { OutOfMemory } ! ? types . Location {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-06-10 18:48:40 +01:00
var handle = decl_handle . handle ;
2020-05-25 23:51:50 +01:00
2022-09-16 01:33:49 +01:00
const name_token = switch ( decl_handle . decl . * ) {
2020-06-10 17:01:44 +01:00
. ast_node = > | node | block : {
2020-06-15 10:37:42 +01:00
if ( resolve_alias ) {
2022-07-31 23:44:07 +01:00
if ( try analysis . resolveVarDeclAlias ( & server . document_store , & server . arena , . { . node = node , . handle = handle } ) ) | result | {
2020-06-15 10:37:42 +01:00
handle = result . handle ;
2022-09-16 01:33:49 +01:00
break : block result . nameToken ( ) ;
2020-06-15 10:37:42 +01:00
}
2020-06-14 23:19:21 +01:00
}
2020-06-10 18:48:40 +01:00
2022-09-22 19:09:16 +01:00
break : block analysis . getDeclNameToken ( handle . tree , node ) orelse return null ;
2020-06-10 17:01:44 +01:00
} ,
2022-09-16 01:33:49 +01:00
else = > decl_handle . nameToken ( ) ,
2020-06-10 17:01:44 +01:00
} ;
2020-05-25 23:51:50 +01:00
2022-09-22 19:09:16 +01:00
return types . Location {
2022-10-01 01:45:45 +01:00
. uri = handle . uri ,
2022-09-22 19:09:16 +01:00
. range = offsets . tokenToRange ( handle . tree , name_token , server . offset_encoding ) ,
} ;
2020-05-25 23:51:50 +01:00
}
2022-09-22 19:09:16 +01:00
fn hoverSymbol ( server : * Server , decl_handle : analysis . DeclWithHandle ) error { OutOfMemory } ! ? types . Hover {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-06-10 20:52:33 +01:00
const handle = decl_handle . handle ;
2021-03-01 15:02:24 +00:00
const tree = handle . tree ;
2020-06-10 20:52:33 +01:00
2022-07-17 11:00:29 +01:00
const hover_kind : types . MarkupContent . Kind = if ( server . client_capabilities . hover_supports_md ) . Markdown else . PlainText ;
2021-04-07 14:10:18 +01:00
var doc_str : ? [ ] const u8 = null ;
const def_str = switch ( decl_handle . decl . * ) {
. ast_node = > | node | def : {
2022-07-31 23:44:07 +01:00
if ( try analysis . resolveVarDeclAlias ( & server . document_store , & server . arena , . { . node = node , . handle = handle } ) ) | result | {
2022-09-22 19:09:16 +01:00
return try server . hoverSymbol ( result ) ;
2020-06-14 23:19:21 +01:00
}
2022-07-31 23:44:07 +01:00
doc_str = try analysis . getDocComments ( server . arena . allocator ( ) , tree , node , hover_kind ) ;
2020-05-25 23:51:50 +01:00
2021-10-01 02:44:06 +01:00
var buf : [ 1 ] Ast . Node . Index = undefined ;
2020-06-10 18:48:40 +01:00
2021-10-03 00:39:24 +01:00
if ( ast . varDecl ( tree , node ) ) | var_decl | {
2021-04-07 14:10:18 +01:00
break : def analysis . getVariableSignature ( tree , var_decl ) ;
2021-10-03 00:39:24 +01:00
} else if ( ast . fnProto ( tree , node , & buf ) ) | fn_proto | {
2021-04-07 14:10:18 +01:00
break : def analysis . getFunctionSignature ( tree , fn_proto ) ;
2021-10-03 00:39:24 +01:00
} else if ( ast . containerField ( tree , node ) ) | field | {
2021-04-07 14:10:18 +01:00
break : def analysis . getContainerFieldSignature ( tree , field ) ;
} else {
2022-09-22 19:09:16 +01:00
break : def analysis . nodeToString ( tree , node ) orelse return null ;
2021-04-07 14:10:18 +01:00
}
2020-06-10 20:52:33 +01:00
} ,
2022-09-24 20:25:32 +01:00
. param_payload = > | pay | def : {
const param = pay . param ;
2021-04-07 14:10:18 +01:00
if ( param . first_doc_comment ) | doc_comments | {
2022-07-31 23:44:07 +01:00
doc_str = try analysis . collectDocComments ( server . arena . allocator ( ) , handle . tree , doc_comments , hover_kind , false ) ;
2021-04-07 14:10:18 +01:00
}
2020-06-10 20:52:33 +01:00
2022-09-24 20:26:55 +01:00
const first_token = ast . paramFirstToken ( tree , param ) ;
const last_token = ast . paramLastToken ( tree , param ) ;
2021-03-01 15:02:24 +00:00
2022-09-16 01:33:49 +01:00
const start = offsets . tokenToIndex ( tree , first_token ) ;
const end = offsets . tokenToLoc ( tree , last_token ) . end ;
2021-04-07 14:10:18 +01:00
break : def tree . source [ start . . end ] ;
2020-05-25 23:51:50 +01:00
} ,
2021-04-07 14:10:18 +01:00
. pointer_payload = > | payload | tree . tokenSlice ( payload . name ) ,
. array_payload = > | payload | handle . tree . tokenSlice ( payload . identifier ) ,
. array_index = > | payload | handle . tree . tokenSlice ( payload ) ,
. switch_payload = > | payload | tree . tokenSlice ( payload . node ) ,
. label_decl = > | label_decl | tree . tokenSlice ( label_decl ) ,
} ;
2022-08-23 11:44:26 +01:00
var bound_type_params = analysis . BoundTypeParams { } ;
2022-07-31 23:44:07 +01:00
const resolved_type = try decl_handle . resolveType ( & server . document_store , & server . arena , & bound_type_params ) ;
2022-07-08 19:29:53 +01:00
const resolved_type_str = if ( resolved_type ) | rt |
2022-10-29 06:46:22 +01:00
if ( rt . type . is_type_val ) switch ( rt . type . data ) {
2022-10-29 22:28:44 +01:00
. @ " comptime " = > | * co | try std . fmt . allocPrint ( server . arena . allocator ( ) , " { } " , . { co . interpreter . formatTypeInfo ( co . type . getTypeInfo ( ) ) } ) ,
2022-10-29 06:46:22 +01:00
else = > " type " ,
} else switch ( rt . type . data ) { // TODO: Investigate random weird numbers like 897 that cause index of bounds
2022-07-08 19:29:53 +01:00
. pointer ,
. slice ,
. error_union ,
. primitive ,
2022-09-16 01:33:49 +01:00
= > | p | if ( p > = tree . nodes . len ) " unknown " else offsets . nodeToSlice ( tree , p ) ,
2022-07-09 10:22:02 +01:00
. other = > | p | if ( p > = tree . nodes . len ) " unknown " else switch ( tree . nodes . items ( . tag ) [ p ] ) {
2022-07-08 19:29:53 +01:00
. container_decl ,
. container_decl_arg ,
. container_decl_arg_trailing ,
. container_decl_trailing ,
. container_decl_two ,
. container_decl_two_trailing ,
. tagged_union ,
. tagged_union_trailing ,
. tagged_union_two ,
. tagged_union_two_trailing ,
. tagged_union_enum_tag ,
. tagged_union_enum_tag_trailing ,
= > tree . tokenSlice ( tree . nodes . items ( . main_token ) [ p ] - 2 ) , // NOTE: This is a hacky nightmare but it works :P
2022-07-08 21:57:34 +01:00
. fn_proto ,
. fn_proto_multi ,
. fn_proto_one ,
. fn_proto_simple ,
. fn_decl ,
= > " fn " , // TODO:(?) Add more info?
2022-07-08 23:00:27 +01:00
. array_type ,
. array_type_sentinel ,
. ptr_type ,
. ptr_type_aligned ,
. ptr_type_bit_range ,
. ptr_type_sentinel ,
2022-09-16 01:33:49 +01:00
= > offsets . nodeToSlice ( tree , p ) ,
2022-07-08 23:00:27 +01:00
else = > " unknown " , // TODO: Implement more "other" type expressions; better safe than sorry
2022-07-08 19:29:53 +01:00
} ,
else = > " unknown " ,
}
else
" unknown " ;
2021-04-07 14:10:18 +01:00
var hover_text : [ ] const u8 = undefined ;
if ( hover_kind = = . Markdown ) {
hover_text =
if ( doc_str ) | doc |
2022-07-31 23:44:07 +01:00
try std . fmt . allocPrint ( server . arena . allocator ( ) , " ```zig \n {s} \n ``` \n ```zig \n ({s}) \n ``` \n {s} " , . { def_str , resolved_type_str , doc } )
2021-03-09 18:53:59 +00:00
else
2022-07-31 23:44:07 +01:00
try std . fmt . allocPrint ( server . arena . allocator ( ) , " ```zig \n {s} \n ``` \n ```zig \n ({s}) \n ``` " , . { def_str , resolved_type_str } ) ;
2021-04-07 14:10:18 +01:00
} else {
hover_text =
if ( doc_str ) | doc |
2022-07-31 23:44:07 +01:00
try std . fmt . allocPrint ( server . arena . allocator ( ) , " {s} ({s}) \n {s} " , . { def_str , resolved_type_str , doc } )
2020-06-12 15:42:41 +01:00
else
2022-07-31 23:44:07 +01:00
try std . fmt . allocPrint ( server . arena . allocator ( ) , " {s} ({s}) " , . { def_str , resolved_type_str } ) ;
2021-04-07 14:10:18 +01:00
}
2020-06-10 20:52:33 +01:00
2022-09-22 19:09:16 +01:00
return types . Hover {
. contents = . { . value = hover_text } ,
} ;
2020-05-25 23:51:50 +01:00
}
2022-10-05 12:40:11 +01:00
fn getLabelGlobal ( pos_index : usize , handle : * const DocumentStore . Handle ) error { OutOfMemory } ! ? analysis . DeclWithHandle {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-06-14 20:24:18 +01:00
const name = identifierFromPosition ( pos_index , handle . * ) ;
if ( name . len = = 0 ) return null ;
return try analysis . lookupLabel ( handle , name , pos_index ) ;
}
2022-07-17 11:00:29 +01:00
fn getSymbolGlobal (
server : * Server ,
pos_index : usize ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-09-22 19:09:16 +01:00
) error { OutOfMemory } ! ? analysis . DeclWithHandle {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-06-10 17:01:44 +01:00
const name = identifierFromPosition ( pos_index , handle . * ) ;
2020-05-25 22:37:18 +01:00
if ( name . len = = 0 ) return null ;
2022-07-31 23:44:07 +01:00
return try analysis . lookupSymbolGlobal ( & server . document_store , & server . arena , handle , name , pos_index ) ;
2020-05-25 22:37:18 +01:00
}
2020-05-21 12:36:14 +01:00
2022-07-17 11:00:29 +01:00
fn gotoDefinitionLabel (
server : * Server ,
pos_index : usize ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-09-22 19:09:16 +01:00
) error { OutOfMemory } ! ? types . Location {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
const decl = ( try getLabelGlobal ( pos_index , handle ) ) orelse return null ;
return try server . gotoDefinitionSymbol ( decl , false ) ;
2020-06-14 20:24:18 +01:00
}
2022-07-17 11:00:29 +01:00
fn gotoDefinitionGlobal (
server : * Server ,
pos_index : usize ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-07-17 11:00:29 +01:00
resolve_alias : bool ,
2022-09-22 19:09:16 +01:00
) error { OutOfMemory } ! ? types . Location {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
const decl = ( try server . getSymbolGlobal ( pos_index , handle ) ) orelse return null ;
return try server . gotoDefinitionSymbol ( decl , resolve_alias ) ;
2020-05-25 23:51:50 +01:00
}
2020-05-18 21:19:23 +01:00
2022-10-05 12:40:11 +01:00
fn hoverDefinitionLabel ( server : * Server , pos_index : usize , handle : * const DocumentStore . Handle ) error { OutOfMemory } ! ? types . Hover {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
const decl = ( try getLabelGlobal ( pos_index , handle ) ) orelse return null ;
return try server . hoverSymbol ( decl ) ;
2020-06-14 20:24:18 +01:00
}
2022-10-05 12:40:11 +01:00
fn hoverDefinitionBuiltin ( server : * Server , pos_index : usize , handle : * const DocumentStore . Handle ) error { OutOfMemory } ! ? types . Hover {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-11-04 22:54:47 +00:00
const name = identifierFromPosition ( pos_index , handle . * ) ;
2022-09-22 19:09:16 +01:00
if ( name . len = = 0 ) return null ;
2020-11-04 22:54:47 +00:00
2022-09-22 04:39:04 +01:00
for ( data . builtins ) | builtin | {
2020-11-04 22:54:47 +00:00
if ( std . mem . eql ( u8 , builtin . name [ 1 . . ] , name ) ) {
2022-09-22 19:09:16 +01:00
return types . Hover {
. contents = . {
. value = try std . fmt . allocPrint (
server . arena . allocator ( ) ,
" ```zig \n {s} \n ``` \n {s} " ,
. { builtin . signature , builtin . documentation } ,
) ,
2020-11-04 22:54:47 +00:00
} ,
2022-09-22 19:09:16 +01:00
} ;
2020-11-04 22:54:47 +00:00
}
}
2022-09-22 04:39:04 +01:00
2022-09-22 19:09:16 +01:00
return null ;
2020-11-04 22:54:47 +00:00
}
2022-10-05 12:40:11 +01:00
fn hoverDefinitionGlobal ( server : * Server , pos_index : usize , handle : * const DocumentStore . Handle ) error { OutOfMemory } ! ? types . Hover {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
const decl = ( try server . getSymbolGlobal ( pos_index , handle ) ) orelse return null ;
return try server . hoverSymbol ( decl ) ;
2020-05-18 21:19:23 +01:00
}
2022-07-17 11:00:29 +01:00
fn getSymbolFieldAccess (
server : * Server ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-09-16 01:33:49 +01:00
source_index : usize ,
loc : offsets . Loc ,
2022-07-17 11:00:29 +01:00
) ! ? analysis . DeclWithHandle {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-16 01:33:49 +01:00
const name = identifierFromPosition ( source_index , handle . * ) ;
2020-05-25 22:37:18 +01:00
if ( name . len = = 0 ) return null ;
2022-10-01 01:45:45 +01:00
var held_range = try server . arena . allocator ( ) . dupeZ ( u8 , offsets . locToSlice ( handle . text , loc ) ) ;
var tokenizer = std . zig . Tokenizer . init ( held_range ) ;
2021-07-10 17:58:37 +01:00
2022-09-16 01:33:49 +01:00
if ( try analysis . getFieldAccessType ( & server . document_store , & server . arena , handle , source_index , & tokenizer ) ) | result | {
2020-07-07 21:50:32 +01:00
const container_handle = result . unwrapped orelse result . original ;
2020-06-17 03:12:12 +01:00
const container_handle_node = switch ( container_handle . type . data ) {
. other = > | n | n ,
else = > return null ,
} ;
return try analysis . lookupSymbolContainer (
2022-07-17 11:00:29 +01:00
& server . document_store ,
2022-07-31 23:44:07 +01:00
& server . arena ,
2020-06-17 03:12:12 +01:00
. { . node = container_handle_node , . handle = container_handle . handle } ,
name ,
true ,
) ;
2020-05-25 22:37:18 +01:00
}
return null ;
}
2022-07-17 11:00:29 +01:00
fn gotoDefinitionFieldAccess (
server : * Server ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-09-16 01:33:49 +01:00
source_index : usize ,
loc : offsets . Loc ,
2022-07-17 11:00:29 +01:00
resolve_alias : bool ,
2022-09-22 19:09:16 +01:00
) error { OutOfMemory } ! ? types . Location {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
const decl = ( try server . getSymbolFieldAccess ( handle , source_index , loc ) ) orelse return null ;
return try server . gotoDefinitionSymbol ( decl , resolve_alias ) ;
2020-05-25 23:51:50 +01:00
}
2020-05-18 21:19:23 +01:00
2022-07-17 11:00:29 +01:00
fn hoverDefinitionFieldAccess (
server : * Server ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-09-16 01:33:49 +01:00
source_index : usize ,
loc : offsets . Loc ,
2022-09-22 19:09:16 +01:00
) error { OutOfMemory } ! ? types . Hover {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
const decl = ( try server . getSymbolFieldAccess ( handle , source_index , loc ) ) orelse return null ;
return try server . hoverSymbol ( decl ) ;
2020-05-18 21:19:23 +01:00
}
2022-07-17 11:00:29 +01:00
fn gotoDefinitionString (
server : * Server ,
pos_index : usize ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-09-22 19:09:16 +01:00
) error { OutOfMemory } ! ? types . Location {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
const import_str = analysis . getImportStr ( handle . tree , 0 , pos_index ) orelse return null ;
2022-10-05 12:23:38 +01:00
const uri = try server . document_store . uriFromImportStr ( server . arena . allocator ( ) , handle . * , import_str ) ;
2020-05-22 16:51:57 +01:00
2022-09-22 19:09:16 +01:00
return types . Location {
. uri = uri orelse return null ,
. range = . {
. start = . { . line = 0 , . character = 0 } ,
. end = . { . line = 0 , . character = 0 } ,
2020-05-22 16:51:57 +01:00
} ,
2022-09-22 19:09:16 +01:00
} ;
2021-03-01 13:32:19 +00:00
}
2021-04-03 16:54:26 +01:00
const DeclToCompletionContext = struct {
2022-07-17 11:17:55 +01:00
server : * Server ,
2022-08-23 11:44:26 +01:00
completions : * std . ArrayListUnmanaged ( types . CompletionItem ) ,
2022-10-05 12:40:11 +01:00
orig_handle : * const DocumentStore . Handle ,
2021-04-03 16:54:26 +01:00
parent_is_type_val : ? bool = null ,
} ;
2022-07-17 11:17:55 +01:00
fn declToCompletion ( context : DeclToCompletionContext , decl_handle : analysis . DeclWithHandle ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-08-23 11:44:26 +01:00
var allocator = context . server . arena . allocator ( ) ;
2020-06-10 20:52:33 +01:00
const tree = decl_handle . handle . tree ;
2020-06-10 17:01:44 +01:00
switch ( decl_handle . decl . * ) {
2022-07-17 11:17:55 +01:00
. ast_node = > | node | try context . server . nodeToCompletion (
2021-03-06 19:55:59 +00:00
context . completions ,
. { . node = node , . handle = decl_handle . handle } ,
null ,
context . orig_handle ,
false ,
2021-04-03 16:54:26 +01:00
context . parent_is_type_val ,
2021-03-06 19:55:59 +00:00
) ,
2022-09-24 20:25:32 +01:00
. param_payload = > | pay | {
const param = pay . param ;
2022-07-17 11:17:55 +01:00
const doc_kind : types . MarkupContent . Kind = if ( context . server . client_capabilities . completion_doc_supports_md ) . Markdown else . PlainText ;
2021-03-01 13:32:19 +00:00
const doc = if ( param . first_doc_comment ) | doc_comments |
2020-06-10 20:52:33 +01:00
types . MarkupContent {
2020-06-12 15:42:41 +01:00
. kind = doc_kind ,
2022-08-23 11:44:26 +01:00
. value = try analysis . collectDocComments ( allocator , tree , doc_comments , doc_kind , false ) ,
2020-06-10 20:52:33 +01:00
}
else
null ;
2022-09-24 20:26:55 +01:00
const first_token = ast . paramFirstToken ( tree , param ) ;
const last_token = ast . paramLastToken ( tree , param ) ;
2021-03-01 13:32:19 +00:00
2022-08-23 11:44:26 +01:00
try context . completions . append ( allocator , . {
2020-06-10 20:52:33 +01:00
. label = tree . tokenSlice ( param . name_token . ? ) ,
. kind = . Constant ,
. documentation = doc ,
2022-09-16 01:33:49 +01:00
. detail = tree . source [ offsets . tokenToIndex ( tree , first_token ) . . offsets . tokenToLoc ( tree , last_token ) . end ] ,
2021-03-28 15:02:48 +01:00
. insertText = tree . tokenSlice ( param . name_token . ? ) ,
. insertTextFormat = . PlainText ,
2020-06-10 20:52:33 +01:00
} ) ;
} ,
. pointer_payload = > | payload | {
2022-08-23 11:44:26 +01:00
try context . completions . append ( allocator , . {
2021-03-01 13:32:19 +00:00
. label = tree . tokenSlice ( payload . name ) ,
2020-06-10 20:52:33 +01:00
. kind = . Variable ,
2021-03-28 15:02:48 +01:00
. insertText = tree . tokenSlice ( payload . name ) ,
. insertTextFormat = . PlainText ,
2020-06-10 20:52:33 +01:00
} ) ;
} ,
2021-03-08 18:46:23 +00:00
. array_payload = > | payload | {
2022-08-23 11:44:26 +01:00
try context . completions . append ( allocator , . {
2021-03-08 18:46:23 +00:00
. label = tree . tokenSlice ( payload . identifier ) ,
. kind = . Variable ,
2021-03-28 15:02:48 +01:00
. insertText = tree . tokenSlice ( payload . identifier ) ,
. insertTextFormat = . PlainText ,
2021-03-08 18:46:23 +00:00
} ) ;
} ,
2021-03-09 18:53:59 +00:00
. array_index = > | payload | {
2022-08-23 11:44:26 +01:00
try context . completions . append ( allocator , . {
2021-03-09 18:53:59 +00:00
. label = tree . tokenSlice ( payload ) ,
. kind = . Variable ,
2021-03-28 15:02:48 +01:00
. insertText = tree . tokenSlice ( payload ) ,
. insertTextFormat = . PlainText ,
2021-03-09 18:53:59 +00:00
} ) ;
} ,
2020-06-10 20:52:33 +01:00
. switch_payload = > | payload | {
2022-08-23 11:44:26 +01:00
try context . completions . append ( allocator , . {
2021-03-09 11:35:56 +00:00
. label = tree . tokenSlice ( payload . node ) ,
2020-06-10 20:52:33 +01:00
. kind = . Variable ,
2021-03-28 15:02:48 +01:00
. insertText = tree . tokenSlice ( payload . node ) ,
. insertTextFormat = . PlainText ,
2020-06-10 20:52:33 +01:00
} ) ;
2020-06-10 17:01:44 +01:00
} ,
2020-06-14 20:24:18 +01:00
. label_decl = > | label_decl | {
2022-08-23 11:44:26 +01:00
try context . completions . append ( allocator , . {
2021-03-01 13:32:19 +00:00
. label = tree . tokenSlice ( label_decl ) ,
2020-06-14 20:24:18 +01:00
. kind = . Variable ,
2021-03-28 15:02:48 +01:00
. insertText = tree . tokenSlice ( label_decl ) ,
. insertTextFormat = . PlainText ,
2020-06-14 20:24:18 +01:00
} ) ;
} ,
2020-06-10 17:01:44 +01:00
}
}
2022-07-17 11:17:55 +01:00
fn completeLabel (
server : * Server ,
pos_index : usize ,
2022-10-05 12:40:11 +01:00
handle : * const DocumentStore . Handle ,
2022-09-22 19:09:16 +01:00
) ! [ ] types . CompletionItem {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-08-23 11:44:26 +01:00
var completions = std . ArrayListUnmanaged ( types . CompletionItem ) { } ;
2020-06-14 20:24:18 +01:00
const context = DeclToCompletionContext {
2022-07-17 11:17:55 +01:00
. server = server ,
2020-06-14 20:24:18 +01:00
. completions = & completions ,
. orig_handle = handle ,
} ;
try analysis . iterateLabels ( handle , pos_index , declToCompletion , context ) ;
2022-09-22 19:09:16 +01:00
return completions . toOwnedSlice ( server . arena . allocator ( ) ) ;
2020-06-14 20:24:18 +01:00
}
2022-09-25 01:45:02 +01:00
fn populateSnippedCompletions (
allocator : std . mem . Allocator ,
completions : * std . ArrayListUnmanaged ( types . CompletionItem ) ,
snippets : [ ] const snipped_data . Snipped ,
config : Config ,
start_with : ? [ ] const u8 ,
) error { OutOfMemory } ! void {
try completions . ensureUnusedCapacity ( allocator , snippets . len ) ;
for ( snippets ) | snipped | {
if ( start_with ) | needle | {
if ( ! std . mem . startsWith ( u8 , snipped . label , needle ) ) continue ;
}
completions . appendAssumeCapacity ( . {
. label = snipped . label ,
. kind = snipped . kind ,
. detail = if ( config . enable_snippets ) snipped . text else null ,
. insertText = if ( config . enable_snippets ) snipped . text else null ,
. insertTextFormat = if ( config . enable_snippets and snipped . text ! = null ) . Snippet else . PlainText ,
} ) ;
}
}
2022-10-05 12:40:11 +01:00
fn completeGlobal ( server : * Server , pos_index : usize , handle : * const DocumentStore . Handle ) ! [ ] types . CompletionItem {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-08-23 11:44:26 +01:00
var completions = std . ArrayListUnmanaged ( types . CompletionItem ) { } ;
2020-05-07 11:56:08 +01:00
2020-06-10 17:01:44 +01:00
const context = DeclToCompletionContext {
2022-07-17 11:17:55 +01:00
. server = server ,
2020-06-10 17:01:44 +01:00
. completions = & completions ,
2020-06-10 18:48:40 +01:00
. orig_handle = handle ,
2020-06-10 17:01:44 +01:00
} ;
2022-07-31 23:44:07 +01:00
try analysis . iterateSymbolsGlobal ( & server . document_store , & server . arena , handle , pos_index , declToCompletion , context ) ;
2022-09-25 01:45:02 +01:00
try populateSnippedCompletions ( server . arena . allocator ( ) , & completions , & snipped_data . generic , server . config . * , null ) ;
2020-06-09 04:21:55 +01:00
2022-07-17 11:00:29 +01:00
if ( server . client_capabilities . label_details_support ) {
2022-06-23 15:44:22 +01:00
for ( completions . items ) | * item | {
2022-09-11 22:48:15 +01:00
try formatDetailledLabel ( item , server . arena . allocator ( ) ) ;
2022-06-23 15:44:22 +01:00
}
}
2022-09-22 19:09:16 +01:00
return completions . toOwnedSlice ( server . arena . allocator ( ) ) ;
2020-04-24 23:19:03 +01:00
}
2022-10-05 12:40:11 +01:00
fn completeFieldAccess ( server : * Server , handle : * const DocumentStore . Handle , source_index : usize , loc : offsets . Loc ) ! ? [ ] types . CompletionItem {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-10-01 01:45:45 +01:00
const allocator = server . arena . allocator ( ) ;
2022-08-23 11:44:26 +01:00
var completions = std . ArrayListUnmanaged ( types . CompletionItem ) { } ;
2021-07-10 17:58:37 +01:00
2022-10-01 01:45:45 +01:00
var held_loc = try allocator . dupeZ ( u8 , offsets . locToSlice ( handle . text , loc ) ) ;
var tokenizer = std . zig . Tokenizer . init ( held_loc ) ;
2021-07-10 17:58:37 +01:00
2022-09-22 19:09:16 +01:00
const result = ( try analysis . getFieldAccessType ( & server . document_store , & server . arena , handle , source_index , & tokenizer ) ) orelse return null ;
try server . typeToCompletion ( & completions , result , handle ) ;
if ( server . client_capabilities . label_details_support ) {
for ( completions . items ) | * item | {
2022-10-01 01:45:45 +01:00
try formatDetailledLabel ( item , allocator ) ;
2022-06-23 15:44:22 +01:00
}
2020-05-13 15:10:20 +01:00
}
2020-06-10 18:48:40 +01:00
2022-12-02 20:14:58 +00:00
return try completions . toOwnedSlice ( allocator ) ;
2020-05-11 13:28:08 +01:00
}
2020-05-07 12:36:40 +01:00
2022-09-11 22:48:15 +01:00
fn formatDetailledLabel ( item : * types . CompletionItem , alloc : std . mem . Allocator ) ! void {
2022-06-23 15:44:22 +01:00
// NOTE: this is not ideal, we should build a detailled label like we do for label/detail
// because this implementation is very loose, nothing is formated properly so we need to clean
// things a little bit, wich is quite messy
// but it works, it provide decent results
if ( item . detail = = null )
return ;
var detailLen : usize = item . detail . ? . len ;
var it : [ ] u8 = try alloc . alloc ( u8 , detailLen ) ;
detailLen - = std . mem . replace ( u8 , item . detail . ? , " " , " " , it ) * 3 ;
it = it [ 0 . . detailLen ] ;
// HACK: for enums 'MyEnum.', item.detail shows everything, we don't want that
const isValue = std . mem . startsWith ( u8 , item . label , it ) ;
const isVar = std . mem . startsWith ( u8 , it , " var " ) ;
const isConst = std . mem . startsWith ( u8 , it , " const " ) ;
// we don't want the entire content of things, see the NOTE above
if ( std . mem . indexOf ( u8 , it , " { " ) ) | end | {
it = it [ 0 . . end ] ;
}
if ( std . mem . indexOf ( u8 , it , " } " ) ) | end | {
it = it [ 0 . . end ] ;
}
if ( std . mem . indexOf ( u8 , it , " ; " ) ) | end | {
it = it [ 0 . . end ] ;
}
2022-09-11 22:48:15 +01:00
// loggerger.info("## label: {s} it: {s} kind: {} isValue: {}", .{item.label, it, item.kind, isValue});
2022-06-23 15:44:22 +01:00
2022-10-14 17:24:22 +01:00
if ( std . mem . startsWith ( u8 , it , " fn " ) or std . mem . startsWith ( u8 , it , " @ " ) ) {
2022-06-23 15:44:22 +01:00
var s : usize = std . mem . indexOf ( u8 , it , " ( " ) orelse return ;
var e : usize = std . mem . lastIndexOf ( u8 , it , " ) " ) orelse return ;
if ( e < s ) {
2022-09-11 22:48:15 +01:00
log . warn ( " something wrong when trying to build label detail for {s} kind: {} " , . { it , item . kind } ) ;
2022-06-23 15:44:22 +01:00
return ;
}
item . detail = item . label ;
item . labelDetails = . { . detail = it [ s . . e + 1 ] , . description = it [ e + 1 . . ] } ;
if ( item . kind = = . Constant ) {
if ( std . mem . indexOf ( u8 , it , " = struct " ) ) | _ | {
item . labelDetails . ? . description = " struct " ;
} else if ( std . mem . indexOf ( u8 , it , " = union " ) ) | _ | {
var us : usize = std . mem . indexOf ( u8 , it , " ( " ) orelse return ;
var ue : usize = std . mem . lastIndexOf ( u8 , it , " ) " ) orelse return ;
if ( ue < us ) {
2022-09-11 22:48:15 +01:00
log . warn ( " something wrong when trying to build label detail for a .Constant|union {s} " , . { it } ) ;
2022-06-23 15:44:22 +01:00
return ;
}
item . labelDetails . ? . description = it [ us - 5 . . ue + 1 ] ;
}
}
} else if ( ( item . kind = = . Variable or item . kind = = . Constant ) and ( isVar or isConst ) ) {
item . insertText = item . label ;
item . insertTextFormat = . PlainText ;
item . detail = item . label ;
const eqlPos = std . mem . indexOf ( u8 , it , " = " ) ;
if ( std . mem . indexOf ( u8 , it , " : " ) ) | start | {
if ( eqlPos ! = null ) {
if ( start > eqlPos . ? ) return ;
}
var e : usize = eqlPos orelse it . len ;
item . labelDetails = . {
. detail = " " , // left
. description = it [ start + 1 . . e ] , // right
} ;
} else if ( std . mem . indexOf ( u8 , it , " = . " ) ) | start | {
item . labelDetails = . {
. detail = " " , // left
. description = it [ start + 2 . . it . len ] , // right
} ;
} else if ( eqlPos ) | start | {
item . labelDetails = . {
. detail = " " , // left
. description = it [ start + 2 . . it . len ] , // right
} ;
}
} else if ( item . kind = = . Variable ) {
var s : usize = std . mem . indexOf ( u8 , it , " : " ) orelse return ;
var e : usize = std . mem . indexOf ( u8 , it , " = " ) orelse return ;
if ( e < s ) {
2022-09-11 22:48:15 +01:00
log . warn ( " something wrong when trying to build label detail for a .Variable {s} " , . { it } ) ;
2022-06-23 15:44:22 +01:00
return ;
}
2022-09-11 22:48:15 +01:00
// loggerger.info("s: {} -> {}", .{s, e});
2022-06-23 15:44:22 +01:00
item . insertText = item . label ;
item . insertTextFormat = . PlainText ;
item . detail = item . label ;
item . labelDetails = . {
. detail = " " , // left
. description = it [ s + 1 . . e ] , // right
} ;
} else if ( std . mem . indexOf ( u8 , it , " @import " ) ! = null ) {
item . insertText = item . label ;
item . insertTextFormat = . PlainText ;
item . detail = item . label ;
item . labelDetails = . {
. detail = " " , // left
. description = it , // right
} ;
} else if ( item . kind = = . Constant or item . kind = = . Field ) {
var s : usize = std . mem . indexOf ( u8 , it , " " ) orelse return ;
var e : usize = std . mem . indexOf ( u8 , it , " = " ) orelse it . len ;
if ( e < s ) {
2022-09-11 22:48:15 +01:00
log . warn ( " something wrong when trying to build label detail for a .Variable {s} " , . { it } ) ;
2022-06-23 15:44:22 +01:00
return ;
}
2022-09-11 22:48:15 +01:00
// loggerger.info("s: {} -> {}", .{s, e});
2022-06-23 15:44:22 +01:00
item . insertText = item . label ;
item . insertTextFormat = . PlainText ;
item . detail = item . label ;
item . labelDetails = . {
. detail = " " , // left
. description = it [ s + 1 . . e ] , // right
} ;
if ( std . mem . indexOf ( u8 , it , " = union( " ) ) | _ | {
var us : usize = std . mem . indexOf ( u8 , it , " ( " ) orelse return ;
var ue : usize = std . mem . lastIndexOf ( u8 , it , " ) " ) orelse return ;
if ( ue < us ) {
2022-09-11 22:48:15 +01:00
log . warn ( " something wrong when trying to build label detail for a .Constant|union {s} " , . { it } ) ;
2022-06-23 15:44:22 +01:00
return ;
}
item . labelDetails . ? . description = it [ us - 5 . . ue + 1 ] ;
} else if ( std . mem . indexOf ( u8 , it , " = enum( " ) ) | _ | {
var es : usize = std . mem . indexOf ( u8 , it , " ( " ) orelse return ;
var ee : usize = std . mem . lastIndexOf ( u8 , it , " ) " ) orelse return ;
if ( ee < es ) {
2022-09-11 22:48:15 +01:00
log . warn ( " something wrong when trying to build label detail for a .Constant|enum {s} " , . { it } ) ;
2022-06-23 15:44:22 +01:00
return ;
}
item . labelDetails . ? . description = it [ es - 4 . . ee + 1 ] ;
} else if ( std . mem . indexOf ( u8 , it , " = struct " ) ) | _ | {
item . labelDetails . ? . description = " struct " ;
} else if ( std . mem . indexOf ( u8 , it , " = union " ) ) | _ | {
item . labelDetails . ? . description = " union " ;
} else if ( std . mem . indexOf ( u8 , it , " = enum " ) ) | _ | {
item . labelDetails . ? . description = " enum " ;
}
} else if ( item . kind = = . Field and isValue ) {
item . insertText = item . label ;
item . insertTextFormat = . PlainText ;
item . detail = item . label ;
item . labelDetails = . {
. detail = " " , // left
. description = item . label , // right
} ;
} else {
// TODO: if something is missing, it neecs to be implemented here
}
// if (item.labelDetails != null)
// logger.info("labelDetails: {s} :: {s}", .{item.labelDetails.?.detail, item.labelDetails.?.description});
}
2022-10-05 12:40:11 +01:00
fn completeError ( server : * Server , handle : * const DocumentStore . Handle ) ! [ ] types . CompletionItem {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-10-05 12:23:38 +01:00
return try server . document_store . errorCompletionItems ( server . arena . allocator ( ) , handle . * ) ;
2021-03-27 19:37:51 +00:00
}
2022-09-22 19:09:16 +01:00
fn kindToSortScore ( kind : types . CompletionItem . Kind ) ? [ ] const u8 {
2022-07-08 08:25:26 +01:00
return switch ( kind ) {
2022-09-22 19:09:16 +01:00
. Module = > " 1_ " , // use for packages
. Folder = > " 2_ " ,
. File = > " 3_ " ,
2022-06-24 10:26:57 +01:00
. Constant = > " 1_ " ,
2022-06-23 15:44:22 +01:00
. Variable = > " 2_ " ,
. Field = > " 3_ " ,
. Function = > " 4_ " ,
2022-07-08 08:25:26 +01:00
2022-09-25 01:45:02 +01:00
. Keyword , . Snippet , . EnumMember = > " 5_ " ,
2022-07-08 08:25:26 +01:00
2022-06-23 15:44:22 +01:00
. Class ,
. Interface ,
. Struct ,
// Union?
2022-07-08 08:25:26 +01:00
. TypeParameter ,
= > " 6_ " ,
2022-09-22 19:09:16 +01:00
else = > {
std . log . debug ( @typeName ( types . CompletionItem . Kind ) + + " {s} has no sort score specified! " , . { @tagName ( kind ) } ) ;
return null ;
} ,
2022-06-23 15:44:22 +01:00
} ;
}
2022-10-05 12:40:11 +01:00
fn completeDot ( server : * Server , handle : * const DocumentStore . Handle ) ! [ ] types . CompletionItem {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-10-05 12:23:38 +01:00
var completions = try server . document_store . enumCompletionItems ( server . arena . allocator ( ) , handle . * ) ;
2021-03-28 15:02:48 +01:00
2022-09-22 19:09:16 +01:00
return completions ;
}
2022-10-05 12:40:11 +01:00
fn completeFileSystemStringLiteral ( allocator : std . mem . Allocator , store : * const DocumentStore , handle : * const DocumentStore . Handle , completing : [ ] const u8 , is_import : bool ) ! [ ] types . CompletionItem {
2022-09-22 19:09:16 +01:00
var subpath_present = false ;
var completions = std . ArrayListUnmanaged ( types . CompletionItem ) { } ;
fsc : {
2022-10-01 01:45:45 +01:00
var document_path = try uri_utils . parse ( allocator , handle . uri ) ;
2022-09-22 19:09:16 +01:00
var document_dir_path = std . fs . openIterableDirAbsolute ( std . fs . path . dirname ( document_path ) orelse break : fsc , . { } ) catch break : fsc ;
defer document_dir_path . close ( ) ;
if ( std . mem . lastIndexOfScalar ( u8 , completing , '/' ) ) | subpath_index | {
var subpath = completing [ 0 . . subpath_index ] ;
if ( std . mem . startsWith ( u8 , subpath , " ./ " ) and subpath_index > 2 ) {
subpath = completing [ 2 . . subpath_index ] ;
} else if ( std . mem . startsWith ( u8 , subpath , " . " ) and subpath_index > 1 ) {
subpath = completing [ 1 . . subpath_index ] ;
}
var old = document_dir_path ;
2022-10-20 17:25:06 +01:00
document_dir_path = document_dir_path . dir . openIterableDir ( subpath , . { } ) catch break : fsc ; // NOTE: Is this even safe lol?
2022-09-22 19:09:16 +01:00
old . close ( ) ;
subpath_present = true ;
}
var dir_iterator = document_dir_path . iterate ( ) ;
while ( try dir_iterator . next ( ) ) | entry | {
if ( std . mem . startsWith ( u8 , entry . name , " . " ) ) continue ;
if ( entry . kind = = . File and is_import and ! std . mem . endsWith ( u8 , entry . name , " .zig " ) ) continue ;
const l = try allocator . dupe ( u8 , entry . name ) ;
try completions . append ( allocator , types . CompletionItem {
. label = l ,
. insertText = l ,
. kind = if ( entry . kind = = . File ) . File else . Folder ,
} ) ;
}
}
if ( ! subpath_present and is_import ) {
2022-10-05 12:23:38 +01:00
if ( handle . associated_build_file ) | uri | {
const build_file = store . build_files . get ( uri ) . ? ;
try completions . ensureUnusedCapacity ( allocator , build_file . config . packages . len ) ;
2022-09-22 19:09:16 +01:00
2022-10-05 12:23:38 +01:00
for ( build_file . config . packages ) | pkg | {
2022-09-22 19:09:16 +01:00
completions . appendAssumeCapacity ( . {
. label = pkg . name ,
. kind = . Module ,
} ) ;
}
}
}
return completions . toOwnedSlice ( allocator ) ;
2021-03-27 19:37:51 +00:00
}
2022-10-05 12:40:11 +01:00
fn documentSymbol ( server : * Server , writer : anytype , id : types . RequestId , handle : * const DocumentStore . Handle ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-31 23:44:07 +01:00
try send ( writer , server . arena . allocator ( ) , types . Response {
2020-06-06 13:40:33 +01:00
. id = id ,
2022-07-31 23:44:07 +01:00
. result = . { . DocumentSymbols = try analysis . getDocumentSymbols ( server . arena . allocator ( ) , handle . tree , server . offset_encoding ) } ,
2020-05-28 01:39:36 +01:00
} ) ;
}
2022-07-31 23:44:07 +01:00
fn initializeHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . Initialize ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-22 19:09:16 +01:00
if ( req . params . capabilities . general ) | general | {
2022-09-16 01:33:49 +01:00
var supports_utf8 = false ;
var supports_utf16 = false ;
var supports_utf32 = false ;
2022-09-22 19:09:16 +01:00
for ( general . positionEncodings . value ) | encoding | {
2022-09-16 01:33:49 +01:00
if ( std . mem . eql ( u8 , encoding , " utf-8 " ) ) {
supports_utf8 = true ;
2022-09-22 19:09:16 +01:00
} else if ( std . mem . eql ( u8 , encoding , " utf-16 " ) ) {
2022-09-16 01:33:49 +01:00
supports_utf16 = true ;
2022-09-22 19:09:16 +01:00
} else if ( std . mem . eql ( u8 , encoding , " utf-32 " ) ) {
2022-09-16 01:33:49 +01:00
supports_utf32 = true ;
}
}
2022-09-22 19:09:16 +01:00
if ( supports_utf8 ) {
2022-07-17 11:00:29 +01:00
server . offset_encoding = . utf8 ;
2022-09-22 19:09:16 +01:00
} else if ( supports_utf32 ) {
2022-09-16 01:33:49 +01:00
server . offset_encoding = . utf32 ;
} else {
server . offset_encoding = . utf16 ;
2020-07-03 10:00:00 +01:00
}
}
2020-06-29 23:34:21 +01:00
if ( req . params . capabilities . textDocument ) | textDocument | {
2022-07-17 11:00:29 +01:00
server . client_capabilities . supports_semantic_tokens = textDocument . semanticTokens . exists ;
2022-07-24 12:38:13 +01:00
server . client_capabilities . supports_inlay_hints = textDocument . inlayHint . exists ;
2020-06-29 23:34:21 +01:00
if ( textDocument . hover ) | hover | {
for ( hover . contentFormat . value ) | format | {
if ( std . mem . eql ( u8 , " markdown " , format ) ) {
2022-07-17 11:00:29 +01:00
server . client_capabilities . hover_supports_md = true ;
2020-06-29 23:34:21 +01:00
}
2020-06-16 16:49:31 +01:00
}
}
2020-06-29 23:34:21 +01:00
if ( textDocument . completion ) | completion | {
if ( completion . completionItem ) | completionItem | {
2022-07-17 11:00:29 +01:00
server . client_capabilities . label_details_support = completionItem . labelDetailsSupport . value ;
server . client_capabilities . supports_snippets = completionItem . snippetSupport . value ;
2020-06-29 23:34:21 +01:00
for ( completionItem . documentationFormat . value ) | documentationFormat | {
if ( std . mem . eql ( u8 , " markdown " , documentationFormat ) ) {
2022-07-17 11:00:29 +01:00
server . client_capabilities . completion_doc_supports_md = true ;
2020-06-12 15:42:41 +01:00
}
}
}
2020-06-29 23:34:21 +01:00
}
2022-11-26 01:14:33 +00:00
if ( textDocument . synchronization ) | synchronization | {
2022-11-25 21:32:08 +00:00
server . client_capabilities . supports_will_save = synchronization . willSave . value ;
server . client_capabilities . supports_will_save_wait_until = synchronization . willSaveWaitUntil . value ;
}
2020-06-29 23:34:21 +01:00
}
2020-06-12 15:42:41 +01:00
2022-10-14 17:24:22 +01:00
// NOTE: everything is initialized, we got the client capabilities
// so we can now format the prebuilt builtins items for labelDetails
if ( server . client_capabilities . label_details_support ) {
2022-10-21 17:24:26 +01:00
for ( server . builtin_completions . items ) | * item | {
2022-10-14 17:24:22 +01:00
try formatDetailledLabel ( item , std . heap . page_allocator ) ;
}
}
2022-07-31 23:44:07 +01:00
try send ( writer , server . arena . allocator ( ) , types . Response {
2020-11-06 08:08:20 +00:00
. id = id ,
. result = . {
. InitializeResult = . {
. serverInfo = . {
. name = " zls " ,
2022-11-16 22:58:37 +00:00
. version = build_options . version ,
2020-11-06 08:08:20 +00:00
} ,
. capabilities = . {
2022-09-30 05:04:27 +01:00
. positionEncoding = server . offset_encoding ,
2020-11-06 08:08:20 +00:00
. signatureHelpProvider = . {
2021-04-01 12:14:49 +01:00
. triggerCharacters = & . { " ( " } ,
. retriggerCharacters = & . { " , " } ,
2020-11-06 08:08:20 +00:00
} ,
2022-09-28 17:07:24 +01:00
. textDocumentSync = . {
. openClose = true ,
2022-11-23 02:05:29 +00:00
. change = . Incremental ,
2022-09-28 17:07:24 +01:00
. save = true ,
2022-11-25 21:32:08 +00:00
. willSave = true ,
. willSaveWaitUntil = true ,
2022-09-28 17:07:24 +01:00
} ,
2020-11-06 08:08:20 +00:00
. renameProvider = true ,
2022-08-14 22:45:05 +01:00
. completionProvider = . { . resolveProvider = false , . triggerCharacters = & [ _ ] [ ] const u8 { " . " , " : " , " @ " , " ] " } , . completionItem = . { . labelDetailsSupport = true } } ,
2022-05-08 04:03:40 +01:00
. documentHighlightProvider = true ,
2020-11-06 08:08:20 +00:00
. hoverProvider = true ,
2022-09-25 00:04:29 +01:00
. codeActionProvider = true ,
2020-11-06 08:08:20 +00:00
. declarationProvider = true ,
. definitionProvider = true ,
. typeDefinitionProvider = true ,
. implementationProvider = false ,
. referencesProvider = true ,
. documentSymbolProvider = true ,
. colorProvider = false ,
. documentFormattingProvider = true ,
. documentRangeFormattingProvider = false ,
2022-10-25 15:35:16 +01:00
. foldingRangeProvider = true ,
2022-11-26 01:14:33 +00:00
. selectionRangeProvider = true ,
2020-11-06 08:08:20 +00:00
. workspaceSymbolProvider = false ,
. rangeProvider = false ,
. documentProvider = true ,
. workspace = . {
. workspaceFolders = . {
2020-11-15 18:51:56 +00:00
. supported = false ,
. changeNotifications = false ,
2020-11-06 08:08:20 +00:00
} ,
} ,
. semanticTokensProvider = . {
2020-11-06 13:55:00 +00:00
. full = true ,
. range = false ,
2020-11-06 08:08:20 +00:00
. legend = . {
. tokenTypes = comptime block : {
const tokTypeFields = std . meta . fields ( semantic_tokens . TokenType ) ;
var names : [ tokTypeFields . len ] [ ] const u8 = undefined ;
for ( tokTypeFields ) | field , i | {
names [ i ] = field . name ;
}
break : block & names ;
} ,
. tokenModifiers = comptime block : {
const tokModFields = std . meta . fields ( semantic_tokens . TokenModifiers ) ;
var names : [ tokModFields . len ] [ ] const u8 = undefined ;
for ( tokModFields ) | field , i | {
names [ i ] = field . name ;
}
break : block & names ;
} ,
} ,
} ,
2022-07-24 12:38:13 +01:00
. inlayHintProvider = true ,
2020-11-06 08:08:20 +00:00
} ,
} ,
} ,
} ) ;
2022-09-30 05:04:27 +01:00
server . status = . initializing ;
2021-12-17 01:41:21 +00:00
if ( req . params . capabilities . workspace ) | workspace | {
2022-07-17 11:00:29 +01:00
server . client_capabilities . supports_configuration = workspace . configuration . value ;
2022-07-11 14:45:31 +01:00
if ( workspace . didChangeConfiguration ! = null and workspace . didChangeConfiguration . ? . dynamicRegistration . value ) {
2022-07-31 23:44:07 +01:00
try server . registerCapability ( writer , " workspace/didChangeConfiguration " ) ;
2021-12-17 01:41:21 +00:00
}
}
2022-09-30 05:04:27 +01:00
log . info ( " zls initializing " , . { } ) ;
2022-09-11 22:48:15 +01:00
log . info ( " {} " , . { server . client_capabilities } ) ;
log . info ( " Using offset encoding: {s} " , . { std . meta . tagName ( server . offset_encoding ) } ) ;
2022-09-29 00:30:26 +01:00
// TODO avoid having to call getZigEnv twice
// once in init and here
const env = Config . getZigEnv ( server . allocator , server . config . zig_exe_path . ? ) orelse return ;
defer std . json . parseFree ( Config . Env , env , . { . allocator = server . allocator } ) ;
const zig_exe_version = std . SemanticVersion . parse ( env . version ) catch return ;
if ( zig_builtin . zig_version . order ( zig_exe_version ) = = . gt ) {
2022-11-10 04:13:35 +00:00
const version_mismatch_message = try std . fmt . allocPrint ( server . arena . allocator ( ) , " ZLS was built with Zig {}, but your Zig version is {s}. Update Zig to avoid unexpected behavior. " , . { zig_builtin . zig_version , env . version } ) ;
try server . showMessage ( writer , . Warning , version_mismatch_message ) ;
2022-09-29 00:30:26 +01:00
}
2020-06-29 23:34:21 +01:00
}
2020-06-16 16:49:31 +01:00
2022-09-30 05:04:27 +01:00
fn initializedHandler ( server : * Server , writer : anytype , id : types . RequestId ) ! void {
_ = id ;
if ( server . status ! = . initializing ) {
std . log . warn ( " received a initialized notification but the server has not send a initialize request! " , . { } ) ;
}
server . status = . initialized ;
if ( server . client_capabilities . supports_configuration )
try server . requestConfiguration ( writer ) ;
}
fn shutdownHandler ( server : * Server , writer : anytype , id : types . RequestId ) ! void {
if ( server . status ! = . initialized ) {
return try sendErrorResponse (
writer ,
server . arena . allocator ( ) ,
types . ErrorCodes . InvalidRequest ,
" received a shutdown request but the server is not initialized! " ,
) ;
}
// Technically we should deinitialize first and send possible errors to the client
return try respondGeneric ( writer , id , null_result_response ) ;
}
fn exitHandler ( server : * Server , writer : anytype , id : types . RequestId ) noreturn {
_ = writer ;
_ = id ;
log . info ( " Server exiting... " , . { } ) ;
// Technically we should deinitialize first and send possible errors to the client
const error_code : u8 = switch ( server . status ) {
. uninitialized , . shutdown = > 0 ,
else = > 1 ,
} ;
std . os . exit ( error_code ) ;
}
2022-12-01 09:00:08 +00:00
fn cancelRequestHandler ( server : * Server , writer : anytype , id : types . RequestId ) ! void {
_ = id ;
_ = writer ;
_ = server ;
// TODO implement $/cancelRequest
}
2022-07-31 23:44:07 +01:00
fn registerCapability ( server : * Server , writer : anytype , method : [ ] const u8 ) ! void {
const id = try std . fmt . allocPrint ( server . arena . allocator ( ) , " register-{s} " , . { method } ) ;
2022-09-30 05:04:55 +01:00
log . debug ( " Dynamically registering method '{s}' " , . { method } ) ;
2022-07-11 14:45:31 +01:00
2022-09-30 05:04:55 +01:00
if ( zig_builtin . zig_backend = = . stage1 ) {
const reg = types . RegistrationParams . Registration {
. id = id ,
. method = method ,
} ;
const registrations = [ 1 ] types . RegistrationParams . Registration { reg } ;
const params = types . RegistrationParams {
. registrations = & registrations ,
} ;
2022-07-11 14:45:31 +01:00
2022-09-30 05:04:55 +01:00
const respp = types . ResponseParams {
. RegistrationParams = params ,
} ;
const req = types . Request {
. id = . { . String = id } ,
. method = " client/registerCapability " ,
. params = respp ,
} ;
2022-07-11 14:45:31 +01:00
2022-09-30 05:04:55 +01:00
try send ( writer , server . arena . allocator ( ) , req ) ;
} else {
try send ( writer , server . arena . allocator ( ) , types . Request {
. id = . { . String = id } ,
. method = " client/registerCapability " ,
. params = types . ResponseParams {
. RegistrationParams = types . RegistrationParams {
. registrations = & . {
. {
. id = id ,
. method = method ,
} ,
} ,
} ,
} ,
} ) ;
}
2022-07-11 14:45:31 +01:00
}
2022-07-31 23:44:07 +01:00
fn requestConfiguration ( server : * Server , writer : anytype ) ! void {
2022-07-11 14:45:31 +01:00
const configuration_items = comptime confi : {
var comp_confi : [ std . meta . fields ( Config ) . len ] types . ConfigurationParams . ConfigurationItem = undefined ;
inline for ( std . meta . fields ( Config ) ) | field , index | {
comp_confi [ index ] = . {
. section = " zls. " + + field . name ,
} ;
}
break : confi comp_confi ;
} ;
2022-07-31 23:44:07 +01:00
try send ( writer , server . arena . allocator ( ) , types . Request {
2022-07-11 14:45:31 +01:00
. id = . { . String = " i_haz_configuration " } ,
. method = " workspace/configuration " ,
2022-09-30 05:04:55 +01:00
. params = types . ResponseParams {
2022-07-11 14:45:31 +01:00
. ConfigurationParams = . {
. items = & configuration_items ,
} ,
} ,
} ) ;
}
2022-07-31 23:44:07 +01:00
fn openDocumentHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . OpenDocument ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-29 19:01:38 +01:00
_ = id ;
2022-07-17 11:00:29 +01:00
const handle = try server . document_store . openDocument ( req . params . textDocument . uri , req . params . textDocument . text ) ;
2022-09-24 20:54:31 +01:00
try server . publishDiagnostics ( writer , handle ) ;
2020-06-29 23:34:21 +01:00
}
2020-04-27 21:38:35 +01:00
2022-07-31 23:44:07 +01:00
fn changeDocumentHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . ChangeDocument ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2021-06-24 11:38:01 +01:00
_ = id ;
2022-10-05 12:23:38 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse return ;
2020-06-13 19:20:04 +01:00
2022-10-05 12:23:38 +01:00
const new_text = try diff . applyTextEdits ( server . allocator , handle . text , req . params . contentChanges , server . offset_encoding ) ;
2022-10-17 19:33:37 +01:00
try server . document_store . refreshDocument ( handle . uri , new_text ) ;
2022-10-05 12:23:38 +01:00
try server . publishDiagnostics ( writer , handle . * ) ;
2020-06-29 23:34:21 +01:00
}
2020-06-13 19:20:04 +01:00
2022-09-25 00:05:12 +01:00
fn saveDocumentHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . SaveDocument ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2021-06-24 11:38:01 +01:00
_ = id ;
2022-09-25 00:05:12 +01:00
const allocator = server . arena . allocator ( ) ;
const uri = req . params . textDocument . uri ;
2022-09-11 22:48:15 +01:00
2022-10-05 12:23:38 +01:00
const handle = server . document_store . getHandle ( uri ) orelse return ;
2022-07-17 11:00:29 +01:00
try server . document_store . applySave ( handle ) ;
2022-09-25 00:05:12 +01:00
if ( handle . tree . errors . len ! = 0 ) return ;
if ( ! server . config . enable_ast_check_diagnostics ) return ;
if ( ! server . config . enable_autofix ) return ;
2022-11-25 21:32:08 +00:00
if ( server . client_capabilities . supports_will_save ) return ;
if ( server . client_capabilities . supports_will_save_wait_until ) return ;
2022-09-25 00:05:12 +01:00
2022-11-25 21:32:08 +00:00
const text_edits = try server . autofix ( allocator , handle ) ;
2022-09-25 00:05:12 +01:00
var workspace_edit = types . WorkspaceEdit { . changes = . { } } ;
try workspace_edit . changes . putNoClobber ( allocator , uri , text_edits ) ;
// NOTE: stage1 moment
const params = types . ResponseParams {
. ApplyEdit = types . ApplyWorkspaceEditParams {
. label = " autofix " ,
. edit = workspace_edit ,
} ,
} ;
try send ( writer , allocator , types . Request {
. id = . { . String = " apply_edit " } ,
. method = " workspace/applyEdit " ,
. params = params ,
} ) ;
2020-06-29 23:34:21 +01:00
}
2022-07-31 23:44:07 +01:00
fn closeDocumentHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . CloseDocument ) error { } ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2021-06-24 11:38:01 +01:00
_ = id ;
2022-07-31 22:38:27 +01:00
_ = writer ;
2022-07-17 11:00:29 +01:00
server . document_store . closeDocument ( req . params . textDocument . uri ) ;
2020-06-29 23:34:21 +01:00
}
2022-11-25 21:32:08 +00:00
fn willSaveHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . WillSave ) ! void {
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-11-26 01:14:33 +00:00
if ( server . client_capabilities . supports_will_save_wait_until ) return ;
2022-11-25 21:32:08 +00:00
try willSaveWaitUntilHandler ( server , writer , id , req ) ;
}
fn willSaveWaitUntilHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . WillSave ) ! void {
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
if ( ! server . config . enable_ast_check_diagnostics ) return ;
if ( ! server . config . enable_autofix ) return ;
const allocator = server . arena . allocator ( ) ;
const uri = req . params . textDocument . uri ;
2022-11-26 01:14:33 +00:00
2022-11-25 21:32:08 +00:00
const handle = server . document_store . getHandle ( uri ) orelse return ;
if ( handle . tree . errors . len ! = 0 ) return ;
var text_edits = try server . autofix ( allocator , handle ) ;
return try send ( writer , allocator , types . Response {
. id = id ,
2022-12-02 20:14:58 +00:00
. result = . { . TextEdits = try text_edits . toOwnedSlice ( allocator ) } ,
2022-11-25 21:32:08 +00:00
} ) ;
}
2022-07-31 23:44:07 +01:00
fn semanticTokensFullHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . SemanticTokensFull ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-29 19:01:38 +01:00
if ( ! server . config . enable_semantic_tokens ) return try respondGeneric ( writer , id , no_semantic_tokens_response ) ;
2020-05-14 00:10:41 +01:00
2022-09-29 19:01:38 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
return try respondGeneric ( writer , id , no_semantic_tokens_response ) ;
} ;
2020-05-18 21:19:23 +01:00
2022-09-29 19:01:38 +01:00
const token_array = try semantic_tokens . writeAllSemanticTokens ( & server . arena , & server . document_store , handle , server . offset_encoding ) ;
return try send ( writer , server . arena . allocator ( ) , types . Response {
. id = id ,
. result = . { . SemanticTokensFull = . { . data = token_array } } ,
} ) ;
2020-06-29 23:34:21 +01:00
}
2020-05-25 22:37:18 +01:00
2022-07-31 23:44:07 +01:00
fn completionHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . Completion ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-17 11:00:29 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , no_completions_response ) ;
2020-06-30 13:46:43 +01:00
} ;
2022-09-25 01:45:02 +01:00
if ( req . params . position . character = = 0 ) {
var completions = std . ArrayListUnmanaged ( types . CompletionItem ) { } ;
try populateSnippedCompletions ( server . arena . allocator ( ) , & completions , & snipped_data . top_level_decl_data , server . config . * , null ) ;
return try send ( writer , server . arena . allocator ( ) , types . Response {
. id = id ,
. result = . {
. CompletionList = . { . isIncomplete = false , . items = completions . items } ,
} ,
} ) ;
}
2021-03-27 19:37:51 +00:00
2022-10-01 01:45:45 +01:00
const source_index = offsets . positionToIndex ( handle . text , req . params . position , server . offset_encoding ) ;
const pos_context = try analysis . getPositionContext ( server . arena . allocator ( ) , handle . text , source_index ) ;
2021-04-01 12:14:49 +01:00
2022-09-22 19:09:16 +01:00
const maybe_completions = switch ( pos_context ) {
2022-09-29 19:01:38 +01:00
. builtin = > server . builtin_completions . items ,
2022-09-22 19:09:16 +01:00
. var_access , . empty = > try server . completeGlobal ( source_index , handle ) ,
. field_access = > | loc | try server . completeFieldAccess ( handle , source_index , loc ) ,
. global_error_set = > try server . completeError ( handle ) ,
. enum_literal = > try server . completeDot ( handle ) ,
. label = > try server . completeLabel ( source_index , handle ) ,
. import_string_literal , . embedfile_string_literal = > | loc | blk : {
if ( ! server . config . enable_import_embedfile_argument_completions ) break : blk null ;
2022-07-09 10:22:02 +01:00
2022-09-16 01:33:49 +01:00
const completing = offsets . locToSlice ( handle . tree . source , loc ) ;
2022-09-22 19:09:16 +01:00
const is_import = pos_context = = . import_string_literal ;
2022-10-05 12:23:38 +01:00
break : blk try completeFileSystemStringLiteral ( server . arena . allocator ( ) , & server . document_store , handle , completing , is_import ) ;
2022-09-22 19:09:16 +01:00
} ,
else = > null ,
} ;
2022-07-09 09:43:46 +01:00
2022-09-22 19:09:16 +01:00
const completions = maybe_completions orelse return try respondGeneric ( writer , id , no_completions_response ) ;
2022-09-29 19:01:38 +01:00
// truncate completions
for ( completions ) | * item | {
if ( item . detail ) | det | {
if ( det . len > server . config . max_detail_length ) {
item . detail = det [ 0 . . server . config . max_detail_length ] ;
}
}
}
// TODO: config for sorting rule?
for ( completions ) | * c | {
const prefix = kindToSortScore ( c . kind ) orelse continue ;
c . sortText = try std . fmt . allocPrint ( server . arena . allocator ( ) , " {s}{s} " , . { prefix , c . label } ) ;
}
2022-07-09 09:43:46 +01:00
2022-09-22 19:09:16 +01:00
try send ( writer , server . arena . allocator ( ) , types . Response {
. id = id ,
. result = . {
. CompletionList = . {
. isIncomplete = false ,
. items = completions ,
} ,
2022-07-09 09:43:46 +01:00
} ,
2022-09-22 19:09:16 +01:00
} ) ;
2020-06-30 13:46:43 +01:00
}
2022-07-31 23:44:07 +01:00
fn signatureHelpHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . SignatureHelp ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2021-04-02 18:49:01 +01:00
const getSignatureInfo = @import ( " signature_help.zig " ) . getSignatureInfo ;
2022-07-17 11:00:29 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , no_signatures_response ) ;
2021-04-01 12:14:49 +01:00
} ;
if ( req . params . position . character = = 0 )
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , no_signatures_response ) ;
2021-04-01 12:14:49 +01:00
2022-10-01 01:45:45 +01:00
const source_index = offsets . positionToIndex ( handle . text , req . params . position , server . offset_encoding ) ;
2021-04-02 18:49:01 +01:00
if ( try getSignatureInfo (
2022-07-17 11:00:29 +01:00
& server . document_store ,
2022-07-31 23:44:07 +01:00
& server . arena ,
2021-04-02 18:49:01 +01:00
handle ,
2022-09-16 01:33:49 +01:00
source_index ,
2021-04-02 18:49:01 +01:00
data ,
) ) | sig_info | {
2022-07-31 23:44:07 +01:00
return try send ( writer , server . arena . allocator ( ) , types . Response {
2021-04-02 18:49:01 +01:00
. id = id ,
2021-04-07 14:10:18 +01:00
. result = . {
. SignatureHelp = . {
. signatures = & [ 1 ] types . SignatureInformation { sig_info } ,
. activeSignature = 0 ,
. activeParameter = sig_info . activeParameter ,
} ,
} ,
2021-04-02 18:49:01 +01:00
} ) ;
2021-04-01 12:14:49 +01:00
}
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , no_signatures_response ) ;
2020-06-30 13:46:43 +01:00
}
2022-07-31 23:44:07 +01:00
fn gotoHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . GotoDefinition , resolve_alias : bool ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-17 11:00:29 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , null_result_response ) ;
2020-06-30 13:46:43 +01:00
} ;
2022-09-22 19:09:16 +01:00
if ( req . params . position . character = = 0 ) return try respondGeneric ( writer , id , null_result_response ) ;
2020-06-30 13:46:43 +01:00
2022-10-01 01:45:45 +01:00
const source_index = offsets . positionToIndex ( handle . text , req . params . position , server . offset_encoding ) ;
const pos_context = try analysis . getPositionContext ( server . arena . allocator ( ) , handle . text , source_index ) ;
2022-09-22 19:09:16 +01:00
const maybe_location = switch ( pos_context ) {
. var_access = > try server . gotoDefinitionGlobal ( source_index , handle , resolve_alias ) ,
. field_access = > | loc | try server . gotoDefinitionFieldAccess ( handle , source_index , loc , resolve_alias ) ,
. import_string_literal = > try server . gotoDefinitionString ( source_index , handle ) ,
. label = > try server . gotoDefinitionLabel ( source_index , handle ) ,
else = > null ,
} ;
const location = maybe_location orelse return try respondGeneric ( writer , id , null_result_response ) ;
try send ( writer , server . arena . allocator ( ) , types . Response {
. id = id ,
. result = . { . Location = location } ,
} ) ;
2020-06-30 13:46:43 +01:00
}
2022-07-31 23:44:07 +01:00
fn gotoDefinitionHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . GotoDefinition ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-31 23:44:07 +01:00
try server . gotoHandler ( writer , id , req , true ) ;
2020-06-30 13:46:43 +01:00
}
2022-07-31 23:44:07 +01:00
fn gotoDeclarationHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . GotoDeclaration ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-31 23:44:07 +01:00
try server . gotoHandler ( writer , id , req , false ) ;
2020-06-30 13:46:43 +01:00
}
2022-07-31 23:44:07 +01:00
fn hoverHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . Hover ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-17 11:00:29 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , null_result_response ) ;
2020-06-30 13:46:43 +01:00
} ;
2022-09-22 19:09:16 +01:00
if ( req . params . position . character = = 0 ) return try respondGeneric ( writer , id , null_result_response ) ;
2022-10-01 01:45:45 +01:00
const source_index = offsets . positionToIndex ( handle . text , req . params . position , server . offset_encoding ) ;
const pos_context = try analysis . getPositionContext ( server . arena . allocator ( ) , handle . text , source_index ) ;
2022-09-22 19:09:16 +01:00
const maybe_hover = switch ( pos_context ) {
. builtin = > try server . hoverDefinitionBuiltin ( source_index , handle ) ,
. var_access = > try server . hoverDefinitionGlobal ( source_index , handle ) ,
. field_access = > | loc | try server . hoverDefinitionFieldAccess ( handle , source_index , loc ) ,
. label = > try server . hoverDefinitionLabel ( source_index , handle ) ,
else = > null ,
} ;
const hover = maybe_hover orelse return try respondGeneric ( writer , id , null_result_response ) ;
2022-10-30 08:07:49 +00:00
// TODO: Figure out a better solution for comptime interpreter diags
try server . publishDiagnostics ( writer , handle . * ) ;
2022-09-22 19:09:16 +01:00
try send ( writer , server . arena . allocator ( ) , types . Response {
. id = id ,
. result = . { . Hover = hover } ,
} ) ;
2020-06-30 13:46:43 +01:00
}
2022-07-31 23:44:07 +01:00
fn documentSymbolsHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . DocumentSymbols ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-17 11:00:29 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , null_result_response ) ;
2020-06-30 13:46:43 +01:00
} ;
2022-07-31 23:44:07 +01:00
try server . documentSymbol ( writer , id , handle ) ;
2020-06-30 13:46:43 +01:00
}
2022-07-31 23:44:07 +01:00
fn formattingHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . Formatting ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-11-16 22:33:15 +00:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
return try respondGeneric ( writer , id , null_result_response ) ;
} ;
2020-06-30 13:46:43 +01:00
2022-11-18 23:49:59 +00:00
if ( handle . tree . errors . len ! = 0 ) {
return try respondGeneric ( writer , id , null_result_response ) ;
}
2022-11-16 22:33:15 +00:00
const formatted = try handle . tree . render ( server . allocator ) ;
defer server . allocator . free ( formatted ) ;
2020-06-30 13:46:43 +01:00
2022-11-16 22:33:15 +00:00
if ( std . mem . eql ( u8 , handle . text , formatted ) ) return try respondGeneric ( writer , id , null_result_response ) ;
2022-08-11 03:47:33 +01:00
2022-11-16 22:33:15 +00:00
// avoid computing diffs if the output is small
const maybe_edits = if ( formatted . len < = 512 ) null else diff . edits ( server . arena . allocator ( ) , handle . text , formatted ) catch null ;
2022-08-11 03:47:33 +01:00
2022-11-16 22:33:15 +00:00
const edits = maybe_edits orelse {
// if edits have been computed we replace the entire file with the formatted text
return try send ( writer , server . arena . allocator ( ) , types . Response {
. id = id ,
. result = . {
. TextEdits = & [ 1 ] types . TextEdit { . {
. range = offsets . locToRange ( handle . text , . { . start = 0 , . end = handle . text . len } , server . offset_encoding ) ,
. newText = formatted ,
} } ,
2020-06-30 13:46:43 +01:00
} ,
2022-11-16 22:33:15 +00:00
} ) ;
} ;
// Convert from `[]diff.Edit` to `[]types.TextEdit`
var text_edits = try std . ArrayListUnmanaged ( types . TextEdit ) . initCapacity ( server . arena . allocator ( ) , edits . items . len ) ;
for ( edits . items ) | edit | {
text_edits . appendAssumeCapacity ( . {
. range = edit . range ,
. newText = edit . newText . items ,
} ) ;
2020-06-30 13:46:43 +01:00
}
2022-11-16 22:33:15 +00:00
return try send (
writer ,
server . arena . allocator ( ) ,
types . Response {
. id = id ,
. result = . { . TextEdits = text_edits . items } ,
} ,
) ;
2020-06-30 13:46:43 +01:00
}
2022-10-01 01:47:40 +01:00
fn didChangeConfigurationHandler ( server : * Server , writer : anytype , id : types . RequestId , req : Config . DidChangeConfigurationParams ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2021-12-17 01:41:21 +00:00
_ = id ;
2022-10-01 01:47:40 +01:00
// NOTE: VS Code seems to always respond with null
if ( req . settings ) | configuration | {
inline for ( std . meta . fields ( Config . Configuration ) ) | field | {
if ( @field ( configuration , field . name ) ) | value | {
2022-10-30 04:30:03 +00:00
blk : {
if ( @TypeOf ( value ) = = [ ] const u8 ) {
if ( value . len = = 0 ) {
break : blk ;
}
}
@field ( server . config , field . name ) = if ( @TypeOf ( value ) = = [ ] const u8 ) try server . allocator . dupe ( u8 , value ) else value ;
log . debug ( " setting configuration option '{s}' to '{any}' " , . { field . name , value } ) ;
}
2022-07-11 14:45:31 +01:00
}
2021-12-17 01:41:21 +00:00
}
2022-07-15 17:06:18 +01:00
2022-09-22 02:31:48 +01:00
try server . config . configChanged ( server . allocator , null ) ;
2022-10-01 01:47:40 +01:00
} else if ( server . client_capabilities . supports_configuration ) {
2022-07-31 23:44:07 +01:00
try server . requestConfiguration ( writer ) ;
2022-10-01 01:47:40 +01:00
}
2021-12-17 01:41:21 +00:00
}
2022-09-16 02:11:39 +01:00
fn renameHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . Rename ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-16 02:11:39 +01:00
try generalReferencesHandler ( server , writer , id , . { . rename = req } ) ;
}
2020-07-05 23:32:14 +01:00
2022-09-16 02:11:39 +01:00
fn referencesHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . References ) ! void {
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2020-07-05 23:32:14 +01:00
2022-09-16 02:11:39 +01:00
try generalReferencesHandler ( server , writer , id , . { . references = req } ) ;
2022-05-08 04:03:40 +01:00
}
2022-07-31 23:44:07 +01:00
fn documentHighlightHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . DocumentHighlight ) ! void {
2022-05-08 04:03:40 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-16 02:11:39 +01:00
try generalReferencesHandler ( server , writer , id , . { . highlight = req } ) ;
}
const GeneralReferencesRequest = union ( enum ) {
rename : requests . Rename ,
references : requests . References ,
highlight : requests . DocumentHighlight ,
pub fn uri ( self : @This ( ) ) [ ] const u8 {
return switch ( self ) {
. rename = > | rename | rename . params . textDocument . uri ,
. references = > | ref | ref . params . textDocument . uri ,
. highlight = > | highlight | highlight . params . textDocument . uri ,
} ;
}
pub fn position ( self : @This ( ) ) types . Position {
return switch ( self ) {
. rename = > | rename | rename . params . position ,
. references = > | ref | ref . params . position ,
. highlight = > | highlight | highlight . params . position ,
} ;
}
} ;
fn generalReferencesHandler ( server : * Server , writer : anytype , id : types . RequestId , req : GeneralReferencesRequest ) ! void {
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
const allocator = server . arena . allocator ( ) ;
const handle = server . document_store . getHandle ( req . uri ( ) ) orelse {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , null_result_response ) ;
2022-05-08 04:03:40 +01:00
} ;
2022-09-16 02:11:39 +01:00
if ( req . position ( ) . character < = 0 ) return try respondGeneric ( writer , id , null_result_response ) ;
2022-05-08 04:03:40 +01:00
2022-10-01 01:45:45 +01:00
const source_index = offsets . positionToIndex ( handle . text , req . position ( ) , server . offset_encoding ) ;
const pos_context = try analysis . getPositionContext ( server . arena . allocator ( ) , handle . text , source_index ) ;
2022-09-16 02:11:39 +01:00
const decl = switch ( pos_context ) {
. var_access = > try server . getSymbolGlobal ( source_index , handle ) ,
. field_access = > | range | try server . getSymbolFieldAccess ( handle , source_index , range ) ,
. label = > try getLabelGlobal ( source_index , handle ) ,
else = > null ,
} orelse return try respondGeneric ( writer , id , null_result_response ) ;
const include_decl = switch ( req ) {
. references = > | ref | ref . params . context . includeDeclaration ,
else = > true ,
} ;
const locations = if ( pos_context = = . label )
2022-11-22 10:08:43 +00:00
// FIXME https://github.com/zigtools/zls/issues/728
// try references.labelReferences(allocator, decl, server.offset_encoding, include_decl)
std . ArrayListUnmanaged ( types . Location ) { }
2022-09-16 02:11:39 +01:00
else
try references . symbolReferences (
& server . arena ,
& server . document_store ,
decl ,
server . offset_encoding ,
include_decl ,
server . config . skip_std_references ,
req ! = . highlight , // scan the entire workspace except for highlight
) ;
const result : types . ResponseParams = switch ( req ) {
. rename = > | rename | blk : {
var edits : types . WorkspaceEdit = . { . changes = . { } } ;
for ( locations . items ) | loc | {
const gop = try edits . changes . getOrPutValue ( allocator , loc . uri , . { } ) ;
try gop . value_ptr . append ( allocator , . {
. range = loc . range ,
. newText = rename . params . newName ,
} ) ;
}
break : blk . { . WorkspaceEdit = edits } ;
} ,
. references = > . { . Locations = locations . items } ,
. highlight = > blk : {
var highlights = try std . ArrayListUnmanaged ( types . DocumentHighlight ) . initCapacity ( allocator , locations . items . len ) ;
2022-10-01 01:45:45 +01:00
const uri = handle . uri ;
2022-09-16 02:11:39 +01:00
for ( locations . items ) | loc | {
if ( ! std . mem . eql ( u8 , loc . uri , uri ) ) continue ;
highlights . appendAssumeCapacity ( . {
. range = loc . range ,
. kind = . Text ,
} ) ;
}
break : blk . { . DocumentHighlight = highlights . items } ;
} ,
} ;
try send ( writer , allocator , types . Response {
. id = id ,
. result = result ,
} ) ;
2020-07-05 23:32:14 +01:00
}
2022-07-24 12:38:13 +01:00
fn isPositionBefore ( lhs : types . Position , rhs : types . Position ) bool {
if ( lhs . line = = rhs . line ) {
return lhs . character < rhs . character ;
} else {
return lhs . line < rhs . line ;
}
}
2022-07-31 23:44:07 +01:00
fn inlayHintHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . InlayHint ) ! void {
2022-07-24 12:38:13 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-09-29 19:01:38 +01:00
if ( ! server . config . enable_inlay_hints ) return try respondGeneric ( writer , id , null_result_response ) ;
2022-07-24 12:38:13 +01:00
2022-09-29 19:01:38 +01:00
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
return try respondGeneric ( writer , id , null_result_response ) ;
} ;
2022-07-24 12:38:13 +01:00
2022-09-29 19:01:38 +01:00
const hover_kind : types . MarkupContent . Kind = if ( server . client_capabilities . hover_supports_md ) . Markdown else . PlainText ;
2022-07-24 12:38:13 +01:00
2022-09-29 19:01:38 +01:00
// TODO cache hints per document
// because the function could be stored in a different document
// we need the regenerate hints when the document itself or its imported documents change
// with caching it would also make sense to generate all hints instead of only the visible ones
const hints = try inlay_hints . writeRangeInlayHint (
& server . arena ,
server . config . * ,
& server . document_store ,
handle ,
req . params . range ,
hover_kind ,
server . offset_encoding ,
) ;
defer {
for ( hints ) | hint | {
server . allocator . free ( hint . tooltip . value ) ;
2022-07-24 12:38:13 +01:00
}
2022-09-29 19:01:38 +01:00
server . allocator . free ( hints ) ;
}
2022-07-24 12:38:13 +01:00
2022-09-29 19:01:38 +01:00
// and only convert and return all hints in range for every request
var visible_hints = hints ;
// small_hints should roughly be sorted by position
for ( hints ) | hint , i | {
if ( isPositionBefore ( hint . position , req . params . range . start ) ) continue ;
visible_hints = hints [ i . . ] ;
break ;
2022-07-24 12:38:13 +01:00
}
2022-09-29 19:01:38 +01:00
for ( visible_hints ) | hint , i | {
if ( isPositionBefore ( hint . position , req . params . range . end ) ) continue ;
visible_hints = visible_hints [ 0 . . i ] ;
break ;
}
return try send ( writer , server . arena . allocator ( ) , types . Response {
. id = id ,
. result = . { . InlayHint = visible_hints } ,
} ) ;
2022-07-24 12:38:13 +01:00
}
2022-09-25 00:04:29 +01:00
fn codeActionHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . CodeAction ) ! void {
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
return try respondGeneric ( writer , id , null_result_response ) ;
} ;
const allocator = server . arena . allocator ( ) ;
var builder = code_actions . Builder {
. arena = & server . arena ,
. document_store = & server . document_store ,
. handle = handle ,
. offset_encoding = server . offset_encoding ,
} ;
var actions = std . ArrayListUnmanaged ( types . CodeAction ) { } ;
for ( req . params . context . diagnostics ) | diagnostic | {
try builder . generateCodeAction ( diagnostic , & actions ) ;
}
for ( actions . items ) | * action | {
// TODO query whether SourceFixAll is supported by the server
if ( action . kind = = . SourceFixAll ) action . kind = . QuickFix ;
}
return try send ( writer , allocator , types . Response {
. id = id ,
. result = . { . CodeAction = actions . items } ,
} ) ;
}
2022-10-28 19:35:22 +01:00
fn foldingRangeHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . FoldingRange ) ! void {
const Token = std . zig . Token ;
const Node = Ast . Node ;
2022-10-25 15:35:16 +01:00
const allocator = server . arena . allocator ( ) ;
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
log . warn ( " Trying to get folding ranges of non existent document {s} " , . { req . params . textDocument . uri } ) ;
return try respondGeneric ( writer , id , null_result_response ) ;
} ;
2022-10-28 19:35:22 +01:00
const helper = struct {
const Inclusivity = enum { inclusive , exclusive } ;
/// Returns true if added.
fn maybeAddTokRange (
p_ranges : * std . ArrayList ( types . FoldingRange ) ,
tree : Ast ,
start : Ast . TokenIndex ,
end : Ast . TokenIndex ,
end_reach : Inclusivity ,
) std . mem . Allocator . Error ! bool {
const can_add = ! tree . tokensOnSameLine ( start , end ) ;
if ( can_add ) {
try addTokRange ( p_ranges , tree , start , end , end_reach ) ;
}
return can_add ;
}
fn addTokRange (
p_ranges : * std . ArrayList ( types . FoldingRange ) ,
tree : Ast ,
start : Ast . TokenIndex ,
end : Ast . TokenIndex ,
end_reach : Inclusivity ,
) std . mem . Allocator . Error ! void {
std . debug . assert ( ! std . debug . runtime_safety or ! tree . tokensOnSameLine ( start , end ) ) ;
const start_loc = tree . tokenLocation ( 0 , start ) ;
const end_loc_rel = tree . tokenLocation ( @intCast ( Ast . ByteOffset , start_loc . line_start ) , end ) ;
std . debug . assert ( end_loc_rel . line ! = 0 ) ;
try p_ranges . append ( . {
. startLine = start_loc . line ,
. endLine = ( start_loc . line + end_loc_rel . line ) -
@boolToInt ( end_reach = = . exclusive ) ,
} ) ;
}
} ;
2022-10-25 15:35:16 +01:00
// Used to store the result
var ranges = std . ArrayList ( types . FoldingRange ) . init ( allocator ) ;
2022-10-28 19:35:22 +01:00
const token_tags : [ ] const Token . Tag = handle . tree . tokens . items ( . tag ) ;
const node_tags : [ ] const Node . Tag = handle . tree . nodes . items ( . tag ) ;
2022-10-25 15:35:16 +01:00
2022-10-28 19:35:22 +01:00
if ( token_tags . len = = 0 ) return ;
if ( token_tags [ 0 ] = = . container_doc_comment ) {
var tok : Ast . TokenIndex = 1 ;
while ( tok < token_tags . len ) : ( tok + = 1 ) {
if ( token_tags [ tok ] ! = . container_doc_comment ) {
break ;
}
}
if ( tok > 1 ) { // each container doc comment has its own line, so each one counts for a line
try ranges . append ( . {
2022-10-28 21:59:33 +01:00
. startLine = 0 ,
. endLine = tok - 1 ,
2022-10-28 19:35:22 +01:00
} ) ;
2022-10-25 15:35:16 +01:00
}
2022-10-28 19:35:22 +01:00
}
2022-10-25 15:35:16 +01:00
2022-10-28 19:35:22 +01:00
for ( node_tags ) | node_tag , i | {
const node = @intCast ( Node . Index , i ) ;
switch ( node_tag ) {
// only fold the expression pertaining to the if statement, and the else statement, each respectively.
// TODO: Should folding multiline condition expressions also be supported? Ditto for the other control flow structures.
. @ " if " , . if_simple = > {
const if_full = ast . ifFull ( handle . tree , node ) ;
const start_tok_1 = handle . tree . lastToken ( if_full . ast . cond_expr ) ;
const end_tok_1 = handle . tree . lastToken ( if_full . ast . then_expr ) ;
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , start_tok_1 , end_tok_1 , . inclusive ) ;
if ( if_full . ast . else_expr = = 0 ) continue ;
const start_tok_2 = if_full . else_token ;
const end_tok_2 = handle . tree . lastToken ( if_full . ast . else_expr ) ;
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , start_tok_2 , end_tok_2 , . inclusive ) ;
} ,
// same as if/else
. @ " for " ,
. for_simple ,
. @ " while " ,
. while_cont ,
. while_simple ,
= > {
const loop_full = ast . whileAst ( handle . tree , node ) . ? ;
const start_tok_1 = handle . tree . lastToken ( loop_full . ast . cond_expr ) ;
const end_tok_1 = handle . tree . lastToken ( loop_full . ast . then_expr ) ;
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , start_tok_1 , end_tok_1 , . inclusive ) ;
if ( loop_full . ast . else_expr = = 0 ) continue ;
const start_tok_2 = loop_full . else_token ;
const end_tok_2 = handle . tree . lastToken ( loop_full . ast . else_expr ) ;
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , start_tok_2 , end_tok_2 , . inclusive ) ;
} ,
. global_var_decl ,
. simple_var_decl ,
. aligned_var_decl ,
. container_field_init ,
. container_field_align ,
. container_field ,
. fn_proto ,
. fn_proto_multi ,
. fn_proto_one ,
. fn_proto_simple ,
. fn_decl ,
= > decl_node_blk : {
doc_comment_range : {
const first_tok : Ast . TokenIndex = handle . tree . firstToken ( node ) ;
if ( first_tok = = 0 ) break : doc_comment_range ;
const end_doc_tok = first_tok - 1 ;
if ( token_tags [ end_doc_tok ] ! = . doc_comment ) break : doc_comment_range ;
var start_doc_tok = end_doc_tok ;
while ( start_doc_tok ! = 0 ) {
if ( token_tags [ start_doc_tok - 1 ] ! = . doc_comment ) break ;
start_doc_tok - = 1 ;
}
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , start_doc_tok , end_doc_tok , . inclusive ) ;
}
// Function prototype folding regions
var fn_proto_buffer : [ 1 ] Node . Index = undefined ;
const fn_proto = ast . fnProto ( handle . tree , node , fn_proto_buffer [ 0 . . ] ) orelse
break : decl_node_blk ;
const list_start_tok : Ast . TokenIndex = fn_proto . lparen ;
const list_end_tok : Ast . TokenIndex = handle . tree . lastToken ( fn_proto . ast . proto_node ) ;
if ( handle . tree . tokensOnSameLine ( list_start_tok , list_end_tok ) ) break : decl_node_blk ;
try ranges . ensureUnusedCapacity ( 1 + fn_proto . ast . params . len ) ; // best guess, doesn't include anytype params
helper . addTokRange ( & ranges , handle . tree , list_start_tok , list_end_tok , . exclusive ) catch | err | switch ( err ) {
error . OutOfMemory = > unreachable ,
} ;
var it = fn_proto . iterate ( & handle . tree ) ;
while ( ast . nextFnParam ( & it ) ) | param | {
const doc_start_tok = param . first_doc_comment orelse continue ;
var doc_end_tok = doc_start_tok ;
while ( token_tags [ doc_end_tok + 1 ] = = . doc_comment )
doc_end_tok + = 1 ;
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , doc_start_tok , doc_end_tok , . inclusive ) ;
}
} ,
. @ " catch " ,
. @ " orelse " ,
. multiline_string_literal ,
// TODO: Similar to condition expressions in control flow structures, should folding multiline grouped expressions be enabled?
// .grouped_expression,
= > {
const start_tok = handle . tree . firstToken ( node ) ;
const end_tok = handle . tree . lastToken ( node ) ;
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , start_tok , end_tok , . inclusive ) ;
} ,
// most other trivial cases can go through here.
else = > {
switch ( node_tag ) {
. array_init ,
. array_init_one ,
. array_init_dot_two ,
. array_init_one_comma ,
. array_init_dot_two_comma ,
. array_init_dot ,
. array_init_dot_comma ,
. array_init_comma ,
. struct_init ,
. struct_init_one ,
. struct_init_one_comma ,
. struct_init_dot_two ,
. struct_init_dot_two_comma ,
. struct_init_dot ,
. struct_init_dot_comma ,
. struct_init_comma ,
. @ " switch " ,
. switch_comma ,
= > { } ,
else = > disallow_fold : {
if ( ast . isBlock ( handle . tree , node ) )
break : disallow_fold ;
if ( ast . isCall ( handle . tree , node ) )
break : disallow_fold ;
if ( ast . isBuiltinCall ( handle . tree , node ) )
break : disallow_fold ;
if ( ast . isContainer ( handle . tree , node ) and node_tag ! = . root )
break : disallow_fold ;
continue ; // no conditions met, continue iterating without adding this potential folding range
} ,
}
const start_tok = handle . tree . firstToken ( node ) ;
const end_tok = handle . tree . lastToken ( node ) ;
_ = try helper . maybeAddTokRange ( & ranges , handle . tree , start_tok , end_tok , . exclusive ) ;
} ,
2022-10-25 15:35:16 +01:00
}
}
// Iterate over the source code and look for code regions with #region #endregion
{
2022-10-28 19:35:22 +01:00
// We add opened folding regions to a stack as we go and pop one off when we find a closing brace.
// As an optimization we start with a reasonable capacity, which should work well in most cases since
// people will almost never have nesting that deep.
var stack = try std . ArrayList ( usize ) . initCapacity ( allocator , 10 ) ;
2022-10-25 15:35:16 +01:00
var i : usize = 0 ;
var lines_count : usize = 0 ;
while ( i < handle . tree . source . len ) : ( i + = 1 ) {
const slice = handle . tree . source [ i . . ] ;
2022-10-28 19:35:22 +01:00
2022-10-25 15:35:16 +01:00
if ( slice [ 0 ] = = '\n' ) {
lines_count + = 1 ;
}
2022-10-28 19:35:22 +01:00
2022-10-25 15:35:16 +01:00
if ( std . mem . startsWith ( u8 , slice , " //#region " ) ) {
try stack . append ( lines_count ) ;
}
if ( std . mem . startsWith ( u8 , slice , " //#endregion " ) and stack . items . len > 0 ) {
const start_line = stack . pop ( ) ;
const end_line = lines_count ;
2022-10-28 19:35:22 +01:00
2022-10-25 15:35:16 +01:00
// Add brace pairs but discard those from the same line, no need to waste memory on them
2022-10-28 19:35:22 +01:00
if ( start_line ! = end_line ) {
2022-10-25 15:35:16 +01:00
try ranges . append ( . {
. startLine = start_line ,
. endLine = end_line ,
} ) ;
}
}
}
}
2022-10-28 19:35:22 +01:00
try send ( writer , allocator , types . Response {
2022-10-25 15:35:16 +01:00
. id = id ,
. result = . { . FoldingRange = ranges . items } ,
} ) ;
}
2022-11-26 01:14:33 +00:00
fn selectionRangeHandler ( server : * Server , writer : anytype , id : types . RequestId , req : requests . SelectionRange ) ! void {
const allocator = server . arena . allocator ( ) ;
const handle = server . document_store . getHandle ( req . params . textDocument . uri ) orelse {
log . warn ( " Trying to get selection range of non existent document {s} " , . { req . params . textDocument . uri } ) ;
return try respondGeneric ( writer , id , null_result_response ) ;
} ;
// For each of the input positons, we need to compute the stack of AST
// nodes/ranges which contain the position. At the moment, we do this in a
// super inefficient way, by iterationg _all_ nodes, selecting the ones that
// contain position, and then sorting.
//
// A faster algorithm would be to walk the tree starting from the root,
// descending into the child containing the position at every step.
var result = try allocator . alloc ( * types . SelectionRange , req . params . positions . len ) ;
var locs = try std . ArrayListUnmanaged ( offsets . Loc ) . initCapacity ( allocator , 32 ) ;
for ( req . params . positions ) | position , position_index | {
const index = offsets . positionToIndex ( handle . text , position , server . offset_encoding ) ;
locs . clearRetainingCapacity ( ) ;
for ( handle . tree . nodes . items ( . data ) ) | _ , i | {
const node = @intCast ( u32 , i ) ;
const loc = offsets . nodeToLoc ( handle . tree , node ) ;
if ( loc . start < = index and index < = loc . end ) {
( try locs . addOne ( allocator ) ) . * = loc ;
}
}
std . sort . sort ( offsets . Loc , locs . items , { } , shorterLocsFirst ) ;
{
var i : usize = 0 ;
while ( i + 1 < locs . items . len ) {
if ( std . meta . eql ( locs . items [ i ] , locs . items [ i + 1 ] ) ) {
_ = locs . orderedRemove ( i ) ;
} else {
i + = 1 ;
}
}
}
var selection_ranges = try allocator . alloc ( types . SelectionRange , locs . items . len ) ;
for ( selection_ranges ) | * range , i | {
range . range = offsets . locToRange ( handle . text , locs . items [ i ] , server . offset_encoding ) ;
range . parent = if ( i + 1 < selection_ranges . len ) & selection_ranges [ i + 1 ] else null ;
}
result [ position_index ] = & selection_ranges [ 0 ] ;
}
try send ( writer , allocator , types . Response {
. id = id ,
. result = . { . SelectionRange = result } ,
} ) ;
}
fn shorterLocsFirst ( _ : void , lhs : offsets . Loc , rhs : offsets . Loc ) bool {
return ( lhs . end - lhs . start ) < ( rhs . end - rhs . start ) ;
}
2020-06-30 16:00:33 +01:00
// Needed for the hack seen below.
2020-07-12 20:12:09 +01:00
fn extractErr ( val : anytype ) anyerror {
2020-06-30 16:00:33 +01:00
val catch | e | return e ;
return error . HackDone ;
}
2022-07-31 23:44:07 +01:00
pub fn processJsonRpc ( server : * Server , writer : anytype , json : [ ] const u8 ) ! void {
2022-06-06 04:50:17 +01:00
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
2022-07-31 23:44:07 +01:00
server . arena = std . heap . ArenaAllocator . init ( server . allocator ) ;
defer server . arena . deinit ( ) ;
var parser = std . json . Parser . init ( server . arena . allocator ( ) , false ) ;
2022-07-31 22:38:27 +01:00
defer parser . deinit ( ) ;
2020-06-29 23:34:21 +01:00
var tree = try parser . parse ( json ) ;
defer tree . deinit ( ) ;
2020-05-28 01:39:36 +01:00
2020-07-05 22:56:41 +01:00
const id = if ( tree . root . Object . get ( " id " ) ) | id | switch ( id ) {
2022-09-30 05:04:27 +01:00
. Integer = > | int | types . RequestId { . Integer = @intCast ( i32 , int ) } ,
2020-06-29 23:34:21 +01:00
. String = > | str | types . RequestId { . String = str } ,
else = > types . RequestId { . Integer = 0 } ,
} else types . RequestId { . Integer = 0 } ;
2020-06-16 20:02:31 +01:00
2022-07-11 19:28:50 +01:00
if ( id = = . String and std . mem . startsWith ( u8 , id . String , " register " ) )
2022-07-11 14:45:31 +01:00
return ;
2022-09-25 00:05:12 +01:00
if ( id = = . String and std . mem . startsWith ( u8 , id . String , " apply_edit " ) )
return ;
2022-07-11 14:45:31 +01:00
if ( id = = . String and std . mem . eql ( u8 , id . String , " i_haz_configuration " ) ) {
2022-09-11 22:48:15 +01:00
log . info ( " Setting configuration... " , . { } ) ;
2022-07-11 14:45:31 +01:00
// NOTE: Does this work with other editors?
// Yes, String ids are officially supported by LSP
// but not sure how standard this "standard" really is
2022-11-13 22:28:00 +00:00
if ( tree . root . Object . get ( " error " ) ) | _ | return ;
2022-07-11 14:45:31 +01:00
const result = tree . root . Object . get ( " result " ) . ? . Array ;
inline for ( std . meta . fields ( Config ) ) | field , index | {
const value = result . items [ index ] ;
const ft = if ( @typeInfo ( field . field_type ) = = . Optional )
@typeInfo ( field . field_type ) . Optional . child
else
field . field_type ;
const ti = @typeInfo ( ft ) ;
if ( value ! = . Null ) {
const new_value : field . field_type = switch ( ft ) {
[ ] const u8 = > switch ( value ) {
2022-08-22 01:11:50 +01:00
. String = > | s | blk : {
2022-10-30 04:30:03 +00:00
if ( s . len = = 0 ) {
if ( field . field_type = = ? [ ] const u8 ) {
break : blk null ;
2022-11-13 22:28:00 +00:00
} else {
2022-10-30 04:30:03 +00:00
break : blk s ;
}
}
2022-08-22 01:11:50 +01:00
var nv = try server . allocator . dupe ( u8 , s ) ;
2022-08-22 18:49:15 +01:00
if ( @field ( server . config , field . name ) ) | prev_val | server . allocator . free ( prev_val ) ;
2022-08-22 01:11:50 +01:00
break : blk nv ;
} , // TODO: Allocation model? (same with didChangeConfiguration); imo this isn't *that* bad but still
2022-07-11 14:45:31 +01:00
else = > @panic ( " Invalid configuration value " ) , // TODO: Handle this
} ,
else = > switch ( ti ) {
. Int = > switch ( value ) {
. Integer = > | s | std . math . cast ( ft , s ) orelse @panic ( " Invalid configuration value " ) ,
else = > @panic ( " Invalid configuration value " ) , // TODO: Handle this
} ,
. Bool = > switch ( value ) {
. Bool = > | b | b ,
else = > @panic ( " Invalid configuration value " ) , // TODO: Handle this
} ,
else = > @compileError ( " Not implemented for " + + @typeName ( ft ) ) ,
} ,
} ;
2022-09-11 22:48:15 +01:00
log . debug ( " setting configuration option '{s}' to '{any}' " , . { field . name , new_value } ) ;
2022-07-17 11:00:29 +01:00
@field ( server . config , field . name ) = new_value ;
2022-07-11 14:45:31 +01:00
}
}
2022-09-22 02:31:48 +01:00
try server . config . configChanged ( server . allocator , null ) ;
2022-07-15 17:06:18 +01:00
2022-07-11 14:45:31 +01:00
return ;
}
2020-07-05 22:56:41 +01:00
const method = tree . root . Object . get ( " method " ) . ? . String ;
2020-06-16 20:02:31 +01:00
2022-09-30 05:04:27 +01:00
switch ( server . status ) {
. uninitialized = > blk : {
if ( std . mem . eql ( u8 , method , " initialize " ) ) break : blk ;
if ( std . mem . eql ( u8 , method , " exit " ) ) break : blk ;
// ignore notifications
if ( tree . root . Object . get ( " id " ) = = null ) break : blk ;
return try sendErrorResponse ( writer , server . arena . allocator ( ) , . ServerNotInitialized , " server received a request before being initialized! " ) ;
} ,
. initializing = > blk : {
if ( std . mem . eql ( u8 , method , " initialized " ) ) break : blk ;
if ( std . mem . eql ( u8 , method , " $/progress " ) ) break : blk ;
return try sendErrorResponse ( writer , server . arena . allocator ( ) , . InvalidRequest , " server received a request during initialization! " ) ;
} ,
. initialized = > { } ,
. shutdown = > return try sendErrorResponse ( writer , server . arena . allocator ( ) , . InvalidRequest , " server received a request after shutdown! " ) ,
}
2020-06-29 23:34:21 +01:00
const start_time = std . time . milliTimestamp ( ) ;
defer {
2022-07-31 22:39:33 +01:00
// makes `zig build test` look nice
if ( ! zig_builtin . is_test and ! std . mem . eql ( u8 , method , " shutdown " ) ) {
const end_time = std . time . milliTimestamp ( ) ;
2022-09-11 22:48:15 +01:00
log . debug ( " Took {}ms to process method {s} " , . { end_time - start_time , method } ) ;
2022-07-31 22:39:33 +01:00
}
2020-06-29 23:34:21 +01:00
}
2020-06-27 01:16:14 +01:00
2020-06-29 23:34:21 +01:00
const method_map = . {
2022-07-11 14:45:31 +01:00
. { " initialized " , void , initializedHandler } ,
2020-06-30 13:46:43 +01:00
. { " initialize " , requests . Initialize , initializeHandler } ,
. { " shutdown " , void , shutdownHandler } ,
2022-09-30 05:04:27 +01:00
. { " exit " , void , exitHandler } ,
2022-12-01 09:00:08 +00:00
. { " $/cancelRequest " , void , cancelRequestHandler } ,
2020-06-30 13:46:43 +01:00
. { " textDocument/didOpen " , requests . OpenDocument , openDocumentHandler } ,
. { " textDocument/didChange " , requests . ChangeDocument , changeDocumentHandler } ,
. { " textDocument/didSave " , requests . SaveDocument , saveDocumentHandler } ,
. { " textDocument/didClose " , requests . CloseDocument , closeDocumentHandler } ,
2022-11-26 01:14:33 +00:00
. { " textDocument/willSave " , requests . WillSave , willSaveHandler } ,
. { " textDocument/willSaveWaitUntil " , requests . WillSave , willSaveWaitUntilHandler } ,
2020-09-25 22:24:10 +01:00
. { " textDocument/semanticTokens/full " , requests . SemanticTokensFull , semanticTokensFullHandler } ,
2022-07-24 12:38:13 +01:00
. { " textDocument/inlayHint " , requests . InlayHint , inlayHintHandler } ,
2020-06-30 13:46:43 +01:00
. { " textDocument/completion " , requests . Completion , completionHandler } ,
2021-04-02 18:49:01 +01:00
. { " textDocument/signatureHelp " , requests . SignatureHelp , signatureHelpHandler } ,
2020-06-30 13:46:43 +01:00
. { " textDocument/definition " , requests . GotoDefinition , gotoDefinitionHandler } ,
. { " textDocument/typeDefinition " , requests . GotoDefinition , gotoDefinitionHandler } ,
. { " textDocument/implementation " , requests . GotoDefinition , gotoDefinitionHandler } ,
. { " textDocument/declaration " , requests . GotoDeclaration , gotoDeclarationHandler } ,
. { " textDocument/hover " , requests . Hover , hoverHandler } ,
. { " textDocument/documentSymbol " , requests . DocumentSymbols , documentSymbolsHandler } ,
. { " textDocument/formatting " , requests . Formatting , formattingHandler } ,
. { " textDocument/rename " , requests . Rename , renameHandler } ,
2020-07-05 23:32:14 +01:00
. { " textDocument/references " , requests . References , referencesHandler } ,
2022-05-08 04:03:40 +01:00
. { " textDocument/documentHighlight " , requests . DocumentHighlight , documentHighlightHandler } ,
2022-09-25 00:04:29 +01:00
. { " textDocument/codeAction " , requests . CodeAction , codeActionHandler } ,
2022-10-01 01:47:40 +01:00
. { " workspace/didChangeConfiguration " , Config . DidChangeConfigurationParams , didChangeConfigurationHandler } ,
2022-10-28 19:35:22 +01:00
. { " textDocument/foldingRange " , requests . FoldingRange , foldingRangeHandler } ,
2022-11-26 01:14:33 +00:00
. { " textDocument/selectionRange " , requests . SelectionRange , selectionRangeHandler } ,
2020-06-29 23:34:21 +01:00
} ;
2022-09-30 05:04:55 +01:00
if ( zig_builtin . zig_backend = = . stage1 ) {
// Hack to avoid `return`ing in the inline for, which causes bugs.
var done : ? anyerror = null ;
inline for ( method_map ) | method_info | {
if ( done = = null and std . mem . eql ( u8 , method , method_info [ 0 ] ) ) {
if ( method_info . len = = 1 ) {
log . warn ( " method not mapped: {s} " , . { method } ) ;
2020-06-30 16:00:33 +01:00
done = error . HackDone ;
2022-09-30 05:04:55 +01:00
} else if ( method_info [ 1 ] ! = void ) {
const ReqT = method_info [ 1 ] ;
if ( requests . fromDynamicTree ( & server . arena , ReqT , tree . root ) ) | request_obj | {
done = error . HackDone ;
done = extractErr ( method_info [ 2 ] ( server , writer , id , request_obj ) ) ;
} else | err | {
if ( err = = error . MalformedJson ) {
log . warn ( " Could not create request type {s} from JSON {s} " , . { @typeName ( ReqT ) , json } ) ;
}
done = err ;
2020-06-30 13:46:43 +01:00
}
2022-09-30 05:04:55 +01:00
} else {
done = error . HackDone ;
( method_info [ 2 ] ) ( server , writer , id ) catch | err | {
done = err ;
} ;
2020-06-30 16:00:33 +01:00
}
2022-09-30 05:04:55 +01:00
}
}
if ( done ) | err | switch ( err ) {
error . MalformedJson = > return try respondGeneric ( writer , id , null_result_response ) ,
error . HackDone = > return ,
else = > return err ,
} ;
} else {
inline for ( method_map ) | method_info | {
if ( std . mem . eql ( u8 , method , method_info [ 0 ] ) ) {
if ( method_info . len = = 1 ) {
log . warn ( " method not mapped: {s} " , . { method } ) ;
} else if ( method_info [ 1 ] ! = void ) {
const ReqT = method_info [ 1 ] ;
const request_obj = try requests . fromDynamicTree ( & server . arena , ReqT , tree . root ) ;
method_info [ 2 ] ( server , writer , id , request_obj ) catch | err | {
log . err ( " failed to process request: {s} " , . { @errorName ( err ) } ) ;
} ;
} else {
method_info [ 2 ] ( server , writer , id ) catch | err | {
log . err ( " failed to process request: {s} " , . { @errorName ( err ) } ) ;
} ;
}
return ;
2020-06-27 01:16:14 +01:00
}
}
2020-04-24 23:19:03 +01:00
}
2020-06-29 23:34:21 +01:00
2022-06-05 20:34:02 +01:00
// Boolean value is true if the method is a request (and thus the client
// needs a response) or false if the method is a notification (in which
// case it should be silently ignored)
const unimplemented_map = std . ComptimeStringMap ( bool , . {
. { " textDocument/codeLens " , true } ,
. { " textDocument/documentLink " , true } ,
. { " textDocument/rangeFormatting " , true } ,
. { " textDocument/onTypeFormatting " , true } ,
. { " textDocument/prepareRename " , true } ,
. { " textDocument/selectionRange " , true } ,
. { " textDocument/semanticTokens/range " , true } ,
. { " workspace/didChangeWorkspaceFolders " , false } ,
2020-06-30 13:46:43 +01:00
} ) ;
2022-06-05 20:34:02 +01:00
if ( unimplemented_map . get ( method ) ) | request | {
2020-06-30 13:46:43 +01:00
// TODO: Unimplemented methods, implement them and add them to server capabilities.
2022-06-05 20:34:02 +01:00
if ( request ) {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , null_result_response ) ;
2022-06-05 20:34:02 +01:00
}
2022-09-11 22:48:15 +01:00
log . debug ( " Notification method {s} is not implemented " , . { method } ) ;
2022-06-05 20:34:02 +01:00
return ;
2020-06-30 13:46:43 +01:00
}
2020-07-05 22:56:41 +01:00
if ( tree . root . Object . get ( " id " ) ) | _ | {
2022-07-31 22:38:27 +01:00
return try respondGeneric ( writer , id , not_implemented_response ) ;
2020-06-30 13:46:43 +01:00
}
2022-09-11 22:48:15 +01:00
log . debug ( " Method without return value not implemented: {s} " , . { method } ) ;
2020-04-24 23:19:03 +01:00
}
2022-07-31 22:38:27 +01:00
pub fn init (
allocator : std . mem . Allocator ,
2022-09-22 02:31:48 +01:00
config : * Config ,
2022-07-31 22:38:27 +01:00
config_path : ? [ ] const u8 ,
) ! Server {
// TODO replace global with something like an Analyser struct
// which contains using_trail & resolve_trail and place it inside Server
2022-08-03 22:29:03 +01:00
// see: https://github.com/zigtools/zls/issues/536
2021-04-07 14:10:18 +01:00
analysis . init ( allocator ) ;
2020-05-09 14:43:51 +01:00
2022-09-22 02:31:48 +01:00
try config . configChanged ( allocator , config_path ) ;
2022-08-05 12:01:39 +01:00
2022-10-05 12:23:38 +01:00
var document_store = DocumentStore {
. allocator = allocator ,
. config = config ,
} ;
2022-09-22 02:31:48 +01:00
errdefer document_store . deinit ( ) ;
2022-08-05 12:01:39 +01:00
2022-09-29 19:01:38 +01:00
var builtin_completions = try std . ArrayListUnmanaged ( types . CompletionItem ) . initCapacity ( allocator , data . builtins . len ) ;
2022-10-14 17:24:22 +01:00
errdefer builtin_completions . deinit ( allocator ) ;
2022-09-29 19:01:38 +01:00
for ( data . builtins ) | builtin | {
const insert_text = if ( config . enable_snippets ) builtin . snippet else builtin . name ;
builtin_completions . appendAssumeCapacity ( . {
. label = builtin . name ,
. kind = . Function ,
. filterText = builtin . name [ 1 . . ] ,
. detail = builtin . signature ,
. insertText = if ( config . include_at_in_builtins ) insert_text else insert_text [ 1 . . ] ,
. insertTextFormat = if ( config . enable_snippets ) . Snippet else . PlainText ,
. documentation = . {
. kind = . Markdown ,
. value = builtin . documentation ,
} ,
} ) ;
}
2022-08-05 12:01:39 +01:00
return Server {
2022-09-22 02:31:48 +01:00
. config = config ,
2022-07-17 11:00:29 +01:00
. allocator = allocator ,
2022-09-22 02:31:48 +01:00
. document_store = document_store ,
2022-09-29 19:01:38 +01:00
. builtin_completions = builtin_completions ,
2022-09-30 05:04:27 +01:00
. status = . uninitialized ,
2022-07-17 11:00:29 +01:00
} ;
2022-07-31 22:38:27 +01:00
}
pub fn deinit ( server : * Server ) void {
server . document_store . deinit ( ) ;
analysis . deinit ( ) ;
2020-06-29 23:34:21 +01:00
2022-09-29 19:01:38 +01:00
server . builtin_completions . deinit ( server . allocator ) ;
2020-04-24 23:19:03 +01:00
}