From 1ae341850ec0b48dddd5c0d25316ace95c92a67a Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Sun, 11 Dec 2022 12:10:48 +0100 Subject: [PATCH] automatically generate config associated files (#813) --- README.md | 41 ++--- build.zig | 12 ++ schema.json | 77 +++++----- src/Config.zig | 279 +++++----------------------------- src/Server.zig | 37 +++-- src/config_gen/config.json | 158 +++++++++++++++++++ src/config_gen/config_gen.zig | 239 +++++++++++++++++++++++++++++ src/configuration.zig | 210 +++++++++++++++++++++++++ src/main.zig | 7 +- src/requests.zig | 6 + src/setup.zig | 178 +++------------------- 11 files changed, 772 insertions(+), 472 deletions(-) create mode 100644 src/config_gen/config.json create mode 100644 src/config_gen/config_gen.zig create mode 100644 src/configuration.zig diff --git a/README.md b/README.md index a98fdf5..806e60c 100644 --- a/README.md +++ b/README.md @@ -59,29 +59,32 @@ zls will look for a zls.json configuration file in multiple locations with the f The following options are currently available. + | Option | Type | Default value | What it Does | | --- | --- | --- | --- | -| `enable_snippets` | `bool` | `false` | Enables snippet completions when the client also supports them. | -| `enable_ast_check_diagnostics` | `bool` | `true`| Whether to enable ast-check diagnostics | -| `enable_autofix` | `bool` | `false`| Whether to automatically fix errors on save. Currently supports adding and removing discards. | +| `enable_snippets` | `bool` | `false` | Enables snippet completions when the client also supports them | +| `enable_ast_check_diagnostics` | `bool` | `true` | Whether to enable ast-check diagnostics | +| `enable_autofix` | `bool` | `false` | Whether to automatically fix errors on save. Currently supports adding and removing discards. | | `enable_import_embedfile_argument_completions` | `bool` | `false` | Whether to enable import/embedFile argument completions | -| `zig_lib_path` | `?[]const u8` | `null` | zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports. | -| `zig_exe_path` | `?[]const u8` | `null` | zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided. | -| `warn_style` | `bool` | `false` | Enables warnings for style *guideline* mismatches | -| `build_runner_path` | `?[]const u8` | `null` | Path to the build_runner.zig file provided by zls. `null` is equivalent to `${executable_directory}/build_runner.zig` | -| `global_cache_path` | `?[]const u8` | `null` | Path to a directroy that will be used as zig's cache. `null` is equivalent to `${KnownFloders.Cache}/zls` | -| `enable_semantic_tokens` | `bool` | `true` | Enables semantic token support when the client also supports it. | -| `enable_inlay_hints` | `bool` | `false` | Enables inlay hint support when the client also supports it. | +| `enable_semantic_tokens` | `bool` | `true` | Enables semantic token support when the client also supports it | +| `enable_inlay_hints` | `bool` | `false` | Enables inlay hint support when the client also supports it | | `inlay_hints_show_builtin` | `bool` | `true` | Enable inlay hints for builtin functions | -| `inlay_hints_exclude_single_argument` | `bool` | `true`| Don't show inlay hints for single argument calls | -| `inlay_hints_hide_redundant_param_names` | `bool` | `false`| Hides inlay hints when parameter name matches the identifier (e.g. foo: foo) | -| `inlay_hints_hide_redundant_param_names_last_token` | `bool` | `false`| Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo) | -| `operator_completions` | `bool` | `true` | Enables `*` and `?` operators in completion lists. | -|`include_at_in_builtins`|`bool`|`false`| Whether the @ sign should be part of the completion of builtins. -|`max_detail_length`|`usize`|`1024 * 1024`| The detail field of completions is truncated to be no longer than this (in bytes). -| `skip_std_references` | `bool` | `false` | When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is. -| `highlight_global_var_declarations` | `bool` | `false` | Whether to highlight global var declarations. -| `use_comptime_interpreter` | `bool` | `false` | Whether to use the comptime interpreter. +| `inlay_hints_exclude_single_argument` | `bool` | `true` | Don't show inlay hints for single argument calls | +| `inlay_hints_hide_redundant_param_names` | `bool` | `false` | Hides inlay hints when parameter name matches the identifier (e.g. foo: foo) | +| `inlay_hints_hide_redundant_param_names_last_token` | `bool` | `false` | Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo) | +| `operator_completions` | `bool` | `true` | Enables `*` and `?` operators in completion lists | +| `warn_style` | `bool` | `false` | Enables warnings for style guideline mismatches | +| `highlight_global_var_declarations` | `bool` | `false` | Whether to highlight global var declarations | +| `use_comptime_interpreter` | `bool` | `false` | Whether to use the comptime interpreter | +| `include_at_in_builtins` | `bool` | `false` | Whether the @ sign should be part of the completion of builtins | +| `skip_std_references` | `bool` | `false` | When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is | +| `max_detail_length` | `usize` | `1048576` | The detail field of completions is truncated to be no longer than this (in bytes) | +| `builtin_path` | `?[]const u8` | `null` | Path to 'builtin;' useful for debugging, automatically set if let null | +| `zig_lib_path` | `?[]const u8` | `null` | Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports | +| `zig_exe_path` | `?[]const u8` | `null` | Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided | +| `build_runner_path` | `?[]const u8` | `null` | Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig` | +| `global_cache_path` | `?[]const u8` | `null` | Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls` | + ### Per-build Configuration Options diff --git a/build.zig b/build.zig index 1007ad0..baf89bb 100644 --- a/build.zig +++ b/build.zig @@ -116,6 +116,18 @@ pub fn build(b: *std.build.Builder) !void { exe.setBuildMode(mode); exe.install(); + const gen_exe = b.addExecutable("zls_gen", "src/config_gen/config_gen.zig"); + + const gen_cmd = gen_exe.run(); + gen_cmd.addArgs(&.{ + b.fmt("{s}/src/Config.zig", .{b.build_root}), + b.fmt("{s}/schema.json", .{b.build_root}), + b.fmt("{s}/README.md", .{b.build_root}), + }); + + const gen_step = b.step("gen", "Regenerate config files"); + gen_step.dependOn(&gen_cmd.step); + const test_step = b.step("test", "Run all the tests"); test_step.dependOn(b.getInstallStep()); diff --git a/schema.json b/schema.json index f400f26..ce0c552 100644 --- a/schema.json +++ b/schema.json @@ -24,27 +24,6 @@ "type": "boolean", "default": "false" }, - "zig_lib_path": { - "description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports", - "type": "string" - }, - "zig_exe_path": { - "description": "Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided", - "type": "string" - }, - "warn_style": { - "description": "Enables warnings for style guideline mismatches", - "type": "boolean", - "default": "false" - }, - "build_runner_path": { - "description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`", - "type": "string" - }, - "global_cache_path": { - "description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`", - "type": "string" - }, "enable_semantic_tokens": { "description": "Enables semantic token support when the client also supports it", "type": "boolean", @@ -80,18 +59,8 @@ "type": "boolean", "default": "true" }, - "include_at_in_builtins": { - "description": "Whether the @ sign should be part of the completion of builtins", - "type": "boolean", - "default": "false" - }, - "max_detail_length": { - "description": "The detail field of completions is truncated to be no longer than this (in bytes)", - "type": "integer", - "default": "1048576" - }, - "skip_std_references": { - "description": "When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is", + "warn_style": { + "description": "Enables warnings for style guideline mismatches", "type": "boolean", "default": "false" }, @@ -104,6 +73,46 @@ "description": "Whether to use the comptime interpreter", "type": "boolean", "default": "false" + }, + "include_at_in_builtins": { + "description": "Whether the @ sign should be part of the completion of builtins", + "type": "boolean", + "default": "false" + }, + "skip_std_references": { + "description": "When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is", + "type": "boolean", + "default": "false" + }, + "max_detail_length": { + "description": "The detail field of completions is truncated to be no longer than this (in bytes)", + "type": "integer", + "default": "1048576" + }, + "builtin_path": { + "description": "Path to 'builtin;' useful for debugging, automatically set if let null", + "type": "string", + "default": "null" + }, + "zig_lib_path": { + "description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports", + "type": "string", + "default": "null" + }, + "zig_exe_path": { + "description": "Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided", + "type": "string", + "default": "null" + }, + "build_runner_path": { + "description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`", + "type": "string", + "default": "null" + }, + "global_cache_path": { + "description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`", + "type": "string", + "default": "null" } } -} +} diff --git a/src/Config.zig b/src/Config.zig index aa56127..55af114 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -1,284 +1,73 @@ +//! DO NOT EDIT //! Configuration options for zls. -//! Keep in sync with schema.json and zls-vscode's package.json! +//! If you want to add a config option edit +//! src/config_gen/config.zig and run `zig build gen` +//! GENERATED BY src/config_gen/config_gen.zig -const Config = @This(); - -const std = @import("std"); -const setup = @import("setup.zig"); -const tracy = @import("tracy.zig"); -const known_folders = @import("known-folders"); - -const logger = std.log.scoped(.config); - -/// Whether to enable snippet completions +/// Enables snippet completions when the client also supports them enable_snippets: bool = false, /// Whether to enable ast-check diagnostics enable_ast_check_diagnostics: bool = true, -/// Whether to automatically fix errors on save. -/// Currently supports adding and removing discards. +/// Whether to automatically fix errors on save. Currently supports adding and removing discards. enable_autofix: bool = false, -/// Whether to enable import/embedFile argument completions (NOTE: these are triggered manually as updating the autotrigger characters may cause issues) +/// Whether to enable import/embedFile argument completions enable_import_embedfile_argument_completions: bool = false, -/// Zig library path -zig_lib_path: ?[]const u8 = null, - -/// Zig executable path used to run the custom build runner. -/// May be used to find a lib path if none is provided. -zig_exe_path: ?[]const u8 = null, - -/// 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, - -/// Path to the build_runner.zig file. -build_runner_path: ?[]const u8 = null, - -/// Path to the global cache directory -global_cache_path: ?[]const u8 = null, - -/// Semantic token support +/// Enables semantic token support when the client also supports it enable_semantic_tokens: bool = true, -/// Inlay hint support +/// Enables inlay hint support when the client also supports it enable_inlay_hints: bool = false, -/// enable inlay hints for builtin functions +/// Enable inlay hints for builtin functions inlay_hints_show_builtin: bool = true, -/// don't show inlay hints for single argument calls +/// Don't show inlay hints for single argument calls inlay_hints_exclude_single_argument: bool = true, -/// don't show inlay hints when parameter name matches the identifier -/// for example: `foo: foo` +/// Hides inlay hints when parameter name matches the identifier (e.g. 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` +/// Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo) inlay_hints_hide_redundant_param_names_last_token: bool = false, -/// Whether to enable `*` and `?` operators in completion lists +/// Enables `*` and `?` operators in completion lists operator_completions: bool = true, -/// Whether the @ sign should be part of the completion of builtins -include_at_in_builtins: bool = false, +/// Enables warnings for style guideline mismatches +warn_style: bool = false, -/// The detail field of completions is truncated to be no longer than this (in bytes). -max_detail_length: usize = 1048576, - -/// Skips references to std. This will improve lookup speeds. -/// Going to definition however will continue to work -skip_std_references: bool = false, - -/// Path to "builtin;" useful for debugging, automatically set if let null -builtin_path: ?[]const u8 = null, - -/// Whether to highlight global var declarations. +/// Whether to highlight global var declarations highlight_global_var_declarations: bool = false, /// Whether to use the comptime interpreter use_comptime_interpreter: bool = false, -pub fn loadFromFile(allocator: std.mem.Allocator, file_path: []const u8) ?Config { - const tracy_zone = tracy.trace(@src()); - defer tracy_zone.end(); +/// Whether the @ sign should be part of the completion of builtins +include_at_in_builtins: bool = false, - var file = std.fs.cwd().openFile(file_path, .{}) catch |err| { - if (err != error.FileNotFound) - logger.warn("Error while reading configuration file: {}", .{err}); - return null; - }; +/// When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is +skip_std_references: bool = false, - defer file.close(); +/// The detail field of completions is truncated to be no longer than this (in bytes) +max_detail_length: usize = 1048576, - const file_buf = file.readToEndAlloc(allocator, 0x1000000) catch return null; - defer allocator.free(file_buf); - @setEvalBranchQuota(10000); +/// Path to 'builtin;' useful for debugging, automatically set if let null +builtin_path: ?[]const u8 = null, - var token_stream = std.json.TokenStream.init(file_buf); - const parse_options = std.json.ParseOptions{ .allocator = allocator, .ignore_unknown_fields = true }; +/// Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports +zig_lib_path: ?[]const u8 = null, - // TODO: Better errors? Doesn't seem like std.json can provide us positions or context. - var config = std.json.parse(Config, &token_stream, parse_options) catch |err| { - logger.warn("Error while parsing configuration file: {}", .{err}); - return null; - }; +/// Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided +zig_exe_path: ?[]const u8 = 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; - } - } +/// Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig` +build_runner_path: ?[]const u8 = null, - return config; -} +/// Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls` +global_cache_path: ?[]const u8 = null, -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. -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); - } - - if (config.zig_exe_path) |exe_path| blk: { - logger.info("Using zig executable {s}", .{exe_path}); - - if (config.zig_lib_path != null) break :blk; - - var env = getZigEnv(allocator, exe_path) orelse break :blk; - defer std.json.parseFree(Env, env, .{ .allocator = allocator }); - - // Make sure the path is absolute - config.zig_lib_path = try std.fs.realpathAlloc(allocator, env.lib_dir.?); - logger.info("Using zig lib path '{s}'", .{config.zig_lib_path.?}); - } 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" }); - } - - 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" }); - - std.fs.cwd().makePath(config.global_cache_path.?) catch |err| switch (err) { - error.PathAlreadyExists => {}, - else => return err, - }; - } - - if (null == config.build_runner_path) { - config.build_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ config.global_cache_path.?, "build_runner.zig" }); - - const file = try std.fs.createFileAbsolute(config.build_runner_path.?, .{}); - defer file.close(); - - try file.writeAll(@embedFile("special/build_runner.zig")); - } -} - -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; - }; -} - -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); -} +// DO NOT EDIT diff --git a/src/Server.zig b/src/Server.zig index 7aabb99..6c58be4 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -4,6 +4,7 @@ const std = @import("std"); const zig_builtin = @import("builtin"); const build_options = @import("build_options"); const Config = @import("Config.zig"); +const configuration = @import("configuration.zig"); const DocumentStore = @import("DocumentStore.zig"); const requests = @import("requests.zig"); const types = @import("types.zig"); @@ -1570,6 +1571,22 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req: const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); + if (req.params.clientInfo) |clientInfo| { + std.log.info("client is '{s}-{s}'", .{ clientInfo.name, clientInfo.version orelse "" }); + + if (std.mem.eql(u8, clientInfo.name, "Sublime Text LSP")) blk: { + server.config.max_detail_length = 256; + + const version_str = clientInfo.version orelse break :blk; + const version = std.SemanticVersion.parse(version_str) catch break :blk; + // this indicates a LSP version for sublime text 3 + // this check can be made more precise if the version that fixed this issue is known + if (version.major == 0) { + server.config.include_at_in_builtins = true; + } + } + } + if (req.params.capabilities.general) |general| { var supports_utf8 = false; var supports_utf16 = false; @@ -1717,8 +1734,8 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req: // 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 env = configuration.getZigEnv(server.allocator, server.config.zig_exe_path.?) orelse return; + defer std.json.parseFree(configuration.Env, env, .{ .allocator = server.allocator }); const zig_exe_version = std.SemanticVersion.parse(env.version) catch return; @@ -2197,16 +2214,16 @@ fn formattingHandler(server: *Server, writer: anytype, id: types.RequestId, req: ); } -fn didChangeConfigurationHandler(server: *Server, writer: anytype, id: types.RequestId, req: Config.DidChangeConfigurationParams) !void { +fn didChangeConfigurationHandler(server: *Server, writer: anytype, id: types.RequestId, req: configuration.DidChangeConfigurationParams) !void { const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); _ = id; // 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| { + if (req.settings) |cfg| { + inline for (std.meta.fields(configuration.Configuration)) |field| { + if (@field(cfg, field.name)) |value| { blk: { if (@TypeOf(value) == []const u8) { if (value.len == 0) { @@ -2219,7 +2236,7 @@ fn didChangeConfigurationHandler(server: *Server, writer: anytype, id: types.Req } } - try server.config.configChanged(server.allocator, null); + try configuration.configChanged(server.config, server.allocator, null); } else if (server.client_capabilities.supports_configuration) { try server.requestConfiguration(writer); } @@ -2838,7 +2855,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void } } - try server.config.configChanged(server.allocator, null); + try configuration.configChanged(server.config, server.allocator, null); return; } @@ -2901,7 +2918,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void .{ "textDocument/references", requests.References, referencesHandler }, .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, - .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, + .{ "workspace/didChangeConfiguration", configuration.DidChangeConfigurationParams, didChangeConfigurationHandler }, .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler }, .{ "textDocument/selectionRange", requests.SelectionRange, selectionRangeHandler }, }; @@ -2998,7 +3015,7 @@ pub fn init( // see: https://github.com/zigtools/zls/issues/536 analysis.init(allocator); - try config.configChanged(allocator, config_path); + try configuration.configChanged(config, allocator, config_path); var document_store = DocumentStore{ .allocator = allocator, diff --git a/src/config_gen/config.json b/src/config_gen/config.json new file mode 100644 index 0000000..e637338 --- /dev/null +++ b/src/config_gen/config.json @@ -0,0 +1,158 @@ +{ + "options": [ + { + "name": "enable_snippets", + "description": "Enables snippet completions when the client also supports them", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable snippets?" + }, + { + "name": "enable_ast_check_diagnostics", + "description": "Whether to enable ast-check diagnostics", + "type": "bool", + "default": "true", + "setup_question": "Do you want to enable ast-check diagnostics?" + }, + { + "name": "enable_autofix", + "description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.", + "type": "bool", + "default": "false", + "setup_question": "Do you want to zls to automatically try to fix errors on save? (supports adding & removing discards)" + }, + { + "name": "enable_import_embedfile_argument_completions", + "description": "Whether to enable import/embedFile argument completions", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable @import/@embedFile argument path completion?" + }, + { + "name": "enable_semantic_tokens", + "description": "Enables semantic token support when the client also supports it", + "type": "bool", + "default": "true", + "setup_question": "Do you want to enable semantic highlighting?" + }, + { + "name": "enable_inlay_hints", + "description": "Enables inlay hint support when the client also supports it", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable inlay hints?" + }, + { + "name": "inlay_hints_show_builtin", + "description": "Enable inlay hints for builtin functions", + "type": "bool", + "default": "true", + "setup_question": null + }, + { + "name": "inlay_hints_exclude_single_argument", + "description": "Don't show inlay hints for single argument calls", + "type": "bool", + "default": "true", + "setup_question": null + }, + { + "name": "inlay_hints_hide_redundant_param_names", + "description": "Hides inlay hints when parameter name matches the identifier (e.g. foo: foo)", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "inlay_hints_hide_redundant_param_names_last_token", + "description": "Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo)", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "operator_completions", + "description": "Enables `*` and `?` operators in completion lists", + "type": "bool", + "default": "true", + "setup_question": "Do you want to enable .* and .? completions?" + }, + { + "name": "warn_style", + "description": "Enables warnings for style guideline mismatches", + "type": "bool", + "default": "false", + "setup_question": "Do you want to enable style warnings?" + }, + { + "name": "highlight_global_var_declarations", + "description": "Whether to highlight global var declarations", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "use_comptime_interpreter", + "description": "Whether to use the comptime interpreter", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "include_at_in_builtins", + "description": "Whether the @ sign should be part of the completion of builtins", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "skip_std_references", + "description": "When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is", + "type": "bool", + "default": "false", + "setup_question": null + }, + { + "name": "max_detail_length", + "description": "The detail field of completions is truncated to be no longer than this (in bytes)", + "type": "usize", + "default": "1048576", + "setup_question": null + }, + { + "name": "builtin_path", + "description": "Path to 'builtin;' useful for debugging, automatically set if let null", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "zig_lib_path", + "description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "zig_exe_path", + "description": "Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "build_runner_path", + "description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`", + "type": "?[]const u8", + "default": "null", + "setup_question": null + }, + { + "name": "global_cache_path", + "description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`", + "type": "?[]const u8", + "default": "null", + "setup_question": null + } + ] +} \ No newline at end of file diff --git a/src/config_gen/config_gen.zig b/src/config_gen/config_gen.zig new file mode 100644 index 0000000..0952df1 --- /dev/null +++ b/src/config_gen/config_gen.zig @@ -0,0 +1,239 @@ +const std = @import("std"); + +const ConfigOption = struct { + /// Name of config option + name: []const u8, + /// (used in doc comments & schema.json) + description: []const u8, + /// zig type in string form. e.g "u32", "[]const u8", "?usize" + type: []const u8, + /// used in Config.zig as the default initializer + default: []const u8, + /// If set, this option can be configured through `zls --config` + /// currently unused but could laer be used to automatically generate queries for setup.zig + setup_question: ?[]const u8, +}; + +const Config = struct { + options: []ConfigOption, +}; + +const Schema = struct { + @"$schema": []const u8 = "http://json-schema.org/schema", + title: []const u8 = "ZLS Config", + description: []const u8 = "Configuration file for the zig language server (ZLS)", + type: []const u8 = "object", + properties: std.StringArrayHashMap(SchemaEntry), +}; + +const SchemaEntry = struct { + description: []const u8, + type: []const u8, + default: []const u8, +}; + +fn zigTypeToTypescript(ty: []const u8) ![]const u8 { + return if (std.mem.eql(u8, ty, "?[]const u8")) + "string" + else if (std.mem.eql(u8, ty, "bool")) + "boolean" + else if (std.mem.eql(u8, ty, "usize")) + "integer" + else + error.UnsupportedType; +} + +fn generateConfigFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { + _ = allocator; + + const config_file = try std.fs.openFileAbsolute(path, .{ + .mode = .write_only, + }); + defer config_file.close(); + + var buff_out = std.io.bufferedWriter(config_file.writer()); + + _ = try buff_out.write( + \\//! DO NOT EDIT + \\//! Configuration options for zls. + \\//! If you want to add a config option edit + \\//! src/config_gen/config.zig and run `zig build gen` + \\//! GENERATED BY src/config_gen/config_gen.zig + \\ + ); + + for (config.options) |option| { + try buff_out.writer().print( + \\ + \\/// {s} + \\{s}: {s} = {s}, + \\ + , .{ + std.mem.trim(u8, option.description, " \t\n\r"), + std.mem.trim(u8, option.name, " \t\n\r"), + std.mem.trim(u8, option.type, " \t\n\r"), + std.mem.trim(u8, option.default, " \t\n\r"), + }); + } + + _ = try buff_out.write( + \\ + \\// DO NOT EDIT + \\ + ); + + try buff_out.flush(); +} + +fn generateSchemaFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { + const schema_file = try std.fs.openFileAbsolute(path, .{ + .mode = .write_only, + }); + defer schema_file.close(); + + var buff_out = std.io.bufferedWriter(schema_file.writer()); + + var properties = std.StringArrayHashMapUnmanaged(SchemaEntry){}; + defer properties.deinit(allocator); + try properties.ensureTotalCapacity(allocator, config.options.len); + + for (config.options) |option| { + properties.putAssumeCapacityNoClobber(option.name, .{ + .description = option.description, + .type = try zigTypeToTypescript(option.type), + .default = option.default, + }); + } + + _ = try buff_out.write( + \\{ + \\ "$schema": "http://json-schema.org/schema", + \\ "title": "ZLS Config", + \\ "description": "Configuration file for the zig language server (ZLS)", + \\ "type": "object", + \\ "properties": + ); + + try serializeObjectMap(properties, .{ + .whitespace = .{ + .indent_level = 1, + }, + }, buff_out.writer()); + + _ = try buff_out.write("\n}\n"); + try buff_out.flush(); +} + +fn updateREADMEFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { + var readme_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_write }); + defer readme_file.close(); + + var readme = std.ArrayListUnmanaged(u8){ + .items = try readme_file.readToEndAlloc(allocator, std.math.maxInt(usize)), + }; + defer readme.deinit(allocator); + + const start_indicator = ""; + const end_indicator = ""; + + const start = start_indicator.len + (std.mem.indexOf(u8, readme.items, start_indicator) orelse return error.SectionNotFound); + const end = std.mem.indexOfPos(u8, readme.items, start, end_indicator) orelse return error.SectionNotFound; + + var new_readme = std.ArrayListUnmanaged(u8){}; + defer new_readme.deinit(allocator); + var writer = new_readme.writer(allocator); + + try writer.writeAll( + \\ + \\| Option | Type | Default value | What it Does | + \\| --- | --- | --- | --- | + \\ + ); + + for (config.options) |option| { + try writer.print( + \\| `{s}` | `{s}` | `{s}` | {s} | + \\ + , .{ + std.mem.trim(u8, option.name, " \t\n\r"), + std.mem.trim(u8, option.type, " \t\n\r"), + std.mem.trim(u8, option.default, " \t\n\r"), + std.mem.trim(u8, option.description, " \t\n\r"), + }); + } + + try readme.replaceRange(allocator, start, end - start, new_readme.items); + + try readme_file.seekTo(0); + try readme_file.writeAll(readme.items); +} + +pub fn main() !void { + var arg_it = std.process.args(); + + _ = arg_it.next() orelse @panic(""); + const config_path = arg_it.next() orelse @panic("first argument must be path to Config.zig"); + const schema_path = arg_it.next() orelse @panic("second argument must be path to schema.json"); + const readme_path = arg_it.next() orelse @panic("third argument must be path to README.md"); + + var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = general_purpose_allocator.allocator(); + + const parse_options = std.json.ParseOptions{ + .allocator = gpa, + }; + var token_stream = std.json.TokenStream.init(@embedFile("config.json")); + const config = try std.json.parse(Config, &token_stream, parse_options); + defer std.json.parseFree(Config, config, parse_options); + + try generateConfigFile(gpa, config, config_path); + try generateSchemaFile(gpa, config, schema_path); + try updateREADMEFile(gpa, config, readme_path); + + std.log.warn( + \\ If you have added a new configuration option and it should be configuration through the config wizard, then edit src/setup.zig + , .{}); + + std.log.info( + \\ Changing configuration options may also require editing the `package.json` from zls-vscode at https://github.com/zigtools/zls-vscode/blob/master/package.json + , .{}); +} + +fn serializeObjectMap( + value: anytype, + options: std.json.StringifyOptions, + out_stream: anytype, +) @TypeOf(out_stream).Error!void { + try out_stream.writeByte('{'); + var field_output = false; + var child_options = options; + if (child_options.whitespace) |*child_whitespace| { + child_whitespace.indent_level += 1; + } + var it = value.iterator(); + while (it.next()) |entry| { + if (!field_output) { + field_output = true; + } else { + try out_stream.writeByte(','); + } + if (child_options.whitespace) |child_whitespace| { + try child_whitespace.outputIndent(out_stream); + } + + try std.json.stringify(entry.key_ptr.*, options, out_stream); + try out_stream.writeByte(':'); + if (child_options.whitespace) |child_whitespace| { + if (child_whitespace.separator) { + try out_stream.writeByte(' '); + } + } + try std.json.stringify(entry.value_ptr.*, child_options, out_stream); + } + if (field_output) { + if (options.whitespace) |whitespace| { + try whitespace.outputIndent(out_stream); + } + } + try out_stream.writeByte('}'); +} diff --git a/src/configuration.zig b/src/configuration.zig new file mode 100644 index 0000000..9204e2b --- /dev/null +++ b/src/configuration.zig @@ -0,0 +1,210 @@ +const std = @import("std"); + +const setup = @import("setup.zig"); +const tracy = @import("tracy.zig"); +const known_folders = @import("known-folders"); + +const Config = @import("Config.zig"); + +const logger = std.log.scoped(.config); + +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); + @setEvalBranchQuota(10000); + + var token_stream = std.json.TokenStream.init(file_buf); + const parse_options = std.json.ParseOptions{ .allocator = allocator, .ignore_unknown_fields = true }; + + // TODO: Better errors? Doesn't seem like std.json can provide us positions or context. + var config = std.json.parse(Config, &token_stream, parse_options) catch |err| { + 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. +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); + } + + if (config.zig_exe_path) |exe_path| blk: { + logger.info("Using zig executable {s}", .{exe_path}); + + if (config.zig_lib_path != null) break :blk; + + var env = getZigEnv(allocator, exe_path) orelse break :blk; + defer std.json.parseFree(Env, env, .{ .allocator = allocator }); + + // Make sure the path is absolute + config.zig_lib_path = try std.fs.realpathAlloc(allocator, env.lib_dir.?); + logger.info("Using zig lib path '{s}'", .{config.zig_lib_path.?}); + } 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" }); + } + + 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" }); + + std.fs.cwd().makePath(config.global_cache_path.?) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => return err, + }; + } + + if (null == config.build_runner_path) { + config.build_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ config.global_cache_path.?, "build_runner.zig" }); + + const file = try std.fs.createFileAbsolute(config.build_runner_path.?, .{}); + defer file.close(); + + try file.writeAll(@embedFile("special/build_runner.zig")); + } +} + +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; + }; +} + +pub const Configuration = 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); +} diff --git a/src/main.zig b/src/main.zig index 4f74bf8..03776c4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,6 +4,7 @@ const build_options = @import("build_options"); const tracy = @import("tracy.zig"); const known_folders = @import("known-folders"); const Config = @import("Config.zig"); +const configuration = @import("configuration.zig"); const Server = @import("Server.zig"); const setup = @import("setup.zig"); const readRequestHeader = @import("header.zig").readRequestHeader; @@ -65,7 +66,7 @@ fn getConfig( free_old_config_path: bool, ) !ConfigWithPath { if (config_path) |path| { - if (Config.loadFromFile(allocator, path)) |conf| { + if (configuration.loadFromFile(allocator, path)) |conf| { return ConfigWithPath{ .config = conf, .config_path = path, @@ -82,7 +83,7 @@ fn getConfig( } if (try known_folders.getPath(allocator, .local_configuration)) |path| { - if (Config.loadFromFolder(allocator, path)) |conf| { + if (configuration.loadFromFolder(allocator, path)) |conf| { return ConfigWithPath{ .config = conf, .config_path = path, @@ -92,7 +93,7 @@ fn getConfig( } if (try known_folders.getPath(allocator, .global_configuration)) |path| { - if (Config.loadFromFolder(allocator, path)) |conf| { + if (configuration.loadFromFolder(allocator, path)) |conf| { return ConfigWithPath{ .config = conf, .config_path = path, diff --git a/src/requests.zig b/src/requests.zig index 466a7bd..b728161 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -178,7 +178,13 @@ pub const Initialize = struct { }, }; + pub const ClientInfo = struct { + name: []const u8, + version: ?[]const u8, + }; + params: struct { + clientInfo: ?ClientInfo, capabilities: ClientCapabilities, workspaceFolders: ?[]const types.WorkspaceFolder, }, diff --git a/src/setup.zig b/src/setup.zig index c32861e..6085a03 100644 --- a/src/setup.zig +++ b/src/setup.zig @@ -90,19 +90,11 @@ pub fn askSelectOne(prompt: []const u8, comptime Options: type) !Options { } } -fn print(comptime fmt: []const u8, args: anytype) void { - const stdout = std.io.getStdOut().writer(); - stdout.print(fmt, args) catch @panic("Could not write to stdout"); -} - -fn write(text: []const u8) void { - const stdout = std.io.getStdOut().writer(); - stdout.writeAll(text) catch @panic("Could not write to stdout"); -} - pub fn wizard(allocator: std.mem.Allocator) !void { @setEvalBranchQuota(2500); - write( + const stdout = std.io.getStdOut().writer(); + + try stdout.writeAll( \\Welcome to the ZLS configuration wizard! \\ * \\ |\ @@ -127,7 +119,7 @@ pub fn wizard(allocator: std.mem.Allocator) !void { }; if (global_path == null and local_path == null) { - write("Could not open a global or local config directory.\n"); + try stdout.writeAll("Could not open a global or local config directory.\n"); return; } var config_path: []const u8 = undefined; @@ -137,17 +129,17 @@ pub fn wizard(allocator: std.mem.Allocator) !void { if (local_path) |p| { config_path = p; } else { - write("Could not find a local config directory.\n"); + try stdout.writeAll("Could not find a local config directory.\n"); return; } } var dir = std.fs.cwd().openDir(config_path, .{}) catch |err| { - print("Could not open {s}: {}.\n", .{ config_path, err }); + try stdout.print("Could not open {s}: {}.\n", .{ config_path, err }); return; }; defer dir.close(); var file = dir.createFile("zls.json", .{}) catch |err| { - print("Could not create {s}/zls.json: {}.\n", .{ config_path, err }); + try stdout.print("Could not create {s}/zls.json: {}.\n", .{ config_path, err }); return; }; defer file.close(); @@ -157,9 +149,9 @@ pub fn wizard(allocator: std.mem.Allocator) !void { defer if (zig_exe_path) |p| allocator.free(p); if (zig_exe_path) |path| { - print("Found zig executable '{s}' in PATH.\n", .{path}); + try stdout.print("Found zig executable '{s}' in PATH.\n", .{path}); } else { - write("Could not find 'zig' in PATH\n"); + try stdout.writeAll("Could not find 'zig' in PATH\n"); zig_exe_path = try askString(allocator, if (builtin.os.tag == .windows) \\What is the path to the 'zig' executable you would like to use? \\Note that due to a bug in zig (https://github.com/ziglang/zig/issues/6044), @@ -168,7 +160,6 @@ pub fn wizard(allocator: std.mem.Allocator) !void { "What is the path to the 'zig' executable you would like to use?", std.fs.MAX_PATH_BYTES); } - const editor = try askSelectOne("Which code editor do you use?", enum { VSCode, Sublime, Kate, Neovim, Vim8, Emacs, Doom, Spacemacs, Helix, Other }); const snippets = try askBool("Do you want to enable snippets?"); const ast_check = try askBool("Do you want to enable ast-check diagnostics?"); const autofix = try askBool("Do you want to zls to automatically try to fix errors on save? (supports adding & removing discards)"); @@ -177,15 +168,6 @@ pub fn wizard(allocator: std.mem.Allocator) !void { const semantic_tokens = try askBool("Do you want to enable semantic highlighting?"); const inlay_hints = try askBool("Do you want to enable inlay hints?"); const operator_completions = try askBool("Do you want to enable .* and .? completions?"); - const include_at_in_builtins = switch (editor) { - .Sublime => !try askBool("Are you using a Sublime Text version > 4000?"), - .VSCode, .Kate, .Neovim, .Vim8, .Emacs, .Doom, .Spacemacs, .Helix => false, - else => try askBool("Should the @ sign be included in completions of builtin functions?\nChange this later if `@inc` completes to `include` or `@@include`"), - }; - const max_detail_length: usize = switch (editor) { - .Sublime => 256, - else => 1024 * 1024, - }; std.debug.print("Writing config to {s}/zls.json ... ", .{config_path}); @@ -200,144 +182,18 @@ pub fn wizard(allocator: std.mem.Allocator) !void { .enable_semantic_tokens = semantic_tokens, .enable_inlay_hints = inlay_hints, .operator_completions = operator_completions, - .include_at_in_builtins = include_at_in_builtins, - .max_detail_length = max_detail_length, }, .{ .whitespace = .{}, }, out); - write("successful.\n\n\n\n"); - - // Keep synced with README.md - switch (editor) { - .VSCode => { - write( - \\To use ZLS in Visual Studio Code, install the 'ZLS for VSCode' extension from - \\'https://github.com/zigtools/zls-vscode/releases' or via the extensions menu. - \\ZLS will automatically be installed if it is not found in your PATH - ); - }, - .Sublime => { - write( - \\To use ZLS in Sublime, install the `LSP` package from - \\https://github.com/sublimelsp/LSP/releases or via Package Control. - \\Then, add the following snippet to LSP's user settings: - \\ - \\For Sublime Text 3: - \\ - \\{ - \\ "clients": { - \\ "zig": { - \\ "command": ["zls"], - \\ "enabled": true, - \\ "languageId": "zig", - \\ "scopes": ["source.zig"], - \\ "syntaxes": ["Packages/Zig Language/Syntaxes/Zig.tmLanguage"] - \\ } - \\ } - \\} - \\ - \\For Sublime Text 4: - \\ - \\{ - \\ "clients": { - \\ "zig": { - \\ "command": ["zls"], - \\ "enabled": true, - \\ "selector": "source.zig" - \\ } - \\ } - \\} - ); - }, - .Kate => { - write( - \\To use ZLS in Kate, enable `LSP client` plugin in Kate settings. - \\Then, add the following snippet to `LSP client's` user settings: - \\(or paste it in `LSP client's` GUI settings) - \\ - \\{ - \\ "servers": { - \\ "zig": { - \\ "command": ["zls"], - \\ "url": "https://github.com/zigtools/zls", - \\ "highlightingModeRegex": "^Zig$" - \\ } - \\ } - \\} - ); - }, - .Neovim, .Vim8 => { - write( - \\To use ZLS in Neovim/Vim8, we recommend using CoC engine. - \\You can get it from https://github.com/neoclide/coc.nvim. - \\Then, simply issue cmd from Neovim/Vim8 `:CocConfig`, and add this to your CoC config: - \\ - \\{ - \\ "languageserver": { - \\ "zls" : { - \\ "command": "command_or_path_to_zls", - \\ "filetypes": ["zig"] - \\ } - \\ } - \\} - ); - }, - .Emacs => { - write( - \\To use ZLS in Emacs, install lsp-mode (https://github.com/emacs-lsp/lsp-mode) from melpa. - \\Zig mode (https://github.com/ziglang/zig-mode) is also useful! - \\Then, add the following to your emacs config: - \\ - \\(require 'lsp-mode) - \\(setq lsp-zig-zls-executable "") - ); - }, - .Doom => { - write( - \\To use ZLS in Doom Emacs, enable the lsp module - \\And install the `zig-mode` (https://github.com/ziglang/zig-mode) - \\package by adding `(package! zig-mode)` to your packages.el file. - \\ - \\(use-package! zig-mode - \\ :hook ((zig-mode . lsp-deferred)) - \\ :custom (zig-format-on-save nil) - \\ :config - \\ (after! lsp-mode - \\ (add-to-list 'lsp-language-id-configuration '(zig-mode . "zig")) - \\ (lsp-register-client - \\ (make-lsp-client - \\ :new-connection (lsp-stdio-connection "") - \\ :major-modes '(zig-mode) - \\ :server-id 'zls)))) - ); - }, - .Spacemacs => { - write( - \\To use ZLS in Spacemacs, add the `lsp` and `zig` layers - \\to `dotspacemacs-configuration-layers` in your .spacemacs file. - \\Then, if you don't have `zls` in your PATH, add the following to - \\`dotspacemacs/user-config` in your .spacemacs file: - \\ - \\(setq lsp-zig-zls-executable "") - ); - }, - .Helix => { - write( - \\Helix has out of the box support for ZLS - \\Make sure you have added ZLS to your PATH - \\run hx --health to check if helix has found it. - ); - }, - .Other => { - write( - \\We might not *officially* support your editor, but you can definitely still use ZLS! - \\Simply configure your editor for use with language servers and point it to the ZLS executable! - ); - }, - } - - write("\n\nThank you for choosing ZLS!\n"); + try stdout.writeAll( + \\successful. + \\ + \\You can find information on how to setup zls for your editor on zigtools.github.io/install-zls/ + \\ + \\Thank you for choosing ZLS! + \\ + ); } pub fn findZig(allocator: std.mem.Allocator) !?[]const u8 {