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..370093f 100644 --- a/build.zig +++ b/build.zig @@ -6,7 +6,7 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; pub fn build(b: *std.build.Builder) !void { const current_zig = builtin.zig_version; - const min_zig = std.SemanticVersion.parse("0.11.0-dev.399+44ee1c885") catch return; // whereabouts allocgate 2.0 + const min_zig = std.SemanticVersion.parse("0.11.0-dev.874+40ed6ae84") catch return; // Changes to builtin.Type API if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); const target = b.standardTargetOptions(.{}); @@ -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/flake.lock b/flake.lock index f081163..99cfc96 100644 --- a/flake.lock +++ b/flake.lock @@ -68,11 +68,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1667292599, - "narHash": "sha256-7ISOUI1aj6UKMPIL+wwthENL22L3+A9V+jS8Is3QsRo=", + "lastModified": 1670086663, + "narHash": "sha256-hT8C8AQB74tdoCPwz4nlJypLMD7GI2F5q+vn+VE/qQk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ef2f213d9659a274985778bff4ca322f3ef3ac68", + "rev": "813836d64fa57285d108f0dbf2356457ccd304e3", "type": "github" }, "original": { @@ -99,11 +99,11 @@ ] }, "locked": { - "lastModified": 1667477360, - "narHash": "sha256-EE1UMXQr4FkQFGoOHDslqi3Q7V2BUkLMrOBeV/UJYoE=", + "lastModified": 1670113356, + "narHash": "sha256-43aMRMU0OuBin6M2LM+nxVG+whazyHuHnUvu92xoth0=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "c8900204fcd5c09ab0c9b7bb7f4d04dacab3c462", + "rev": "17352071583eda4be43fa2a312f6e061326374f7", "type": "github" }, "original": { 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/ComptimeInterpreter.zig b/src/ComptimeInterpreter.zig index 7d7f606..7c33fe9 100644 --- a/src/ComptimeInterpreter.zig +++ b/src/ComptimeInterpreter.zig @@ -587,7 +587,7 @@ pub fn interpret( }, .field_access => { if (data[node_idx].rhs == 0) return error.CriticalAstFailure; - const rhs_str = ast.tokenSlice(tree, data[node_idx].rhs) catch return error.CriticalAstFailure; + const rhs_str = tree.tokenSlice(data[node_idx].rhs); var ir = try interpreter.interpret(data[node_idx].lhs, scope, options); var irv = try ir.getValue(); 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/DocumentStore.zig b/src/DocumentStore.zig index 1f23f2c..8276b08 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -317,7 +317,11 @@ fn garbageCollectionCImports(self: *DocumentStore) error{OutOfMemory}!void { continue; } var kv = self.cimports.fetchSwapRemove(hash).?; - std.log.debug("Destroying cimport {s}", .{kv.value.success}); + const message = switch (kv.value) { + .failure => "", + .success => |uri| uri, + }; + std.log.debug("Destroying cimport {s}", .{message}); kv.value.deinit(self.allocator); } } @@ -854,10 +858,7 @@ pub fn resolveCImport(self: *DocumentStore, handle: Handle, node: Ast.Node.Index /// caller owns the returned memory pub fn uriFromImportStr(self: *const DocumentStore, allocator: std.mem.Allocator, handle: Handle, import_str: []const u8) error{OutOfMemory}!?Uri { if (std.mem.eql(u8, import_str, "std")) { - const zig_lib_path = self.config.zig_lib_path orelse { - log.debug("Cannot resolve std library import, path is null.", .{}); - return null; - }; + const zig_lib_path = self.config.zig_lib_path orelse return null; const std_path = std.fs.path.resolve(allocator, &[_][]const u8{ zig_lib_path, "./std/std.zig" }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, diff --git a/src/Server.zig b/src/Server.zig index eaf63f2..780e8bc 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"); @@ -179,26 +180,6 @@ fn publishDiagnostics(server: *Server, writer: anytype, handle: DocumentStore.Ha }); } - 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, " "); - - 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 - - const node = handle.cimports.items(.node)[i]; - try diagnostics.append(allocator, .{ - .range = offsets.nodeToRange(handle.tree, node, server.offset_encoding), - .severity = .Error, - .code = "cImport", - .source = "zls", - .message = try allocator.dupe(u8, pos_and_diag_iterator.rest()), - }); - } - if (server.config.enable_ast_check_diagnostics and tree.errors.len == 0) { try getAstCheckDiagnostics(server, handle, &diagnostics); } @@ -788,7 +769,7 @@ pub fn identifierFromPosition(pos_index: usize, handle: DocumentStore.Handle) [] } fn isSymbolChar(char: u8) bool { - return std.ascii.isAlNum(char) or char == '_'; + return std.ascii.isAlphanumeric(char) or char == '_'; } fn gotoDefinitionSymbol( @@ -864,11 +845,7 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO const end = offsets.tokenToLoc(tree, last_token).end; break :def tree.source[start..end]; }, - .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), + .pointer_payload, .array_payload, .array_index, .switch_payload, .label_decl => tree.tokenSlice(decl_handle.nameToken()), }; var bound_type_params = analysis.BoundTypeParams{}; @@ -1152,43 +1129,18 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.Decl .insertTextFormat = .PlainText, }); }, - .pointer_payload => |payload| { + .pointer_payload, + .array_payload, + .array_index, + .switch_payload, + .label_decl, + => { + const name = tree.tokenSlice(decl_handle.nameToken()); + try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload.name), + .label = name, .kind = .Variable, - .insertText = tree.tokenSlice(payload.name), - .insertTextFormat = .PlainText, - }); - }, - .array_payload => |payload| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload.identifier), - .kind = .Variable, - .insertText = tree.tokenSlice(payload.identifier), - .insertTextFormat = .PlainText, - }); - }, - .array_index => |payload| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload), - .kind = .Variable, - .insertText = tree.tokenSlice(payload), - .insertTextFormat = .PlainText, - }); - }, - .switch_payload => |payload| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(payload.node), - .kind = .Variable, - .insertText = tree.tokenSlice(payload.node), - .insertTextFormat = .PlainText, - }); - }, - .label_decl => |label_decl| { - try context.completions.append(allocator, .{ - .label = tree.tokenSlice(label_decl), - .kind = .Variable, - .insertText = tree.tokenSlice(label_decl), + .insertText = name, .insertTextFormat = .PlainText, }); }, @@ -1563,6 +1515,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; @@ -1708,16 +1676,29 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req: log.info("{}", .{server.client_capabilities}); log.info("Using offset encoding: {s}", .{std.meta.tagName(server.offset_encoding)}); - // 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 }); + if (server.config.zig_exe_path) |exe_path| blk: { + // TODO avoid having to call getZigEnv twice + // once in init and here + const env = configuration.getZigEnv(server.allocator, exe_path) orelse break :blk; + defer std.json.parseFree(configuration.Env, env, .{ .allocator = server.allocator }); - const zig_exe_version = std.SemanticVersion.parse(env.version) catch return; + const zig_exe_version = std.SemanticVersion.parse(env.version) catch break :blk; - if (zig_builtin.zig_version.order(zig_exe_version) == .gt) { - 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); + if (zig_builtin.zig_version.order(zig_exe_version) == .gt) { + 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); + } + } else { + try server.showMessage( + writer, + .Warning, + \\ZLS failed to find Zig. Please add Zig to your PATH or set the zig_exe_path config option in your zls.json. + , + ); } } @@ -1773,42 +1754,20 @@ fn registerCapability(server: *Server, writer: anytype, method: []const u8) !voi const id = try std.fmt.allocPrint(server.arena.allocator(), "register-{s}", .{method}); log.debug("Dynamically registering method '{s}'", .{method}); - 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 = ®istrations, - }; - - const respp = types.ResponseParams{ - .RegistrationParams = params, - }; - const req = types.Request{ - .id = .{ .String = id }, - .method = "client/registerCapability", - .params = respp, - }; - - 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, - }, + 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, }, }, }, - }); - } + }, + }); } fn requestConfiguration(server: *Server, writer: anytype) !void { @@ -1880,18 +1839,15 @@ fn saveDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, re 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, + .params = .{ + .ApplyEdit = .{ + .label = "autofix", + .edit = workspace_edit, + }, + }, }); } @@ -1916,20 +1872,28 @@ fn willSaveWaitUntilHandler(server: *Server, writer: anytype, id: types.RequestI 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; - const handle = server.document_store.getHandle(uri) orelse return; - if (handle.tree.errors.len != 0) return; + b: { + if (!server.config.enable_ast_check_diagnostics or !server.config.enable_autofix) + break :b; - var text_edits = try server.autofix(allocator, handle); + const uri = req.params.textDocument.uri; + + const handle = server.document_store.getHandle(uri) orelse break :b; + if (handle.tree.errors.len != 0) break :b; + + var text_edits = try server.autofix(allocator, handle); + + return try send(writer, allocator, types.Response{ + .id = id, + .result = .{ .TextEdits = try text_edits.toOwnedSlice(allocator) }, + }); + } return try send(writer, allocator, types.Response{ .id = id, - .result = .{ .TextEdits = try text_edits.toOwnedSlice(allocator) }, + .result = .{ .TextEdits = &.{} }, }); } @@ -2190,16 +2154,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) { @@ -2212,7 +2176,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); } @@ -2289,9 +2253,7 @@ fn generalReferencesHandler(server: *Server, writer: anytype, id: types.RequestI }; const locations = if (pos_context == .label) - // FIXME https://github.com/zigtools/zls/issues/728 - // try references.labelReferences(allocator, decl, server.offset_encoding, include_decl) - std.ArrayListUnmanaged(types.Location){} + try references.labelReferences(allocator, decl, server.offset_encoding, include_decl) else try references.symbolReferences( &server.arena, @@ -2791,18 +2753,18 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void 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 + const ft = if (@typeInfo(field.type) == .Optional) + @typeInfo(field.type).Optional.child else - field.field_type; + field.type; const ti = @typeInfo(ft); if (value != .Null) { - const new_value: field.field_type = switch (ft) { + const new_value: field.type = switch (ft) { []const u8 => switch (value) { .String => |s| blk: { if (s.len == 0) { - if (field.field_type == ?[]const u8) { + if (field.type == ?[]const u8) { break :blk null; } else { break :blk s; @@ -2831,7 +2793,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; } @@ -2894,61 +2856,27 @@ 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 }, }; - 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}); - done = error.HackDone; - } 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; - } - } else { - done = error.HackDone; - (method_info[2])(server, writer, id) catch |err| { - done = err; - }; - } - } - } - 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; + 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; } } @@ -2991,7 +2919,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/analysis.zig b/src/analysis.zig index d2b2d12..4de63b7 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -77,7 +77,7 @@ pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: while (true) : (curr_line_tok += 1) { const comm = tokens[curr_line_tok]; if ((container_doc and comm == .container_doc_comment) or (!container_doc and comm == .doc_comment)) { - try lines.append(std.mem.trim(u8, tree.tokenSlice(curr_line_tok)[3..], &std.ascii.spaces)); + try lines.append(std.mem.trim(u8, tree.tokenSlice(curr_line_tok)[3..], &std.ascii.whitespace)); } else break; } @@ -275,10 +275,11 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex { const main_token = tree.nodes.items(.main_token)[node]; return switch (tags[node]) { // regular declaration names. + 1 to mut token because name comes after 'const'/'var' - .local_var_decl => tree.localVarDecl(node).ast.mut_token + 1, - .global_var_decl => tree.globalVarDecl(node).ast.mut_token + 1, - .simple_var_decl => tree.simpleVarDecl(node).ast.mut_token + 1, - .aligned_var_decl => tree.alignedVarDecl(node).ast.mut_token + 1, + .local_var_decl, + .global_var_decl, + .simple_var_decl, + .aligned_var_decl, + => ast.varDecl(tree, node).?.ast.mut_token + 1, // function declaration names .fn_proto, .fn_proto_multi, @@ -291,20 +292,9 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex { }, // containers - .container_field => blk: { - const field = tree.containerField(node); - if (field.ast.tuple_like) break :blk null; - break :blk field.ast.main_token; - }, - .container_field_init => blk: { - const field = tree.containerFieldInit(node); - if (field.ast.tuple_like) break :blk null; - break :blk field.ast.main_token; - }, - .container_field_align => blk: { - const field = tree.containerFieldAlign(node); - if (field.ast.tuple_like) break :blk null; - break :blk field.ast.main_token; + .container_field, .container_field_init, .container_field_align => { + const field = ast.containerField(tree, node).?.ast; + return if (field.tuple_like) null else field.main_token; }, .identifier => main_token, @@ -888,7 +878,6 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl }, .field_access => { if (datas[node].rhs == 0) return null; - const rhs_str = ast.tokenSlice(tree, datas[node].rhs) catch return null; // If we are accessing a pointer type, remove one pointerness level :) const left_type = try resolveFieldAccessLhsType( @@ -905,11 +894,12 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl .other => |n| n, else => return null, }; + if (try lookupSymbolContainer( store, arena, .{ .node = left_type_node, .handle = left_type.handle }, - rhs_str, + tree.tokenSlice(datas[node].rhs), !left_type.type.is_type_val, )) |child| { return try child.resolveType(store, arena, bound_type_params); @@ -1424,45 +1414,39 @@ pub fn nodeToString(tree: Ast, node: Ast.Node.Index) ?[]const u8 { const data = tree.nodes.items(.data); const main_token = tree.nodes.items(.main_token)[node]; var buf: [1]Ast.Node.Index = undefined; - switch (tree.nodes.items(.tag)[node]) { - .container_field => { - const field = tree.containerField(node).ast; + return switch (tree.nodes.items(.tag)[node]) { + .container_field, + .container_field_init, + .container_field_align, + => { + const field = ast.containerField(tree, node).?.ast; return if (field.tuple_like) null else tree.tokenSlice(field.main_token); }, - .container_field_init => { - const field = tree.containerFieldInit(node).ast; - return if (field.tuple_like) null else tree.tokenSlice(field.main_token); - }, - .container_field_align => { - const field = tree.containerFieldAlign(node).ast; - return if (field.tuple_like) null else tree.tokenSlice(field.main_token); - }, - .error_value => return tree.tokenSlice(data[node].rhs), - .identifier => return tree.tokenSlice(main_token), + .error_value => tree.tokenSlice(data[node].rhs), + .identifier => tree.tokenSlice(main_token), .fn_proto, .fn_proto_multi, .fn_proto_one, .fn_proto_simple, .fn_decl, - => if (ast.fnProto(tree, node, &buf).?.name_token) |name| - return tree.tokenSlice(name), - .field_access => return ast.tokenSlice(tree, data[node].rhs) catch return null, + => if (ast.fnProto(tree, node, &buf).?.name_token) |name| tree.tokenSlice(name) else null, + .field_access => tree.tokenSlice(data[node].rhs), .call, .call_comma, .async_call, .async_call_comma, - => return tree.tokenSlice(tree.callFull(node).ast.lparen - 1), + => tree.tokenSlice(tree.callFull(node).ast.lparen - 1), .call_one, .call_one_comma, .async_call_one, .async_call_one_comma, - => return tree.tokenSlice(tree.callOne(&buf, node).ast.lparen - 1), - .test_decl => if (data[node].lhs != 0) - return tree.tokenSlice(data[node].lhs), - else => |tag| log.debug("INVALID: {}", .{tag}), - } - - return null; + => tree.tokenSlice(tree.callOne(&buf, node).ast.lparen - 1), + .test_decl => if (data[node].lhs != 0) tree.tokenSlice(data[node].lhs) else null, + else => |tag| { + log.debug("INVALID: {}", .{tag}); + return null; + }, + }; } fn nodeContainsSourceIndex(tree: Ast, node: Ast.Node.Index, source_index: usize) bool { @@ -1642,6 +1626,7 @@ pub fn getPositionContext(allocator: std.mem.Allocator, text: []const u8, doc_in .field_access => {}, .other => {}, .global_error_set => {}, + .label => {}, else => curr_ctx.ctx = .{ .field_access = tokenLocAppend(curr_ctx.ctx.loc().?, tok), }, @@ -1683,38 +1668,33 @@ pub fn getPositionContext(allocator: std.mem.Allocator, text: []const u8, doc_in } } - return block: { - if (stack.popOrNull()) |state| { - switch (state.ctx) { - .empty => {}, - .label => |filled| { - // We need to check this because the state could be a filled - // label if only a space follows it - if (!filled or line[line.len - 1] != ' ') { - break :block state.ctx; - } - }, - else => break :block state.ctx, - } + if (stack.popOrNull()) |state| { + switch (state.ctx) { + .empty => {}, + .label => |filled| { + // We need to check this because the state could be a filled + // label if only a space follows it + if (!filled or line[line.len - 1] != ' ') { + return state.ctx; + } + }, + else => return state.ctx, } + } - if (line.len == 0) return .empty; + if (line.len == 0) return .empty; - var held_line = try allocator.dupeZ(u8, offsets.locToSlice(text, line_loc)); - defer allocator.free(held_line); + var held_line = try allocator.dupeZ(u8, offsets.locToSlice(text, line_loc)); + defer allocator.free(held_line); - switch (line[0]) { - 'a'...'z', 'A'...'Z', '_', '@' => {}, - else => break :block .empty, - } - var tokenizer = std.zig.Tokenizer.init(held_line); - const tok = tokenizer.next(); - if (tok.tag == .identifier) { - break :block PositionContext{ .var_access = tok.loc }; - } else { - break :block .empty; - } - }; + switch (line[0]) { + 'a'...'z', 'A'...'Z', '_', '@' => {}, + else => return .empty, + } + var tokenizer = std.zig.Tokenizer.init(held_line); + const tok = tokenizer.next(); + + return if (tok.tag == .identifier) PositionContext{ .var_access = tok.loc } else .empty; } fn addOutlineNodes(allocator: std.mem.Allocator, tree: Ast, child: Ast.Node.Index, context: *GetDocumentSymbolsContext) anyerror!void { @@ -1970,7 +1950,10 @@ pub const Declaration = union(enum) { switch_expr: Ast.Node.Index, items: []const Ast.Node.Index, }, - label_decl: Ast.TokenIndex, + label_decl: struct { + label: Ast.TokenIndex, + block: Ast.Node.Index, + }, }; pub const DeclWithHandle = struct { @@ -1986,7 +1969,7 @@ pub const DeclWithHandle = struct { .array_payload => |ap| ap.identifier, .array_index => |ai| ai, .switch_payload => |sp| sp.node, - .label_decl => |ld| ld, + .label_decl => |ld| ld.label, }; } @@ -2710,7 +2693,10 @@ fn makeScopeInternal(allocator: std.mem.Allocator, context: ScopeContext, node_i }, .data = .other, }; - try scope.decls.putNoClobber(allocator, tree.tokenSlice(first_token), .{ .label_decl = first_token }); + try scope.decls.putNoClobber(allocator, tree.tokenSlice(first_token), .{ .label_decl = .{ + .label = first_token, + .block = node_idx, + } }); } try scopes.append(allocator, .{ @@ -2831,7 +2817,10 @@ fn makeScopeInternal(allocator: std.mem.Allocator, context: ScopeContext, node_i .data = .other, }; - try scope.decls.putNoClobber(allocator, tree.tokenSlice(label), .{ .label_decl = label }); + try scope.decls.putNoClobber(allocator, tree.tokenSlice(label), .{ .label_decl = .{ + .label = label, + .block = while_node.ast.then_expr, + } }); } if (while_node.payload_token) |payload| { diff --git a/src/ast.zig b/src/ast.zig index 5056a9c..9a1709c 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -9,18 +9,15 @@ const full = Ast.full; fn fullPtrType(tree: Ast, info: full.PtrType.Components) full.PtrType { const token_tags = tree.tokens.items(.tag); - // TODO: looks like stage1 isn't quite smart enough to handle enum - // literals in some places here - const Size = std.builtin.Type.Pointer.Size; - const size: Size = switch (token_tags[info.main_token]) { + const size: std.builtin.Type.Pointer.Size = switch (token_tags[info.main_token]) { .asterisk, .asterisk_asterisk, => switch (token_tags[info.main_token + 1]) { .r_bracket, .colon => .Many, - .identifier => if (token_tags[info.main_token - 1] == .l_bracket) Size.C else .One, + .identifier => if (info.main_token != 0 and token_tags[info.main_token - 1] == .l_bracket) .C else .One, else => .One, }, - .l_bracket => Size.Slice, + .l_bracket => .Slice, else => unreachable, }; var result: full.PtrType = .{ @@ -1227,26 +1224,3 @@ pub fn nextFnParam(it: *Ast.full.FnProto.Iterator) ?Ast.full.FnProto.Param { it.tok_flag = false; } } - -/// A modified version of tree.tokenSlice that returns an error.UnexpectedToken if the tokenizer encounters an unexpected token -// https://github.com/zigtools/zls/issues/381 -pub fn tokenSlice(tree: Ast, token_index: Ast.TokenIndex) ![]const u8 { - const token_starts = tree.tokens.items(.start); - const token_tags = tree.tokens.items(.tag); - const token_tag = token_tags[token_index]; - - // Many tokens can be determined entirely by their tag. - if (token_tag.lexeme()) |lexeme| { - return lexeme; - } - - // For some tokens, re-tokenization is needed to find the end. - var tokenizer: std.zig.Tokenizer = .{ - .buffer = tree.source, - .index = token_starts[token_index], - .pending_invalid_token = null, - }; - const token = tokenizer.next(); - if (token.tag != token_tag) return error.UnexpectedToken; // assert(token.tag == token_tag); - return tree.source[token.loc.start..token.loc.end]; -} diff --git a/src/code_actions.zig b/src/code_actions.zig index 74ead7c..e17fa57 100644 --- a/src/code_actions.zig +++ b/src/code_actions.zig @@ -15,11 +15,7 @@ pub const Builder = struct { handle: *const DocumentStore.Handle, offset_encoding: offsets.Encoding, - pub fn generateCodeAction( - builder: *Builder, - diagnostic: types.Diagnostic, - actions: *std.ArrayListUnmanaged(types.CodeAction) - ) error{OutOfMemory}!void { + pub fn generateCodeAction(builder: *Builder, diagnostic: types.Diagnostic, actions: *std.ArrayListUnmanaged(types.CodeAction)) error{OutOfMemory}!void { const kind = DiagnosticKind.parse(diagnostic.message) orelse return; const loc = offsets.rangeToLoc(builder.handle.text, diagnostic.range, builder.offset_encoding); @@ -30,7 +26,7 @@ pub const Builder = struct { .@"local constant" => try handleUnusedVariableOrConstant(builder, actions, loc), .@"local variable" => try handleUnusedVariableOrConstant(builder, actions, loc), .@"loop index capture" => try handleUnusedIndexCapture(builder, actions, loc), - .@"capture" => try handleUnusedCapture(builder, actions, loc), + .capture => try handleUnusedCapture(builder, actions, loc), }, .non_camelcase_fn => try handleNonCamelcaseFunction(builder, actions, loc), .pointless_discard => try handlePointlessDiscard(builder, actions, loc), @@ -244,21 +240,21 @@ fn detectIndentation(source: []const u8) []const u8 { // Essentially I'm looking for the first indentation in the file. var i: usize = 0; var len = source.len - 1; // I need 1 look-ahead - while(i < len) : (i += 1) { - if(source[i] != '\n') continue; + while (i < len) : (i += 1) { + if (source[i] != '\n') continue; i += 1; - if(source[i] == '\t') return "\t"; + if (source[i] == '\t') return "\t"; var space_count: usize = 0; - while(i < source.len and source[i] == ' ') : (i += 1) { + while (i < source.len and source[i] == ' ') : (i += 1) { space_count += 1; } - if(source[i] == '\n') { // Some editors mess up indentation of empty lines + if (source[i] == '\n') { // Some editors mess up indentation of empty lines i -= 1; continue; } - if(space_count == 0) continue; - if(source[i] == '/') continue; // Comments sometimes have additional alignment. - if(source[i] == '\\') continue; // multi-line strings might as well. + if (space_count == 0) continue; + if (source[i] == '/') continue; // Comments sometimes have additional alignment. + if (source[i] == '\\') continue; // multi-line strings might as well. return source[i - space_count .. i]; } return " " ** 4; // recommended style @@ -298,14 +294,14 @@ fn createCamelcaseText(allocator: std.mem.Allocator, identifier: []const u8) ![] fn createDiscardText(builder: *Builder, identifier_name: []const u8, declaration_start: usize, add_block_indentation: bool) ![]const u8 { const indent = find_indent: { const line = offsets.lineSliceUntilIndex(builder.handle.text, declaration_start); - for(line) |char, i| { - if(!std.ascii.isSpace(char)) { + for (line) |char, i| { + if (!std.ascii.isWhitespace(char)) { break :find_indent line[0..i]; } } break :find_indent line; }; - const additional_indent = if(add_block_indentation) detectIndentation(builder.handle.text) else ""; + const additional_indent = if (add_block_indentation) detectIndentation(builder.handle.text) else ""; const allocator = builder.arena.allocator(); const new_text_len = 1 + indent.len + additional_indent.len + "_ = ;".len + identifier_name.len; @@ -374,7 +370,7 @@ const DiagnosticKind = union(enum) { @"local constant", @"local variable", @"loop index capture", - @"capture", + capture, }; const DiscardCat = enum { @@ -600,5 +596,5 @@ fn getCaptureLoc(text: []const u8, loc: offsets.Loc, is_index_payload: bool) ?Ca } fn isSymbolChar(char: u8) bool { - return std.ascii.isAlNum(char) or char == '_'; + return std.ascii.isAlphanumeric(char) or char == '_'; } 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..8006941 --- /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.type) != .Optional) { + fields[i].type = @Type(std.builtin.Type{ + .Optional = .{ .child = field.type }, + }); + } + } + config_info.Struct.fields = fields[0..]; + config_info.Struct.decls = &.{}; + return @Type(config_info); +} diff --git a/src/data/generate-data.py b/src/data/generate-data.py old mode 100644 new mode 100755 diff --git a/src/data/master.zig b/src/data/master.zig index 9eacf7b..bc38179 100644 --- a/src/data/master.zig +++ b/src/data/master.zig @@ -79,24 +79,6 @@ pub const builtins = [_]Builtin{ "expression", }, }, - .{ - .name = "@asyncCall", - .signature = "@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: anytype) anyframe->T", - .snippet = "@asyncCall(${1:frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8}, ${2:result_ptr}, ${3:function_ptr}, ${4:args: anytype})", - .documentation = - \\`@asyncCall` performs an `async` call on a function pointer, which may or may not be an [async function](https://ziglang.org/documentation/master/#Async-Functions). - \\ - \\The provided `frame_buffer` must be large enough to fit the entire function frame. This size can be determined with [@frameSize](https://ziglang.org/documentation/master/#frameSize). To provide a too-small buffer invokes safety-checked [Undefined Behavior](https://ziglang.org/documentation/master/#Undefined-Behavior). - \\ - \\`result_ptr` is optional ([null](https://ziglang.org/documentation/master/#null) may be provided). If provided, the function call will write its result directly to the result pointer, which will be available to read after [await](https://ziglang.org/documentation/master/#Async-and-Await) completes. Any result location provided to `await` will copy the result from `result_ptr`.

{#code_begin|test|async_struct_field_fn_pointer#} {#backend_stage1#} const std = @import("std"); const expect = std.testing.expect; test "async fn pointer in a struct field" { var data: i32 = 1; const Foo = struct { bar: fn (*i32) callconv(.Async) void, }; var foo = Foo{ .bar = func }; var bytes: [64]u8 align(@alignOf(@Frame(func))) = undefined; const f = @asyncCall(&bytes, {}, foo.bar, .{&data}); try expect(data == 2); resume f; try expect(data == 4); } fn func(y: *i32) void { defer y.* += 2; y.* += 1; suspend {} }` - , - .arguments = &.{ - "frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8", - "result_ptr", - "function_ptr", - "args: anytype", - }, - }, .{ .name = "@atomicLoad", .signature = "@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: builtin.AtomicOrder) T", @@ -291,8 +273,8 @@ pub const builtins = [_]Builtin{ }, .{ .name = "@call", - .signature = "@call(options: std.builtin.CallOptions, function: anytype, args: anytype) anytype", - .snippet = "@call(${1:options: std.builtin.CallOptions}, ${2:function: anytype}, ${3:args: anytype})", + .signature = "@call(modifier: std.builtin.CallModifier, function: anytype, args: anytype) anytype", + .snippet = "@call(${1:modifier: std.builtin.CallModifier}, ${2:function: anytype}, ${3:args: anytype})", .documentation = \\Calls a function, in the same way that invoking an expression with parentheses does: \\ @@ -300,7 +282,7 @@ pub const builtins = [_]Builtin{ \\const expect = @import("std").testing.expect; \\ \\test "noinline function call" { - \\ try expect(@call(.{}, add, .{3, 9}) == 12); + \\ try expect(@call(.auto, add, .{3, 9}) == 12); \\} \\ \\fn add(a: i32, b: i32) i32 { @@ -308,10 +290,10 @@ pub const builtins = [_]Builtin{ \\} \\``` \\ - \\`@call` allows more flexibility than normal function call syntax does. The `CallOptions` struct is reproduced here:

{#syntax_block|zig|builtin.CallOptions struct#} pub const CallOptions = struct { modifier: Modifier = .auto, /// Only valid when `Modifier` is `Modifier.async_kw`. stack: ?[]align(std.Target.stack_align) u8 = null, pub const Modifier = enum { /// Equivalent to function call syntax. auto, /// Equivalent to async keyword used with function call syntax. async_kw, /// Prevents tail call optimization. This guarantees that the return /// address will point to the callsite, as opposed to the callsite's /// callsite. If the call is otherwise required to be tail-called /// or inlined, a compile error is emitted instead. never_tail, /// Guarantees that the call will not be inlined. If the call is /// otherwise required to be inlined, a compile error is emitted instead. never_inline, /// Asserts that the function call will not suspend. This allows a /// non-async function to call an async function. no_async, /// Guarantees that the call will be generated with tail call optimization. /// If this is not possible, a compile error is emitted instead. always_tail, /// Guarantees that the call will inlined at the callsite. /// If this is not possible, a compile error is emitted instead. always_inline, /// Evaluates the call at compile-time. If the call cannot be completed at /// compile-time, a compile error is emitted instead. compile_time, }; }; {#end_syntax_block#} + \\`@call` allows more flexibility than normal function call syntax does. The `CallModifier` enum is reproduced here:

{#syntax_block|zig|builtin.CallModifier struct#} pub const CallModifier = enum { /// Equivalent to function call syntax. auto, /// Equivalent to async keyword used with function call syntax. async_kw, /// Prevents tail call optimization. This guarantees that the return /// address will point to the callsite, as opposed to the callsite's /// callsite. If the call is otherwise required to be tail-called /// or inlined, a compile error is emitted instead. never_tail, /// Guarantees that the call will not be inlined. If the call is /// otherwise required to be inlined, a compile error is emitted instead. never_inline, /// Asserts that the function call will not suspend. This allows a /// non-async function to call an async function. no_async, /// Guarantees that the call will be generated with tail call optimization. /// If this is not possible, a compile error is emitted instead. always_tail, /// Guarantees that the call will inlined at the callsite. /// If this is not possible, a compile error is emitted instead. always_inline, /// Evaluates the call at compile-time. If the call cannot be completed at /// compile-time, a compile error is emitted instead. compile_time, }; {#end_syntax_block#} , .arguments = &.{ - "options: std.builtin.CallOptions", + "modifier: std.builtin.CallModifier", "function: anytype", "args: anytype", }, @@ -524,6 +506,49 @@ pub const builtins = [_]Builtin{ "comptime name: []u8", }, }, + .{ + .name = "@cVaArg", + .signature = "@cVaArg(operand: *std.builtin.VaList, comptime T: type) T", + .snippet = "@cVaArg(${1:operand: *std.builtin.VaList}, ${2:comptime T: type})", + .documentation = + \\Implements the C macro `va_arg`. + , + .arguments = &.{ + "operand: *std.builtin.VaList", + "comptime T: type", + }, + }, + .{ + .name = "@cVaCopy", + .signature = "@cVaCopy(src: *std.builtin.VaList) std.builtin.VaList", + .snippet = "@cVaCopy(${1:src: *std.builtin.VaList})", + .documentation = + \\Implements the C macro `va_copy`. + , + .arguments = &.{ + "src: *std.builtin.VaList", + }, + }, + .{ + .name = "@cVaEnd", + .signature = "@cVaEnd(src: *std.builtin.VaList) void", + .snippet = "@cVaEnd(${1:src: *std.builtin.VaList})", + .documentation = + \\Implements the C macro `va_end`. + , + .arguments = &.{ + "src: *std.builtin.VaList", + }, + }, + .{ + .name = "@cVaStart", + .signature = "@cVaStart() std.builtin.VaList", + .snippet = "@cVaStart()", + .documentation = + \\Implements the C macro `va_start`. Only valid inside a variadic function. + , + .arguments = &.{}, + }, .{ .name = "@divExact", .signature = "@divExact(numerator: T, denominator: T) T", @@ -770,30 +795,6 @@ pub const builtins = [_]Builtin{ "float: anytype", }, }, - .{ - .name = "@frame", - .signature = "@frame() *@Frame(func)", - .snippet = "@frame()", - .documentation = - \\This function returns a pointer to the frame for a given function. This type can be [coerced](https://ziglang.org/documentation/master/#Type-Coercion) to `anyframe->T` and to `anyframe`, where `T` is the return type of the function in scope. - \\ - \\This function does not mark a suspension point, but it does cause the function in scope to become an [async function](https://ziglang.org/documentation/master/#Async-Functions). - , - .arguments = &.{}, - }, - .{ - .name = "@Frame", - .signature = "@Frame(func: anytype) type", - .snippet = "@Frame(${1:func: anytype})", - .documentation = - \\This function returns the frame type of a function. This works for [Async Functions](https://ziglang.org/documentation/master/#Async-Functions) as well as any function without a specific calling convention. - \\ - \\This type is suitable to be used as the return type of [async](https://ziglang.org/documentation/master/#Async-and-Await) which allows one to, for example, heap-allocate an async function frame:

{#code_begin|test|heap_allocated_frame#} {#backend_stage1#} const std = @import("std"); test "heap allocated frame" { const frame = try std.heap.page_allocator.create(@Frame(func)); frame.* = async func(); } fn func() void { suspend {} }` - , - .arguments = &.{ - "func: anytype", - }, - }, .{ .name = "@frameAddress", .signature = "@frameAddress() usize", @@ -807,25 +808,12 @@ pub const builtins = [_]Builtin{ , .arguments = &.{}, }, - .{ - .name = "@frameSize", - .signature = "@frameSize(func: anytype) usize", - .snippet = "@frameSize(${1:func: anytype})", - .documentation = - \\This is the same as `@sizeOf(@Frame(func))`, where `func` may be runtime-known. - \\ - \\This function is typically used in conjunction with [@asyncCall](https://ziglang.org/documentation/master/#asyncCall). - , - .arguments = &.{ - "func: anytype", - }, - }, .{ .name = "@hasDecl", .signature = "@hasDecl(comptime Container: type, comptime name: []const u8) bool", .snippet = "@hasDecl(${1:comptime Container: type}, ${2:comptime name: []const u8})", .documentation = - \\Returns whether or not a [struct](https://ziglang.org/documentation/master/#struct), [enum](https://ziglang.org/documentation/master/#enum), or [union](https://ziglang.org/documentation/master/#union) has a declaration matching `name`.

{#code_begin|test|hasDecl#} const std = @import("std"); const expect = std.testing.expect; const Foo = struct { nope: i32, pub var blah = "xxx"; const hi = 1; }; test "@hasDecl" { try expect(@hasDecl(Foo, "blah")); // Even though `hi` is private, @hasDecl returns true because this test is // in the same file scope as Foo. It would return false if Foo was declared // in a different file. try expect(@hasDecl(Foo, "hi")); // @hasDecl is for declarations; not fields. try expect(!@hasDecl(Foo, "nope")); try expect(!@hasDecl(Foo, "nope1234")); }` + \\Returns whether or not a [container](https://ziglang.org/documentation/master/#Containers) has a declaration matching `name`.

{#code_begin|test|hasDecl#} const std = @import("std"); const expect = std.testing.expect; const Foo = struct { nope: i32, pub var blah = "xxx"; const hi = 1; }; test "@hasDecl" { try expect(@hasDecl(Foo, "blah")); // Even though `hi` is private, @hasDecl returns true because this test is // in the same file scope as Foo. It would return false if Foo was declared // in a different file. try expect(@hasDecl(Foo, "hi")); // @hasDecl is for declarations; not fields. try expect(!@hasDecl(Foo, "nope")); try expect(!@hasDecl(Foo, "nope1234")); }` , .arguments = &.{ "comptime Container: type", @@ -1721,16 +1709,13 @@ pub const builtins = [_]Builtin{ \\ - [Error Union Type](https://ziglang.org/documentation/master/#Error-Union-Type) \\ - [Vectors](https://ziglang.org/documentation/master/#Vectors) \\ - [opaque](https://ziglang.org/documentation/master/#opaque) - \\ - [@Frame](https://ziglang.org/documentation/master/#Frame) \\ - `anyframe` \\ - [struct](https://ziglang.org/documentation/master/#struct) \\ - [enum](https://ziglang.org/documentation/master/#enum) \\ - [Enum Literals](https://ziglang.org/documentation/master/#Enum-Literals) \\ - [union](https://ziglang.org/documentation/master/#union) \\ - \\For these types, `@Type` is not available: - \\ - [Functions](https://ziglang.org/documentation/master/#Functions) - \\ - BoundFn + \\`@Type` is not available for [Functions](https://ziglang.org/documentation/master/#Functions). , .arguments = &.{ "comptime info: std.builtin.Type", diff --git a/src/inlay_hints.zig b/src/inlay_hints.zig index 579bbe2..c524903 100644 --- a/src/inlay_hints.zig +++ b/src/inlay_hints.zig @@ -247,11 +247,10 @@ fn callWriteNodeInlayHint(allocator: std.mem.Allocator, args: anytype) error{Out const FrameSize = @sizeOf(@Frame(writeNodeInlayHint)); var child_frame = try allocator.alignedAlloc(u8, std.Target.stack_align, FrameSize); defer allocator.free(child_frame); - return await @asyncCall(child_frame, {}, writeNodeInlayHint, args); } else { // TODO find a non recursive solution - return @call(.{}, writeNodeInlayHint, args); + return @call(.auto, writeNodeInlayHint, args); } } @@ -266,7 +265,11 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: const node_data = tree.nodes.items(.data); const main_tokens = tree.nodes.items(.main_token); - if (node == 0 or node > node_data.len) return; + // std.log.info("max: {d} | curr: {d}", .{ node_data.len, node }); + // if (node == 0 or node >= node_data.len) return; + if (node == 0) return; + // std.log.info("tag: {any}", .{node_tags[node]}); + // std.log.info("src: {s}", .{tree.getNodeSource(node)}); var allocator = arena.allocator(); @@ -452,7 +455,6 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: .bool_or, .array_access, .switch_range, - .error_value, .error_union, => { try callWriteNodeInlayHint(allocator, .{ builder, arena, store, node_data[node].lhs, range }); @@ -679,6 +681,8 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store: try callWriteNodeInlayHint(allocator, .{ builder, arena, store, asm_node.ast.template, range }); }, + + .error_value => {}, } } 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/references.zig b/src/references.zig index 3d1fa8a..e04df2f 100644 --- a/src/references.zig +++ b/src/references.zig @@ -20,8 +20,8 @@ pub fn labelReferences( // Find while / for / block from label -> iterate over children nodes, find break and continues, change their labels if they match. // This case can be implemented just by scanning tokens. - const first_tok = tree.firstToken(decl.decl.label_decl); - const last_tok = ast.lastToken(tree, decl.decl.label_decl); + const first_tok = decl.decl.label_decl.label; + const last_tok = ast.lastToken(tree, decl.decl.label_decl.block); var locations = std.ArrayListUnmanaged(types.Location){}; errdefer locations.deinit(allocator); @@ -337,7 +337,6 @@ fn symbolReferencesInternal( .field_access => { try symbolReferencesInternal(builder, datas[node].lhs, handle, false); - const rhs_str = ast.tokenSlice(tree, datas[node].rhs) catch return; var bound_type_params = analysis.BoundTypeParams{}; const left_type = try analysis.resolveFieldAccessLhsType( builder.store, @@ -358,7 +357,7 @@ fn symbolReferencesInternal( builder.store, builder.arena, .{ .node = left_type_node, .handle = left_type.handle }, - rhs_str, + tree.tokenSlice(datas[node].rhs), !left_type.type.is_type_val, )) orelse return; @@ -498,6 +497,8 @@ pub fn symbolReferences( for (dependencies.keys()) |uri| { const handle = store.getHandle(uri) orelse continue; + if (std.mem.eql(u8, handle.uri, curr_handle.uri)) continue; + try symbolReferencesInternal(&builder, 0, handle, true); } }, diff --git a/src/requests.zig b/src/requests.zig index 466a7bd..f0cdb2b 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -45,10 +45,10 @@ fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Valu var err = false; inline for (std.meta.fields(T)) |field| { - const is_exists = field.field_type == Exists; + const is_exists = field.type == Exists; - const is_optional = comptime std.meta.trait.is(.Optional)(field.field_type); - const actual_type = if (is_optional) std.meta.Child(field.field_type) else field.field_type; + const is_optional = comptime std.meta.trait.is(.Optional)(field.type); + const actual_type = if (is_optional) std.meta.Child(field.type) else field.type; const is_struct = comptime std.meta.trait.is(.Struct)(actual_type); const is_default = comptime if (is_struct) std.meta.trait.hasDecls(actual_type, .{ "default", "value_type" }) else false; @@ -107,9 +107,6 @@ fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Valu out.* = value; } else if (comptime std.meta.trait.is(.Enum)(T)) { const info = @typeInfo(T).Enum; - if (info.layout != .Auto) - @compileError("Only auto layout enums are allowed"); - const TagType = info.tag_type; if (value != .Integer) return error.MalformedJson; out.* = std.meta.intToEnum( @@ -178,7 +175,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/semantic_tokens.zig b/src/semantic_tokens.zig index d54464f..9e2cf0b 100644 --- a/src/semantic_tokens.zig +++ b/src/semantic_tokens.zig @@ -141,16 +141,16 @@ const Builder = struct { const source = self.handle.tree.source; var i: usize = from; - while (i < to - 1) : (i += 1) { + while (i < to) : (i += 1) { // Skip multi-line string literals if (source[i] == '\\' and source[i + 1] == '\\') { - while (i < to - 1 and source[i] != '\n') : (i += 1) {} + while (i < to and source[i] != '\n') : (i += 1) {} continue; } // Skip normal string literals if (source[i] == '"') { i += 1; - while (i < to - 1 and + while (i < to and source[i] != '\n' and !(source[i] == '"' and source[i - 1] != '\\')) : (i += 1) {} @@ -159,7 +159,7 @@ const Builder = struct { // Skip char literals if (source[i] == '\'') { i += 1; - while (i < to - 1 and + while (i < to and source[i] != '\n' and !(source[i] == '\'' and source[i - 1] != '\\')) : (i += 1) {} @@ -174,7 +174,7 @@ const Builder = struct { if (i + 2 < to and (source[i + 2] == '!' or source[i + 2] == '/')) mods.documentation = true; - while (i < to - 1 and source[i] != '\n') : (i += 1) {} + while (i < to and source[i] != '\n') : (i += 1) {} const length = offsets.locLength(self.handle.tree.source, .{ .start = comment_start, .end = i }, self.encoding); try self.addDirect(TokenType.comment, mods, comment_start, length); @@ -276,7 +276,7 @@ fn callWriteNodeTokens(allocator: std.mem.Allocator, args: anytype) WriteTokensE return await @asyncCall(child_frame, {}, writeNodeTokens, args); } else { // TODO find a non recursive solution - return @call(.{}, writeNodeTokens, args); + return @call(.auto, writeNodeTokens, args); } } @@ -862,7 +862,6 @@ fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) WriteTokensEr .field_access => { const data = node_data[node]; if (data.rhs == 0) return; - const rhs_str = ast.tokenSlice(tree, data.rhs) catch return; try callWriteNodeTokens(allocator, .{ builder, data.lhs }); @@ -889,7 +888,7 @@ fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) WriteTokensEr builder.store, builder.arena, .{ .node = left_type_node, .handle = lhs_type.handle }, - rhs_str, + tree.tokenSlice(data.rhs), !lhs_type.type.is_type_val, )) |decl_type| { switch (decl_type.decl.*) { 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 { diff --git a/src/translate_c.zig b/src/translate_c.zig index dcc7a31..6c9f864 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -52,7 +52,7 @@ fn callConvertCIncludeInternal(allocator: std.mem.Allocator, args: anytype) erro return await @asyncCall(child_frame, {}, convertCIncludeInternal, args); } else { // TODO find a non recursive solution - return @call(.{}, convertCIncludeInternal, args); + return @call(.auto, convertCIncludeInternal, args); } } @@ -162,11 +162,11 @@ pub fn translate(allocator: std.mem.Allocator, config: Config, include_dirs: []c }; const base_args = &[_][]const u8{ - config.zig_exe_path.?, + config.zig_exe_path orelse return null, "translate-c", "--enable-cache", "--zig-lib-dir", - config.zig_lib_path.?, + config.zig_lib_path orelse return null, "--cache-dir", config.global_cache_path.?, "-lc", diff --git a/src/uri.zig b/src/uri.zig index a1064d8..41bc5a4 100644 --- a/src/uri.zig +++ b/src/uri.zig @@ -41,7 +41,7 @@ pub fn fromPath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 { // On windows, we need to lowercase the drive name. if (builtin.os.tag == .windows) { if (buf.items.len > prefix.len + 1 and - std.ascii.isAlpha(buf.items[prefix.len]) and + std.ascii.isAlphanumeric(buf.items[prefix.len]) and std.mem.startsWith(u8, buf.items[prefix.len + 1 ..], "%3A")) { buf.items[prefix.len] = std.ascii.toLower(buf.items[prefix.len]); diff --git a/tests/lsp_features/semantic_tokens.zig b/tests/lsp_features/semantic_tokens.zig index 113589e..ba906a9 100644 --- a/tests/lsp_features/semantic_tokens.zig +++ b/tests/lsp_features/semantic_tokens.zig @@ -22,6 +22,16 @@ test "semantic tokens" { // TODO more tests } +test "semantic tokens - comments" { + try testSemanticTokens( + \\//!─ + , + &.{ 0, 0, 4, 8, 128 }, + ); + + // TODO more tests +} + const file_uri = switch (builtin.os.tag) { .windows => "file:///C:/test.zig", else => "file:///test.zig",