Merge branch 'master' into intern-pool

This commit is contained in:
Techatrix 2023-01-04 04:42:06 +01:00
commit d56a274c16
44 changed files with 9838 additions and 2585 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
zig-* zig-*
debug debug
release release
*.zlsreplay

3
.gitmodules vendored
View File

@ -4,3 +4,6 @@
[submodule "src/tracy"] [submodule "src/tracy"]
path = src/tracy path = src/tracy
url = https://github.com/wolfpld/tracy url = https://github.com/wolfpld/tracy
[submodule "src/tres"]
path = src/tres
url = https://github.com/ziglibs/tres.git

View File

@ -34,7 +34,6 @@ Building `zls` is very easy. You will need [a build of Zig master](https://zigla
git clone --recurse-submodules https://github.com/zigtools/zls git clone --recurse-submodules https://github.com/zigtools/zls
cd zls cd zls
zig build -Drelease-safe zig build -Drelease-safe
./zig-out/bin/zls --config # Configure ZLS
``` ```
#### Build Options #### Build Options
@ -48,12 +47,21 @@ zig build -Drelease-safe
There is a `generate-data.py` in the `src/data` folder, run this file to update data files. There is a `generate-data.py` in the `src/data` folder, run this file to update data files.
It writes to stdout and you can redirect output to a zig file like `master.zig`. By default it generates data file for `master`, but can be configured to generate for a different version by modifying the `zig_version` variable. Files generated by this tool **contains** formatting information. It writes to stdout and you can redirect output to a zig file like `master.zig`. By default it generates data file for `master`, but can be configured to generate for a different version by modifying the `zig_version` variable. Files generated by this tool **contains** formatting information.
On Powershell 5.1 (the default Powershell on Windows 10 & 11), the following will update `master.zig`.
```pwsh
New-Item -Force .\src\data\master.zig -Value ((python .\src\data\generate-data.py) -split "`r?`n" -join "`n")
```
This unweidly command is necesary in order to guarantee Unix-style line endings and UTF-8 text encoding.
There is also a `generate-data.js` in the `src/data` folder, you'll need to run this inside a Chrome DevTools console and copy the output. Files generated by this tool **does not contain** formatting information. There is also a `generate-data.js` in the `src/data` folder, you'll need to run this inside a Chrome DevTools console and copy the output. Files generated by this tool **does not contain** formatting information.
### Configuration Options ### Configuration Options
You can configure zls by running `zls --config` or manually creating your own `zls.json` configuration file. You can configure zls by editing your `zls.json` configuration file.
zls will look for a zls.json configuration file in multiple locations with the following priority: Running `zls --show-config-path` will a path to an already existing `zls.json` or a path to the local configuration folder instead.
zls will look for a `zls.json` configuration file in multiple locations with the following priority:
- In the local configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders/blob/master/RESOURCES.md#folder-list)) - In the local configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders/blob/master/RESOURCES.md#folder-list))
- In the global configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders/blob/master/RESOURCES.md#folder-list)) - In the global configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders/blob/master/RESOURCES.md#folder-list))
@ -62,12 +70,12 @@ The following options are currently available.
<!-- DO NOT EDIT | THIS SECTION IS AUTO-GENERATED | DO NOT EDIT --> <!-- DO NOT EDIT | THIS SECTION IS AUTO-GENERATED | DO NOT EDIT -->
| Option | Type | Default value | What it Does | | Option | Type | Default value | What it Does |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `enable_snippets` | `bool` | `false` | Enables snippet completions when the client also supports them | | `enable_snippets` | `bool` | `true` | Enables snippet completions when the client also supports them |
| `enable_ast_check_diagnostics` | `bool` | `true` | Whether to enable ast-check diagnostics | | `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_autofix` | `bool` | `true` | 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 | | `enable_import_embedfile_argument_completions` | `bool` | `true` | Whether to enable import/embedFile argument completions |
| `enable_semantic_tokens` | `bool` | `true` | Enables semantic token 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 | | `enable_inlay_hints` | `bool` | `true` | Enables inlay hint support when the client also supports it |
| `inlay_hints_show_builtin` | `bool` | `true` | Enable inlay hints for builtin functions | | `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_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` | `bool` | `false` | Hides inlay hints when parameter name matches the identifier (e.g. foo: foo) |
@ -79,6 +87,9 @@ The following options are currently available.
| `include_at_in_builtins` | `bool` | `false` | Whether the @ sign should be part of the completion of builtins | | `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 | | `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) | | `max_detail_length` | `usize` | `1048576` | The detail field of completions is truncated to be no longer than this (in bytes) |
| `record_session` | `bool` | `false` | When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay` |
| `record_session_path` | `?[]const u8` | `null` | Output file path when `record_session` is set. The recommended file extension *.zlsreplay |
| `replay_session_path` | `?[]const u8` | `null` | Used when calling `zls replay` for specifying the replay file. If no extra argument is given `record_session_path` is used as the default path. |
| `builtin_path` | `?[]const u8` | `null` | Path to 'builtin;' useful for debugging, automatically set if let null | | `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_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 | | `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 |

View File

@ -50,6 +50,18 @@ pub fn build(b: *std.build.Builder) !void {
b.option(bool, "enable_tracy_callstack", "Enable callstack graphs.") orelse false, b.option(bool, "enable_tracy_callstack", "Enable callstack graphs.") orelse false,
); );
exe_options.addOption(
bool,
"enable_failing_allocator",
b.option(bool, "enable_failing_allocator", "Whether to use a randomly failing allocator.") orelse false,
);
exe_options.addOption(
u32,
"enable_failing_allocator_likelihood",
b.option(u32, "enable_failing_allocator_likelihood", "The chance that an allocation will fail is `1/likelihood`") orelse 256,
);
const version = v: { const version = v: {
const version_string = b.fmt("{d}.{d}.{d}", .{ zls_version.major, zls_version.minor, zls_version.patch }); const version_string = b.fmt("{d}.{d}.{d}", .{ zls_version.major, zls_version.minor, zls_version.patch });
@ -92,6 +104,10 @@ pub fn build(b: *std.build.Builder) !void {
const known_folders_path = b.option([]const u8, "known-folders", "Path to known-folders package (default: " ++ KNOWN_FOLDERS_DEFAULT_PATH ++ ")") orelse KNOWN_FOLDERS_DEFAULT_PATH; const known_folders_path = b.option([]const u8, "known-folders", "Path to known-folders package (default: " ++ KNOWN_FOLDERS_DEFAULT_PATH ++ ")") orelse KNOWN_FOLDERS_DEFAULT_PATH;
exe.addPackage(.{ .name = "known-folders", .source = .{ .path = known_folders_path } }); exe.addPackage(.{ .name = "known-folders", .source = .{ .path = known_folders_path } });
const TRES_DEFAULT_PATH = "src/tres/tres.zig";
const tres_path = b.option([]const u8, "tres", "Path to tres package (default: " ++ TRES_DEFAULT_PATH ++ ")") orelse TRES_DEFAULT_PATH;
exe.addPackage(.{ .name = "tres", .source = .{ .path = tres_path } });
if (enable_tracy) { if (enable_tracy) {
const client_cpp = "src/tracy/TracyClient.cpp"; const client_cpp = "src/tracy/TracyClient.cpp";
@ -117,6 +133,7 @@ pub fn build(b: *std.build.Builder) !void {
exe.install(); exe.install();
const gen_exe = b.addExecutable("zls_gen", "src/config_gen/config_gen.zig"); const gen_exe = b.addExecutable("zls_gen", "src/config_gen/config_gen.zig");
gen_exe.addPackage(.{ .name = "tres", .source = .{ .path = "src/tres/tres.zig" } });
const gen_cmd = gen_exe.run(); const gen_cmd = gen_exe.run();
gen_cmd.addArgs(&.{ gen_cmd.addArgs(&.{
@ -125,6 +142,10 @@ pub fn build(b: *std.build.Builder) !void {
b.fmt("{s}/README.md", .{b.build_root}), b.fmt("{s}/README.md", .{b.build_root}),
}); });
if (b.option([]const u8, "vscode-config-path", "Output path to vscode-config")) |path| {
gen_cmd.addArg(b.pathFromRoot(path));
}
const gen_step = b.step("gen", "Regenerate config files"); const gen_step = b.step("gen", "Regenerate config files");
gen_step.dependOn(&gen_cmd.step); gen_step.dependOn(&gen_cmd.step);
@ -146,6 +167,7 @@ pub fn build(b: *std.build.Builder) !void {
} }
tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items }); tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items });
tests.addPackage(.{ .name = "tres", .source = .{ .path = tres_path } });
tests.setBuildMode(.Debug); tests.setBuildMode(.Debug);
tests.setTarget(target); tests.setTarget(target);
test_step.dependOn(&tests.step); test_step.dependOn(&tests.step);

View File

@ -68,11 +68,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1670086663, "lastModified": 1672057183,
"narHash": "sha256-hT8C8AQB74tdoCPwz4nlJypLMD7GI2F5q+vn+VE/qQk=", "narHash": "sha256-GN7/10DNNvs1FPj9tlZA2qgNdFuYKKuS3qlHTqAxasQ=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "813836d64fa57285d108f0dbf2356457ccd304e3", "rev": "b139e44d78c36c69bcbb825b20dbfa51e7738347",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -88,9 +88,26 @@
"gitignore": "gitignore", "gitignore": "gitignore",
"known-folders": "known-folders", "known-folders": "known-folders",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"tres": "tres",
"zig-overlay": "zig-overlay" "zig-overlay": "zig-overlay"
} }
}, },
"tres": {
"flake": false,
"locked": {
"lastModified": 1672008284,
"narHash": "sha256-AtM9SV56PEud1MfbKDZMU2FlsNrI46PkcFQh3yMcDX0=",
"owner": "ziglibs",
"repo": "tres",
"rev": "16774b94efa61757a5302a690837dfb8cf750a11",
"type": "github"
},
"original": {
"owner": "ziglibs",
"repo": "tres",
"type": "github"
}
},
"zig-overlay": { "zig-overlay": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2", "flake-utils": "flake-utils_2",
@ -99,11 +116,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1670113356, "lastModified": 1672142864,
"narHash": "sha256-43aMRMU0OuBin6M2LM+nxVG+whazyHuHnUvu92xoth0=", "narHash": "sha256-uXljuSZK8DP5c4o9u+gcF+Yc3dKYH1wsHmDpWcFBVRQ=",
"owner": "mitchellh", "owner": "mitchellh",
"repo": "zig-overlay", "repo": "zig-overlay",
"rev": "17352071583eda4be43fa2a312f6e061326374f7", "rev": "16e9191142d2a13d7870c03e500842321a466a74",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -12,9 +12,12 @@
known-folders.url = "github:ziglibs/known-folders"; known-folders.url = "github:ziglibs/known-folders";
known-folders.flake = false; known-folders.flake = false;
tres.url = "github:ziglibs/tres";
tres.flake = false;
}; };
outputs = { self, nixpkgs, zig-overlay, gitignore, flake-utils, known-folders }: outputs = { self, nixpkgs, zig-overlay, gitignore, flake-utils, known-folders, tres }:
let let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
inherit (gitignore.lib) gitignoreSource; inherit (gitignore.lib) gitignoreSource;
@ -35,7 +38,7 @@
dontInstall = true; dontInstall = true;
buildPhase = '' buildPhase = ''
mkdir -p $out mkdir -p $out
zig build install -Dcpu=baseline -Drelease-safe=true -Ddata_version=master -Dknown-folders=${known-folders}/known-folders.zig --prefix $out zig build install -Dcpu=baseline -Drelease-safe=true -Ddata_version=master -Dtres=${tres}/tres.zig -Dknown-folders=${known-folders}/known-folders.zig --prefix $out
''; '';
XDG_CACHE_HOME = ".cache"; XDG_CACHE_HOME = ".cache";
}; };

View File

@ -7,7 +7,7 @@
"enable_snippets": { "enable_snippets": {
"description": "Enables snippet completions when the client also supports them", "description": "Enables snippet completions when the client also supports them",
"type": "boolean", "type": "boolean",
"default": "false" "default": "true"
}, },
"enable_ast_check_diagnostics": { "enable_ast_check_diagnostics": {
"description": "Whether to enable ast-check diagnostics", "description": "Whether to enable ast-check diagnostics",
@ -17,12 +17,12 @@
"enable_autofix": { "enable_autofix": {
"description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.", "description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.",
"type": "boolean", "type": "boolean",
"default": "false" "default": "true"
}, },
"enable_import_embedfile_argument_completions": { "enable_import_embedfile_argument_completions": {
"description": "Whether to enable import/embedFile argument completions", "description": "Whether to enable import/embedFile argument completions",
"type": "boolean", "type": "boolean",
"default": "false" "default": "true"
}, },
"enable_semantic_tokens": { "enable_semantic_tokens": {
"description": "Enables semantic token support when the client also supports it", "description": "Enables semantic token support when the client also supports it",
@ -32,7 +32,7 @@
"enable_inlay_hints": { "enable_inlay_hints": {
"description": "Enables inlay hint support when the client also supports it", "description": "Enables inlay hint support when the client also supports it",
"type": "boolean", "type": "boolean",
"default": "false" "default": "true"
}, },
"inlay_hints_show_builtin": { "inlay_hints_show_builtin": {
"description": "Enable inlay hints for builtin functions", "description": "Enable inlay hints for builtin functions",
@ -89,6 +89,21 @@
"type": "integer", "type": "integer",
"default": "1048576" "default": "1048576"
}, },
"record_session": {
"description": "When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay`",
"type": "boolean",
"default": "false"
},
"record_session_path": {
"description": "Output file path when `record_session` is set. The recommended file extension *.zlsreplay",
"type": "string",
"default": "null"
},
"replay_session_path": {
"description": "Used when calling `zls replay` for specifying the replay file. If no extra argument is given `record_session_path` is used as the default path.",
"type": "string",
"default": "null"
},
"builtin_path": { "builtin_path": {
"description": "Path to 'builtin;' useful for debugging, automatically set if let null", "description": "Path to 'builtin;' useful for debugging, automatically set if let null",
"type": "string", "type": "string",

View File

@ -5,22 +5,22 @@
//! GENERATED BY src/config_gen/config_gen.zig //! GENERATED BY src/config_gen/config_gen.zig
/// Enables snippet completions when the client also supports them /// Enables snippet completions when the client also supports them
enable_snippets: bool = false, enable_snippets: bool = true,
/// Whether to enable ast-check diagnostics /// Whether to enable ast-check diagnostics
enable_ast_check_diagnostics: bool = true, 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, enable_autofix: bool = true,
/// Whether to enable import/embedFile argument completions /// Whether to enable import/embedFile argument completions
enable_import_embedfile_argument_completions: bool = false, enable_import_embedfile_argument_completions: bool = true,
/// Enables semantic token support when the client also supports it /// Enables semantic token support when the client also supports it
enable_semantic_tokens: bool = true, enable_semantic_tokens: bool = true,
/// Enables inlay hint support when the client also supports it /// Enables inlay hint support when the client also supports it
enable_inlay_hints: bool = false, enable_inlay_hints: bool = true,
/// Enable inlay hints for builtin functions /// Enable inlay hints for builtin functions
inlay_hints_show_builtin: bool = true, inlay_hints_show_builtin: bool = true,
@ -55,6 +55,15 @@ skip_std_references: bool = false,
/// The detail field of completions is truncated to be no longer than this (in bytes) /// The detail field of completions is truncated to be no longer than this (in bytes)
max_detail_length: usize = 1048576, max_detail_length: usize = 1048576,
/// When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay`
record_session: bool = false,
/// Output file path when `record_session` is set. The recommended file extension *.zlsreplay
record_session_path: ?[]const u8 = null,
/// Used when calling `zls replay` for specifying the replay file. If no extra argument is given `record_session_path` is used as the default path.
replay_session_path: ?[]const u8 = null,
/// Path to 'builtin;' useful for debugging, automatically set if let null /// Path to 'builtin;' useful for debugging, automatically set if let null
builtin_path: ?[]const u8 = null, builtin_path: ?[]const u8 = null,

View File

@ -1,7 +1,6 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const types = @import("types.zig"); const types = @import("lsp.zig");
const requests = @import("requests.zig");
const URI = @import("uri.zig"); const URI = @import("uri.zig");
const analysis = @import("analysis.zig"); const analysis = @import("analysis.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
@ -67,7 +66,6 @@ pub const Handle = struct {
/// `DocumentStore.build_files` is guaranteed to contain this uri /// `DocumentStore.build_files` is guaranteed to contain this uri
/// uri memory managed by its build_file /// uri memory managed by its build_file
associated_build_file: ?Uri = null, associated_build_file: ?Uri = null,
is_build_file: bool = false,
pub fn deinit(self: *Handle, allocator: std.mem.Allocator) void { pub fn deinit(self: *Handle, allocator: std.mem.Allocator) void {
self.document_scope.deinit(allocator); self.document_scope.deinit(allocator);
@ -125,11 +123,11 @@ fn getOrLoadHandleInternal(self: *DocumentStore, uri: Uri) !?*const Handle {
var handle = try self.allocator.create(Handle); var handle = try self.allocator.create(Handle);
errdefer self.allocator.destroy(handle); errdefer self.allocator.destroy(handle);
const dependency_uri = try self.allocator.dupe(u8, uri); handle.* = (try self.createDocumentFromURI(uri, false)) orelse return error.Unknown; // error name doesn't matter
handle.* = (try self.createDocumentFromURI(dependency_uri, false)) orelse return error.Unknown; // error name doesn't matter errdefer handle.deinit(self.allocator);
const gop = try self.handles.getOrPutValue(self.allocator, handle.uri, handle); const gop = try self.handles.getOrPutValue(self.allocator, handle.uri, handle);
std.debug.assert(!gop.found_existing); if (gop.found_existing) return error.Unknown;
return gop.value_ptr.*; return gop.value_ptr.*;
} }
@ -147,18 +145,15 @@ pub fn openDocument(self: *DocumentStore, uri: Uri, text: []const u8) error{OutO
return handle.*; return handle.*;
} }
const duped_text = try self.allocator.dupeZ(u8, text);
errdefer self.allocator.free(duped_text);
const duped_uri = try self.allocator.dupeZ(u8, uri);
errdefer self.allocator.free(duped_uri);
var handle = try self.allocator.create(Handle); var handle = try self.allocator.create(Handle);
errdefer self.allocator.destroy(handle); errdefer self.allocator.destroy(handle);
handle.* = try self.createDocument(duped_uri, duped_text, true); const duped_text = try self.allocator.dupeZ(u8, text);
handle.* = try self.createDocument(uri, duped_text, true);
errdefer handle.deinit(self.allocator); errdefer handle.deinit(self.allocator);
try self.handles.putNoClobber(self.allocator, duped_uri, handle); try self.handles.putNoClobber(self.allocator, handle.uri, handle);
return handle.*; return handle.*;
} }
@ -229,7 +224,7 @@ pub fn applySave(self: *DocumentStore, handle: *const Handle) !void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
if (handle.is_build_file) { if (isBuildFile(handle.uri)) {
const build_file = self.build_files.getPtr(handle.uri).?; const build_file = self.build_files.getPtr(handle.uri).?;
const build_config = loadBuildConfiguration(self.allocator, build_file.*, self.config.*) catch |err| { const build_config = loadBuildConfiguration(self.allocator, build_file.*, self.config.*) catch |err| {
@ -251,33 +246,29 @@ fn garbageCollectionImports(self: *DocumentStore) error{OutOfMemory}!void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
var reachable_handles = std.StringHashMapUnmanaged(void){}; var reachable_handles = std.StringHashMapUnmanaged(void){};
defer reachable_handles.deinit(self.allocator); defer reachable_handles.deinit(arena.allocator());
var queue = std.ArrayListUnmanaged(Uri){}; var queue = std.ArrayListUnmanaged(Uri){};
defer {
for (queue.items) |uri| {
self.allocator.free(uri);
}
queue.deinit(self.allocator);
}
for (self.handles.values()) |handle| { for (self.handles.values()) |handle| {
if (!handle.open) continue; if (!handle.open) continue;
try reachable_handles.put(self.allocator, handle.uri, {}); try reachable_handles.put(arena.allocator(), handle.uri, {});
try self.collectDependencies(self.allocator, handle.*, &queue); try self.collectDependencies(arena.allocator(), handle.*, &queue);
} }
while (queue.popOrNull()) |uri| { while (queue.popOrNull()) |uri| {
if (reachable_handles.contains(uri)) continue; const gop = try reachable_handles.getOrPut(arena.allocator(), uri);
if (gop.found_existing) continue;
try reachable_handles.putNoClobber(self.allocator, uri, {});
const handle = self.handles.get(uri) orelse continue; const handle = self.handles.get(uri) orelse continue;
try self.collectDependencies(self.allocator, handle.*, &queue); try self.collectDependencies(arena.allocator(), handle.*, &queue);
} }
var i: usize = 0; var i: usize = 0;
@ -287,7 +278,7 @@ fn garbageCollectionImports(self: *DocumentStore) error{OutOfMemory}!void {
i += 1; i += 1;
continue; continue;
} }
std.log.debug("Closing document {s}", .{handle.uri}); log.debug("Closing document {s}", .{handle.uri});
var kv = self.handles.fetchSwapRemove(handle.uri).?; var kv = self.handles.fetchSwapRemove(handle.uri).?;
kv.value.deinit(self.allocator); kv.value.deinit(self.allocator);
self.allocator.destroy(kv.value); self.allocator.destroy(kv.value);
@ -321,7 +312,7 @@ fn garbageCollectionCImports(self: *DocumentStore) error{OutOfMemory}!void {
.failure => "", .failure => "",
.success => |uri| uri, .success => |uri| uri,
}; };
std.log.debug("Destroying cimport {s}", .{message}); log.debug("Destroying cimport {s}", .{message});
kv.value.deinit(self.allocator); kv.value.deinit(self.allocator);
} }
} }
@ -347,11 +338,24 @@ fn garbageCollectionBuildFiles(self: *DocumentStore) error{OutOfMemory}!void {
continue; continue;
} }
var kv = self.build_files.fetchSwapRemove(hash).?; var kv = self.build_files.fetchSwapRemove(hash).?;
std.log.debug("Destroying build file {s}", .{kv.value.uri}); log.debug("Destroying build file {s}", .{kv.value.uri});
kv.value.deinit(self.allocator); kv.value.deinit(self.allocator);
} }
} }
pub fn isBuildFile(uri: Uri) bool {
return std.mem.endsWith(u8, uri, "/build.zig");
}
pub fn isBuiltinFile(uri: Uri) bool {
return std.mem.endsWith(u8, uri, "/builtin.zig");
}
pub fn isInStd(uri: Uri) bool {
// TODO: Better logic for detecting std or subdirectories?
return std.mem.indexOf(u8, uri, "/std/") != null;
}
/// looks for a `zls.build.json` file in the build file directory /// looks for a `zls.build.json` file in the build file directory
/// has to be freed with `std.json.parseFree` /// has to be freed with `std.json.parseFree`
fn loadBuildAssociatedConfiguration(allocator: std.mem.Allocator, build_file: BuildFile) !BuildAssociatedConfig { fn loadBuildAssociatedConfiguration(allocator: std.mem.Allocator, build_file: BuildFile) !BuildAssociatedConfig {
@ -451,6 +455,7 @@ fn loadBuildConfiguration(
const parse_options = std.json.ParseOptions{ .allocator = allocator }; const parse_options = std.json.ParseOptions{ .allocator = allocator };
var token_stream = std.json.TokenStream.init(zig_run_result.stdout); var token_stream = std.json.TokenStream.init(zig_run_result.stdout);
var build_config = std.json.parse(BuildConfig, &token_stream, parse_options) catch return error.RunFailed; var build_config = std.json.parse(BuildConfig, &token_stream, parse_options) catch return error.RunFailed;
errdefer std.json.parseFree(BuildConfig, build_config, parse_options);
for (build_config.packages) |*pkg| { for (build_config.packages) |*pkg| {
const pkg_abs_path = try std.fs.path.resolve(allocator, &[_][]const u8{ directory_path, pkg.path }); const pkg_abs_path = try std.fs.path.resolve(allocator, &[_][]const u8{ directory_path, pkg.path });
@ -552,14 +557,8 @@ fn uriAssociatedWithBuild(
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
var checked_uris = std.StringHashMap(void).init(self.allocator); var checked_uris = std.StringHashMapUnmanaged(void){};
defer { defer checked_uris.deinit(self.allocator);
var it = checked_uris.iterator();
while (it.next()) |entry|
self.allocator.free(entry.key_ptr.*);
checked_uris.deinit();
}
for (build_file.config.packages) |package| { for (build_file.config.packages) |package| {
const package_uri = try URI.fromPath(self.allocator, package.path); const package_uri = try URI.fromPath(self.allocator, package.path);
@ -569,7 +568,7 @@ fn uriAssociatedWithBuild(
return true; return true;
} }
if (try self.uriInImports(&checked_uris, package_uri, uri)) if (try self.uriInImports(&checked_uris, build_file, package_uri, uri))
return true; return true;
} }
@ -578,38 +577,47 @@ fn uriAssociatedWithBuild(
fn uriInImports( fn uriInImports(
self: *DocumentStore, self: *DocumentStore,
checked_uris: *std.StringHashMap(void), checked_uris: *std.StringHashMapUnmanaged(void),
build_file: BuildFile,
source_uri: Uri, source_uri: Uri,
uri: Uri, uri: Uri,
) error{OutOfMemory}!bool { ) error{OutOfMemory}!bool {
if (checked_uris.contains(source_uri)) if (checked_uris.contains(source_uri))
return false; return false;
if (isInStd(source_uri)) return false;
// consider it checked even if a failure happens // consider it checked even if a failure happens
try checked_uris.put(try self.allocator.dupe(u8, source_uri), {}); try checked_uris.put(self.allocator, source_uri, {});
const handle = self.getOrLoadHandle(source_uri) orelse return false; const handle = self.getOrLoadHandle(source_uri) orelse return false;
if (handle.associated_build_file) |associated_build_file_uri| {
return std.mem.eql(u8, associated_build_file_uri, build_file.uri);
}
for (handle.import_uris.items) |import_uri| { for (handle.import_uris.items) |import_uri| {
if (std.mem.eql(u8, uri, import_uri)) if (std.mem.eql(u8, uri, import_uri))
return true; return true;
if (self.uriInImports(checked_uris, import_uri, uri) catch false) if (try self.uriInImports(checked_uris, build_file, import_uri, uri))
return true; return true;
} }
return false; return false;
} }
/// takes ownership of the uri and text passed in. /// takes ownership of the text passed in.
fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) error{OutOfMemory}!Handle { fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) error{OutOfMemory}!Handle {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
var handle: Handle = blk: { var handle: Handle = blk: {
errdefer self.allocator.free(uri);
errdefer self.allocator.free(text); errdefer self.allocator.free(text);
var duped_uri = try self.allocator.dupe(u8, uri);
errdefer self.allocator.free(duped_uri);
var tree = try std.zig.parse(self.allocator, text); var tree = try std.zig.parse(self.allocator, text);
errdefer tree.deinit(self.allocator); errdefer tree.deinit(self.allocator);
@ -618,7 +626,7 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
break :blk Handle{ break :blk Handle{
.open = open, .open = open,
.uri = uri, .uri = duped_uri,
.text = text, .text = text,
.tree = tree, .tree = tree,
.document_scope = document_scope, .document_scope = document_scope,
@ -629,7 +637,7 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
defer { defer {
if (handle.associated_build_file) |build_file_uri| { if (handle.associated_build_file) |build_file_uri| {
log.debug("Opened document `{s}` with build file `{s}`", .{ handle.uri, build_file_uri }); log.debug("Opened document `{s}` with build file `{s}`", .{ handle.uri, build_file_uri });
} else if (handle.is_build_file) { } else if (isBuildFile(handle.uri)) {
log.debug("Opened document `{s}` (build file)", .{handle.uri}); log.debug("Opened document `{s}` (build file)", .{handle.uri});
} else { } else {
log.debug("Opened document `{s}`", .{handle.uri}); log.debug("Opened document `{s}`", .{handle.uri});
@ -639,18 +647,20 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
handle.import_uris = try self.collectImportUris(handle); handle.import_uris = try self.collectImportUris(handle);
handle.cimports = try self.collectCIncludes(handle); handle.cimports = try self.collectCIncludes(handle);
// TODO: Better logic for detecting std or subdirectories? if (self.config.zig_exe_path != null and isBuildFile(handle.uri) and !isInStd(handle.uri)) {
const in_std = std.mem.indexOf(u8, uri, "/std/") != null; const gop = try self.build_files.getOrPut(self.allocator, uri);
if (self.config.zig_exe_path != null and std.mem.endsWith(u8, uri, "/build.zig") and !in_std) { errdefer |err| {
const dupe_uri = try self.allocator.dupe(u8, uri); self.build_files.swapRemoveAt(gop.index);
if (self.createBuildFile(dupe_uri)) |build_file| {
try self.build_files.put(self.allocator, dupe_uri, build_file);
handle.is_build_file = true;
} else |err| {
log.debug("Failed to load build file {s}: (error: {})", .{ uri, err }); log.debug("Failed to load build file {s}: (error: {})", .{ uri, err });
} }
} else if (self.config.zig_exe_path != null and !std.mem.endsWith(u8, uri, "/builtin.zig") and !in_std) blk: { if (!gop.found_existing) {
log.debug("Going to walk down the tree towards: {s}", .{uri}); const duped_uri = try self.allocator.dupe(u8, uri);
gop.value_ptr.* = try self.createBuildFile(duped_uri);
gop.key_ptr.* = gop.value_ptr.uri;
}
} else if (self.config.zig_exe_path != null and !isBuiltinFile(handle.uri) and !isInStd(handle.uri)) blk: {
// log.debug("Going to walk down the tree towards: {s}", .{uri});
// walk down the tree towards the uri. When we hit build.zig files // walk down the tree towards the uri. When we hit build.zig files
// determine if the uri we're interested in is involved with the build. // determine if the uri we're interested in is involved with the build.
// This ensures that _relevant_ build.zig files higher in the // This ensures that _relevant_ build.zig files higher in the
@ -658,30 +668,29 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
const path = URI.parse(self.allocator, uri) catch break :blk; const path = URI.parse(self.allocator, uri) catch break :blk;
defer self.allocator.free(path); defer self.allocator.free(path);
var prev_build_file: ?Uri = null;
var build_it = try BuildDotZigIterator.init(self.allocator, path); var build_it = try BuildDotZigIterator.init(self.allocator, path);
while (try build_it.next()) |build_path| { while (try build_it.next()) |build_path| {
defer self.allocator.free(build_path); defer self.allocator.free(build_path);
log.debug("found build path: {s}", .{build_path}); // log.debug("found build path: {s}", .{build_path});
const build_file_uri = try URI.fromPath(self.allocator, build_path);
const gop = self.build_files.getOrPut(self.allocator, build_file_uri) catch |err| {
self.allocator.free(build_file_uri);
return err;
};
const build_file_uri = URI.fromPath(self.allocator, build_path) catch unreachable;
const gop = try self.build_files.getOrPut(self.allocator, build_file_uri);
if (!gop.found_existing) { if (!gop.found_existing) {
errdefer self.build_files.swapRemoveAt(gop.index);
gop.value_ptr.* = try self.createBuildFile(build_file_uri); gop.value_ptr.* = try self.createBuildFile(build_file_uri);
} else {
self.allocator.free(build_file_uri);
} }
if (try self.uriAssociatedWithBuild(gop.value_ptr.*, uri)) { if (try self.uriAssociatedWithBuild(gop.value_ptr.*, uri)) {
handle.associated_build_file = build_file_uri; handle.associated_build_file = gop.key_ptr.*;
break; break;
} else { } else if (handle.associated_build_file == null) {
prev_build_file = build_file_uri;
}
}
// if there was no direct imports found, use the closest build file if possible
if (handle.associated_build_file == null) {
if (prev_build_file) |build_file_uri| {
handle.associated_build_file = build_file_uri; handle.associated_build_file = build_file_uri;
} }
} }
@ -690,7 +699,6 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
return handle; return handle;
} }
/// takes ownership of the uri passed in.
fn createDocumentFromURI(self: *DocumentStore, uri: Uri, open: bool) error{OutOfMemory}!?Handle { fn createDocumentFromURI(self: *DocumentStore, uri: Uri, open: bool) error{OutOfMemory}!?Handle {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -706,15 +714,21 @@ fn createDocumentFromURI(self: *DocumentStore, uri: Uri, open: bool) error{OutOf
return try self.createDocument(uri, file_contents, open); return try self.createDocument(uri, file_contents, open);
} }
/// Caller owns returned memory.
fn collectImportUris(self: *const DocumentStore, handle: Handle) error{OutOfMemory}!std.ArrayListUnmanaged(Uri) { fn collectImportUris(self: *const DocumentStore, handle: Handle) error{OutOfMemory}!std.ArrayListUnmanaged(Uri) {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
var imports = try analysis.collectImports(self.allocator, handle.tree); var imports = try analysis.collectImports(self.allocator, handle.tree);
errdefer imports.deinit(self.allocator);
var i: usize = 0;
errdefer {
// only free the uris
for (imports.items[0..i]) |uri| self.allocator.free(uri);
imports.deinit(self.allocator);
}
// Convert to URIs // Convert to URIs
var i: usize = 0;
while (i < imports.items.len) { while (i < imports.items.len) {
const maybe_uri = try self.uriFromImportStr(self.allocator, handle, imports.items[i]); const maybe_uri = try self.uriFromImportStr(self.allocator, handle, imports.items[i]);

57
src/Header.zig Normal file
View File

@ -0,0 +1,57 @@
const std = @import("std");
const Header = @This();
content_length: usize,
/// null implies "application/vscode-jsonrpc; charset=utf-8"
content_type: ?[]const u8 = null,
pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
if (self.content_type) |ct| allocator.free(ct);
}
// Caller owns returned memory.
pub fn parse(allocator: std.mem.Allocator, include_carriage_return: bool, reader: anytype) !Header {
var r = Header{
.content_length = undefined,
.content_type = null,
};
errdefer r.deinit(allocator);
var has_content_length = false;
while (true) {
const header = try reader.readUntilDelimiterAlloc(allocator, '\n', 0x100);
defer allocator.free(header);
if (include_carriage_return) {
if (header.len == 0 or header[header.len - 1] != '\r') return error.MissingCarriageReturn;
if (header.len == 1) break;
} else {
if (header.len == 0) break;
}
const header_name = header[0 .. std.mem.indexOf(u8, header, ": ") orelse return error.MissingColon];
const header_value = header[header_name.len + 2 .. header.len - @boolToInt(include_carriage_return)];
if (std.mem.eql(u8, header_name, "Content-Length")) {
if (header_value.len == 0) return error.MissingHeaderValue;
r.content_length = std.fmt.parseInt(usize, header_value, 10) catch return error.InvalidContentLength;
has_content_length = true;
} else if (std.mem.eql(u8, header_name, "Content-Type")) {
r.content_type = try allocator.dupe(u8, header_value);
} else {
return error.UnknownHeader;
}
}
if (!has_content_length) return error.MissingContentLength;
return r;
}
pub fn write(header: Header, include_carriage_return: bool, writer: anytype) @TypeOf(writer).Error!void {
const seperator: []const u8 = if (include_carriage_return) "\r\n" else "\n";
try writer.print("Content-Length: {}{s}", .{ header.content_length, seperator });
if (header.content_type) |content_type| {
try writer.print("Content-Type: {s}{s}", .{ content_type, seperator });
}
try writer.writeAll(seperator);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
const std = @import("std"); const std = @import("std");
const DocumentStore = @import("DocumentStore.zig"); const DocumentStore = @import("DocumentStore.zig");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
const types = @import("types.zig"); const types = @import("lsp.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
const log = std.log.scoped(.analysis); const log = std.log.scoped(.analysis);
const ast = @import("ast.zig"); const ast = @import("ast.zig");
@ -19,7 +19,7 @@ pub fn deinit() void {
} }
/// Gets a declaration's doc comments. Caller owns returned memory. /// Gets a declaration's doc comments. Caller owns returned memory.
pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, format: types.MarkupContent.Kind) !?[]const u8 { pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, format: types.MarkupKind) !?[]const u8 {
const base = tree.nodes.items(.main_token)[node]; const base = tree.nodes.items(.main_token)[node];
const base_kind = tree.nodes.items(.tag)[node]; const base_kind = tree.nodes.items(.tag)[node];
const tokens = tree.tokens.items(.tag); const tokens = tree.tokens.items(.tag);
@ -68,7 +68,7 @@ pub fn getDocCommentTokenIndex(tokens: []const std.zig.Token.Tag, base_token: As
} else idx + 1; } else idx + 1;
} }
pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: Ast.TokenIndex, format: types.MarkupContent.Kind, container_doc: bool) ![]const u8 { pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: Ast.TokenIndex, format: types.MarkupKind, container_doc: bool) ![]const u8 {
var lines = std.ArrayList([]const u8).init(allocator); var lines = std.ArrayList([]const u8).init(allocator);
defer lines.deinit(); defer lines.deinit();
const tokens = tree.tokens.items(.tag); const tokens = tree.tokens.items(.tag);
@ -81,7 +81,7 @@ pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments:
} else break; } else break;
} }
return try std.mem.join(allocator, if (format == .Markdown) " \n" else "\n", lines.items); return try std.mem.join(allocator, if (format == .markdown) " \n" else "\n", lines.items);
} }
/// Gets a function's keyword, name, arguments and return value. /// Gets a function's keyword, name, arguments and return value.
@ -1162,12 +1162,7 @@ pub fn resolveTypeOfNode(store: *DocumentStore, arena: *std.heap.ArenaAllocator,
/// Collects all `@import`'s we can find into a slice of import paths (without quotes). /// Collects all `@import`'s we can find into a slice of import paths (without quotes).
pub fn collectImports(allocator: std.mem.Allocator, tree: Ast) error{OutOfMemory}!std.ArrayListUnmanaged([]const u8) { pub fn collectImports(allocator: std.mem.Allocator, tree: Ast) error{OutOfMemory}!std.ArrayListUnmanaged([]const u8) {
var imports = std.ArrayListUnmanaged([]const u8){}; var imports = std.ArrayListUnmanaged([]const u8){};
errdefer { errdefer imports.deinit(allocator);
for (imports.items) |imp| {
allocator.free(imp);
}
imports.deinit(allocator);
}
const tags = tree.tokens.items(.tag); const tags = tree.tokens.items(.tag);
@ -1485,6 +1480,8 @@ pub fn getImportStr(tree: Ast, node: Ast.Node.Index, source_index: usize) ?[]con
if (params.len != 1) return null; if (params.len != 1) return null;
if (node_tags[params[0]] != .string_literal) return null;
const import_str = tree.tokenSlice(tree.nodes.items(.main_token)[params[0]]); const import_str = tree.tokenSlice(tree.nodes.items(.main_token)[params[0]]);
return import_str[1 .. import_str.len - 1]; return import_str[1 .. import_str.len - 1];
} }
@ -2406,41 +2403,23 @@ pub const DocumentScope = struct {
error_completions: CompletionSet, error_completions: CompletionSet,
enum_completions: CompletionSet, enum_completions: CompletionSet,
pub fn debugPrint(self: DocumentScope) void {
for (self.scopes.items) |scope| {
log.debug(
\\--------------------------
\\Scope {}, loc: [{d}, {d})
\\ {d} usingnamespaces
\\Decls:
, .{
scope.data,
scope.loc.start,
scope.loc.end,
scope.uses.len,
});
var decl_it = scope.decls.iterator();
var idx: usize = 0;
while (decl_it.next()) |_| : (idx += 1) {
if (idx != 0) log.debug(", ", .{});
}
// log.debug("{s}", .{name_decl.key});
log.debug("\n--------------------------\n", .{});
}
}
pub fn deinit(self: *DocumentScope, allocator: std.mem.Allocator) void { pub fn deinit(self: *DocumentScope, allocator: std.mem.Allocator) void {
for (self.scopes.items) |*scope| { for (self.scopes.items) |*scope| {
scope.deinit(allocator); scope.deinit(allocator);
} }
self.scopes.deinit(allocator); self.scopes.deinit(allocator);
for (self.error_completions.entries.items(.key)) |item| { for (self.error_completions.entries.items(.key)) |item| {
if (item.documentation) |doc| allocator.free(doc.value); switch (item.documentation orelse continue) {
.string => |str| allocator.free(str),
.MarkupContent => |content| allocator.free(content.value),
}
} }
self.error_completions.deinit(allocator); self.error_completions.deinit(allocator);
for (self.enum_completions.entries.items(.key)) |item| { for (self.enum_completions.entries.items(.key)) |item| {
if (item.documentation) |doc| allocator.free(doc.value); switch (item.documentation orelse continue) {
.string => |str| allocator.free(str),
.MarkupContent => |content| allocator.free(content.value),
}
} }
self.enum_completions.deinit(allocator); self.enum_completions.deinit(allocator);
} }
@ -2575,13 +2554,18 @@ fn makeInnerScope(allocator: std.mem.Allocator, context: ScopeContext, node_idx:
if (container_field) |_| { if (container_field) |_| {
if (!std.mem.eql(u8, name, "_")) { if (!std.mem.eql(u8, name, "_")) {
var doc = if (try getDocComments(allocator, tree, decl, .Markdown)) |docs| const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation);
types.MarkupContent{ .kind = .Markdown, .value = docs }
else var doc: Documentation = if (try getDocComments(allocator, tree, decl, .markdown)) |docs| .{ .MarkupContent = types.MarkupContent{ .kind = .markdown, .value = docs } } else null;
null; var gop_res = try context.enums.getOrPut(allocator, .{
var gop_res = try context.enums.getOrPut(allocator, .{ .label = name, .kind = .Constant, .insertText = name, .insertTextFormat = .PlainText, .documentation = doc }); .label = name,
.kind = .Constant,
.insertText = name,
.insertTextFormat = .PlainText,
.documentation = doc,
});
if (gop_res.found_existing) { if (gop_res.found_existing) {
if (doc) |d| allocator.free(d.value); if (doc) |d| allocator.free(d.MarkupContent.value);
} }
} }
} }

View File

@ -167,15 +167,15 @@ fn fullWhile(tree: Ast, info: full.While.Components) full.While {
.else_token = undefined, .else_token = undefined,
.error_token = null, .error_token = null,
}; };
var tok_i = info.while_token - 1; var tok_i = info.while_token -| 1;
if (token_tags[tok_i] == .keyword_inline) { if (token_tags[tok_i] == .keyword_inline) {
result.inline_token = tok_i; result.inline_token = tok_i;
tok_i -= 1; tok_i -= 1;
} }
if (token_tags[tok_i] == .colon and if (token_tags[tok_i] == .colon and
token_tags[tok_i - 1] == .identifier) token_tags[tok_i -| 1] == .identifier)
{ {
result.label_token = tok_i - 1; result.label_token = tok_i -| 1;
} }
const last_cond_token = lastToken(tree, info.cond_expr); const last_cond_token = lastToken(tree, info.cond_expr);
if (token_tags[last_cond_token + 2] == .pipe) { if (token_tags[last_cond_token + 2] == .pipe) {
@ -539,10 +539,15 @@ pub fn lastToken(tree: Ast, node: Ast.Node.Index) Ast.TokenIndex {
.container_decl_arg_trailing, .container_decl_arg_trailing,
.switch_comma, .switch_comma,
=> { => {
const members = tree.extraData(datas[n].rhs, Node.SubRange); if (datas[n].rhs != 0) {
std.debug.assert(members.end - members.start > 0); const members = tree.extraData(datas[n].rhs, Node.SubRange);
end_offset += 2; // for the comma + rbrace std.debug.assert(members.end - members.start > 0);
n = tree.extra_data[members.end - 1]; // last parameter end_offset += 2; // for the comma + rbrace
n = tree.extra_data[members.end - 1]; // last parameter
} else {
end_offset += 1;
n = datas[n].lhs;
}
}, },
.array_init_dot, .array_init_dot,
.struct_init_dot, .struct_init_dot,

View File

@ -5,8 +5,7 @@ const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig"); const analysis = @import("analysis.zig");
const ast = @import("ast.zig"); const ast = @import("ast.zig");
const types = @import("types.zig"); const types = @import("lsp.zig");
const requests = @import("requests.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
pub const Builder = struct { pub const Builder = struct {
@ -55,11 +54,9 @@ pub const Builder = struct {
} }
pub fn createWorkspaceEdit(self: *Builder, edits: []const types.TextEdit) error{OutOfMemory}!types.WorkspaceEdit { pub fn createWorkspaceEdit(self: *Builder, edits: []const types.TextEdit) error{OutOfMemory}!types.WorkspaceEdit {
var text_edits = std.ArrayListUnmanaged(types.TextEdit){}; const allocator = self.arena.allocator();
try text_edits.appendSlice(self.arena.allocator(), edits);
var workspace_edit = types.WorkspaceEdit{ .changes = .{} }; var workspace_edit = types.WorkspaceEdit{ .changes = .{} };
try workspace_edit.changes.putNoClobber(self.arena.allocator(), self.handle.uri, text_edits); try workspace_edit.changes.?.putNoClobber(allocator, self.handle.uri, try allocator.dupe(types.TextEdit, edits));
return workspace_edit; return workspace_edit;
} }
@ -74,7 +71,7 @@ fn handleNonCamelcaseFunction(builder: *Builder, actions: *std.ArrayListUnmanage
const action1 = types.CodeAction{ const action1 = types.CodeAction{
.title = "make function name camelCase", .title = "make function name camelCase",
.kind = .QuickFix, .kind = .quickfix,
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(loc, new_text)}), .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(loc, new_text)}),
}; };
@ -115,7 +112,7 @@ fn handleUnusedFunctionParameter(builder: *Builder, actions: *std.ArrayListUnman
const action1 = types.CodeAction{ const action1 = types.CodeAction{
.title = "discard function parameter", .title = "discard function parameter",
.kind = .SourceFixAll, .kind = .@"source.fixAll",
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditPos(index, new_text)}), .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditPos(index, new_text)}),
}; };
@ -123,7 +120,7 @@ fn handleUnusedFunctionParameter(builder: *Builder, actions: *std.ArrayListUnman
// TODO fix formatting // TODO fix formatting
const action2 = types.CodeAction{ const action2 = types.CodeAction{
.title = "remove function parameter", .title = "remove function parameter",
.kind = .QuickFix, .kind = .quickfix,
.isPreferred = false, .isPreferred = false,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(getParamRemovalRange(tree, payload.param), "")}), .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(getParamRemovalRange(tree, payload.param), "")}),
}; };
@ -162,7 +159,7 @@ fn handleUnusedVariableOrConstant(builder: *Builder, actions: *std.ArrayListUnma
try actions.append(builder.arena.allocator(), .{ try actions.append(builder.arena.allocator(), .{
.title = "discard value", .title = "discard value",
.kind = .SourceFixAll, .kind = .@"source.fixAll",
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditPos(index, new_text)}), .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditPos(index, new_text)}),
}); });
@ -179,7 +176,7 @@ fn handleUnusedIndexCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(
// TODO fix formatting // TODO fix formatting
try actions.append(builder.arena.allocator(), .{ try actions.append(builder.arena.allocator(), .{
.title = "remove capture", .title = "remove capture",
.kind = .QuickFix, .kind = .quickfix,
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.loc, "")}), .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.loc, "")}),
}); });
@ -188,7 +185,7 @@ fn handleUnusedIndexCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(
// |v, _| -> |v| // |v, _| -> |v|
try actions.append(builder.arena.allocator(), .{ try actions.append(builder.arena.allocator(), .{
.title = "remove index capture", .title = "remove index capture",
.kind = .QuickFix, .kind = .quickfix,
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc( .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(
.{ .start = capture_locs.value.end, .end = capture_locs.loc.end - 1 }, .{ .start = capture_locs.value.end, .end = capture_locs.loc.end - 1 },
@ -207,7 +204,7 @@ fn handleUnusedCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(types
// |v, i| -> |_, i| // |v, i| -> |_, i|
try actions.append(builder.arena.allocator(), .{ try actions.append(builder.arena.allocator(), .{
.title = "discard capture", .title = "discard capture",
.kind = .QuickFix, .kind = .quickfix,
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.value, "_")}), .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.value, "_")}),
}); });
@ -216,7 +213,7 @@ fn handleUnusedCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(types
// TODO fix formatting // TODO fix formatting
try actions.append(builder.arena.allocator(), .{ try actions.append(builder.arena.allocator(), .{
.title = "remove capture", .title = "remove capture",
.kind = .QuickFix, .kind = .quickfix,
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.loc, "")}), .edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.loc, "")}),
}); });
@ -228,7 +225,7 @@ fn handlePointlessDiscard(builder: *Builder, actions: *std.ArrayListUnmanaged(ty
try actions.append(builder.arena.allocator(), .{ try actions.append(builder.arena.allocator(), .{
.title = "remove pointless discard", .title = "remove pointless discard",
.kind = .SourceFixAll, .kind = .@"source.fixAll",
.isPreferred = true, .isPreferred = true,
.edit = try builder.createWorkspaceEdit(&.{ .edit = try builder.createWorkspaceEdit(&.{
builder.createTextEditLoc(edit_loc, ""), builder.createTextEditLoc(edit_loc, ""),

View File

@ -4,155 +4,151 @@
"name": "enable_snippets", "name": "enable_snippets",
"description": "Enables snippet completions when the client also supports them", "description": "Enables snippet completions when the client also supports them",
"type": "bool", "type": "bool",
"default": "false", "default": "true",
"setup_question": "Do you want to enable snippets?"
}, },
{ {
"name": "enable_ast_check_diagnostics", "name": "enable_ast_check_diagnostics",
"description": "Whether to enable ast-check diagnostics", "description": "Whether to enable ast-check diagnostics",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
"setup_question": "Do you want to enable ast-check diagnostics?"
}, },
{ {
"name": "enable_autofix", "name": "enable_autofix",
"description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.", "description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.",
"type": "bool", "type": "bool",
"default": "false", "default": "true",
"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", "name": "enable_import_embedfile_argument_completions",
"description": "Whether to enable import/embedFile argument completions", "description": "Whether to enable import/embedFile argument completions",
"type": "bool", "type": "bool",
"default": "false", "default": "true",
"setup_question": "Do you want to enable @import/@embedFile argument path completion?"
}, },
{ {
"name": "enable_semantic_tokens", "name": "enable_semantic_tokens",
"description": "Enables semantic token support when the client also supports it", "description": "Enables semantic token support when the client also supports it",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
"setup_question": "Do you want to enable semantic highlighting?"
}, },
{ {
"name": "enable_inlay_hints", "name": "enable_inlay_hints",
"description": "Enables inlay hint support when the client also supports it", "description": "Enables inlay hint support when the client also supports it",
"type": "bool", "type": "bool",
"default": "false", "default": "true",
"setup_question": "Do you want to enable inlay hints?"
}, },
{ {
"name": "inlay_hints_show_builtin", "name": "inlay_hints_show_builtin",
"description": "Enable inlay hints for builtin functions", "description": "Enable inlay hints for builtin functions",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
"setup_question": null
}, },
{ {
"name": "inlay_hints_exclude_single_argument", "name": "inlay_hints_exclude_single_argument",
"description": "Don't show inlay hints for single argument calls", "description": "Don't show inlay hints for single argument calls",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
"setup_question": null
}, },
{ {
"name": "inlay_hints_hide_redundant_param_names", "name": "inlay_hints_hide_redundant_param_names",
"description": "Hides inlay hints when parameter name matches the identifier (e.g. foo: foo)", "description": "Hides inlay hints when parameter name matches the identifier (e.g. foo: foo)",
"type": "bool", "type": "bool",
"default": "false", "default": "false"
"setup_question": null
}, },
{ {
"name": "inlay_hints_hide_redundant_param_names_last_token", "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)", "description": "Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo)",
"type": "bool", "type": "bool",
"default": "false", "default": "false"
"setup_question": null
}, },
{ {
"name": "operator_completions", "name": "operator_completions",
"description": "Enables `*` and `?` operators in completion lists", "description": "Enables `*` and `?` operators in completion lists",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
"setup_question": "Do you want to enable .* and .? completions?"
}, },
{ {
"name": "warn_style", "name": "warn_style",
"description": "Enables warnings for style guideline mismatches", "description": "Enables warnings for style guideline mismatches",
"type": "bool", "type": "bool",
"default": "false", "default": "false"
"setup_question": "Do you want to enable style warnings?"
}, },
{ {
"name": "highlight_global_var_declarations", "name": "highlight_global_var_declarations",
"description": "Whether to highlight global var declarations", "description": "Whether to highlight global var declarations",
"type": "bool", "type": "bool",
"default": "false", "default": "false"
"setup_question": null
}, },
{ {
"name": "use_comptime_interpreter", "name": "use_comptime_interpreter",
"description": "Whether to use the comptime interpreter", "description": "Whether to use the comptime interpreter",
"type": "bool", "type": "bool",
"default": "false", "default": "false"
"setup_question": null
}, },
{ {
"name": "include_at_in_builtins", "name": "include_at_in_builtins",
"description": "Whether the @ sign should be part of the completion of builtins", "description": "Whether the @ sign should be part of the completion of builtins",
"type": "bool", "type": "bool",
"default": "false", "default": "false"
"setup_question": null
}, },
{ {
"name": "skip_std_references", "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", "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", "type": "bool",
"default": "false", "default": "false"
"setup_question": null
}, },
{ {
"name": "max_detail_length", "name": "max_detail_length",
"description": "The detail field of completions is truncated to be no longer than this (in bytes)", "description": "The detail field of completions is truncated to be no longer than this (in bytes)",
"type": "usize", "type": "usize",
"default": "1048576", "default": "1048576"
"setup_question": null },
{
"name": "record_session",
"description": "When true, zls will record all request is receives and write in into `record_session_path`, so that they can replayed with `zls replay`",
"type": "bool",
"default": "false"
},
{
"name": "record_session_path",
"description": "Output file path when `record_session` is set. The recommended file extension *.zlsreplay",
"type": "?[]const u8",
"default": "null"
},
{
"name": "replay_session_path",
"description": "Used when calling `zls replay` for specifying the replay file. If no extra argument is given `record_session_path` is used as the default path.",
"type": "?[]const u8",
"default": "null"
}, },
{ {
"name": "builtin_path", "name": "builtin_path",
"description": "Path to 'builtin;' useful for debugging, automatically set if let null", "description": "Path to 'builtin;' useful for debugging, automatically set if let null",
"type": "?[]const u8", "type": "?[]const u8",
"default": "null", "default": "null"
"setup_question": null
}, },
{ {
"name": "zig_lib_path", "name": "zig_lib_path",
"description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports", "description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports",
"type": "?[]const u8", "type": "?[]const u8",
"default": "null", "default": "null"
"setup_question": null
}, },
{ {
"name": "zig_exe_path", "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", "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", "type": "?[]const u8",
"default": "null", "default": "null"
"setup_question": null
}, },
{ {
"name": "build_runner_path", "name": "build_runner_path",
"description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`", "description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`",
"type": "?[]const u8", "type": "?[]const u8",
"default": "null", "default": "null"
"setup_question": null
}, },
{ {
"name": "global_cache_path", "name": "global_cache_path",
"description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`", "description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`",
"type": "?[]const u8", "type": "?[]const u8",
"default": "null", "default": "null"
"setup_question": null
} }
] ]
} }

View File

@ -1,4 +1,6 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const tres = @import("tres");
const ConfigOption = struct { const ConfigOption = struct {
/// Name of config option /// Name of config option
@ -9,9 +11,6 @@ const ConfigOption = struct {
type: []const u8, type: []const u8,
/// used in Config.zig as the default initializer /// used in Config.zig as the default initializer
default: []const u8, 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 { const Config = struct {
@ -46,9 +45,7 @@ fn zigTypeToTypescript(ty: []const u8) ![]const u8 {
fn generateConfigFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { fn generateConfigFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void {
_ = allocator; _ = allocator;
const config_file = try std.fs.openFileAbsolute(path, .{ const config_file = try std.fs.createFileAbsolute(path, .{});
.mode = .write_only,
});
defer config_file.close(); defer config_file.close();
var buff_out = std.io.bufferedWriter(config_file.writer()); var buff_out = std.io.bufferedWriter(config_file.writer());
@ -69,10 +66,10 @@ fn generateConfigFile(allocator: std.mem.Allocator, config: Config, path: []cons
\\{s}: {s} = {s}, \\{s}: {s} = {s},
\\ \\
, .{ , .{
std.mem.trim(u8, option.description, " \t\n\r"), std.mem.trim(u8, option.description, &std.ascii.whitespace),
std.mem.trim(u8, option.name, " \t\n\r"), std.mem.trim(u8, option.name, &std.ascii.whitespace),
std.mem.trim(u8, option.type, " \t\n\r"), std.mem.trim(u8, option.type, &std.ascii.whitespace),
std.mem.trim(u8, option.default, " \t\n\r"), std.mem.trim(u8, option.default, &std.ascii.whitespace),
}); });
} }
@ -114,7 +111,7 @@ fn generateSchemaFile(allocator: std.mem.Allocator, config: Config, path: []cons
\\ "properties": \\ "properties":
); );
try serializeObjectMap(properties, .{ try tres.stringify(properties, .{
.whitespace = .{ .whitespace = .{
.indent_level = 1, .indent_level = 1,
}, },
@ -122,26 +119,26 @@ fn generateSchemaFile(allocator: std.mem.Allocator, config: Config, path: []cons
_ = try buff_out.write("\n}\n"); _ = try buff_out.write("\n}\n");
try buff_out.flush(); try buff_out.flush();
try schema_file.setEndPos(try schema_file.getPos());
} }
fn updateREADMEFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void { fn updateREADMEFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void {
var readme_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_write }); var readme_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_write });
defer readme_file.close(); defer readme_file.close();
var readme = std.ArrayListUnmanaged(u8){ var readme = try readme_file.readToEndAlloc(allocator, std.math.maxInt(usize));
.items = try readme_file.readToEndAlloc(allocator, std.math.maxInt(usize)), defer allocator.free(readme);
};
defer readme.deinit(allocator);
const start_indicator = "<!-- DO NOT EDIT | THIS SECTION IS AUTO-GENERATED | DO NOT EDIT -->"; const start_indicator = "<!-- DO NOT EDIT | THIS SECTION IS AUTO-GENERATED | DO NOT EDIT -->";
const end_indicator = "<!-- DO NOT EDIT -->"; const end_indicator = "<!-- DO NOT EDIT -->";
const start = start_indicator.len + (std.mem.indexOf(u8, readme.items, start_indicator) orelse return error.SectionNotFound); const start = start_indicator.len + (std.mem.indexOf(u8, readme, start_indicator) orelse return error.SectionNotFound);
const end = std.mem.indexOfPos(u8, readme.items, start, end_indicator) orelse return error.SectionNotFound; const end = std.mem.indexOfPos(u8, readme, start, end_indicator) orelse return error.SectionNotFound;
var new_readme = std.ArrayListUnmanaged(u8){}; try readme_file.seekTo(0);
defer new_readme.deinit(allocator); var writer = readme_file.writer();
var writer = new_readme.writer(allocator);
try writer.writeAll(readme[0..start]);
try writer.writeAll( try writer.writeAll(
\\ \\
@ -155,29 +152,95 @@ fn updateREADMEFile(allocator: std.mem.Allocator, config: Config, path: []const
\\| `{s}` | `{s}` | `{s}` | {s} | \\| `{s}` | `{s}` | `{s}` | {s} |
\\ \\
, .{ , .{
std.mem.trim(u8, option.name, " \t\n\r"), std.mem.trim(u8, option.name, &std.ascii.whitespace),
std.mem.trim(u8, option.type, " \t\n\r"), std.mem.trim(u8, option.type, &std.ascii.whitespace),
std.mem.trim(u8, option.default, " \t\n\r"), std.mem.trim(u8, option.default, &std.ascii.whitespace),
std.mem.trim(u8, option.description, " \t\n\r"), std.mem.trim(u8, option.description, &std.ascii.whitespace),
}); });
} }
try readme.replaceRange(allocator, start, end - start, new_readme.items); try writer.writeAll(readme[end..]);
try readme_file.seekTo(0); try readme_file.setEndPos(try readme_file.getPos());
try readme_file.writeAll(readme.items); }
const ConfigurationProperty = struct {
scope: []const u8 = "resource",
type: []const u8,
description: []const u8,
@"enum": ?[]const []const u8 = null,
format: ?[]const u8 = null,
default: ?std.json.Value = null,
};
fn generateVSCodeConfigFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void {
var config_file = try std.fs.createFileAbsolute(path, .{});
defer config_file.close();
const predefined_configurations: usize = 3;
var configuration: std.StringArrayHashMapUnmanaged(ConfigurationProperty) = .{};
try configuration.ensureTotalCapacity(allocator, predefined_configurations + @intCast(u32, config.options.len));
defer {
for (configuration.keys()[predefined_configurations..]) |name| allocator.free(name);
configuration.deinit(allocator);
}
configuration.putAssumeCapacityNoClobber("trace.server", .{
.scope = "window",
.type = "string",
.@"enum" = &.{ "off", "message", "verbose" },
.description = "Traces the communication between VS Code and the language server.",
.default = .{ .String = "off" },
});
configuration.putAssumeCapacityNoClobber("check_for_update", .{
.type = "boolean",
.description = "Whether to automatically check for new updates",
.default = .{ .Bool = true },
});
configuration.putAssumeCapacityNoClobber("path", .{
.type = "string",
.description = "Path to `zls` executable. Example: `C:/zls/zig-cache/bin/zls.exe`.",
.format = "path",
.default = null,
});
for (config.options) |option| {
const name = try std.fmt.allocPrint(allocator, "zls.{s}", .{option.name});
var parser = std.json.Parser.init(allocator, false);
const default = (try parser.parse(option.default)).root;
configuration.putAssumeCapacityNoClobber(name, .{
.type = try zigTypeToTypescript(option.type),
.description = option.description,
.format = if (std.mem.indexOf(u8, option.name, "path") != null) "path" else null,
.default = if (default == .Null) null else default,
});
}
var buffered_writer = std.io.bufferedWriter(config_file.writer());
var writer = buffered_writer.writer();
try tres.stringify(configuration, .{
.whitespace = .{},
.emit_null_optional_fields = false,
}, writer);
try buffered_writer.flush();
} }
pub fn main() !void { pub fn main() !void {
var arg_it = std.process.args(); var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = general_purpose_allocator.allocator();
var arg_it = try std.process.argsWithAllocator(gpa);
defer arg_it.deinit();
_ = arg_it.next() orelse @panic(""); _ = arg_it.next() orelse @panic("");
const config_path = arg_it.next() orelse @panic("first argument must be path to Config.zig"); 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 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"); const readme_path = arg_it.next() orelse @panic("third argument must be path to README.md");
const maybe_vscode_config_path = arg_it.next();
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = general_purpose_allocator.allocator();
const parse_options = std.json.ParseOptions{ const parse_options = std.json.ParseOptions{
.allocator = gpa, .allocator = gpa,
@ -190,50 +253,19 @@ pub fn main() !void {
try generateSchemaFile(gpa, config, schema_path); try generateSchemaFile(gpa, config, schema_path);
try updateREADMEFile(gpa, config, readme_path); try updateREADMEFile(gpa, config, readme_path);
std.log.warn( if (maybe_vscode_config_path) |vscode_config_path| {
\\ If you have added a new configuration option and it should be configuration through the config wizard, then edit src/setup.zig try generateVSCodeConfigFile(gpa, config, vscode_config_path);
, .{}); }
std.log.info( if (builtin.os.tag == .windows) {
\\ Changing configuration options may also require editing the `package.json` from zls-vscode at https://github.com/zigtools/zls-vscode/blob/master/package.json std.log.warn("Running on windows may result in CRLF and LF mismatch", .{});
, .{}); }
}
try std.io.getStdOut().writeAll(
fn serializeObjectMap( \\If you have added a new configuration option and it should be configuration through the config wizard, then edit `src/setup.zig`
value: anytype, \\
options: std.json.StringifyOptions, \\Changing configuration options may also require editing the `package.json` from zls-vscode at https://github.com/zigtools/zls-vscode/blob/master/package.json
out_stream: anytype, \\You can use `zig build gen -Dvscode-config-path=/path/to/output/file.json` to generate the new configuration properties which you can then copy into `package.json`
) @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('}');
} }

View File

@ -1,6 +1,6 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const setup = @import("setup.zig");
const tracy = @import("tracy.zig"); const tracy = @import("tracy.zig");
const known_folders = @import("known-folders"); const known_folders = @import("known-folders");
@ -65,7 +65,7 @@ pub fn configChanged(config: *Config, allocator: std.mem.Allocator, builtin_crea
logger.debug("zig path `{s}` is not absolute, will look in path", .{exe_path}); logger.debug("zig path `{s}` is not absolute, will look in path", .{exe_path});
allocator.free(exe_path); allocator.free(exe_path);
} }
config.zig_exe_path = try setup.findZig(allocator); config.zig_exe_path = try findZig(allocator);
} }
if (config.zig_exe_path) |exe_path| blk: { if (config.zig_exe_path) |exe_path| blk: {
@ -208,3 +208,36 @@ fn getConfigurationType() type {
config_info.Struct.decls = &.{}; config_info.Struct.decls = &.{};
return @Type(config_info); return @Type(config_info);
} }
pub fn findZig(allocator: std.mem.Allocator) !?[]const u8 {
const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) {
error.EnvironmentVariableNotFound => {
return null;
},
else => return err,
};
defer allocator.free(env_path);
const exe_extension = builtin.target.exeFileExt();
const zig_exe = try std.fmt.allocPrint(allocator, "zig{s}", .{exe_extension});
defer allocator.free(zig_exe);
var it = std.mem.tokenize(u8, env_path, &[_]u8{std.fs.path.delimiter});
while (it.next()) |path| {
if (builtin.os.tag == .windows) {
if (std.mem.indexOfScalar(u8, path, '/') != null) continue;
}
const full_path = try std.fs.path.join(allocator, &[_][]const u8{ path, zig_exe });
defer allocator.free(full_path);
if (!std.fs.path.isAbsolute(full_path)) continue;
const file = std.fs.openFileAbsolute(full_path, .{}) catch continue;
defer file.close();
const stat = file.stat() catch continue;
if (stat.kind == .Directory) continue;
return try allocator.dupe(u8, full_path);
}
return null;
}

View File

@ -1,45 +0,0 @@
// Run with node
const fs = require("fs");
const path = require("path");
const sourceOfTruth = fs.readFileSync(path.join(__dirname, "..", "Config.zig"));
const lines = sourceOfTruth.toString().split("\n");
function mapType(type) {
switch (type) {
case "?[]const u8":
return "string";
case "bool":
return "boolean";
case "usize":
return "integer";
default:
throw new Error("unknown type!");
}
}
let comment = null;
for (const line of lines) {
if (line.startsWith("///")) {
if (comment === null) comment = line.slice(3).trim();
else comment += line.slice(3);
} else if (comment) {
const name = line.split(":")[0].trim();
const type = line.split(":")[1].split("=")[0].trim();
const defaultValue = line.split(":")[1].split("=")[1].trim().replace(",","");
console.log(`"zls.${name}": ${JSON.stringify({
"scope": "resource",
"type": mapType(type),
"description": comment,
"default": JSON.parse(defaultValue)
})},`);
comment = null;
}
}

View File

@ -21,16 +21,14 @@ pub const builtins = [_]Builtin{
}, },
.{ .{
.name = "@addWithOverflow", .name = "@addWithOverflow",
.signature = "@addWithOverflow(comptime T: type, a: T, b: T, result: *T) bool", .signature = "@addWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }",
.snippet = "@addWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})", .snippet = "@addWithOverflow(${1:a: anytype}, ${2:b: anytype})",
.documentation = .documentation =
\\Performs `result.* = a + b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. \\Performs `a + b` and returns a tuple with the result and a possible overflow bit.
, ,
.arguments = &.{ .arguments = &.{
"comptime T: type", "a: anytype",
"a: T", "b: anytype",
"b: T",
"result: *T",
}, },
}, },
.{ .{
@ -1066,16 +1064,14 @@ pub const builtins = [_]Builtin{
}, },
.{ .{
.name = "@mulWithOverflow", .name = "@mulWithOverflow",
.signature = "@mulWithOverflow(comptime T: type, a: T, b: T, result: *T) bool", .signature = "@mulWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }",
.snippet = "@mulWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})", .snippet = "@mulWithOverflow(${1:a: anytype}, ${2:b: anytype})",
.documentation = .documentation =
\\Performs `result.* = a * b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. \\Performs `a * b` and returns a tuple with the result and a possible overflow bit.
, ,
.arguments = &.{ .arguments = &.{
"comptime T: type", "a: anytype",
"a: T", "b: anytype",
"b: T",
"result: *T",
}, },
}, },
.{ .{
@ -1326,18 +1322,16 @@ pub const builtins = [_]Builtin{
}, },
.{ .{
.name = "@shlWithOverflow", .name = "@shlWithOverflow",
.signature = "@shlWithOverflow(comptime T: type, a: T, shift_amt: Log2T, result: *T) bool", .signature = "@shlWithOverflow(a: anytype, shift_amt: Log2T) struct { @TypeOf(a), u1 }",
.snippet = "@shlWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:shift_amt: Log2T}, ${4:result: *T})", .snippet = "@shlWithOverflow(${1:a: anytype}, ${2:shift_amt: Log2T})",
.documentation = .documentation =
\\Performs `result.* = a << b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. \\Performs `a << b` and returns a tuple with the result and a possible overflow bit.
\\ \\
\\The type of `shift_amt` is an unsigned integer with `log2(@typeInfo(T).Int.bits)` bits. This is because `shift_amt >= @typeInfo(T).Int.bits` is undefined behavior. \\The type of `shift_amt` is an unsigned integer with `log2(@typeInfo(@TypeOf(a)).Int.bits)` bits. This is because `shift_amt >= @typeInfo(@TypeOf(a)).Int.bits` is undefined behavior.
, ,
.arguments = &.{ .arguments = &.{
"comptime T: type", "a: anytype",
"a: T",
"shift_amt: Log2T", "shift_amt: Log2T",
"result: *T",
}, },
}, },
.{ .{
@ -1619,16 +1613,14 @@ pub const builtins = [_]Builtin{
}, },
.{ .{
.name = "@subWithOverflow", .name = "@subWithOverflow",
.signature = "@subWithOverflow(comptime T: type, a: T, b: T, result: *T) bool", .signature = "@subWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }",
.snippet = "@subWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})", .snippet = "@subWithOverflow(${1:a: anytype}, ${2:b: anytype})",
.documentation = .documentation =
\\Performs `result.* = a - b`. If overflow or underflow occurs, stores the overflowed bits in `result` and returns `true`. If no overflow or underflow occurs, returns `false`. \\Performs `a - b` and returns a tuple with the result and a possible overflow bit.
, ,
.arguments = &.{ .arguments = &.{
"comptime T: type", "a: anytype",
"a: T", "b: anytype",
"b: T",
"result: *T",
}, },
}, },
.{ .{

View File

@ -1,8 +1,8 @@
const types = @import("../types.zig"); const types = @import("../lsp.zig");
pub const Snipped = struct { pub const Snipped = struct {
label: []const u8, label: []const u8,
kind: types.CompletionItem.Kind, kind: types.CompletionItemKind,
text: ?[]const u8 = null, text: ?[]const u8 = null,
}; };

136
src/debug.zig Normal file
View File

@ -0,0 +1,136 @@
const std = @import("std");
const analysis = @import("analysis.zig");
const offsets = @import("offsets.zig");
pub fn printTree(tree: std.zig.Ast) void {
if (!std.debug.runtime_safety) @compileError("this function should only be used in debug mode!");
std.debug.print(
\\
\\nodes tag lhs rhs token
\\-----------------------------------------------
\\
, .{});
var i: usize = 0;
while (i < tree.nodes.len) : (i += 1) {
std.debug.print(" {d:<3} {s:<20} {d:<3} {d:<3} {d:<3} {s}\n", .{
i,
@tagName(tree.nodes.items(.tag)[i]),
tree.nodes.items(.data)[i].lhs,
tree.nodes.items(.data)[i].rhs,
tree.nodes.items(.main_token)[i],
offsets.tokenToSlice(tree, tree.nodes.items(.main_token)[i]),
});
}
std.debug.print(
\\
\\tokens tag start
\\----------------------------------
\\
, .{});
i = 0;
while (i < tree.tokens.len) : (i += 1) {
std.debug.print(" {d:<3} {s:<20} {d:<}\n", .{
i,
@tagName(tree.tokens.items(.tag)[i]),
tree.tokens.items(.start)[i],
});
}
}
pub fn printDocumentScope(doc_scope: analysis.DocumentScope) void {
if (!std.debug.runtime_safety) @compileError("this function should only be used in debug mode!");
for (doc_scope.scopes.items) |scope, i| {
if (i != 0) std.debug.print("\n\n", .{});
std.debug.print(
\\[{d}, {d}] {}
\\usingnamespaces: {d}
\\Decls:
\\
, .{
scope.loc.start,
scope.loc.end,
scope.data,
scope.uses.items.len,
});
var decl_it = scope.decls.iterator();
var idx: usize = 0;
while (decl_it.next()) |entry| : (idx += 1) {
std.debug.print(" {s:<8} {}\n", .{ entry.key_ptr.*, entry.value_ptr.* });
}
}
}
pub const FailingAllocator = struct {
internal_allocator: std.mem.Allocator,
random: std.rand.DefaultPrng,
likelihood: u32,
/// the chance that an allocation will fail is `1/likelihood`
/// `likelihood == 0` means that every allocation will fail
/// `likelihood == std.math.intMax(u32)` means that no allocation will be forced to fail
pub fn init(internal_allocator: std.mem.Allocator, likelihood: u32) FailingAllocator {
var seed = std.mem.zeroes([8]u8);
std.os.getrandom(&seed) catch {};
return FailingAllocator{
.internal_allocator = internal_allocator,
.random = std.rand.DefaultPrng.init(@bitCast(u64, seed)),
.likelihood = likelihood,
};
}
pub fn allocator(self: *FailingAllocator) std.mem.Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.free = free,
},
};
}
fn alloc(
ctx: *anyopaque,
len: usize,
log2_ptr_align: u8,
return_address: usize,
) ?[*]u8 {
const self = @ptrCast(*FailingAllocator, @alignCast(@alignOf(FailingAllocator), ctx));
if (shouldFail(self)) return null;
return self.internal_allocator.rawAlloc(len, log2_ptr_align, return_address);
}
fn resize(
ctx: *anyopaque,
old_mem: []u8,
log2_old_align: u8,
new_len: usize,
ra: usize,
) bool {
const self = @ptrCast(*FailingAllocator, @alignCast(@alignOf(FailingAllocator), ctx));
if (!self.internal_allocator.rawResize(old_mem, log2_old_align, new_len, ra))
return false;
return true;
}
fn free(
ctx: *anyopaque,
old_mem: []u8,
log2_old_align: u8,
ra: usize,
) void {
const self = @ptrCast(*FailingAllocator, @alignCast(@alignOf(FailingAllocator), ctx));
self.internal_allocator.rawFree(old_mem, log2_old_align, ra);
}
fn shouldFail(self: *FailingAllocator) bool {
if (self.likelihood == std.math.maxInt(u32)) return false;
return 0 == self.random.random().intRangeAtMostBiased(u32, 0, self.likelihood);
}
};

View File

@ -1,6 +1,5 @@
const std = @import("std"); const std = @import("std");
const types = @import("types.zig"); const types = @import("lsp.zig");
const requests = @import("requests.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
pub const Error = error{ OutOfMemory, InvalidRange }; pub const Error = error{ OutOfMemory, InvalidRange };
@ -357,14 +356,14 @@ fn char_pos_to_range(
pub fn applyTextEdits( pub fn applyTextEdits(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
text: []const u8, text: []const u8,
content_changes: []const requests.TextDocumentContentChangeEvent, content_changes: []const types.TextDocumentContentChangeEvent,
encoding: offsets.Encoding, encoding: offsets.Encoding,
) ![:0]const u8 { ) ![:0]const u8 {
var last_full_text_change: ?usize = null; var last_full_text_change: ?usize = null;
var i: usize = content_changes.len; var i: usize = content_changes.len;
while (i > 0) { while (i > 0) {
i -= 1; i -= 1;
if (content_changes[i].range == null) { if (content_changes[i] == .literal_1) {
last_full_text_change = i; last_full_text_change = i;
continue; continue;
} }
@ -373,16 +372,16 @@ pub fn applyTextEdits(
var text_array = std.ArrayListUnmanaged(u8){}; var text_array = std.ArrayListUnmanaged(u8){};
errdefer text_array.deinit(allocator); errdefer text_array.deinit(allocator);
try text_array.appendSlice(allocator, if (last_full_text_change) |index| content_changes[index].text else text); try text_array.appendSlice(allocator, if (last_full_text_change) |index| content_changes[index].literal_1.text else text);
// don't even bother applying changes before a full text change // don't even bother applying changes before a full text change
const changes = content_changes[if (last_full_text_change) |index| index + 1 else 0..]; const changes = content_changes[if (last_full_text_change) |index| index + 1 else 0..];
for (changes) |item| { for (changes) |item| {
const range = item.range.?; // every element is guaranteed to have `range` set const range = item.literal_0.range;
const loc = offsets.rangeToLoc(text_array.items, range, encoding); const loc = offsets.rangeToLoc(text_array.items, range, encoding);
try text_array.replaceRange(allocator, loc.start, loc.end - loc.start, item.text); try text_array.replaceRange(allocator, loc.start, loc.end - loc.start, item.literal_0.text);
} }
return try text_array.toOwnedSliceSentinel(allocator, 0); return try text_array.toOwnedSliceSentinel(allocator, 0);

View File

@ -1,43 +0,0 @@
const std = @import("std");
const RequestHeader = struct {
content_length: usize,
/// null implies "application/vscode-jsonrpc; charset=utf-8"
content_type: ?[]const u8,
pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
if (self.content_type) |ct| allocator.free(ct);
}
};
pub fn readRequestHeader(allocator: std.mem.Allocator, instream: anytype) !RequestHeader {
var r = RequestHeader{
.content_length = undefined,
.content_type = null,
};
errdefer r.deinit(allocator);
var has_content_length = false;
while (true) {
const header = try instream.readUntilDelimiterAlloc(allocator, '\n', 0x100);
defer allocator.free(header);
if (header.len == 0 or header[header.len - 1] != '\r') return error.MissingCarriageReturn;
if (header.len == 1) break;
const header_name = header[0 .. std.mem.indexOf(u8, header, ": ") orelse return error.MissingColon];
const header_value = header[header_name.len + 2 .. header.len - 1];
if (std.mem.eql(u8, header_name, "Content-Length")) {
if (header_value.len == 0) return error.MissingHeaderValue;
r.content_length = std.fmt.parseInt(usize, header_value, 10) catch return error.InvalidContentLength;
has_content_length = true;
} else if (std.mem.eql(u8, header_name, "Content-Type")) {
r.content_type = try allocator.dupe(u8, header_value);
} else {
return error.UnknownHeader;
}
}
if (!has_content_length) return error.MissingContentLength;
return r;
}

View File

@ -2,7 +2,7 @@ const std = @import("std");
const zig_builtin = @import("builtin"); const zig_builtin = @import("builtin");
const DocumentStore = @import("DocumentStore.zig"); const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig"); const analysis = @import("analysis.zig");
const types = @import("types.zig"); const types = @import("lsp.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
const log = std.log.scoped(.inlay_hint); const log = std.log.scoped(.inlay_hint);
@ -22,7 +22,7 @@ pub const inlay_hints_max_inline_children = 12;
/// checks whether node is inside the range /// checks whether node is inside the range
fn isNodeInRange(tree: Ast, node: Ast.Node.Index, range: types.Range) bool { fn isNodeInRange(tree: Ast, node: Ast.Node.Index, range: types.Range) bool {
const endLocation = tree.tokenLocation(0, tree.lastToken(node)); const endLocation = tree.tokenLocation(0, ast.lastToken(tree, node));
if (endLocation.line < range.start.line) return false; if (endLocation.line < range.start.line) return false;
const beginLocation = tree.tokenLocation(0, tree.firstToken(node)); const beginLocation = tree.tokenLocation(0, tree.firstToken(node));
@ -32,20 +32,13 @@ fn isNodeInRange(tree: Ast, node: Ast.Node.Index, range: types.Range) bool {
} }
const Builder = struct { const Builder = struct {
allocator: std.mem.Allocator, arena: std.mem.Allocator,
config: *const Config, config: *const Config,
handle: *const DocumentStore.Handle, handle: *const DocumentStore.Handle,
hints: std.ArrayListUnmanaged(types.InlayHint), hints: std.ArrayListUnmanaged(types.InlayHint),
hover_kind: types.MarkupContent.Kind, hover_kind: types.MarkupKind,
encoding: offsets.Encoding, encoding: offsets.Encoding,
fn deinit(self: *Builder) void {
for (self.hints.items) |hint| {
self.allocator.free(hint.tooltip.value);
}
self.hints.deinit(self.allocator);
}
fn appendParameterHint(self: *Builder, position: types.Position, label: []const u8, tooltip: []const u8, tooltip_noalias: bool, tooltip_comptime: bool) !void { fn appendParameterHint(self: *Builder, position: types.Position, label: []const u8, tooltip: []const u8, tooltip_noalias: bool, tooltip_comptime: bool) !void {
// TODO allocation could be avoided by extending InlayHint.jsonStringify // TODO allocation could be avoided by extending InlayHint.jsonStringify
// adding tooltip_noalias & tooltip_comptime to InlayHint should be enough // adding tooltip_noalias & tooltip_comptime to InlayHint should be enough
@ -53,28 +46,28 @@ const Builder = struct {
if (tooltip.len == 0) break :blk ""; if (tooltip.len == 0) break :blk "";
const prefix = if (tooltip_noalias) if (tooltip_comptime) "noalias comptime " else "noalias " else if (tooltip_comptime) "comptime " else ""; const prefix = if (tooltip_noalias) if (tooltip_comptime) "noalias comptime " else "noalias " else if (tooltip_comptime) "comptime " else "";
if (self.hover_kind == .Markdown) { if (self.hover_kind == .markdown) {
break :blk try std.fmt.allocPrint(self.allocator, "```zig\n{s}{s}\n```", .{ prefix, tooltip }); break :blk try std.fmt.allocPrint(self.arena, "```zig\n{s}{s}\n```", .{ prefix, tooltip });
} }
break :blk try std.fmt.allocPrint(self.allocator, "{s}{s}", .{ prefix, tooltip }); break :blk try std.fmt.allocPrint(self.arena, "{s}{s}", .{ prefix, tooltip });
}; };
try self.hints.append(self.allocator, .{ try self.hints.append(self.arena, .{
.position = position, .position = position,
.label = label, .label = .{ .string = label },
.kind = types.InlayHintKind.Parameter, .kind = types.InlayHintKind.Parameter,
.tooltip = .{ .tooltip = .{ .MarkupContent = .{
.kind = self.hover_kind, .kind = self.hover_kind,
.value = tooltip_text, .value = tooltip_text,
}, } },
.paddingLeft = false, .paddingLeft = false,
.paddingRight = true, .paddingRight = true,
}); });
} }
fn toOwnedSlice(self: *Builder) error{OutOfMemory}![]types.InlayHint { fn toOwnedSlice(self: *Builder) error{OutOfMemory}![]types.InlayHint {
return self.hints.toOwnedSlice(self.allocator); return self.hints.toOwnedSlice(self.arena);
} }
}; };
@ -689,26 +682,23 @@ fn writeNodeInlayHint(builder: *Builder, arena: *std.heap.ArenaAllocator, store:
/// creates a list of `InlayHint`'s from the given document /// creates a list of `InlayHint`'s from the given document
/// only parameter hints are created /// only parameter hints are created
/// only hints in the given range are created /// only hints in the given range are created
/// Caller owns returned memory.
/// `InlayHint.tooltip.value` has to deallocated separately
pub fn writeRangeInlayHint( pub fn writeRangeInlayHint(
arena: *std.heap.ArenaAllocator, arena: *std.heap.ArenaAllocator,
config: Config, config: Config,
store: *DocumentStore, store: *DocumentStore,
handle: *const DocumentStore.Handle, handle: *const DocumentStore.Handle,
range: types.Range, range: types.Range,
hover_kind: types.MarkupContent.Kind, hover_kind: types.MarkupKind,
encoding: offsets.Encoding, encoding: offsets.Encoding,
) error{OutOfMemory}![]types.InlayHint { ) error{OutOfMemory}![]types.InlayHint {
var builder: Builder = .{ var builder: Builder = .{
.allocator = arena.child_allocator, .arena = arena.allocator(),
.config = &config, .config = &config,
.handle = handle, .handle = handle,
.hints = .{}, .hints = .{},
.hover_kind = hover_kind, .hover_kind = hover_kind,
.encoding = encoding, .encoding = encoding,
}; };
errdefer builder.deinit();
var buf: [2]Ast.Node.Index = undefined; var buf: [2]Ast.Node.Index = undefined;
for (ast.declMembers(handle.tree, 0, &buf)) |child| { for (ast.declMembers(handle.tree, 0, &buf)) |child| {

7852
src/lsp.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,8 @@ const known_folders = @import("known-folders");
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const configuration = @import("configuration.zig"); const configuration = @import("configuration.zig");
const Server = @import("Server.zig"); const Server = @import("Server.zig");
const setup = @import("setup.zig"); const Header = @import("Header.zig");
const readRequestHeader = @import("header.zig").readRequestHeader; const debug = @import("debug.zig");
const logger = std.log.scoped(.main); const logger = std.log.scoped(.main);
@ -34,21 +34,114 @@ pub fn log(
std.debug.print(format ++ "\n", args); std.debug.print(format ++ "\n", args);
} }
fn loop(server: *Server) !void { fn loop(
var reader = std.io.getStdIn().reader(); server: *Server,
record_file: ?std.fs.File,
replay_file: ?std.fs.File,
) !void {
const std_in = std.io.getStdIn().reader();
const std_out = std.io.getStdOut().writer();
var buffered_reader = std.io.bufferedReader(if (replay_file) |file| file.reader() else std_in);
const reader = buffered_reader.reader();
var buffered_writer = std.io.bufferedWriter(std_out);
const writer = buffered_writer.writer();
while (true) { while (true) {
const headers = readRequestHeader(server.allocator, reader) catch |err| { var arena = std.heap.ArenaAllocator.init(server.allocator);
logger.err("{s}; exiting!", .{@errorName(err)}); defer arena.deinit();
return;
};
const buffer = try server.allocator.alloc(u8, headers.content_length);
defer server.allocator.free(buffer);
try reader.readNoEof(buffer); // write server -> client messages
for (server.outgoing_messages.items) |outgoing_message| {
const header = Header{ .content_length = outgoing_message.len };
try header.write(true, writer);
try writer.writeAll(outgoing_message);
}
try buffered_writer.flush();
for (server.outgoing_messages.items) |outgoing_message| {
server.allocator.free(outgoing_message);
}
server.outgoing_messages.clearRetainingCapacity();
const writer = std.io.getStdOut().writer(); // read and handle client -> server message
try server.processJsonRpc(writer, buffer); const header = try Header.parse(arena.allocator(), replay_file == null, reader);
const json_message = try arena.allocator().alloc(u8, header.content_length);
try reader.readNoEof(json_message);
if (record_file) |file| {
try header.write(false, file.writer());
try file.writeAll(json_message);
}
server.processJsonRpc(&arena, json_message);
}
}
fn getRecordFile(config: Config) ?std.fs.File {
if (!config.record_session) return null;
if (config.record_session_path) |record_path| {
if (std.fs.createFileAbsolute(record_path, .{})) |file| {
std.debug.print("recording to {s}\n", .{record_path});
return file;
} else |err| {
std.log.err("failed to create record file at {s}: {}", .{ record_path, err });
return null;
}
} else {
std.log.err("`record_session` is set but `record_session_path` is unspecified", .{});
return null;
}
}
fn getReplayFile(config: Config) ?std.fs.File {
const replay_path = config.replay_session_path orelse config.record_session_path orelse return null;
if (std.fs.openFileAbsolute(replay_path, .{})) |file| {
std.debug.print("replaying from {s}\n", .{replay_path});
return file;
} else |err| {
std.log.err("failed to open replay file at {s}: {}", .{ replay_path, err });
return null;
}
}
/// when recording we add a message that saves the current configuration in the replay
/// when replaying we read this message and replace the current config
fn updateConfig(
allocator: std.mem.Allocator,
config: *Config,
record_file: ?std.fs.File,
replay_file: ?std.fs.File,
) !void {
if (record_file) |file| {
var cfg = config.*;
cfg.record_session = false;
cfg.record_session_path = null;
cfg.replay_session_path = null;
var buffer = std.ArrayListUnmanaged(u8){};
defer buffer.deinit(allocator);
try std.json.stringify(cfg, .{}, buffer.writer(allocator));
const header = Header{ .content_length = buffer.items.len };
try header.write(false, file.writer());
try file.writeAll(buffer.items);
}
if (replay_file) |file| {
const header = try Header.parse(allocator, false, file.reader());
defer header.deinit(allocator);
const json_message = try allocator.alloc(u8, header.content_length);
defer allocator.free(json_message);
try file.reader().readNoEof(json_message);
var token_stream = std.json.TokenStream.init(json_message);
const new_config = try std.json.parse(Config, &token_stream, .{ .allocator = allocator });
std.json.parseFree(Config, config.*, .{ .allocator = allocator });
config.* = new_config;
} }
} }
@ -60,44 +153,28 @@ const ConfigWithPath = struct {
fn getConfig( fn getConfig(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
config_path: ?[]const u8, config_path: ?[]const u8,
/// If true, and the provided config_path is non-null, frees
/// the aforementioned path, in the case that it is
/// not returned.
free_old_config_path: bool,
) !ConfigWithPath { ) !ConfigWithPath {
if (config_path) |path| { if (config_path) |path| {
if (configuration.loadFromFile(allocator, path)) |conf| { if (configuration.loadFromFile(allocator, path)) |config| {
return ConfigWithPath{ return ConfigWithPath{ .config = config, .config_path = path };
.config = conf,
.config_path = path,
};
} }
std.debug.print( std.debug.print(
\\Could not open configuration file '{s}' \\Could not open configuration file '{s}'
\\Falling back to a lookup in the local and global configuration folders \\Falling back to a lookup in the local and global configuration folders
\\ \\
, .{path}); , .{path});
if (free_old_config_path) {
allocator.free(path);
}
} }
if (try known_folders.getPath(allocator, .local_configuration)) |path| { if (try known_folders.getPath(allocator, .local_configuration)) |path| {
if (configuration.loadFromFolder(allocator, path)) |conf| { if (configuration.loadFromFolder(allocator, path)) |config| {
return ConfigWithPath{ return ConfigWithPath{ .config = config, .config_path = path };
.config = conf,
.config_path = path,
};
} }
allocator.free(path); allocator.free(path);
} }
if (try known_folders.getPath(allocator, .global_configuration)) |path| { if (try known_folders.getPath(allocator, .global_configuration)) |path| {
if (configuration.loadFromFolder(allocator, path)) |conf| { if (configuration.loadFromFolder(allocator, path)) |config| {
return ConfigWithPath{ return ConfigWithPath{ .config = config, .config_path = path };
.config = conf,
.config_path = path,
};
} }
allocator.free(path); allocator.free(path);
} }
@ -108,22 +185,32 @@ fn getConfig(
}; };
} }
const ParseArgsResult = enum { proceed, exit }; const ParseArgsResult = struct {
fn parseArgs( action: enum { proceed, exit },
allocator: std.mem.Allocator, config_path: ?[]const u8,
config: *ConfigWithPath, replay_enabled: bool,
) !ParseArgsResult { replay_session_path: ?[]const u8,
};
fn parseArgs(allocator: std.mem.Allocator) !ParseArgsResult {
var result = ParseArgsResult{
.action = .exit,
.config_path = null,
.replay_enabled = false,
.replay_session_path = null,
};
const ArgId = enum { const ArgId = enum {
help, help,
version, version,
config, replay,
@"enable-debug-log", @"enable-debug-log",
@"show-config-path", @"show-config-path",
@"config-path", @"config-path",
}; };
const arg_id_map = std.ComptimeStringMap(ArgId, comptime blk: { const arg_id_map = std.ComptimeStringMap(ArgId, comptime blk: {
const fields = @typeInfo(ArgId).Enum.fields; const fields = @typeInfo(ArgId).Enum.fields;
const KV = std.meta.Tuple(&.{ []const u8, ArgId }); const KV = struct { []const u8, ArgId };
var pairs: [fields.len]KV = undefined; var pairs: [fields.len]KV = undefined;
for (pairs) |*pair, i| pair.* = .{ fields[i].name, @intToEnum(ArgId, fields[i].value) }; for (pairs) |*pair, i| pair.* = .{ fields[i].name, @intToEnum(ArgId, fields[i].value) };
break :blk pairs[0..]; break :blk pairs[0..];
@ -140,10 +227,10 @@ fn parseArgs(
var cmd_infos: InfoMap = InfoMap.init(.{ var cmd_infos: InfoMap = InfoMap.init(.{
.help = "Prints this message.", .help = "Prints this message.",
.version = "Prints the compiler version with which the server was compiled.", .version = "Prints the compiler version with which the server was compiled.",
.replay = "Replay a previous recorded zls session",
.@"enable-debug-log" = "Enables debug logs.", .@"enable-debug-log" = "Enables debug logs.",
.@"config-path" = "Specify the path to a configuration file specifying LSP behaviour.", .@"config-path" = "Specify the path to a configuration file specifying LSP behaviour.",
.@"show-config-path" = "Prints the path to the configuration file to stdout", .@"show-config-path" = "Prints the path to the configuration file to stdout",
.config = "Run the ZLS configuration wizard.",
}); });
var info_it = cmd_infos.iterator(); var info_it = cmd_infos.iterator();
while (info_it.next()) |entry| { while (info_it.next()) |entry| {
@ -159,9 +246,6 @@ fn parseArgs(
// Makes behavior of enabling debug more logging consistent regardless of argument order. // Makes behavior of enabling debug more logging consistent regardless of argument order.
var specified = std.enums.EnumArray(ArgId, bool).initFill(false); var specified = std.enums.EnumArray(ArgId, bool).initFill(false);
var config_path: ?[]const u8 = null;
errdefer if (config_path) |path| allocator.free(path);
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
@ -169,76 +253,82 @@ fn parseArgs(
if (!std.mem.startsWith(u8, tok, "--") or tok.len == 2) { if (!std.mem.startsWith(u8, tok, "--") or tok.len == 2) {
try stderr.print("{s}\n", .{help_message}); try stderr.print("{s}\n", .{help_message});
try stderr.print("Unexpected positional argument '{s}'.\n", .{tok}); try stderr.print("Unexpected positional argument '{s}'.\n", .{tok});
return .exit; return result;
} }
const argname = tok["--".len..]; const argname = tok["--".len..];
const id = arg_id_map.get(argname) orelse { const id = arg_id_map.get(argname) orelse {
try stderr.print("{s}\n", .{help_message}); try stderr.print("{s}\n", .{help_message});
try stderr.print("Unrecognized argument '{s}'.\n", .{argname}); try stderr.print("Unrecognized argument '{s}'.\n", .{argname});
return .exit; return result;
}; };
if (specified.get(id)) { if (specified.get(id)) {
try stderr.print("{s}\n", .{help_message}); try stderr.print("{s}\n", .{help_message});
try stderr.print("Duplicate argument '{s}'.\n", .{argname}); try stderr.print("Duplicate argument '{s}'.\n", .{argname});
return .exit; return result;
} }
specified.set(id, true); specified.set(id, true);
switch (id) { switch (id) {
.help => {}, .help,
.version => {}, .version,
.@"enable-debug-log" => {}, .@"enable-debug-log",
.config => {}, .@"show-config-path",
.@"show-config-path" => {}, => {},
.@"config-path" => { .@"config-path" => {
const path = args_it.next() orelse { const path = args_it.next() orelse {
try stderr.print("Expected configuration file path after --config-path argument.\n", .{}); try stderr.print("Expected configuration file path after --config-path argument.\n", .{});
return .exit; return result;
}; };
config.config_path = try allocator.dupe(u8, path); result.config_path = try allocator.dupe(u8, path);
},
.replay => {
result.replay_enabled = true;
const path = args_it.next() orelse break;
result.replay_session_path = try allocator.dupe(u8, path);
}, },
} }
} }
if (specified.get(.help)) { if (specified.get(.help)) {
try stderr.print("{s}\n", .{help_message}); try stderr.print("{s}\n", .{help_message});
return .exit; return result;
} }
if (specified.get(.version)) { if (specified.get(.version)) {
try std.io.getStdOut().writeAll(build_options.version ++ "\n"); try stdout.writeAll(build_options.version ++ "\n");
return .exit; return result;
}
if (specified.get(.config)) {
try setup.wizard(allocator);
return .exit;
} }
if (specified.get(.@"enable-debug-log")) { if (specified.get(.@"enable-debug-log")) {
actual_log_level = .debug; actual_log_level = .debug;
logger.info("Enabled debug logging.\n", .{}); logger.info("Enabled debug logging.\n", .{});
} }
if (specified.get(.@"config-path")) { if (specified.get(.@"config-path")) {
std.debug.assert(config.config_path != null); std.debug.assert(result.config_path != null);
} }
if (specified.get(.@"show-config-path")) { if (specified.get(.@"show-config-path")) {
const new_config = try getConfig(allocator, config.config_path, true); const new_config = try getConfig(allocator, result.config_path);
defer if (new_config.config_path) |path| allocator.free(path); defer if (new_config.config_path) |path| allocator.free(path);
defer std.json.parseFree(Config, new_config.config, .{ .allocator = allocator }); defer std.json.parseFree(Config, new_config.config, .{ .allocator = allocator });
if (new_config.config_path) |path| { const full_path = if (new_config.config_path) |path| blk: {
const full_path = try std.fs.path.resolve(allocator, &.{ path, "zls.json" }); break :blk try std.fs.path.resolve(allocator, &.{ path, "zls.json" });
defer allocator.free(full_path); } else blk: {
const local_config_path = try known_folders.getPath(allocator, .local_configuration) orelse {
try stdout.writeAll(full_path); logger.err("failed to find local configuration folder", .{});
try stdout.writeByte('\n'); return result;
} else { };
logger.err("Failed to find zls.json!\n", .{}); defer allocator.free(local_config_path);
} break :blk try std.fs.path.resolve(allocator, &.{ local_config_path, "zls.json" });
return .exit; };
defer allocator.free(full_path);
try stdout.writeAll(full_path);
try stdout.writeByte('\n');
return result;
} }
return .proceed; result.action = .proceed;
return result;
} }
const stack_frames = switch (zig_builtin.mode) { const stack_frames = switch (zig_builtin.mode) {
@ -249,34 +339,52 @@ const stack_frames = switch (zig_builtin.mode) {
pub fn main() !void { pub fn main() !void {
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){}; var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){};
defer _ = gpa_state.deinit(); defer _ = gpa_state.deinit();
var tracy_state = if (tracy.enable_allocation) tracy.tracyAllocator(gpa_state.allocator()) else void{}; var tracy_state = if (tracy.enable_allocation) tracy.tracyAllocator(gpa_state.allocator()) else void{};
const inner_allocator: std.mem.Allocator = if (tracy.enable_allocation) tracy_state.allocator() else gpa_state.allocator();
const allocator: std.mem.Allocator = if (tracy.enable_allocation) tracy_state.allocator() else gpa_state.allocator(); var failing_allocator_state = if (build_options.enable_failing_allocator) debug.FailingAllocator.init(inner_allocator, build_options.enable_failing_allocator_likelihood) else void{};
const allocator: std.mem.Allocator = if (build_options.enable_failing_allocator) failing_allocator_state.allocator() else inner_allocator;
var config = ConfigWithPath{ const result = try parseArgs(allocator);
.config = undefined, defer if (result.config_path) |path| allocator.free(path);
.config_path = null, defer if (result.replay_session_path) |path| allocator.free(path);
}; switch (result.action) {
defer if (config.config_path) |path| allocator.free(path);
switch (try parseArgs(allocator, &config)) {
.proceed => {}, .proceed => {},
.exit => return, .exit => return,
} }
config = try getConfig(allocator, config.config_path, true); var config = try getConfig(allocator, result.config_path);
defer std.json.parseFree(Config, config.config, .{ .allocator = allocator }); defer std.json.parseFree(Config, config.config, .{ .allocator = allocator });
defer if (config.config_path) |path| allocator.free(path);
if (result.replay_enabled and config.config.replay_session_path == null and config.config.record_session_path == null) {
logger.err("No replay file specified", .{});
return;
}
if (config.config_path == null) { if (config.config_path == null) {
logger.info("No config file zls.json found.", .{}); logger.info("No config file zls.json found.", .{});
} }
const record_file = if (!result.replay_enabled) getRecordFile(config.config) else null;
defer if (record_file) |file| file.close();
const replay_file = if (result.replay_enabled) getReplayFile(config.config) else null;
defer if (replay_file) |file| file.close();
std.debug.assert(record_file == null or replay_file == null);
try updateConfig(allocator, &config.config, record_file, replay_file);
var server = try Server.init( var server = try Server.init(
allocator, allocator,
&config.config, &config.config,
config.config_path, config.config_path,
record_file != null,
replay_file != null,
); );
defer server.deinit(); defer server.deinit();
try loop(&server); try loop(&server, record_file, replay_file);
} }

View File

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const types = @import("types.zig"); const types = @import("lsp.zig");
const ast = @import("ast.zig"); const ast = @import("ast.zig");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
@ -263,8 +263,8 @@ pub fn advancePosition(text: []const u8, position: types.Position, from_index: u
/// returns the number of code units in `text` /// returns the number of code units in `text`
pub fn countCodeUnits(text: []const u8, encoding: Encoding) usize { pub fn countCodeUnits(text: []const u8, encoding: Encoding) usize {
switch (encoding) { switch (encoding) {
.utf8 => return text.len, .@"utf-8" => return text.len,
.utf16 => { .@"utf-16" => {
var iter: std.unicode.Utf8Iterator = .{ .bytes = text, .i = 0 }; var iter: std.unicode.Utf8Iterator = .{ .bytes = text, .i = 0 };
var utf16_len: usize = 0; var utf16_len: usize = 0;
@ -277,15 +277,15 @@ pub fn countCodeUnits(text: []const u8, encoding: Encoding) usize {
} }
return utf16_len; return utf16_len;
}, },
.utf32 => return std.unicode.utf8CountCodepoints(text) catch unreachable, .@"utf-32" => return std.unicode.utf8CountCodepoints(text) catch unreachable,
} }
} }
/// returns the number of (utf-8 code units / bytes) that represent `n` code units in `text` /// returns the number of (utf-8 code units / bytes) that represent `n` code units in `text`
pub fn getNCodeUnitByteCount(text: []const u8, n: usize, encoding: Encoding) usize { pub fn getNCodeUnitByteCount(text: []const u8, n: usize, encoding: Encoding) usize {
switch (encoding) { switch (encoding) {
.utf8 => return n, .@"utf-8" => return n,
.utf16 => { .@"utf-16" => {
if (n == 0) return 0; if (n == 0) return 0;
var iter: std.unicode.Utf8Iterator = .{ .bytes = text, .i = 0 }; var iter: std.unicode.Utf8Iterator = .{ .bytes = text, .i = 0 };
@ -300,7 +300,7 @@ pub fn getNCodeUnitByteCount(text: []const u8, n: usize, encoding: Encoding) usi
} }
return iter.i; return iter.i;
}, },
.utf32 => { .@"utf-32" => {
var i: usize = 0; var i: usize = 0;
var count: usize = 0; var count: usize = 0;
while (count != n) : (count += 1) { while (count != n) : (count += 1) {

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
const DocumentStore = @import("DocumentStore.zig"); const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig"); const analysis = @import("analysis.zig");
const types = @import("types.zig"); const types = @import("lsp.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
const log = std.log.scoped(.references); const log = std.log.scoped(.references);
const ast = @import("ast.zig"); const ast = @import("ast.zig");
@ -488,7 +488,7 @@ pub fn symbolReferences(
} }
var handle_dependencies = std.ArrayListUnmanaged([]const u8){}; var handle_dependencies = std.ArrayListUnmanaged([]const u8){};
try store.collectDependencies(store.allocator, handle.*, &handle_dependencies); try store.collectDependencies(arena.allocator(), handle.*, &handle_dependencies);
for (handle_dependencies.items) |uri| { for (handle_dependencies.items) |uri| {
try dependencies.put(arena.allocator(), uri, {}); try dependencies.put(arena.allocator(), uri, {});

View File

@ -1,324 +0,0 @@
//! This file contains request types zls handles.
//! Note that the parameter types may be incomplete.
//! We only define what we actually use.
const std = @import("std");
const types = @import("types.zig");
/// Only check for the field's existence.
const Exists = struct {
exists: bool,
};
fn Default(comptime T: type, comptime default_value: T) type {
return struct {
pub const value_type = T;
pub const default = default_value;
value: T,
};
}
pub fn ErrorUnwrappedReturnOf(comptime func: anytype) type {
return switch (@typeInfo(@TypeOf(func))) {
.Fn, .BoundFn => |fn_info| switch (@typeInfo(fn_info.return_type.?)) {
.ErrorUnion => |err_union| err_union.payload,
else => |T| return T,
},
else => unreachable,
};
}
fn Transform(comptime Original: type, comptime transform_fn: anytype) type {
return struct {
pub const original_type = Original;
pub const transform = transform_fn;
value: ErrorUnwrappedReturnOf(transform_fn),
};
}
fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Value, out: anytype) error{ MalformedJson, OutOfMemory }!void {
const T = comptime std.meta.Child(@TypeOf(out));
if (comptime std.meta.trait.is(.Struct)(T)) {
if (value != .Object) return error.MalformedJson;
var err = false;
inline for (std.meta.fields(T)) |field| {
const is_exists = field.type == Exists;
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;
const is_transform = comptime if (is_struct) std.meta.trait.hasDecls(actual_type, .{ "original_type", "transform" }) else false;
if (value.Object.get(field.name)) |json_field| {
if (is_exists) {
@field(out, field.name) = Exists{ .exists = true };
} else if (is_transform) {
var original_value: actual_type.original_type = undefined;
try fromDynamicTreeInternal(arena, json_field, &original_value);
@field(out, field.name) = actual_type{
.value = actual_type.transform(original_value) catch
return error.MalformedJson,
};
} else if (is_default) {
try fromDynamicTreeInternal(arena, json_field, &@field(out, field.name).value);
} else if (is_optional) {
if (json_field == .Null) {
@field(out, field.name) = null;
} else {
var actual_value: actual_type = undefined;
try fromDynamicTreeInternal(arena, json_field, &actual_value);
@field(out, field.name) = actual_value;
}
} else {
try fromDynamicTreeInternal(arena, json_field, &@field(out, field.name));
}
} else {
if (is_exists) {
@field(out, field.name) = Exists{ .exists = false };
} else if (is_optional) {
@field(out, field.name) = null;
} else if (is_default) {
@field(out, field.name) = actual_type{ .value = actual_type.default };
} else {
err = true;
}
}
}
if (err) return error.MalformedJson;
} else if (comptime (std.meta.trait.isSlice(T) and T != []const u8)) {
if (value != .Array) return error.MalformedJson;
const Child = std.meta.Child(T);
if (value.Array.items.len == 0) {
out.* = &[0]Child{};
} else {
var slice = try arena.allocator().alloc(Child, value.Array.items.len);
for (value.Array.items) |arr_item, idx| {
try fromDynamicTreeInternal(arena, arr_item, &slice[idx]);
}
out.* = slice;
}
} else if (T == std.json.Value) {
out.* = value;
} else if (comptime std.meta.trait.is(.Enum)(T)) {
const info = @typeInfo(T).Enum;
const TagType = info.tag_type;
if (value != .Integer) return error.MalformedJson;
out.* = std.meta.intToEnum(
T,
std.math.cast(TagType, value.Integer) orelse return error.MalformedJson,
) catch return error.MalformedJson;
} else if (comptime std.meta.trait.is(.Int)(T)) {
if (value != .Integer) return error.MalformedJson;
out.* = std.math.cast(T, value.Integer) orelse return error.MalformedJson;
} else switch (T) {
bool => {
if (value != .Bool) return error.MalformedJson;
out.* = value.Bool;
},
f64 => {
if (value != .Float) return error.MalformedJson;
out.* = value.Float;
},
[]const u8 => {
if (value != .String) return error.MalformedJson;
out.* = value.String;
},
else => @compileError("Invalid type " ++ @typeName(T)),
}
}
pub fn fromDynamicTree(arena: *std.heap.ArenaAllocator, comptime T: type, value: std.json.Value) error{ MalformedJson, OutOfMemory }!T {
var out: T = undefined;
try fromDynamicTreeInternal(arena, value, &out);
return out;
}
const MaybeStringArray = Default([]const []const u8, &.{});
pub const Initialize = struct {
pub const ClientCapabilities = struct {
workspace: ?struct {
configuration: Default(bool, false),
didChangeConfiguration: ?struct {
dynamicRegistration: Default(bool, false), // NOTE: Should this be true? Seems like this critical feature should be nearly universal
},
workspaceFolders: Default(bool, false),
},
textDocument: ?struct {
synchronization: ?struct {
willSave: Default(bool, false),
willSaveWaitUntil: Default(bool, false),
didSave: Default(bool, false),
},
semanticTokens: Exists,
inlayHint: Exists,
hover: ?struct {
contentFormat: MaybeStringArray,
},
completion: ?struct {
completionItem: ?struct {
snippetSupport: Default(bool, false),
labelDetailsSupport: Default(bool, false),
documentationFormat: MaybeStringArray,
},
},
documentHighlight: Exists,
},
general: ?struct {
positionEncodings: MaybeStringArray,
},
};
pub const ClientInfo = struct {
name: []const u8,
version: ?[]const u8,
};
params: struct {
clientInfo: ?ClientInfo,
capabilities: ClientCapabilities,
workspaceFolders: ?[]const types.WorkspaceFolder,
},
};
pub const WorkspaceFoldersChange = struct {
params: struct {
event: struct {
added: []const types.WorkspaceFolder,
removed: []const types.WorkspaceFolder,
},
},
};
pub const OpenDocument = struct {
params: struct {
textDocument: struct {
uri: []const u8,
text: []const u8,
},
},
};
const TextDocumentIdentifier = struct {
uri: []const u8,
};
pub const ChangeDocument = struct {
params: struct {
textDocument: TextDocumentIdentifier,
contentChanges: []TextDocumentContentChangeEvent,
},
};
pub const TextDocumentContentChangeEvent = struct {
range: ?types.Range,
text: []const u8,
};
const TextDocumentIdentifierRequest = struct {
params: struct {
textDocument: TextDocumentIdentifier,
},
};
pub const SaveDocument = TextDocumentIdentifierRequest;
pub const CloseDocument = TextDocumentIdentifierRequest;
pub const SemanticTokensFull = TextDocumentIdentifierRequest;
const TextDocumentIdentifierPositionRequest = struct {
params: struct {
textDocument: TextDocumentIdentifier,
position: types.Position,
},
};
pub const SaveReason = enum(u32) {
Manual = 1,
AfterDelay = 2,
FocusOut = 3,
};
pub const WillSave = struct {
params: struct {
textDocument: TextDocumentIdentifier,
reason: SaveReason,
},
};
pub const SignatureHelp = struct {
params: struct {
textDocument: TextDocumentIdentifier,
position: types.Position,
context: ?struct {
triggerKind: enum(u32) {
invoked = 1,
trigger_character = 2,
content_change = 3,
},
triggerCharacter: ?[]const u8,
isRetrigger: bool,
activeSignatureHelp: ?types.SignatureHelp,
},
},
};
pub const Completion = TextDocumentIdentifierPositionRequest;
pub const GotoDefinition = TextDocumentIdentifierPositionRequest;
pub const GotoDeclaration = TextDocumentIdentifierPositionRequest;
pub const Hover = TextDocumentIdentifierPositionRequest;
pub const DocumentSymbols = TextDocumentIdentifierRequest;
pub const Formatting = TextDocumentIdentifierRequest;
pub const DocumentHighlight = TextDocumentIdentifierPositionRequest;
pub const Rename = struct {
params: struct {
textDocument: TextDocumentIdentifier,
position: types.Position,
newName: []const u8,
},
};
pub const References = struct {
params: struct {
textDocument: TextDocumentIdentifier,
position: types.Position,
context: struct {
includeDeclaration: bool,
},
},
};
pub const InlayHint = struct {
params: struct {
textDocument: TextDocumentIdentifier,
range: types.Range,
},
};
pub const CodeAction = struct {
params: struct {
textDocument: TextDocumentIdentifier,
range: types.Range,
context: struct {
diagnostics: []types.Diagnostic,
},
},
};
pub const FoldingRange = struct {
params: struct {
textDocument: TextDocumentIdentifier,
},
};
pub const SelectionRange = struct {
params: struct {
textDocument: TextDocumentIdentifier,
positions: []types.Position,
},
};

View File

@ -289,7 +289,7 @@ fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) WriteTokensEr
const token_tags = tree.tokens.items(.tag); const token_tags = tree.tokens.items(.tag);
const node_data = tree.nodes.items(.data); const node_data = tree.nodes.items(.data);
const main_tokens = tree.nodes.items(.main_token); const main_tokens = tree.nodes.items(.main_token);
if (node == 0 or node > node_data.len) return; if (node == 0 or node >= node_data.len) return;
var allocator = builder.arena.allocator(); var allocator = builder.arena.allocator();

View File

@ -1,230 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const known_folders = @import("known-folders");
/// Caller must free memory.
pub fn askString(allocator: std.mem.Allocator, prompt: []const u8, max_size: usize) ![]u8 {
const in = std.io.getStdIn().reader();
const out = std.io.getStdOut().writer();
try out.print("? {s}", .{prompt});
const result = try in.readUntilDelimiterAlloc(allocator, '\n', max_size);
return if (std.mem.endsWith(u8, result, "\r")) result[0..(result.len - 1)] else result;
}
/// Caller must free memory. Max size is recommended to be a high value, like 512.
pub fn askDirPath(allocator: std.mem.Allocator, prompt: []const u8, max_size: usize) ![]u8 {
const out = std.io.getStdOut().writer();
while (true) {
const path = try askString(allocator, prompt, max_size);
if (!std.fs.path.isAbsolute(path)) {
try out.writeAll("Error: Invalid directory, please try again.\n\n");
allocator.free(path);
continue;
}
var dir = std.fs.cwd().openDir(path, std.fs.Dir.OpenDirOptions{}) catch {
try out.writeAll("Error: Invalid directory, please try again.\n\n");
allocator.free(path);
continue;
};
dir.close();
return path;
}
}
pub fn askBool(prompt: []const u8) !bool {
const in = std.io.getStdIn().reader();
const out = std.io.getStdOut().writer();
var buffer: [1]u8 = undefined;
while (true) {
try out.print("? {s} (y/n) > ", .{prompt});
const read = in.read(&buffer) catch continue;
try in.skipUntilDelimiterOrEof('\n');
if (read == 0) return error.EndOfStream;
switch (buffer[0]) {
'y' => return true,
'n' => return false,
else => continue,
}
}
}
pub fn askSelectOne(prompt: []const u8, comptime Options: type) !Options {
const in = std.io.getStdIn().reader();
const out = std.io.getStdOut().writer();
try out.print("? {s} (select one)\n\n", .{prompt});
comptime var max_size: usize = 0;
inline for (@typeInfo(Options).Enum.fields) |option| {
try out.print(" - {s}\n", .{option.name});
if (option.name.len > max_size) max_size = option.name.len;
}
while (true) {
var buffer: [max_size + 1]u8 = undefined;
try out.writeAll("\n> ");
var result = (in.readUntilDelimiterOrEof(&buffer, '\n') catch {
try in.skipUntilDelimiterOrEof('\n');
try out.writeAll("Error: Invalid option, please try again.\n");
continue;
}) orelse return error.EndOfStream;
result = if (std.mem.endsWith(u8, result, "\r")) result[0..(result.len - 1)] else result;
inline for (@typeInfo(Options).Enum.fields) |option|
if (std.ascii.eqlIgnoreCase(option.name, result))
return @intToEnum(Options, option.value);
try out.writeAll("Error: Invalid option, please try again.\n");
}
}
pub fn wizard(allocator: std.mem.Allocator) !void {
@setEvalBranchQuota(2500);
const stdout = std.io.getStdOut().writer();
try stdout.writeAll(
\\Welcome to the ZLS configuration wizard!
\\ *
\\ |\
\\ /* \
\\ | *\
\\ _/_*___|_ x
\\ | @ @ /
\\ @ \ /
\\ \__-/ /
\\
\\
);
var local_path = known_folders.getPath(allocator, .local_configuration) catch null;
var global_path = known_folders.getPath(allocator, .global_configuration) catch null;
defer if (local_path) |d| allocator.free(d);
defer if (global_path) |d| allocator.free(d);
const can_access_global = blk: {
std.fs.accessAbsolute(global_path orelse break :blk false, .{}) catch break :blk false;
break :blk true;
};
if (global_path == null and local_path == null) {
try stdout.writeAll("Could not open a global or local config directory.\n");
return;
}
var config_path: []const u8 = undefined;
if (can_access_global and try askBool("Should this configuration be system-wide?")) {
config_path = global_path.?;
} else {
if (local_path) |p| {
config_path = p;
} else {
try stdout.writeAll("Could not find a local config directory.\n");
return;
}
}
var dir = std.fs.cwd().openDir(config_path, .{}) catch |err| {
try stdout.print("Could not open {s}: {}.\n", .{ config_path, err });
return;
};
defer dir.close();
var file = dir.createFile("zls.json", .{}) catch |err| {
try stdout.print("Could not create {s}/zls.json: {}.\n", .{ config_path, err });
return;
};
defer file.close();
const out = file.writer();
var zig_exe_path = try findZig(allocator);
defer if (zig_exe_path) |p| allocator.free(p);
if (zig_exe_path) |path| {
try stdout.print("Found zig executable '{s}' in PATH.\n", .{path});
} else {
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),
\\your zig directory cannot contain the '/' character.
else
"What is the path to the 'zig' executable you would like to use?", std.fs.MAX_PATH_BYTES);
}
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)");
const ief_apc = try askBool("Do you want to enable @import/@embedFile argument path completion?");
const style = try askBool("Do you want to enable style warnings?");
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?");
std.debug.print("Writing config to {s}/zls.json ... ", .{config_path});
try std.json.stringify(.{
.@"$schema" = "https://raw.githubusercontent.com/zigtools/zls/master/schema.json",
.zig_exe_path = zig_exe_path,
.enable_snippets = snippets,
.enable_ast_check_diagnostics = ast_check,
.enable_autofix = autofix,
.enable_import_embedfile_argument_completions = ief_apc,
.warn_style = style,
.enable_semantic_tokens = semantic_tokens,
.enable_inlay_hints = inlay_hints,
.operator_completions = operator_completions,
}, .{
.whitespace = .{},
}, out);
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 {
const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) {
error.EnvironmentVariableNotFound => {
return null;
},
else => return err,
};
defer allocator.free(env_path);
const exe_extension = builtin.target.exeFileExt();
const zig_exe = try std.fmt.allocPrint(allocator, "zig{s}", .{exe_extension});
defer allocator.free(zig_exe);
var it = std.mem.tokenize(u8, env_path, &[_]u8{std.fs.path.delimiter});
while (it.next()) |path| {
if (builtin.os.tag == .windows) {
if (std.mem.indexOfScalar(u8, path, '/') != null) continue;
}
const full_path = try std.fs.path.join(allocator, &[_][]const u8{ path, zig_exe });
defer allocator.free(full_path);
if (!std.fs.path.isAbsolute(full_path)) continue;
const file = std.fs.openFileAbsolute(full_path, .{}) catch continue;
defer file.close();
const stat = file.stat() catch continue;
if (stat.kind == .Directory) continue;
return try allocator.dupe(u8, full_path);
}
return null;
}

View File

@ -2,31 +2,29 @@ const std = @import("std");
const analysis = @import("analysis.zig"); const analysis = @import("analysis.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
const DocumentStore = @import("DocumentStore.zig"); const DocumentStore = @import("DocumentStore.zig");
const types = @import("types.zig"); const types = @import("lsp.zig");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
const Token = std.zig.Token; const Token = std.zig.Token;
const identifierFromPosition = @import("Server.zig").identifierFromPosition; const identifierFromPosition = @import("Server.zig").identifierFromPosition;
const ast = @import("ast.zig"); const ast = @import("ast.zig");
fn fnProtoToSignatureInfo(document_store: *DocumentStore, arena: *std.heap.ArenaAllocator, commas: u32, skip_self_param: bool, handle: *const DocumentStore.Handle, fn_node: Ast.Node.Index, proto: Ast.full.FnProto) !types.SignatureInformation { fn fnProtoToSignatureInfo(document_store: *DocumentStore, arena: *std.heap.ArenaAllocator, commas: u32, skip_self_param: bool, handle: *const DocumentStore.Handle, fn_node: Ast.Node.Index, proto: Ast.full.FnProto) !types.SignatureInformation {
const ParameterInformation = types.SignatureInformation.ParameterInformation;
const tree = handle.tree; const tree = handle.tree;
const token_starts = tree.tokens.items(.start); const token_starts = tree.tokens.items(.start);
const alloc = arena.allocator(); const alloc = arena.allocator();
const label = analysis.getFunctionSignature(tree, proto); const label = analysis.getFunctionSignature(tree, proto);
const proto_comments = (try analysis.getDocComments(alloc, tree, fn_node, .Markdown)) orelse ""; const proto_comments = (try analysis.getDocComments(alloc, tree, fn_node, .markdown)) orelse "";
const arg_idx = if (skip_self_param) blk: { const arg_idx = if (skip_self_param) blk: {
const has_self_param = try analysis.hasSelfParam(arena, document_store, handle, proto); const has_self_param = try analysis.hasSelfParam(arena, document_store, handle, proto);
break :blk commas + @boolToInt(has_self_param); break :blk commas + @boolToInt(has_self_param);
} else commas; } else commas;
var params = std.ArrayListUnmanaged(ParameterInformation){}; var params = std.ArrayListUnmanaged(types.ParameterInformation){};
var param_it = proto.iterate(&tree); var param_it = proto.iterate(&tree);
while (ast.nextFnParam(&param_it)) |param| { while (ast.nextFnParam(&param_it)) |param| {
const param_comments = if (param.first_doc_comment) |dc| const param_comments = if (param.first_doc_comment) |dc|
try analysis.collectDocComments(alloc, tree, dc, .Markdown, false) try analysis.collectDocComments(alloc, tree, dc, .markdown, false)
else else
""; "";
@ -55,13 +53,19 @@ fn fnProtoToSignatureInfo(document_store: *DocumentStore, arena: *std.heap.Arena
} }
const param_label = tree.source[param_label_start..param_label_end]; const param_label = tree.source[param_label_start..param_label_end];
try params.append(alloc, .{ try params.append(alloc, .{
.label = param_label, .label = .{ .string = param_label },
.documentation = types.MarkupContent{ .value = param_comments }, .documentation = .{ .MarkupContent = .{
.kind = .markdown,
.value = param_comments,
} },
}); });
} }
return types.SignatureInformation{ return types.SignatureInformation{
.label = label, .label = label,
.documentation = types.MarkupContent{ .value = proto_comments }, .documentation = .{ .MarkupContent = .{
.kind = .markdown,
.value = proto_comments,
} },
.parameters = params.items, .parameters = params.items,
.activeParameter = arg_idx, .activeParameter = arg_idx,
}; };
@ -188,20 +192,18 @@ pub fn getSignatureInfo(document_store: *DocumentStore, arena: *std.heap.ArenaAl
for (data.builtins) |builtin| { for (data.builtins) |builtin| {
if (std.mem.eql(u8, builtin.name, tree.tokenSlice(expr_last_token))) { if (std.mem.eql(u8, builtin.name, tree.tokenSlice(expr_last_token))) {
const param_infos = try alloc.alloc( const param_infos = try alloc.alloc(
types.SignatureInformation.ParameterInformation, types.ParameterInformation,
builtin.arguments.len, builtin.arguments.len,
); );
for (param_infos) |*info, i| { for (param_infos) |*info, i| {
info.* = .{ info.* = .{
.label = builtin.arguments[i], .label = .{ .string = builtin.arguments[i] },
.documentation = null, .documentation = null,
}; };
} }
return types.SignatureInformation{ return types.SignatureInformation{
.label = builtin.signature, .label = builtin.signature,
.documentation = .{ .documentation = .{ .string = builtin.documentation },
.value = builtin.documentation,
},
.parameters = param_infos, .parameters = param_infos,
.activeParameter = paren_commas, .activeParameter = paren_commas,
}; };

1
src/tres Submodule

@ -0,0 +1 @@
Subproject commit fb23d644500ae5b93dd71b5a8406d0c83e8e4fbe

View File

@ -1,536 +0,0 @@
const std = @import("std");
const string = []const u8;
// LSP types
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
pub const Position = struct {
line: u32,
character: u32,
};
pub const Range = struct {
start: Position,
end: Position,
};
pub const Location = struct {
uri: string,
range: Range,
};
/// Id of a request
pub const RequestId = union(enum) {
String: string,
Integer: i32,
};
/// Params of a response (result)
pub const ResponseParams = union(enum) {
SignatureHelp: SignatureHelp,
CompletionList: CompletionList,
Location: Location,
Hover: Hover,
DocumentSymbols: []DocumentSymbol,
SemanticTokensFull: SemanticTokens,
InlayHint: []InlayHint,
TextEdits: []TextEdit,
Locations: []Location,
WorkspaceEdit: WorkspaceEdit,
InitializeResult: InitializeResult,
ConfigurationParams: ConfigurationParams,
RegistrationParams: RegistrationParams,
DocumentHighlight: []DocumentHighlight,
CodeAction: []CodeAction,
ApplyEdit: ApplyWorkspaceEditParams,
FoldingRange: []FoldingRange,
SelectionRange: []*SelectionRange,
};
pub const Response = struct {
jsonrpc: string = "2.0",
id: RequestId,
result: ResponseParams,
};
pub const Request = struct {
jsonrpc: string = "2.0",
id: RequestId,
method: []const u8,
params: ?ResponseParams,
};
pub const ResponseError = struct {
code: i32,
message: string,
data: std.json.Value,
};
pub const ErrorCodes = enum(i32) {
// Defined by JSON-RPC
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
// JSON-RPC reserved error codes
ServerNotInitialized = -32002,
UnknownErrorCode = -3200,
// LSP reserved error codes
RequestFailed = -32803,
ServerCancelled = -32802,
ContentModified = -32801,
RequestCancelled = -32800,
};
pub const Notification = struct {
jsonrpc: string = "2.0",
method: string,
params: NotificationParams,
};
pub const NotificationParams = union(enum) {
LogMessage: struct {
type: MessageType,
message: string,
},
PublishDiagnostics: struct {
uri: string,
diagnostics: []Diagnostic,
},
ShowMessage: struct {
type: MessageType,
message: string,
},
};
/// Type of a debug message
pub const MessageType = enum(i64) {
Error = 1,
Warning = 2,
Info = 3,
Log = 4,
pub fn jsonStringify(value: MessageType, options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
pub const DiagnosticSeverity = enum(i64) {
Error = 1,
Warning = 2,
Information = 3,
Hint = 4,
pub fn jsonStringify(value: DiagnosticSeverity, options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
pub const DiagnosticRelatedInformation = struct {
location: Location,
message: string,
};
pub const Diagnostic = struct {
range: Range,
severity: ?DiagnosticSeverity,
code: ?string,
source: ?string,
message: string,
relatedInformation: ?[]DiagnosticRelatedInformation = null,
};
pub const WorkspaceEdit = struct {
changes: std.StringHashMapUnmanaged(std.ArrayListUnmanaged(TextEdit)),
pub fn jsonStringify(self: WorkspaceEdit, options: std.json.StringifyOptions, writer: anytype) @TypeOf(writer).Error!void {
try writer.writeAll("{\"changes\": {");
var it = self.changes.iterator();
var idx: usize = 0;
while (it.next()) |entry| : (idx += 1) {
if (idx != 0) try writer.writeAll(", ");
try writer.writeByte('"');
try writer.writeAll(entry.key_ptr.*);
try writer.writeAll("\":");
try std.json.stringify(entry.value_ptr.items, options, writer);
}
try writer.writeAll("}}");
}
};
pub const TextEdit = struct {
range: Range,
newText: string,
};
pub const MarkupContent = struct {
pub const Kind = enum(u1) {
PlainText = 0,
Markdown = 1,
pub fn jsonStringify(value: Kind, options: std.json.StringifyOptions, out_stream: anytype) !void {
const str = switch (value) {
.PlainText => "plaintext",
.Markdown => "markdown",
};
try std.json.stringify(str, options, out_stream);
}
};
kind: Kind = .Markdown,
value: string,
};
pub const CompletionList = struct {
isIncomplete: bool,
items: []const CompletionItem,
};
pub const InsertTextFormat = enum(i64) {
PlainText = 1,
Snippet = 2,
pub fn jsonStringify(value: InsertTextFormat, options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
pub const Hover = struct {
contents: MarkupContent,
};
pub const SemanticTokens = struct {
data: []const u32,
};
pub const CompletionItem = struct {
pub const Kind = enum(i64) {
Text = 1,
Method = 2,
Function = 3,
Constructor = 4,
Field = 5,
Variable = 6,
Class = 7,
Interface = 8,
Module = 9,
Property = 10,
Unit = 11,
Value = 12,
Enum = 13,
Keyword = 14,
Snippet = 15,
Color = 16,
File = 17,
Reference = 18,
Folder = 19,
EnumMember = 20,
Constant = 21,
Struct = 22,
Event = 23,
Operator = 24,
TypeParameter = 25,
pub fn jsonStringify(value: Kind, options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
label: string,
labelDetails: ?CompletionItemLabelDetails = null,
kind: Kind,
detail: ?string = null,
sortText: ?string = null,
filterText: ?string = null,
insertText: ?string = null,
insertTextFormat: ?InsertTextFormat = .PlainText,
documentation: ?MarkupContent = null,
// FIXME: i commented this out, because otherwise the vscode client complains about *ranges*
// and breaks code completion entirely
// see: https://github.com/zigtools/zls-vscode/pull/33
// textEdit: ?TextEdit = null,
};
pub const CompletionItemLabelDetails = struct {
detail: ?string,
description: ?string,
sortText: ?string = null,
};
pub const DocumentSymbol = struct {
const Kind = enum(u32) {
File = 1,
Module = 2,
Namespace = 3,
Package = 4,
Class = 5,
Method = 6,
Property = 7,
Field = 8,
Constructor = 9,
Enum = 10,
Interface = 11,
Function = 12,
Variable = 13,
Constant = 14,
String = 15,
Number = 16,
Boolean = 17,
Array = 18,
Object = 19,
Key = 20,
Null = 21,
EnumMember = 22,
Struct = 23,
Event = 24,
Operator = 25,
TypeParameter = 26,
pub fn jsonStringify(value: Kind, options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
name: string,
detail: ?string = null,
kind: Kind,
deprecated: bool = false,
range: Range,
selectionRange: Range,
children: []const DocumentSymbol = &[_]DocumentSymbol{},
};
pub const WorkspaceFolder = struct {
uri: string,
name: string,
};
pub const SignatureInformation = struct {
pub const ParameterInformation = struct {
// TODO Can also send a pair of encoded offsets
label: string,
documentation: ?MarkupContent,
};
label: string,
documentation: ?MarkupContent,
parameters: ?[]const ParameterInformation,
activeParameter: ?u32,
};
pub const SignatureHelp = struct {
signatures: ?[]const SignatureInformation,
activeSignature: ?u32,
activeParameter: ?u32,
};
pub const InlayHint = struct {
position: Position,
label: string,
kind: InlayHintKind,
tooltip: MarkupContent,
paddingLeft: bool,
paddingRight: bool,
// appends a colon to the label and reduces the output size
pub fn jsonStringify(value: InlayHint, options: std.json.StringifyOptions, writer: anytype) @TypeOf(writer).Error!void {
try writer.writeAll("{\"position\":");
try std.json.stringify(value.position, options, writer);
try writer.writeAll(",\"label\":\"");
try writer.writeAll(value.label);
try writer.writeAll(":\",\"kind\":");
try std.json.stringify(value.kind, options, writer);
if (value.tooltip.value.len != 0) {
try writer.writeAll(",\"tooltip\":");
try std.json.stringify(value.tooltip, options, writer);
}
if (value.paddingLeft) try writer.writeAll(",\"paddingLeft\":true");
if (value.paddingRight) try writer.writeAll(",\"paddingRight\":true");
try writer.writeByte('}');
}
};
pub const InlayHintKind = enum(i64) {
Type = 1,
Parameter = 2,
pub fn jsonStringify(value: InlayHintKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
pub const CodeActionKind = enum {
Empty,
QuickFix,
Refactor,
RefactorExtract,
RefactorInline,
RefactorRewrite,
Source,
SourceOrganizeImports,
SourceFixAll,
pub fn jsonStringify(value: CodeActionKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
const name = switch (value) {
.Empty => "",
.QuickFix => "quickfix",
.Refactor => "refactor",
.RefactorExtract => "refactor.extract",
.RefactorInline => "refactor.inline",
.RefactorRewrite => "refactor.rewrite",
.Source => "source",
.SourceOrganizeImports => "source.organizeImports",
.SourceFixAll => "source.fixAll",
};
try std.json.stringify(name, options, out_stream);
}
};
pub const CodeAction = struct {
title: string,
kind: CodeActionKind,
// diagnostics: []Diagnostic,
isPreferred: bool,
edit: WorkspaceEdit,
};
pub const ApplyWorkspaceEditParams = struct {
label: string,
edit: WorkspaceEdit,
};
pub const PositionEncodingKind = enum {
utf8,
utf16,
utf32,
pub fn jsonStringify(value: PositionEncodingKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
const str = switch (value) {
.utf8 => "utf-8",
.utf16 => "utf-16",
.utf32 => "utf-32",
};
try std.json.stringify(str, options, out_stream);
}
};
const TextDocumentSyncKind = enum(u32) {
None = 0,
Full = 1,
Incremental = 2,
pub fn jsonStringify(value: @This(), options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
// Only includes options we set in our initialize result.
const InitializeResult = struct {
capabilities: struct {
positionEncoding: PositionEncodingKind,
signatureHelpProvider: struct {
triggerCharacters: []const string,
retriggerCharacters: []const string,
},
textDocumentSync: struct {
openClose: bool,
change: TextDocumentSyncKind,
willSave: bool,
willSaveWaitUntil: bool,
save: bool,
},
renameProvider: bool,
completionProvider: struct {
resolveProvider: bool,
triggerCharacters: []const string,
completionItem: struct { labelDetailsSupport: bool },
},
documentHighlightProvider: bool,
hoverProvider: bool,
codeActionProvider: bool,
declarationProvider: bool,
definitionProvider: bool,
typeDefinitionProvider: bool,
implementationProvider: bool,
referencesProvider: bool,
documentSymbolProvider: bool,
colorProvider: bool,
documentFormattingProvider: bool,
documentRangeFormattingProvider: bool,
foldingRangeProvider: bool,
selectionRangeProvider: bool,
workspaceSymbolProvider: bool,
rangeProvider: bool,
documentProvider: bool,
workspace: ?struct {
workspaceFolders: ?struct {
supported: bool,
changeNotifications: bool,
},
},
semanticTokensProvider: struct {
full: bool,
range: bool,
legend: struct {
tokenTypes: []const string,
tokenModifiers: []const string,
},
},
inlayHintProvider: bool,
},
serverInfo: struct {
name: string,
version: ?string = null,
},
};
pub const ConfigurationParams = struct {
items: []const ConfigurationItem,
pub const ConfigurationItem = struct {
section: ?[]const u8,
};
};
pub const RegistrationParams = struct {
registrations: []const Registration,
pub const Registration = struct {
id: string,
method: string,
// registerOptions?: LSPAny;
};
};
pub const DocumentHighlightKind = enum(u8) {
Text = 1,
Read = 2,
Write = 3,
pub fn jsonStringify(value: DocumentHighlightKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
try std.json.stringify(@enumToInt(value), options, out_stream);
}
};
pub const DocumentHighlight = struct {
range: Range,
kind: ?DocumentHighlightKind,
};
pub const FoldingRange = struct {
startLine: usize,
endLine: usize,
};
pub const SelectionRange = struct {
range: Range,
parent: ?*SelectionRange,
};

View File

@ -2,13 +2,13 @@
// zigbot9001 to take advantage of zls' tools // zigbot9001 to take advantage of zls' tools
pub const analysis = @import("analysis.zig"); pub const analysis = @import("analysis.zig");
pub const header = @import("header.zig"); pub const Header = @import("Header.zig");
pub const debug = @import("debug.zig");
pub const offsets = @import("offsets.zig"); pub const offsets = @import("offsets.zig");
pub const requests = @import("requests.zig");
pub const Config = @import("Config.zig"); pub const Config = @import("Config.zig");
pub const Server = @import("Server.zig"); pub const Server = @import("Server.zig");
pub const translate_c = @import("translate_c.zig"); pub const translate_c = @import("translate_c.zig");
pub const types = @import("types.zig"); pub const types = @import("lsp.zig");
pub const URI = @import("uri.zig"); pub const URI = @import("uri.zig");
pub const DocumentStore = @import("DocumentStore.zig"); pub const DocumentStore = @import("DocumentStore.zig");
pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");

File diff suppressed because one or more lines are too long

View File

@ -69,7 +69,7 @@ fn testConvertCInclude(cimport_source: []const u8, expected: []const u8) !void {
else => continue, else => continue,
} }
if(!std.mem.eql(u8, ast.tokenSlice(main_tokens[index]), "@cImport")) continue; if (!std.mem.eql(u8, ast.tokenSlice(main_tokens[index]), "@cImport")) continue;
break :blk @intCast(Ast.Node.Index, index); break :blk @intCast(Ast.Node.Index, index);
} }

View File

@ -8,13 +8,12 @@ const ErrorBuilder = @import("../ErrorBuilder.zig");
const types = zls.types; const types = zls.types;
const offsets = zls.offsets; const offsets = zls.offsets;
const requests = zls.requests;
const allocator: std.mem.Allocator = std.testing.allocator; const allocator: std.mem.Allocator = std.testing.allocator;
const Completion = struct { const Completion = struct {
label: []const u8, label: []const u8,
kind: types.CompletionItem.Kind, kind: types.CompletionItemKind,
detail: ?[]const u8 = null, detail: ?[]const u8 = null,
}; };
@ -412,16 +411,13 @@ fn testCompletion(source: []const u8, expected_completions: []const Completion)
try ctx.requestDidOpen(test_uri, text); try ctx.requestDidOpen(test_uri, text);
const request = requests.Completion{ const params = types.CompletionParams{
.params = .{ .textDocument = .{ .uri = test_uri },
.textDocument = .{ .uri = test_uri }, .position = offsets.indexToPosition(source, cursor_idx, ctx.server.offset_encoding),
.position = offsets.indexToPosition(source, cursor_idx, ctx.server.offset_encoding),
},
}; };
@setEvalBranchQuota(2000); @setEvalBranchQuota(5000);
const response = try ctx.requestGetResponse(?types.CompletionList, "textDocument/completion", request); const response = try ctx.requestGetResponse(?types.CompletionList, "textDocument/completion", params);
defer response.deinit();
const completion_list: types.CompletionList = response.result orelse { const completion_list: types.CompletionList = response.result orelse {
std.debug.print("Server returned `null` as the result\n", .{}); std.debug.print("Server returned `null` as the result\n", .{});
@ -462,11 +458,11 @@ fn testCompletion(source: []const u8, expected_completions: []const Completion)
unreachable; unreachable;
}; };
if (expected_completion.kind != actual_completion.kind) { if (actual_completion.kind == null or expected_completion.kind != actual_completion.kind.?) {
try error_builder.msgAtIndex("label '{s}' should be of kind '{s}' but was '{s}'!", cursor_idx, .err, .{ try error_builder.msgAtIndex("label '{s}' should be of kind '{s}' but was '{?s}'!", cursor_idx, .err, .{
label, label,
@tagName(expected_completion.kind), @tagName(expected_completion.kind),
@tagName(actual_completion.kind), if (actual_completion.kind) |kind| @tagName(kind) else null,
}); });
return error.InvalidCompletionKind; return error.InvalidCompletionKind;
} }
@ -500,9 +496,15 @@ fn extractCompletionLabels(items: anytype) error{ DuplicateCompletionLabel, OutO
errdefer set.deinit(allocator); errdefer set.deinit(allocator);
try set.ensureTotalCapacity(allocator, items.len); try set.ensureTotalCapacity(allocator, items.len);
for (items) |item| { for (items) |item| {
switch (item.kind) { const maybe_kind = switch (@typeInfo(@TypeOf(item.kind))) {
.Keyword, .Snippet => continue, .Optional => item.kind,
else => {}, else => @as(?@TypeOf(item.kind), item.kind),
};
if (maybe_kind) |kind| {
switch (kind) {
.Keyword, .Snippet => continue,
else => {},
}
} }
if (set.fetchPutAssumeCapacity(item.label, {}) != null) return error.DuplicateCompletionLabel; if (set.fetchPutAssumeCapacity(item.label, {}) != null) return error.DuplicateCompletionLabel;
} }

View File

@ -2,10 +2,11 @@ const std = @import("std");
const zls = @import("zls"); const zls = @import("zls");
const builtin = @import("builtin"); const builtin = @import("builtin");
const tres = @import("tres");
const Context = @import("../context.zig").Context; const Context = @import("../context.zig").Context;
const types = zls.types; const types = zls.types;
const requests = zls.requests;
const allocator: std.mem.Allocator = std.testing.allocator; const allocator: std.mem.Allocator = std.testing.allocator;
@ -33,7 +34,7 @@ test "foldingRange - #801" {
\\ }; \\ };
\\} \\}
, ,
\\[] \\[{"startLine":1,"endLine":4},{"startLine":0,"endLine":5}]
); );
} }
@ -48,15 +49,16 @@ fn testFoldingRange(source: []const u8, expect: []const u8) !void {
try ctx.requestDidOpen(test_uri, source); try ctx.requestDidOpen(test_uri, source);
const request = requests.FoldingRange{ .params = .{ .textDocument = .{ .uri = test_uri } } }; const params = types.FoldingRangeParams{ .textDocument = .{ .uri = test_uri } };
const response = try ctx.requestGetResponse(?[]types.FoldingRange, "textDocument/foldingRange", request); const response = try ctx.requestGetResponse(?[]types.FoldingRange, "textDocument/foldingRange", params);
defer response.deinit();
var actual = std.ArrayList(u8).init(allocator); var actual = std.ArrayList(u8).init(allocator);
defer actual.deinit(); defer actual.deinit();
try std.json.stringify(response.result, .{}, actual.writer()); try tres.stringify(response.result, .{
.emit_null_optional_fields = false,
}, actual.writer());
try expectEqualJson(expect, actual.items); try expectEqualJson(expect, actual.items);
} }

View File

@ -8,7 +8,6 @@ const ErrorBuilder = @import("../ErrorBuilder.zig");
const types = zls.types; const types = zls.types;
const offsets = zls.offsets; const offsets = zls.offsets;
const requests = zls.requests;
const allocator: std.mem.Allocator = std.testing.allocator; const allocator: std.mem.Allocator = std.testing.allocator;
@ -83,7 +82,7 @@ fn testInlayHints(source: []const u8) !void {
const range = types.Range{ const range = types.Range{
.start = types.Position{ .line = 0, .character = 0 }, .start = types.Position{ .line = 0, .character = 0 },
.end = offsets.indexToPosition(phr.new_source, phr.new_source.len, .utf16), .end = offsets.indexToPosition(phr.new_source, phr.new_source.len, .@"utf-16"),
}; };
const InlayHint = struct { const InlayHint = struct {
@ -92,15 +91,12 @@ fn testInlayHints(source: []const u8) !void {
kind: types.InlayHintKind, kind: types.InlayHintKind,
}; };
const request = requests.InlayHint{ const params = types.InlayHintParams{
.params = .{ .textDocument = .{ .uri = test_uri },
.textDocument = .{ .uri = test_uri }, .range = range,
.range = range,
},
}; };
const response = try ctx.requestGetResponse(?[]InlayHint, "textDocument/inlayHint", request); const response = try ctx.requestGetResponse(?[]InlayHint, "textDocument/inlayHint", params);
defer response.deinit();
const hints: []InlayHint = response.result orelse { const hints: []InlayHint = response.result orelse {
std.debug.print("Server returned `null` as the result\n", .{}); std.debug.print("Server returned `null` as the result\n", .{});
@ -124,7 +120,7 @@ fn testInlayHints(source: []const u8) !void {
for (hints) |hint| { for (hints) |hint| {
if (position.line != hint.position.line or position.character != hint.position.character) continue; if (position.line != hint.position.line or position.character != hint.position.character) continue;
const actual_label = hint.label[0 .. hint.label.len - 1]; // exclude : const actual_label = hint.label[0..hint.label.len];
if (!std.mem.eql(u8, expected_label, actual_label)) { if (!std.mem.eql(u8, expected_label, actual_label)) {
try error_builder.msgAtLoc("expected label `{s}` here but got `{s}`!", new_loc, .err, .{ expected_label, actual_label }); try error_builder.msgAtLoc("expected label `{s}` here but got `{s}`!", new_loc, .err, .{ expected_label, actual_label });

View File

@ -7,7 +7,6 @@ const Context = @import("../context.zig").Context;
const ErrorBuilder = @import("../ErrorBuilder.zig"); const ErrorBuilder = @import("../ErrorBuilder.zig");
const types = zls.types; const types = zls.types;
const requests = zls.requests;
const offsets = zls.offsets; const offsets = zls.offsets;
const allocator: std.mem.Allocator = std.testing.allocator; const allocator: std.mem.Allocator = std.testing.allocator;
@ -113,16 +112,13 @@ fn testReferences(source: []const u8) !void {
const var_name = offsets.locToSlice(source, var_loc); const var_name = offsets.locToSlice(source, var_loc);
const var_loc_middle = var_loc.start + (var_loc.end - var_loc.start) / 2; const var_loc_middle = var_loc.start + (var_loc.end - var_loc.start) / 2;
const request = requests.References{ const params = types.ReferenceParams{
.params = .{ .textDocument = .{ .uri = file_uri },
.textDocument = .{ .uri = file_uri }, .position = offsets.indexToPosition(source, var_loc_middle, ctx.server.offset_encoding),
.position = offsets.indexToPosition(source, var_loc_middle, ctx.server.offset_encoding), .context = .{ .includeDeclaration = true },
.context = .{ .includeDeclaration = true },
},
}; };
const response = try ctx.requestGetResponse(?[]types.Location, "textDocument/references", request); const response = try ctx.requestGetResponse(?[]types.Location, "textDocument/references", params);
defer response.deinit();
const locations: []types.Location = response.result orelse { const locations: []types.Location = response.result orelse {
std.debug.print("Server returned `null` as the result\n", .{}); std.debug.print("Server returned `null` as the result\n", .{});

View File

@ -38,20 +38,19 @@ fn testSelectionRange(source: []const u8, want: []const []const u8) !void {
try ctx.requestDidOpen(test_uri, phr.new_source); try ctx.requestDidOpen(test_uri, phr.new_source);
const position = offsets.locToRange(phr.new_source, phr.locations.items(.new)[0], .utf16).start; const position = offsets.locToRange(phr.new_source, phr.locations.items(.new)[0], .@"utf-16").start;
const SelectionRange = struct { const SelectionRange = struct {
range: types.Range, range: types.Range,
parent: ?*@This(), parent: ?*@This() = null,
}; };
const request = requests.SelectionRange{ .params = .{ const params = types.SelectionRangeParams{
.textDocument = .{ .uri = test_uri }, .textDocument = .{ .uri = test_uri },
.positions = &[_]types.Position{position}, .positions = &[_]types.Position{position},
} }; };
const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", request); const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", params);
defer response.deinit();
const selectionRanges: []SelectionRange = response.result orelse { const selectionRanges: []SelectionRange = response.result orelse {
std.debug.print("Server returned `null` as the result\n", .{}); std.debug.print("Server returned `null` as the result\n", .{});
@ -63,7 +62,7 @@ fn testSelectionRange(source: []const u8, want: []const []const u8) !void {
var it: ?*SelectionRange = &selectionRanges[0]; var it: ?*SelectionRange = &selectionRanges[0];
while (it) |r| { while (it) |r| {
const slice = offsets.rangeToSlice(phr.new_source, r.range, .utf16); const slice = offsets.rangeToSlice(phr.new_source, r.range, .@"utf-16");
(try got.addOne()).* = slice; (try got.addOne()).* = slice;
it = r.parent; it = r.parent;
} }

View File

@ -4,7 +4,7 @@ const builtin = @import("builtin");
const Context = @import("../context.zig").Context; const Context = @import("../context.zig").Context;
const requests = zls.requests; const types = zls.types;
const allocator: std.mem.Allocator = std.testing.allocator; const allocator: std.mem.Allocator = std.testing.allocator;
@ -41,21 +41,7 @@ fn testSemanticTokens(source: []const u8, expected: []const u32) !void {
var ctx = try Context.init(); var ctx = try Context.init();
defer ctx.deinit(); defer ctx.deinit();
const open_document = requests.OpenDocument{ try ctx.requestDidOpen(file_uri, source);
.params = .{
.textDocument = .{
.uri = file_uri,
// .languageId = "zig",
// .version = 420,
.text = source,
},
},
};
const did_open_method = try std.json.stringifyAlloc(allocator, open_document.params, .{});
defer allocator.free(did_open_method);
try ctx.request("textDocument/didOpen", did_open_method, null);
const Response = struct { const Response = struct {
data: []const u32, data: []const u32,

View File

@ -107,13 +107,13 @@ fn testIndexPosition(text: []const u8, index: usize, line: u32, characters: [3]u
const position16: types.Position = .{ .line = line, .character = characters[1] }; const position16: types.Position = .{ .line = line, .character = characters[1] };
const position32: types.Position = .{ .line = line, .character = characters[2] }; const position32: types.Position = .{ .line = line, .character = characters[2] };
try std.testing.expectEqual(position8, offsets.indexToPosition(text, index, .utf8)); try std.testing.expectEqual(position8, offsets.indexToPosition(text, index, .@"utf-8"));
try std.testing.expectEqual(position16, offsets.indexToPosition(text, index, .utf16)); try std.testing.expectEqual(position16, offsets.indexToPosition(text, index, .@"utf-16"));
try std.testing.expectEqual(position32, offsets.indexToPosition(text, index, .utf32)); try std.testing.expectEqual(position32, offsets.indexToPosition(text, index, .@"utf-32"));
try std.testing.expectEqual(index, offsets.positionToIndex(text, position8, .utf8)); try std.testing.expectEqual(index, offsets.positionToIndex(text, position8, .@"utf-8"));
try std.testing.expectEqual(index, offsets.positionToIndex(text, position16, .utf16)); try std.testing.expectEqual(index, offsets.positionToIndex(text, position16, .@"utf-16"));
try std.testing.expectEqual(index, offsets.positionToIndex(text, position32, .utf32)); try std.testing.expectEqual(index, offsets.positionToIndex(text, position32, .@"utf-32"));
} }
fn testTokenToLoc(text: [:0]const u8, token_index: std.zig.Ast.TokenIndex, start: usize, end: usize) !void { fn testTokenToLoc(text: [:0]const u8, token_index: std.zig.Ast.TokenIndex, start: usize, end: usize) !void {
@ -135,7 +135,7 @@ fn testTokenIndexToLoc(text: [:0]const u8, index: usize, start: usize, end: usiz
fn testAdvancePosition(text: [:0]const u8, expected_line: u32, expected_character: u32, line: u32, character: u32, from: usize, to: usize) !void { fn testAdvancePosition(text: [:0]const u8, expected_line: u32, expected_character: u32, line: u32, character: u32, from: usize, to: usize) !void {
const expected: types.Position = .{ .line = expected_line, .character = expected_character }; const expected: types.Position = .{ .line = expected_line, .character = expected_character };
const actual = offsets.advancePosition(text, .{ .line = line, .character = character }, from, to, .utf16); const actual = offsets.advancePosition(text, .{ .line = line, .character = character }, from, to, .@"utf-16");
try std.testing.expectEqual(expected, actual); try std.testing.expectEqual(expected, actual);
} }
@ -143,9 +143,9 @@ fn testAdvancePosition(text: [:0]const u8, expected_line: u32, expected_characte
fn testConvertPositionEncoding(text: [:0]const u8, line: u32, character: u32, new_characters: [3]u32) !void { fn testConvertPositionEncoding(text: [:0]const u8, line: u32, character: u32, new_characters: [3]u32) !void {
const position: types.Position = .{ .line = line, .character = character }; const position: types.Position = .{ .line = line, .character = character };
const position8 = offsets.convertPositionEncoding(text, position, .utf8, .utf8); const position8 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-8");
const position16 = offsets.convertPositionEncoding(text, position, .utf8, .utf16); const position16 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-16");
const position32 = offsets.convertPositionEncoding(text, position, .utf8, .utf32); const position32 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-32");
try std.testing.expectEqual(line, position8.line); try std.testing.expectEqual(line, position8.line);
try std.testing.expectEqual(line, position16.line); try std.testing.expectEqual(line, position16.line);
@ -157,13 +157,13 @@ fn testConvertPositionEncoding(text: [:0]const u8, line: u32, character: u32, ne
} }
fn testCountCodeUnits(text: []const u8, counts: [3]usize) !void { fn testCountCodeUnits(text: []const u8, counts: [3]usize) !void {
try std.testing.expectEqual(counts[0], offsets.countCodeUnits(text, .utf8)); try std.testing.expectEqual(counts[0], offsets.countCodeUnits(text, .@"utf-8"));
try std.testing.expectEqual(counts[1], offsets.countCodeUnits(text, .utf16)); try std.testing.expectEqual(counts[1], offsets.countCodeUnits(text, .@"utf-16"));
try std.testing.expectEqual(counts[2], offsets.countCodeUnits(text, .utf32)); try std.testing.expectEqual(counts[2], offsets.countCodeUnits(text, .@"utf-32"));
} }
fn testGetNCodeUnitByteCount(text: []const u8, n: [3]usize) !void { fn testGetNCodeUnitByteCount(text: []const u8, n: [3]usize) !void {
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[0], .utf8)); try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[0], .@"utf-8"));
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[1], .utf16)); try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[1], .@"utf-16"));
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[2], .utf32)); try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[2], .@"utf-32"));
} }