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",