2022-07-15 17:06:18 +01:00
//! Configuration options for zls.
2022-08-29 21:55:25 +01:00
//! Keep in sync with schema.json and zls-vscode's package.json!
2022-07-15 17:06:18 +01:00
const Config = @This ( ) ;
const std = @import ( " std " ) ;
const setup = @import ( " setup.zig " ) ;
2022-07-17 11:00:29 +01:00
const tracy = @import ( " tracy.zig " ) ;
2022-07-15 17:06:18 +01:00
const known_folders = @import ( " known-folders " ) ;
const logger = std . log . scoped ( . config ) ;
2020-05-09 14:43:51 +01:00
2020-05-09 20:42:35 +01:00
/// Whether to enable snippet completions
2020-05-19 20:09:00 +01:00
enable_snippets : bool = false ,
2020-05-14 00:10:41 +01:00
2022-09-01 01:48:42 +01:00
/// Whether to enable ast-check diagnostics
2022-09-03 14:12:34 +01:00
enable_ast_check_diagnostics : bool = true ,
2022-07-08 09:13:46 +01:00
2022-09-24 20:30:36 +01:00
/// Whether to automatically fix errors on save.
/// Currently supports adding and removing discards.
enable_autofix : bool = false ,
2022-07-09 10:22:02 +01:00
/// Whether to enable import/embedFile argument completions (NOTE: these are triggered manually as updating the autotrigger characters may cause issues)
enable_import_embedfile_argument_completions : bool = false ,
2022-07-11 14:45:31 +01:00
/// Zig library path
2020-05-14 02:54:05 +01:00
zig_lib_path : ? [ ] const u8 = null ,
2020-05-15 20:10:53 +01:00
2022-07-11 14:45:31 +01:00
/// Zig executable path used to run the custom build runner.
2020-05-30 21:36:18 +01:00
/// May be used to find a lib path if none is provided.
zig_exe_path : ? [ ] const u8 = null ,
2020-05-15 20:10:53 +01:00
/// Whether to pay attention to style issues. This is opt-in since the style
/// guide explicitly states that the style info provided is a guideline only.
warn_style : bool = false ,
2020-05-25 18:04:23 +01:00
2021-01-12 11:10:51 +00:00
/// Path to the build_runner.zig file.
2020-05-25 18:04:23 +01:00
build_runner_path : ? [ ] const u8 = null ,
2020-06-16 12:27:00 +01:00
2022-08-18 22:14:32 +01:00
/// Path to the global cache directory
global_cache_path : ? [ ] const u8 = null ,
2021-01-12 11:10:51 +00:00
2020-06-16 12:27:00 +01:00
/// Semantic token support
2020-11-15 22:07:35 +00:00
enable_semantic_tokens : bool = true ,
2020-07-07 21:26:12 +01:00
2022-07-24 12:38:13 +01:00
/// Inlay hint support
enable_inlay_hints : bool = false ,
/// enable inlay hints for builtin functions
inlay_hints_show_builtin : bool = true ,
/// don't show inlay hints for single argument calls
inlay_hints_exclude_single_argument : bool = true ,
2022-09-29 19:36:29 +01:00
/// don't show inlay hints when parameter name matches the identifier
/// for example: `foo: foo`
inlay_hints_hide_redundant_param_names : bool = false ,
/// don't show inlay hints when parameter names matches the last
/// for example: `foo: bar.foo`, `foo: &foo`
inlay_hints_hide_redundant_param_names_last_token : bool = false ,
2020-07-07 21:26:12 +01:00
/// Whether to enable `*` and `?` operators in completion lists
operator_completions : bool = true ,
2021-03-06 19:55:59 +00:00
2021-03-26 09:20:20 +00:00
/// Whether the @ sign should be part of the completion of builtins
include_at_in_builtins : bool = false ,
2021-03-27 19:37:51 +00:00
/// The detail field of completions is truncated to be no longer than this (in bytes).
2022-07-11 14:45:31 +01:00
max_detail_length : usize = 1048576 ,
2021-03-27 19:37:51 +00:00
2021-03-06 19:55:59 +00:00
/// Skips references to std. This will improve lookup speeds.
/// Going to definition however will continue to work
2021-03-07 13:51:47 +00:00
skip_std_references : bool = false ,
2021-12-30 03:35:16 +00:00
2022-07-11 14:45:31 +01:00
/// Path to "builtin;" useful for debugging, automatically set if let null
2021-12-30 03:35:16 +00:00
builtin_path : ? [ ] const u8 = null ,
2022-07-15 17:06:18 +01:00
2022-07-17 11:00:29 +01:00
pub fn loadFromFile ( allocator : std . mem . Allocator , file_path : [ ] const u8 ) ? Config {
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
var file = std . fs . cwd ( ) . openFile ( file_path , . { } ) catch | err | {
if ( err ! = error . FileNotFound )
logger . warn ( " Error while reading configuration file: {} " , . { err } ) ;
return null ;
} ;
defer file . close ( ) ;
const file_buf = file . readToEndAlloc ( allocator , 0x1000000 ) catch return null ;
defer allocator . free ( file_buf ) ;
2022-09-30 05:04:55 +01:00
@setEvalBranchQuota ( 10000 ) ;
var token_stream = std . json . TokenStream . init ( file_buf ) ;
2022-08-29 21:55:25 +01:00
const parse_options = std . json . ParseOptions { . allocator = allocator , . ignore_unknown_fields = true } ;
2022-09-30 05:04:55 +01:00
2022-07-17 11:00:29 +01:00
// TODO: Better errors? Doesn't seem like std.json can provide us positions or context.
2022-09-30 05:04:55 +01:00
var config = std . json . parse ( Config , & token_stream , parse_options ) catch | err | {
2022-07-17 11:00:29 +01:00
logger . warn ( " Error while parsing configuration file: {} " , . { err } ) ;
return null ;
} ;
if ( config . zig_lib_path ) | zig_lib_path | {
if ( ! std . fs . path . isAbsolute ( zig_lib_path ) ) {
logger . warn ( " zig library path is not absolute, defaulting to null. " , . { } ) ;
allocator . free ( zig_lib_path ) ;
config . zig_lib_path = null ;
}
}
return config ;
}
pub fn loadFromFolder ( allocator : std . mem . Allocator , folder_path : [ ] const u8 ) ? Config {
const tracy_zone = tracy . trace ( @src ( ) ) ;
defer tracy_zone . end ( ) ;
const full_path = std . fs . path . resolve ( allocator , & . { folder_path , " zls.json " } ) catch return null ;
defer allocator . free ( full_path ) ;
return loadFromFile ( allocator , full_path ) ;
}
/// Invoke this once all config values have been changed.
2022-07-15 17:06:18 +01:00
pub fn configChanged ( config : * Config , allocator : std . mem . Allocator , builtin_creation_dir : ? [ ] const u8 ) ! void {
// Find the zig executable in PATH
find_zig : {
if ( config . zig_exe_path ) | exe_path | {
if ( std . fs . path . isAbsolute ( exe_path ) ) not_valid : {
std . fs . cwd ( ) . access ( exe_path , . { } ) catch break : not_valid ;
break : find_zig ;
}
logger . debug ( " zig path `{s}` is not absolute, will look in path " , . { exe_path } ) ;
allocator . free ( exe_path ) ;
}
config . zig_exe_path = try setup . findZig ( allocator ) ;
}
2022-09-29 00:30:26 +01:00
if ( config . zig_exe_path ) | exe_path | blk : {
2022-07-15 17:06:18 +01:00
logger . info ( " Using zig executable {s} " , . { exe_path } ) ;
2022-09-29 00:30:26 +01:00
if ( config . zig_lib_path ! = null ) break : blk ;
2022-07-15 17:06:18 +01:00
2022-09-29 00:30:26 +01:00
var env = getZigEnv ( allocator , exe_path ) orelse break : blk ;
defer std . json . parseFree ( Env , env , . { . allocator = allocator } ) ;
2022-07-15 17:06:18 +01:00
2022-09-29 00:30:26 +01:00
// We know this is allocated with `allocator`, we just steal it!
config . zig_lib_path = env . lib_dir . ? ;
env . lib_dir = null ;
logger . info ( " Using zig lib path '{s}' " , . { config . zig_lib_path . ? } ) ;
2022-07-15 17:06:18 +01:00
} else {
logger . warn ( " Zig executable path not specified in zls.json and could not be found in PATH " , . { } ) ;
}
if ( config . zig_lib_path = = null ) {
logger . warn ( " Zig standard library path not specified in zls.json and could not be resolved from the zig executable " , . { } ) ;
}
if ( config . builtin_path = = null and config . zig_exe_path ! = null and builtin_creation_dir ! = null ) blk : {
const result = try std . ChildProcess . exec ( . {
. allocator = allocator ,
. argv = & . {
config . zig_exe_path . ? ,
" build-exe " ,
" --show-builtin " ,
} ,
. max_output_bytes = 1024 * 1024 * 50 ,
} ) ;
defer allocator . free ( result . stdout ) ;
defer allocator . free ( result . stderr ) ;
var d = try std . fs . cwd ( ) . openDir ( builtin_creation_dir . ? , . { } ) ;
defer d . close ( ) ;
const f = d . createFile ( " builtin.zig " , . { } ) catch | err | switch ( err ) {
error . AccessDenied = > break : blk ,
else = > | e | return e ,
} ;
defer f . close ( ) ;
try f . writer ( ) . writeAll ( result . stdout ) ;
config . builtin_path = try std . fs . path . join ( allocator , & . { builtin_creation_dir . ? , " builtin.zig " } ) ;
}
2022-09-11 20:50:37 +01:00
if ( null = = config . global_cache_path ) {
const cache_dir_path = ( try known_folders . getPath ( allocator , . cache ) ) orelse {
logger . warn ( " Known-folders could not fetch the cache path " , . { } ) ;
return ;
} ;
defer allocator . free ( cache_dir_path ) ;
config . global_cache_path = try std . fs . path . resolve ( allocator , & [ _ ] [ ] const u8 { cache_dir_path , " zls " } ) ;
2022-09-11 21:36:38 +01:00
2022-09-20 19:52:24 +01:00
std . fs . cwd ( ) . makePath ( config . global_cache_path . ? ) catch | err | switch ( err ) {
2022-09-11 21:36:38 +01:00
error . PathAlreadyExists = > { } ,
else = > return err ,
} ;
2022-09-11 20:50:37 +01:00
}
2022-09-07 18:34:48 +01:00
2022-08-22 01:11:50 +01:00
if ( null = = config . build_runner_path ) {
2022-09-11 20:50:37 +01:00
config . build_runner_path = try std . fs . path . resolve ( allocator , & [ _ ] [ ] const u8 { config . global_cache_path . ? , " build_runner.zig " } ) ;
2022-09-04 20:44:07 +01:00
const file = try std . fs . createFileAbsolute ( config . build_runner_path . ? , . { } ) ;
defer file . close ( ) ;
try file . writeAll ( @embedFile ( " special/build_runner.zig " ) ) ;
2022-08-22 01:11:50 +01:00
}
2022-07-15 17:06:18 +01:00
}
2022-09-29 00:30:26 +01:00
pub const Env = struct {
zig_exe : [ ] const u8 ,
lib_dir : ? [ ] const u8 ,
std_dir : [ ] const u8 ,
global_cache_dir : [ ] const u8 ,
version : [ ] const u8 ,
target : ? [ ] const u8 = null ,
} ;
/// result has to be freed with `std.json.parseFree`
pub fn getZigEnv ( allocator : std . mem . Allocator , zig_exe_path : [ ] const u8 ) ? Env {
const zig_env_result = std . ChildProcess . exec ( . {
. allocator = allocator ,
. argv = & [ _ ] [ ] const u8 { zig_exe_path , " env " } ,
} ) catch {
logger . err ( " Failed to execute zig env " , . { } ) ;
return null ;
} ;
defer {
allocator . free ( zig_env_result . stdout ) ;
allocator . free ( zig_env_result . stderr ) ;
}
switch ( zig_env_result . term ) {
. Exited = > | code | {
if ( code ! = 0 ) {
logger . err ( " zig env failed with error_code: {} " , . { code } ) ;
return null ;
}
} ,
else = > logger . err ( " zig env invocation failed " , . { } ) ,
}
var token_stream = std . json . TokenStream . init ( zig_env_result . stdout ) ;
return std . json . parse (
Env ,
& token_stream ,
. {
. allocator = allocator ,
. ignore_unknown_fields = true ,
} ,
) catch {
logger . err ( " Failed to parse zig env JSON result " , . { } ) ;
return null ;
} ;
}
2022-10-01 01:47:40 +01:00
pub const Configuration = Config . getConfigurationType ( ) ;
pub const DidChangeConfigurationParams = struct {
settings : ? Configuration ,
} ;
// returns a Struct which is the same as `Config` except that every field is optional.
fn getConfigurationType ( ) type {
var config_info : std . builtin . Type = @typeInfo ( Config ) ;
var fields : [ config_info . Struct . fields . len ] std . builtin . Type . StructField = undefined ;
for ( config_info . Struct . fields ) | field , i | {
fields [ i ] = field ;
if ( @typeInfo ( field . field_type ) ! = . Optional ) {
fields [ i ] . field_type = @Type ( std . builtin . Type {
. Optional = . { . child = field . field_type } ,
} ) ;
}
}
config_info . Struct . fields = fields [ 0 . . ] ;
config_info . Struct . decls = & . { } ;
return @Type ( config_info ) ;
}