Merge branch 'master' into intern-pool
This commit is contained in:
commit
d56a274c16
1
.gitignore
vendored
1
.gitignore
vendored
@ -2,3 +2,4 @@
|
||||
zig-*
|
||||
debug
|
||||
release
|
||||
*.zlsreplay
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -4,3 +4,6 @@
|
||||
[submodule "src/tracy"]
|
||||
path = src/tracy
|
||||
url = https://github.com/wolfpld/tracy
|
||||
[submodule "src/tres"]
|
||||
path = src/tres
|
||||
url = https://github.com/ziglibs/tres.git
|
||||
|
25
README.md
25
README.md
@ -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
|
||||
cd zls
|
||||
zig build -Drelease-safe
|
||||
./zig-out/bin/zls --config # Configure ZLS
|
||||
```
|
||||
|
||||
#### 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.
|
||||
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.
|
||||
|
||||
### Configuration Options
|
||||
|
||||
You can configure zls by running `zls --config` or manually creating your own `zls.json` configuration file.
|
||||
zls will look for a zls.json configuration file in multiple locations with the following priority:
|
||||
You can configure zls by editing your `zls.json` configuration file.
|
||||
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 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 -->
|
||||
| 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_autofix` | `bool` | `false` | Whether to automatically fix errors on save. Currently supports adding and removing discards. |
|
||||
| `enable_import_embedfile_argument_completions` | `bool` | `false` | Whether to enable import/embedFile argument completions |
|
||||
| `enable_autofix` | `bool` | `true` | Whether to automatically fix errors on save. Currently supports adding and removing discards. |
|
||||
| `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_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_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) |
|
||||
@ -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 |
|
||||
| `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) |
|
||||
| `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 |
|
||||
| `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 |
|
||||
|
22
build.zig
22
build.zig
@ -50,6 +50,18 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
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_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;
|
||||
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) {
|
||||
const client_cpp = "src/tracy/TracyClient.cpp";
|
||||
|
||||
@ -117,6 +133,7 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
exe.install();
|
||||
|
||||
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();
|
||||
gen_cmd.addArgs(&.{
|
||||
@ -125,6 +142,10 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
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");
|
||||
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 = "tres", .source = .{ .path = tres_path } });
|
||||
tests.setBuildMode(.Debug);
|
||||
tests.setTarget(target);
|
||||
test_step.dependOn(&tests.step);
|
||||
|
29
flake.lock
29
flake.lock
@ -68,11 +68,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1670086663,
|
||||
"narHash": "sha256-hT8C8AQB74tdoCPwz4nlJypLMD7GI2F5q+vn+VE/qQk=",
|
||||
"lastModified": 1672057183,
|
||||
"narHash": "sha256-GN7/10DNNvs1FPj9tlZA2qgNdFuYKKuS3qlHTqAxasQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "813836d64fa57285d108f0dbf2356457ccd304e3",
|
||||
"rev": "b139e44d78c36c69bcbb825b20dbfa51e7738347",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -88,9 +88,26 @@
|
||||
"gitignore": "gitignore",
|
||||
"known-folders": "known-folders",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"tres": "tres",
|
||||
"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": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
@ -99,11 +116,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1670113356,
|
||||
"narHash": "sha256-43aMRMU0OuBin6M2LM+nxVG+whazyHuHnUvu92xoth0=",
|
||||
"lastModified": 1672142864,
|
||||
"narHash": "sha256-uXljuSZK8DP5c4o9u+gcF+Yc3dKYH1wsHmDpWcFBVRQ=",
|
||||
"owner": "mitchellh",
|
||||
"repo": "zig-overlay",
|
||||
"rev": "17352071583eda4be43fa2a312f6e061326374f7",
|
||||
"rev": "16e9191142d2a13d7870c03e500842321a466a74",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -12,9 +12,12 @@
|
||||
|
||||
known-folders.url = "github:ziglibs/known-folders";
|
||||
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
|
||||
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||
inherit (gitignore.lib) gitignoreSource;
|
||||
@ -35,7 +38,7 @@
|
||||
dontInstall = true;
|
||||
buildPhase = ''
|
||||
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";
|
||||
};
|
||||
|
23
schema.json
23
schema.json
@ -7,7 +7,7 @@
|
||||
"enable_snippets": {
|
||||
"description": "Enables snippet completions when the client also supports them",
|
||||
"type": "boolean",
|
||||
"default": "false"
|
||||
"default": "true"
|
||||
},
|
||||
"enable_ast_check_diagnostics": {
|
||||
"description": "Whether to enable ast-check diagnostics",
|
||||
@ -17,12 +17,12 @@
|
||||
"enable_autofix": {
|
||||
"description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.",
|
||||
"type": "boolean",
|
||||
"default": "false"
|
||||
"default": "true"
|
||||
},
|
||||
"enable_import_embedfile_argument_completions": {
|
||||
"description": "Whether to enable import/embedFile argument completions",
|
||||
"type": "boolean",
|
||||
"default": "false"
|
||||
"default": "true"
|
||||
},
|
||||
"enable_semantic_tokens": {
|
||||
"description": "Enables semantic token support when the client also supports it",
|
||||
@ -32,7 +32,7 @@
|
||||
"enable_inlay_hints": {
|
||||
"description": "Enables inlay hint support when the client also supports it",
|
||||
"type": "boolean",
|
||||
"default": "false"
|
||||
"default": "true"
|
||||
},
|
||||
"inlay_hints_show_builtin": {
|
||||
"description": "Enable inlay hints for builtin functions",
|
||||
@ -89,6 +89,21 @@
|
||||
"type": "integer",
|
||||
"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": {
|
||||
"description": "Path to 'builtin;' useful for debugging, automatically set if let null",
|
||||
"type": "string",
|
||||
|
@ -5,22 +5,22 @@
|
||||
//! GENERATED BY src/config_gen/config_gen.zig
|
||||
|
||||
/// Enables snippet completions when the client also supports them
|
||||
enable_snippets: bool = false,
|
||||
enable_snippets: bool = true,
|
||||
|
||||
/// Whether to enable ast-check diagnostics
|
||||
enable_ast_check_diagnostics: bool = true,
|
||||
|
||||
/// Whether to automatically fix errors on save. Currently supports adding and removing discards.
|
||||
enable_autofix: bool = false,
|
||||
enable_autofix: bool = true,
|
||||
|
||||
/// 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
|
||||
enable_semantic_tokens: bool = true,
|
||||
|
||||
/// 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
|
||||
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)
|
||||
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
|
||||
builtin_path: ?[]const u8 = null,
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const types = @import("types.zig");
|
||||
const requests = @import("requests.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const URI = @import("uri.zig");
|
||||
const analysis = @import("analysis.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
@ -67,7 +66,6 @@ pub const Handle = struct {
|
||||
/// `DocumentStore.build_files` is guaranteed to contain this uri
|
||||
/// uri memory managed by its build_file
|
||||
associated_build_file: ?Uri = null,
|
||||
is_build_file: bool = false,
|
||||
|
||||
pub fn deinit(self: *Handle, allocator: std.mem.Allocator) void {
|
||||
self.document_scope.deinit(allocator);
|
||||
@ -125,11 +123,11 @@ fn getOrLoadHandleInternal(self: *DocumentStore, uri: Uri) !?*const Handle {
|
||||
var handle = try self.allocator.create(Handle);
|
||||
errdefer self.allocator.destroy(handle);
|
||||
|
||||
const dependency_uri = try self.allocator.dupe(u8, uri);
|
||||
handle.* = (try self.createDocumentFromURI(dependency_uri, false)) orelse return error.Unknown; // error name doesn't matter
|
||||
handle.* = (try self.createDocumentFromURI(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);
|
||||
std.debug.assert(!gop.found_existing);
|
||||
if (gop.found_existing) return error.Unknown;
|
||||
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
@ -147,18 +145,15 @@ pub fn openDocument(self: *DocumentStore, uri: Uri, text: []const u8) error{OutO
|
||||
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);
|
||||
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);
|
||||
|
||||
try self.handles.putNoClobber(self.allocator, duped_uri, handle);
|
||||
try self.handles.putNoClobber(self.allocator, handle.uri, handle);
|
||||
|
||||
return handle.*;
|
||||
}
|
||||
@ -229,7 +224,7 @@ pub fn applySave(self: *DocumentStore, handle: *const Handle) !void {
|
||||
const tracy_zone = tracy.trace(@src());
|
||||
defer tracy_zone.end();
|
||||
|
||||
if (handle.is_build_file) {
|
||||
if (isBuildFile(handle.uri)) {
|
||||
const build_file = self.build_files.getPtr(handle.uri).?;
|
||||
|
||||
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());
|
||||
defer tracy_zone.end();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(self.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
var reachable_handles = std.StringHashMapUnmanaged(void){};
|
||||
defer reachable_handles.deinit(self.allocator);
|
||||
defer reachable_handles.deinit(arena.allocator());
|
||||
|
||||
var queue = std.ArrayListUnmanaged(Uri){};
|
||||
defer {
|
||||
for (queue.items) |uri| {
|
||||
self.allocator.free(uri);
|
||||
}
|
||||
queue.deinit(self.allocator);
|
||||
}
|
||||
|
||||
for (self.handles.values()) |handle| {
|
||||
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| {
|
||||
if (reachable_handles.contains(uri)) continue;
|
||||
|
||||
try reachable_handles.putNoClobber(self.allocator, uri, {});
|
||||
const gop = try reachable_handles.getOrPut(arena.allocator(), uri);
|
||||
if (gop.found_existing) 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;
|
||||
@ -287,7 +278,7 @@ fn garbageCollectionImports(self: *DocumentStore) error{OutOfMemory}!void {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
std.log.debug("Closing document {s}", .{handle.uri});
|
||||
log.debug("Closing document {s}", .{handle.uri});
|
||||
var kv = self.handles.fetchSwapRemove(handle.uri).?;
|
||||
kv.value.deinit(self.allocator);
|
||||
self.allocator.destroy(kv.value);
|
||||
@ -321,7 +312,7 @@ fn garbageCollectionCImports(self: *DocumentStore) error{OutOfMemory}!void {
|
||||
.failure => "",
|
||||
.success => |uri| uri,
|
||||
};
|
||||
std.log.debug("Destroying cimport {s}", .{message});
|
||||
log.debug("Destroying cimport {s}", .{message});
|
||||
kv.value.deinit(self.allocator);
|
||||
}
|
||||
}
|
||||
@ -347,11 +338,24 @@ fn garbageCollectionBuildFiles(self: *DocumentStore) error{OutOfMemory}!void {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
/// has to be freed with `std.json.parseFree`
|
||||
fn loadBuildAssociatedConfiguration(allocator: std.mem.Allocator, build_file: BuildFile) !BuildAssociatedConfig {
|
||||
@ -451,6 +455,7 @@ fn loadBuildConfiguration(
|
||||
const parse_options = std.json.ParseOptions{ .allocator = allocator };
|
||||
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;
|
||||
errdefer std.json.parseFree(BuildConfig, build_config, parse_options);
|
||||
|
||||
for (build_config.packages) |*pkg| {
|
||||
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());
|
||||
defer tracy_zone.end();
|
||||
|
||||
var checked_uris = std.StringHashMap(void).init(self.allocator);
|
||||
defer {
|
||||
var it = checked_uris.iterator();
|
||||
while (it.next()) |entry|
|
||||
self.allocator.free(entry.key_ptr.*);
|
||||
|
||||
checked_uris.deinit();
|
||||
}
|
||||
var checked_uris = std.StringHashMapUnmanaged(void){};
|
||||
defer checked_uris.deinit(self.allocator);
|
||||
|
||||
for (build_file.config.packages) |package| {
|
||||
const package_uri = try URI.fromPath(self.allocator, package.path);
|
||||
@ -569,7 +568,7 @@ fn uriAssociatedWithBuild(
|
||||
return true;
|
||||
}
|
||||
|
||||
if (try self.uriInImports(&checked_uris, package_uri, uri))
|
||||
if (try self.uriInImports(&checked_uris, build_file, package_uri, uri))
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -578,38 +577,47 @@ fn uriAssociatedWithBuild(
|
||||
|
||||
fn uriInImports(
|
||||
self: *DocumentStore,
|
||||
checked_uris: *std.StringHashMap(void),
|
||||
checked_uris: *std.StringHashMapUnmanaged(void),
|
||||
build_file: BuildFile,
|
||||
source_uri: Uri,
|
||||
uri: Uri,
|
||||
) error{OutOfMemory}!bool {
|
||||
if (checked_uris.contains(source_uri))
|
||||
return false;
|
||||
|
||||
if (isInStd(source_uri)) return false;
|
||||
|
||||
// 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;
|
||||
|
||||
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| {
|
||||
if (std.mem.eql(u8, uri, import_uri))
|
||||
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 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 {
|
||||
const tracy_zone = tracy.trace(@src());
|
||||
defer tracy_zone.end();
|
||||
|
||||
var handle: Handle = blk: {
|
||||
errdefer self.allocator.free(uri);
|
||||
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);
|
||||
errdefer tree.deinit(self.allocator);
|
||||
|
||||
@ -618,7 +626,7 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
|
||||
|
||||
break :blk Handle{
|
||||
.open = open,
|
||||
.uri = uri,
|
||||
.uri = duped_uri,
|
||||
.text = text,
|
||||
.tree = tree,
|
||||
.document_scope = document_scope,
|
||||
@ -629,7 +637,7 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
|
||||
defer {
|
||||
if (handle.associated_build_file) |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});
|
||||
} else {
|
||||
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.cimports = try self.collectCIncludes(handle);
|
||||
|
||||
// TODO: Better logic for detecting std or subdirectories?
|
||||
const in_std = std.mem.indexOf(u8, uri, "/std/") != null;
|
||||
if (self.config.zig_exe_path != null and std.mem.endsWith(u8, uri, "/build.zig") and !in_std) {
|
||||
const dupe_uri = try self.allocator.dupe(u8, uri);
|
||||
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| {
|
||||
if (self.config.zig_exe_path != null and isBuildFile(handle.uri) and !isInStd(handle.uri)) {
|
||||
const gop = try self.build_files.getOrPut(self.allocator, uri);
|
||||
errdefer |err| {
|
||||
self.build_files.swapRemoveAt(gop.index);
|
||||
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: {
|
||||
log.debug("Going to walk down the tree towards: {s}", .{uri});
|
||||
if (!gop.found_existing) {
|
||||
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
|
||||
// determine if the uri we're interested in is involved with the build.
|
||||
// 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;
|
||||
defer self.allocator.free(path);
|
||||
|
||||
var prev_build_file: ?Uri = null;
|
||||
var build_it = try BuildDotZigIterator.init(self.allocator, path);
|
||||
while (try build_it.next()) |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) {
|
||||
errdefer self.build_files.swapRemoveAt(gop.index);
|
||||
gop.value_ptr.* = try self.createBuildFile(build_file_uri);
|
||||
} else {
|
||||
self.allocator.free(build_file_uri);
|
||||
}
|
||||
|
||||
if (try self.uriAssociatedWithBuild(gop.value_ptr.*, uri)) {
|
||||
handle.associated_build_file = build_file_uri;
|
||||
handle.associated_build_file = gop.key_ptr.*;
|
||||
break;
|
||||
} else {
|
||||
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| {
|
||||
} else if (handle.associated_build_file == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
/// takes ownership of the uri passed in.
|
||||
fn createDocumentFromURI(self: *DocumentStore, uri: Uri, open: bool) error{OutOfMemory}!?Handle {
|
||||
const tracy_zone = tracy.trace(@src());
|
||||
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);
|
||||
}
|
||||
|
||||
/// Caller owns returned memory.
|
||||
fn collectImportUris(self: *const DocumentStore, handle: Handle) error{OutOfMemory}!std.ArrayListUnmanaged(Uri) {
|
||||
const tracy_zone = tracy.trace(@src());
|
||||
defer tracy_zone.end();
|
||||
|
||||
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
|
||||
var i: usize = 0;
|
||||
while (i < imports.items.len) {
|
||||
const maybe_uri = try self.uriFromImportStr(self.allocator, handle, imports.items[i]);
|
||||
|
||||
|
57
src/Header.zig
Normal file
57
src/Header.zig
Normal 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);
|
||||
}
|
1689
src/Server.zig
1689
src/Server.zig
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
const std = @import("std");
|
||||
const DocumentStore = @import("DocumentStore.zig");
|
||||
const Ast = std.zig.Ast;
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
const log = std.log.scoped(.analysis);
|
||||
const ast = @import("ast.zig");
|
||||
@ -19,7 +19,7 @@ pub fn deinit() void {
|
||||
}
|
||||
|
||||
/// 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_kind = tree.nodes.items(.tag)[node];
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
defer lines.deinit();
|
||||
const tokens = tree.tokens.items(.tag);
|
||||
@ -81,7 +81,7 @@ pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments:
|
||||
} 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.
|
||||
@ -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).
|
||||
pub fn collectImports(allocator: std.mem.Allocator, tree: Ast) error{OutOfMemory}!std.ArrayListUnmanaged([]const u8) {
|
||||
var imports = std.ArrayListUnmanaged([]const u8){};
|
||||
errdefer {
|
||||
for (imports.items) |imp| {
|
||||
allocator.free(imp);
|
||||
}
|
||||
imports.deinit(allocator);
|
||||
}
|
||||
errdefer imports.deinit(allocator);
|
||||
|
||||
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 (node_tags[params[0]] != .string_literal) return null;
|
||||
|
||||
const import_str = tree.tokenSlice(tree.nodes.items(.main_token)[params[0]]);
|
||||
return import_str[1 .. import_str.len - 1];
|
||||
}
|
||||
@ -2406,41 +2403,23 @@ pub const DocumentScope = struct {
|
||||
error_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 {
|
||||
for (self.scopes.items) |*scope| {
|
||||
scope.deinit(allocator);
|
||||
}
|
||||
self.scopes.deinit(allocator);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
@ -2575,13 +2554,18 @@ fn makeInnerScope(allocator: std.mem.Allocator, context: ScopeContext, node_idx:
|
||||
|
||||
if (container_field) |_| {
|
||||
if (!std.mem.eql(u8, name, "_")) {
|
||||
var doc = if (try getDocComments(allocator, tree, decl, .Markdown)) |docs|
|
||||
types.MarkupContent{ .kind = .Markdown, .value = docs }
|
||||
else
|
||||
null;
|
||||
var gop_res = try context.enums.getOrPut(allocator, .{ .label = name, .kind = .Constant, .insertText = name, .insertTextFormat = .PlainText, .documentation = doc });
|
||||
const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation);
|
||||
|
||||
var doc: Documentation = if (try getDocComments(allocator, tree, decl, .markdown)) |docs| .{ .MarkupContent = types.MarkupContent{ .kind = .markdown, .value = docs } } else null;
|
||||
var gop_res = try context.enums.getOrPut(allocator, .{
|
||||
.label = name,
|
||||
.kind = .Constant,
|
||||
.insertText = name,
|
||||
.insertTextFormat = .PlainText,
|
||||
.documentation = doc,
|
||||
});
|
||||
if (gop_res.found_existing) {
|
||||
if (doc) |d| allocator.free(d.value);
|
||||
if (doc) |d| allocator.free(d.MarkupContent.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
src/ast.zig
11
src/ast.zig
@ -167,15 +167,15 @@ fn fullWhile(tree: Ast, info: full.While.Components) full.While {
|
||||
.else_token = undefined,
|
||||
.error_token = null,
|
||||
};
|
||||
var tok_i = info.while_token - 1;
|
||||
var tok_i = info.while_token -| 1;
|
||||
if (token_tags[tok_i] == .keyword_inline) {
|
||||
result.inline_token = tok_i;
|
||||
tok_i -= 1;
|
||||
}
|
||||
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);
|
||||
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,
|
||||
.switch_comma,
|
||||
=> {
|
||||
if (datas[n].rhs != 0) {
|
||||
const members = tree.extraData(datas[n].rhs, Node.SubRange);
|
||||
std.debug.assert(members.end - members.start > 0);
|
||||
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,
|
||||
.struct_init_dot,
|
||||
|
@ -5,8 +5,7 @@ const DocumentStore = @import("DocumentStore.zig");
|
||||
const analysis = @import("analysis.zig");
|
||||
const ast = @import("ast.zig");
|
||||
|
||||
const types = @import("types.zig");
|
||||
const requests = @import("requests.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
|
||||
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 {
|
||||
var text_edits = std.ArrayListUnmanaged(types.TextEdit){};
|
||||
try text_edits.appendSlice(self.arena.allocator(), edits);
|
||||
|
||||
const allocator = self.arena.allocator();
|
||||
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;
|
||||
}
|
||||
@ -74,7 +71,7 @@ fn handleNonCamelcaseFunction(builder: *Builder, actions: *std.ArrayListUnmanage
|
||||
|
||||
const action1 = types.CodeAction{
|
||||
.title = "make function name camelCase",
|
||||
.kind = .QuickFix,
|
||||
.kind = .quickfix,
|
||||
.isPreferred = true,
|
||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(loc, new_text)}),
|
||||
};
|
||||
@ -115,7 +112,7 @@ fn handleUnusedFunctionParameter(builder: *Builder, actions: *std.ArrayListUnman
|
||||
|
||||
const action1 = types.CodeAction{
|
||||
.title = "discard function parameter",
|
||||
.kind = .SourceFixAll,
|
||||
.kind = .@"source.fixAll",
|
||||
.isPreferred = true,
|
||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditPos(index, new_text)}),
|
||||
};
|
||||
@ -123,7 +120,7 @@ fn handleUnusedFunctionParameter(builder: *Builder, actions: *std.ArrayListUnman
|
||||
// TODO fix formatting
|
||||
const action2 = types.CodeAction{
|
||||
.title = "remove function parameter",
|
||||
.kind = .QuickFix,
|
||||
.kind = .quickfix,
|
||||
.isPreferred = false,
|
||||
.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(), .{
|
||||
.title = "discard value",
|
||||
.kind = .SourceFixAll,
|
||||
.kind = .@"source.fixAll",
|
||||
.isPreferred = true,
|
||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditPos(index, new_text)}),
|
||||
});
|
||||
@ -179,7 +176,7 @@ fn handleUnusedIndexCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(
|
||||
// TODO fix formatting
|
||||
try actions.append(builder.arena.allocator(), .{
|
||||
.title = "remove capture",
|
||||
.kind = .QuickFix,
|
||||
.kind = .quickfix,
|
||||
.isPreferred = true,
|
||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.loc, "")}),
|
||||
});
|
||||
@ -188,7 +185,7 @@ fn handleUnusedIndexCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(
|
||||
// |v, _| -> |v|
|
||||
try actions.append(builder.arena.allocator(), .{
|
||||
.title = "remove index capture",
|
||||
.kind = .QuickFix,
|
||||
.kind = .quickfix,
|
||||
.isPreferred = true,
|
||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(
|
||||
.{ .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|
|
||||
try actions.append(builder.arena.allocator(), .{
|
||||
.title = "discard capture",
|
||||
.kind = .QuickFix,
|
||||
.kind = .quickfix,
|
||||
.isPreferred = true,
|
||||
.edit = try builder.createWorkspaceEdit(&.{builder.createTextEditLoc(capture_locs.value, "_")}),
|
||||
});
|
||||
@ -216,7 +213,7 @@ fn handleUnusedCapture(builder: *Builder, actions: *std.ArrayListUnmanaged(types
|
||||
// TODO fix formatting
|
||||
try actions.append(builder.arena.allocator(), .{
|
||||
.title = "remove capture",
|
||||
.kind = .QuickFix,
|
||||
.kind = .quickfix,
|
||||
.isPreferred = true,
|
||||
.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(), .{
|
||||
.title = "remove pointless discard",
|
||||
.kind = .SourceFixAll,
|
||||
.kind = .@"source.fixAll",
|
||||
.isPreferred = true,
|
||||
.edit = try builder.createWorkspaceEdit(&.{
|
||||
builder.createTextEditLoc(edit_loc, ""),
|
||||
|
@ -4,155 +4,151 @@
|
||||
"name": "enable_snippets",
|
||||
"description": "Enables snippet completions when the client also supports them",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": "Do you want to enable snippets?"
|
||||
"default": "true",
|
||||
},
|
||||
{
|
||||
"name": "enable_ast_check_diagnostics",
|
||||
"description": "Whether to enable ast-check diagnostics",
|
||||
"type": "bool",
|
||||
"default": "true",
|
||||
"setup_question": "Do you want to enable ast-check diagnostics?"
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"name": "enable_autofix",
|
||||
"description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": "Do you want to zls to automatically try to fix errors on save? (supports adding & removing discards)"
|
||||
"default": "true",
|
||||
},
|
||||
{
|
||||
"name": "enable_import_embedfile_argument_completions",
|
||||
"description": "Whether to enable import/embedFile argument completions",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": "Do you want to enable @import/@embedFile argument path completion?"
|
||||
"default": "true",
|
||||
},
|
||||
{
|
||||
"name": "enable_semantic_tokens",
|
||||
"description": "Enables semantic token support when the client also supports it",
|
||||
"type": "bool",
|
||||
"default": "true",
|
||||
"setup_question": "Do you want to enable semantic highlighting?"
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"name": "enable_inlay_hints",
|
||||
"description": "Enables inlay hint support when the client also supports it",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": "Do you want to enable inlay hints?"
|
||||
"default": "true",
|
||||
},
|
||||
{
|
||||
"name": "inlay_hints_show_builtin",
|
||||
"description": "Enable inlay hints for builtin functions",
|
||||
"type": "bool",
|
||||
"default": "true",
|
||||
"setup_question": null
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"name": "inlay_hints_exclude_single_argument",
|
||||
"description": "Don't show inlay hints for single argument calls",
|
||||
"type": "bool",
|
||||
"default": "true",
|
||||
"setup_question": null
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"name": "inlay_hints_hide_redundant_param_names",
|
||||
"description": "Hides inlay hints when parameter name matches the identifier (e.g. foo: foo)",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": null
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"name": "inlay_hints_hide_redundant_param_names_last_token",
|
||||
"description": "Hides inlay hints when parameter name matches the last token of a parameter node (e.g. foo: bar.foo, foo: &foo)",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": null
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"name": "operator_completions",
|
||||
"description": "Enables `*` and `?` operators in completion lists",
|
||||
"type": "bool",
|
||||
"default": "true",
|
||||
"setup_question": "Do you want to enable .* and .? completions?"
|
||||
"default": "true"
|
||||
},
|
||||
{
|
||||
"name": "warn_style",
|
||||
"description": "Enables warnings for style guideline mismatches",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": "Do you want to enable style warnings?"
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"name": "highlight_global_var_declarations",
|
||||
"description": "Whether to highlight global var declarations",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": null
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"name": "use_comptime_interpreter",
|
||||
"description": "Whether to use the comptime interpreter",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": null
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"name": "include_at_in_builtins",
|
||||
"description": "Whether the @ sign should be part of the completion of builtins",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": null
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"name": "skip_std_references",
|
||||
"description": "When true, skips searching for references in std. Improves lookup speed for functions in user's code. Renaming and go-to-definition will continue to work as is",
|
||||
"type": "bool",
|
||||
"default": "false",
|
||||
"setup_question": null
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"name": "max_detail_length",
|
||||
"description": "The detail field of completions is truncated to be no longer than this (in bytes)",
|
||||
"type": "usize",
|
||||
"default": "1048576",
|
||||
"setup_question": null
|
||||
"default": "1048576"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"description": "Path to 'builtin;' useful for debugging, automatically set if let null",
|
||||
"type": "?[]const u8",
|
||||
"default": "null",
|
||||
"setup_question": null
|
||||
"default": "null"
|
||||
},
|
||||
{
|
||||
"name": "zig_lib_path",
|
||||
"description": "Zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports",
|
||||
"type": "?[]const u8",
|
||||
"default": "null",
|
||||
"setup_question": null
|
||||
"default": "null"
|
||||
},
|
||||
{
|
||||
"name": "zig_exe_path",
|
||||
"description": "Zig executable path, e.g. `/path/to/zig/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided",
|
||||
"type": "?[]const u8",
|
||||
"default": "null",
|
||||
"setup_question": null
|
||||
"default": "null"
|
||||
},
|
||||
{
|
||||
"name": "build_runner_path",
|
||||
"description": "Path to the `build_runner.zig` file provided by zls. null is equivalent to `${executable_directory}/build_runner.zig`",
|
||||
"type": "?[]const u8",
|
||||
"default": "null",
|
||||
"setup_question": null
|
||||
"default": "null"
|
||||
},
|
||||
{
|
||||
"name": "global_cache_path",
|
||||
"description": "Path to a directroy that will be used as zig's cache. null is equivalent to `${KnownFloders.Cache}/zls`",
|
||||
"type": "?[]const u8",
|
||||
"default": "null",
|
||||
"setup_question": null
|
||||
"default": "null"
|
||||
}
|
||||
]
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const tres = @import("tres");
|
||||
|
||||
const ConfigOption = struct {
|
||||
/// Name of config option
|
||||
@ -9,9 +11,6 @@ const ConfigOption = struct {
|
||||
type: []const u8,
|
||||
/// used in Config.zig as the default initializer
|
||||
default: []const u8,
|
||||
/// If set, this option can be configured through `zls --config`
|
||||
/// currently unused but could laer be used to automatically generate queries for setup.zig
|
||||
setup_question: ?[]const u8,
|
||||
};
|
||||
|
||||
const Config = struct {
|
||||
@ -46,9 +45,7 @@ fn zigTypeToTypescript(ty: []const u8) ![]const u8 {
|
||||
fn generateConfigFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void {
|
||||
_ = allocator;
|
||||
|
||||
const config_file = try std.fs.openFileAbsolute(path, .{
|
||||
.mode = .write_only,
|
||||
});
|
||||
const config_file = try std.fs.createFileAbsolute(path, .{});
|
||||
defer config_file.close();
|
||||
|
||||
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},
|
||||
\\
|
||||
, .{
|
||||
std.mem.trim(u8, option.description, " \t\n\r"),
|
||||
std.mem.trim(u8, option.name, " \t\n\r"),
|
||||
std.mem.trim(u8, option.type, " \t\n\r"),
|
||||
std.mem.trim(u8, option.default, " \t\n\r"),
|
||||
std.mem.trim(u8, option.description, &std.ascii.whitespace),
|
||||
std.mem.trim(u8, option.name, &std.ascii.whitespace),
|
||||
std.mem.trim(u8, option.type, &std.ascii.whitespace),
|
||||
std.mem.trim(u8, option.default, &std.ascii.whitespace),
|
||||
});
|
||||
}
|
||||
|
||||
@ -114,7 +111,7 @@ fn generateSchemaFile(allocator: std.mem.Allocator, config: Config, path: []cons
|
||||
\\ "properties":
|
||||
);
|
||||
|
||||
try serializeObjectMap(properties, .{
|
||||
try tres.stringify(properties, .{
|
||||
.whitespace = .{
|
||||
.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.flush();
|
||||
try schema_file.setEndPos(try schema_file.getPos());
|
||||
}
|
||||
|
||||
fn updateREADMEFile(allocator: std.mem.Allocator, config: Config, path: []const u8) !void {
|
||||
var readme_file = try std.fs.openFileAbsolute(path, .{ .mode = .read_write });
|
||||
defer readme_file.close();
|
||||
|
||||
var readme = std.ArrayListUnmanaged(u8){
|
||||
.items = try readme_file.readToEndAlloc(allocator, std.math.maxInt(usize)),
|
||||
};
|
||||
defer readme.deinit(allocator);
|
||||
var readme = try readme_file.readToEndAlloc(allocator, std.math.maxInt(usize));
|
||||
defer allocator.free(readme);
|
||||
|
||||
const start_indicator = "<!-- DO NOT EDIT | THIS SECTION IS AUTO-GENERATED | 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 end = std.mem.indexOfPos(u8, readme.items, start, end_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, start, end_indicator) orelse return error.SectionNotFound;
|
||||
|
||||
var new_readme = std.ArrayListUnmanaged(u8){};
|
||||
defer new_readme.deinit(allocator);
|
||||
var writer = new_readme.writer(allocator);
|
||||
try readme_file.seekTo(0);
|
||||
var writer = readme_file.writer();
|
||||
|
||||
try writer.writeAll(readme[0..start]);
|
||||
|
||||
try writer.writeAll(
|
||||
\\
|
||||
@ -155,29 +152,95 @@ fn updateREADMEFile(allocator: std.mem.Allocator, config: Config, path: []const
|
||||
\\| `{s}` | `{s}` | `{s}` | {s} |
|
||||
\\
|
||||
, .{
|
||||
std.mem.trim(u8, option.name, " \t\n\r"),
|
||||
std.mem.trim(u8, option.type, " \t\n\r"),
|
||||
std.mem.trim(u8, option.default, " \t\n\r"),
|
||||
std.mem.trim(u8, option.description, " \t\n\r"),
|
||||
std.mem.trim(u8, option.name, &std.ascii.whitespace),
|
||||
std.mem.trim(u8, option.type, &std.ascii.whitespace),
|
||||
std.mem.trim(u8, option.default, &std.ascii.whitespace),
|
||||
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.writeAll(readme.items);
|
||||
try readme_file.setEndPos(try readme_file.getPos());
|
||||
}
|
||||
|
||||
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 {
|
||||
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("");
|
||||
const config_path = arg_it.next() orelse @panic("first argument must be path to Config.zig");
|
||||
const schema_path = arg_it.next() orelse @panic("second argument must be path to schema.json");
|
||||
const readme_path = arg_it.next() orelse @panic("third argument must be path to README.md");
|
||||
|
||||
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
var gpa = general_purpose_allocator.allocator();
|
||||
const maybe_vscode_config_path = arg_it.next();
|
||||
|
||||
const parse_options = std.json.ParseOptions{
|
||||
.allocator = gpa,
|
||||
@ -190,50 +253,19 @@ pub fn main() !void {
|
||||
try generateSchemaFile(gpa, config, schema_path);
|
||||
try updateREADMEFile(gpa, config, readme_path);
|
||||
|
||||
std.log.warn(
|
||||
\\ If you have added a new configuration option and it should be configuration through the config wizard, then edit src/setup.zig
|
||||
, .{});
|
||||
if (maybe_vscode_config_path) |vscode_config_path| {
|
||||
try generateVSCodeConfigFile(gpa, config, vscode_config_path);
|
||||
}
|
||||
|
||||
std.log.info(
|
||||
if (builtin.os.tag == .windows) {
|
||||
std.log.warn("Running on windows may result in CRLF and LF mismatch", .{});
|
||||
}
|
||||
|
||||
try std.io.getStdOut().writeAll(
|
||||
\\If you have added a new configuration option and it should be configuration through the config wizard, then edit `src/setup.zig`
|
||||
\\
|
||||
\\Changing configuration options may also require editing the `package.json` from zls-vscode at https://github.com/zigtools/zls-vscode/blob/master/package.json
|
||||
, .{});
|
||||
}
|
||||
|
||||
fn serializeObjectMap(
|
||||
value: anytype,
|
||||
options: std.json.StringifyOptions,
|
||||
out_stream: anytype,
|
||||
) @TypeOf(out_stream).Error!void {
|
||||
try out_stream.writeByte('{');
|
||||
var field_output = false;
|
||||
var child_options = options;
|
||||
if (child_options.whitespace) |*child_whitespace| {
|
||||
child_whitespace.indent_level += 1;
|
||||
}
|
||||
var it = value.iterator();
|
||||
while (it.next()) |entry| {
|
||||
if (!field_output) {
|
||||
field_output = true;
|
||||
} else {
|
||||
try out_stream.writeByte(',');
|
||||
}
|
||||
if (child_options.whitespace) |child_whitespace| {
|
||||
try child_whitespace.outputIndent(out_stream);
|
||||
}
|
||||
|
||||
try std.json.stringify(entry.key_ptr.*, options, out_stream);
|
||||
try out_stream.writeByte(':');
|
||||
if (child_options.whitespace) |child_whitespace| {
|
||||
if (child_whitespace.separator) {
|
||||
try out_stream.writeByte(' ');
|
||||
}
|
||||
}
|
||||
try std.json.stringify(entry.value_ptr.*, child_options, out_stream);
|
||||
}
|
||||
if (field_output) {
|
||||
if (options.whitespace) |whitespace| {
|
||||
try whitespace.outputIndent(out_stream);
|
||||
}
|
||||
}
|
||||
try out_stream.writeByte('}');
|
||||
\\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`
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const setup = @import("setup.zig");
|
||||
const tracy = @import("tracy.zig");
|
||||
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});
|
||||
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: {
|
||||
@ -208,3 +208,36 @@ fn getConfigurationType() type {
|
||||
config_info.Struct.decls = &.{};
|
||||
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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -21,16 +21,14 @@ pub const builtins = [_]Builtin{
|
||||
},
|
||||
.{
|
||||
.name = "@addWithOverflow",
|
||||
.signature = "@addWithOverflow(comptime T: type, a: T, b: T, result: *T) bool",
|
||||
.snippet = "@addWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})",
|
||||
.signature = "@addWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }",
|
||||
.snippet = "@addWithOverflow(${1:a: anytype}, ${2:b: anytype})",
|
||||
.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 = &.{
|
||||
"comptime T: type",
|
||||
"a: T",
|
||||
"b: T",
|
||||
"result: *T",
|
||||
"a: anytype",
|
||||
"b: anytype",
|
||||
},
|
||||
},
|
||||
.{
|
||||
@ -1066,16 +1064,14 @@ pub const builtins = [_]Builtin{
|
||||
},
|
||||
.{
|
||||
.name = "@mulWithOverflow",
|
||||
.signature = "@mulWithOverflow(comptime T: type, a: T, b: T, result: *T) bool",
|
||||
.snippet = "@mulWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})",
|
||||
.signature = "@mulWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }",
|
||||
.snippet = "@mulWithOverflow(${1:a: anytype}, ${2:b: anytype})",
|
||||
.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 = &.{
|
||||
"comptime T: type",
|
||||
"a: T",
|
||||
"b: T",
|
||||
"result: *T",
|
||||
"a: anytype",
|
||||
"b: anytype",
|
||||
},
|
||||
},
|
||||
.{
|
||||
@ -1326,18 +1322,16 @@ pub const builtins = [_]Builtin{
|
||||
},
|
||||
.{
|
||||
.name = "@shlWithOverflow",
|
||||
.signature = "@shlWithOverflow(comptime T: type, a: T, shift_amt: Log2T, result: *T) bool",
|
||||
.snippet = "@shlWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:shift_amt: Log2T}, ${4:result: *T})",
|
||||
.signature = "@shlWithOverflow(a: anytype, shift_amt: Log2T) struct { @TypeOf(a), u1 }",
|
||||
.snippet = "@shlWithOverflow(${1:a: anytype}, ${2:shift_amt: Log2T})",
|
||||
.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 = &.{
|
||||
"comptime T: type",
|
||||
"a: T",
|
||||
"a: anytype",
|
||||
"shift_amt: Log2T",
|
||||
"result: *T",
|
||||
},
|
||||
},
|
||||
.{
|
||||
@ -1619,16 +1613,14 @@ pub const builtins = [_]Builtin{
|
||||
},
|
||||
.{
|
||||
.name = "@subWithOverflow",
|
||||
.signature = "@subWithOverflow(comptime T: type, a: T, b: T, result: *T) bool",
|
||||
.snippet = "@subWithOverflow(${1:comptime T: type}, ${2:a: T}, ${3:b: T}, ${4:result: *T})",
|
||||
.signature = "@subWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }",
|
||||
.snippet = "@subWithOverflow(${1:a: anytype}, ${2:b: anytype})",
|
||||
.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 = &.{
|
||||
"comptime T: type",
|
||||
"a: T",
|
||||
"b: T",
|
||||
"result: *T",
|
||||
"a: anytype",
|
||||
"b: anytype",
|
||||
},
|
||||
},
|
||||
.{
|
||||
|
@ -1,8 +1,8 @@
|
||||
const types = @import("../types.zig");
|
||||
const types = @import("../lsp.zig");
|
||||
|
||||
pub const Snipped = struct {
|
||||
label: []const u8,
|
||||
kind: types.CompletionItem.Kind,
|
||||
kind: types.CompletionItemKind,
|
||||
text: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
|
136
src/debug.zig
Normal file
136
src/debug.zig
Normal 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);
|
||||
}
|
||||
};
|
13
src/diff.zig
13
src/diff.zig
@ -1,6 +1,5 @@
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
const requests = @import("requests.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
|
||||
pub const Error = error{ OutOfMemory, InvalidRange };
|
||||
@ -357,14 +356,14 @@ fn char_pos_to_range(
|
||||
pub fn applyTextEdits(
|
||||
allocator: std.mem.Allocator,
|
||||
text: []const u8,
|
||||
content_changes: []const requests.TextDocumentContentChangeEvent,
|
||||
content_changes: []const types.TextDocumentContentChangeEvent,
|
||||
encoding: offsets.Encoding,
|
||||
) ![:0]const u8 {
|
||||
var last_full_text_change: ?usize = null;
|
||||
var i: usize = content_changes.len;
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
if (content_changes[i].range == null) {
|
||||
if (content_changes[i] == .literal_1) {
|
||||
last_full_text_change = i;
|
||||
continue;
|
||||
}
|
||||
@ -373,16 +372,16 @@ pub fn applyTextEdits(
|
||||
var text_array = std.ArrayListUnmanaged(u8){};
|
||||
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
|
||||
const changes = content_changes[if (last_full_text_change) |index| index + 1 else 0..];
|
||||
|
||||
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);
|
||||
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);
|
||||
|
@ -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;
|
||||
}
|
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const zig_builtin = @import("builtin");
|
||||
const DocumentStore = @import("DocumentStore.zig");
|
||||
const analysis = @import("analysis.zig");
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
const Ast = std.zig.Ast;
|
||||
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
|
||||
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;
|
||||
|
||||
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 {
|
||||
allocator: std.mem.Allocator,
|
||||
arena: std.mem.Allocator,
|
||||
config: *const Config,
|
||||
handle: *const DocumentStore.Handle,
|
||||
hints: std.ArrayListUnmanaged(types.InlayHint),
|
||||
hover_kind: types.MarkupContent.Kind,
|
||||
hover_kind: types.MarkupKind,
|
||||
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 {
|
||||
// TODO allocation could be avoided by extending InlayHint.jsonStringify
|
||||
// adding tooltip_noalias & tooltip_comptime to InlayHint should be enough
|
||||
@ -53,28 +46,28 @@ const Builder = struct {
|
||||
if (tooltip.len == 0) break :blk "";
|
||||
const prefix = if (tooltip_noalias) if (tooltip_comptime) "noalias comptime " else "noalias " else if (tooltip_comptime) "comptime " else "";
|
||||
|
||||
if (self.hover_kind == .Markdown) {
|
||||
break :blk try std.fmt.allocPrint(self.allocator, "```zig\n{s}{s}\n```", .{ prefix, tooltip });
|
||||
if (self.hover_kind == .markdown) {
|
||||
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,
|
||||
.label = label,
|
||||
.label = .{ .string = label },
|
||||
.kind = types.InlayHintKind.Parameter,
|
||||
.tooltip = .{
|
||||
.tooltip = .{ .MarkupContent = .{
|
||||
.kind = self.hover_kind,
|
||||
.value = tooltip_text,
|
||||
},
|
||||
} },
|
||||
.paddingLeft = false,
|
||||
.paddingRight = true,
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
/// only parameter hints are created
|
||||
/// only hints in the given range are created
|
||||
/// Caller owns returned memory.
|
||||
/// `InlayHint.tooltip.value` has to deallocated separately
|
||||
pub fn writeRangeInlayHint(
|
||||
arena: *std.heap.ArenaAllocator,
|
||||
config: Config,
|
||||
store: *DocumentStore,
|
||||
handle: *const DocumentStore.Handle,
|
||||
range: types.Range,
|
||||
hover_kind: types.MarkupContent.Kind,
|
||||
hover_kind: types.MarkupKind,
|
||||
encoding: offsets.Encoding,
|
||||
) error{OutOfMemory}![]types.InlayHint {
|
||||
var builder: Builder = .{
|
||||
.allocator = arena.child_allocator,
|
||||
.arena = arena.allocator(),
|
||||
.config = &config,
|
||||
.handle = handle,
|
||||
.hints = .{},
|
||||
.hover_kind = hover_kind,
|
||||
.encoding = encoding,
|
||||
};
|
||||
errdefer builder.deinit();
|
||||
|
||||
var buf: [2]Ast.Node.Index = undefined;
|
||||
for (ast.declMembers(handle.tree, 0, &buf)) |child| {
|
||||
|
7852
src/lsp.zig
Normal file
7852
src/lsp.zig
Normal file
File diff suppressed because it is too large
Load Diff
274
src/main.zig
274
src/main.zig
@ -6,8 +6,8 @@ const known_folders = @import("known-folders");
|
||||
const Config = @import("Config.zig");
|
||||
const configuration = @import("configuration.zig");
|
||||
const Server = @import("Server.zig");
|
||||
const setup = @import("setup.zig");
|
||||
const readRequestHeader = @import("header.zig").readRequestHeader;
|
||||
const Header = @import("Header.zig");
|
||||
const debug = @import("debug.zig");
|
||||
|
||||
const logger = std.log.scoped(.main);
|
||||
|
||||
@ -34,21 +34,114 @@ pub fn log(
|
||||
std.debug.print(format ++ "\n", args);
|
||||
}
|
||||
|
||||
fn loop(server: *Server) !void {
|
||||
var reader = std.io.getStdIn().reader();
|
||||
fn loop(
|
||||
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) {
|
||||
const headers = readRequestHeader(server.allocator, reader) catch |err| {
|
||||
logger.err("{s}; exiting!", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
const buffer = try server.allocator.alloc(u8, headers.content_length);
|
||||
defer server.allocator.free(buffer);
|
||||
var arena = std.heap.ArenaAllocator.init(server.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
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();
|
||||
try server.processJsonRpc(writer, buffer);
|
||||
// read and handle client -> server message
|
||||
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(
|
||||
allocator: std.mem.Allocator,
|
||||
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 {
|
||||
if (config_path) |path| {
|
||||
if (configuration.loadFromFile(allocator, path)) |conf| {
|
||||
return ConfigWithPath{
|
||||
.config = conf,
|
||||
.config_path = path,
|
||||
};
|
||||
if (configuration.loadFromFile(allocator, path)) |config| {
|
||||
return ConfigWithPath{ .config = config, .config_path = path };
|
||||
}
|
||||
std.debug.print(
|
||||
\\Could not open configuration file '{s}'
|
||||
\\Falling back to a lookup in the local and global configuration folders
|
||||
\\
|
||||
, .{path});
|
||||
if (free_old_config_path) {
|
||||
allocator.free(path);
|
||||
}
|
||||
}
|
||||
|
||||
if (try known_folders.getPath(allocator, .local_configuration)) |path| {
|
||||
if (configuration.loadFromFolder(allocator, path)) |conf| {
|
||||
return ConfigWithPath{
|
||||
.config = conf,
|
||||
.config_path = path,
|
||||
};
|
||||
if (configuration.loadFromFolder(allocator, path)) |config| {
|
||||
return ConfigWithPath{ .config = config, .config_path = path };
|
||||
}
|
||||
allocator.free(path);
|
||||
}
|
||||
|
||||
if (try known_folders.getPath(allocator, .global_configuration)) |path| {
|
||||
if (configuration.loadFromFolder(allocator, path)) |conf| {
|
||||
return ConfigWithPath{
|
||||
.config = conf,
|
||||
.config_path = path,
|
||||
};
|
||||
if (configuration.loadFromFolder(allocator, path)) |config| {
|
||||
return ConfigWithPath{ .config = config, .config_path = path };
|
||||
}
|
||||
allocator.free(path);
|
||||
}
|
||||
@ -108,22 +185,32 @@ fn getConfig(
|
||||
};
|
||||
}
|
||||
|
||||
const ParseArgsResult = enum { proceed, exit };
|
||||
fn parseArgs(
|
||||
allocator: std.mem.Allocator,
|
||||
config: *ConfigWithPath,
|
||||
) !ParseArgsResult {
|
||||
const ParseArgsResult = struct {
|
||||
action: enum { proceed, exit },
|
||||
config_path: ?[]const u8,
|
||||
replay_enabled: bool,
|
||||
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 {
|
||||
help,
|
||||
version,
|
||||
config,
|
||||
replay,
|
||||
@"enable-debug-log",
|
||||
@"show-config-path",
|
||||
@"config-path",
|
||||
};
|
||||
const arg_id_map = std.ComptimeStringMap(ArgId, comptime blk: {
|
||||
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;
|
||||
for (pairs) |*pair, i| pair.* = .{ fields[i].name, @intToEnum(ArgId, fields[i].value) };
|
||||
break :blk pairs[0..];
|
||||
@ -140,10 +227,10 @@ fn parseArgs(
|
||||
var cmd_infos: InfoMap = InfoMap.init(.{
|
||||
.help = "Prints this message.",
|
||||
.version = "Prints the compiler version with which the server was compiled.",
|
||||
.replay = "Replay a previous recorded zls session",
|
||||
.@"enable-debug-log" = "Enables debug logs.",
|
||||
.@"config-path" = "Specify the path to a configuration file specifying LSP behaviour.",
|
||||
.@"show-config-path" = "Prints the path to the configuration file to stdout",
|
||||
.config = "Run the ZLS configuration wizard.",
|
||||
});
|
||||
var info_it = cmd_infos.iterator();
|
||||
while (info_it.next()) |entry| {
|
||||
@ -159,9 +246,6 @@ fn parseArgs(
|
||||
|
||||
// Makes behavior of enabling debug more logging consistent regardless of argument order.
|
||||
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 stderr = std.io.getStdErr().writer();
|
||||
|
||||
@ -169,76 +253,82 @@ fn parseArgs(
|
||||
if (!std.mem.startsWith(u8, tok, "--") or tok.len == 2) {
|
||||
try stderr.print("{s}\n", .{help_message});
|
||||
try stderr.print("Unexpected positional argument '{s}'.\n", .{tok});
|
||||
return .exit;
|
||||
return result;
|
||||
}
|
||||
|
||||
const argname = tok["--".len..];
|
||||
const id = arg_id_map.get(argname) orelse {
|
||||
try stderr.print("{s}\n", .{help_message});
|
||||
try stderr.print("Unrecognized argument '{s}'.\n", .{argname});
|
||||
return .exit;
|
||||
return result;
|
||||
};
|
||||
|
||||
if (specified.get(id)) {
|
||||
try stderr.print("{s}\n", .{help_message});
|
||||
try stderr.print("Duplicate argument '{s}'.\n", .{argname});
|
||||
return .exit;
|
||||
return result;
|
||||
}
|
||||
specified.set(id, true);
|
||||
|
||||
switch (id) {
|
||||
.help => {},
|
||||
.version => {},
|
||||
.@"enable-debug-log" => {},
|
||||
.config => {},
|
||||
.@"show-config-path" => {},
|
||||
.help,
|
||||
.version,
|
||||
.@"enable-debug-log",
|
||||
.@"show-config-path",
|
||||
=> {},
|
||||
.@"config-path" => {
|
||||
const path = args_it.next() orelse {
|
||||
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)) {
|
||||
try stderr.print("{s}\n", .{help_message});
|
||||
return .exit;
|
||||
return result;
|
||||
}
|
||||
if (specified.get(.version)) {
|
||||
try std.io.getStdOut().writeAll(build_options.version ++ "\n");
|
||||
return .exit;
|
||||
}
|
||||
if (specified.get(.config)) {
|
||||
try setup.wizard(allocator);
|
||||
return .exit;
|
||||
try stdout.writeAll(build_options.version ++ "\n");
|
||||
return result;
|
||||
}
|
||||
if (specified.get(.@"enable-debug-log")) {
|
||||
actual_log_level = .debug;
|
||||
logger.info("Enabled debug logging.\n", .{});
|
||||
}
|
||||
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")) {
|
||||
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 std.json.parseFree(Config, new_config.config, .{ .allocator = allocator });
|
||||
|
||||
if (new_config.config_path) |path| {
|
||||
const full_path = try std.fs.path.resolve(allocator, &.{ path, "zls.json" });
|
||||
const full_path = if (new_config.config_path) |path| blk: {
|
||||
break :blk try std.fs.path.resolve(allocator, &.{ path, "zls.json" });
|
||||
} else blk: {
|
||||
const local_config_path = try known_folders.getPath(allocator, .local_configuration) orelse {
|
||||
logger.err("failed to find local configuration folder", .{});
|
||||
return result;
|
||||
};
|
||||
defer allocator.free(local_config_path);
|
||||
break :blk try std.fs.path.resolve(allocator, &.{ local_config_path, "zls.json" });
|
||||
};
|
||||
defer allocator.free(full_path);
|
||||
|
||||
try stdout.writeAll(full_path);
|
||||
try stdout.writeByte('\n');
|
||||
} else {
|
||||
logger.err("Failed to find zls.json!\n", .{});
|
||||
}
|
||||
return .exit;
|
||||
return result;
|
||||
}
|
||||
|
||||
return .proceed;
|
||||
result.action = .proceed;
|
||||
return result;
|
||||
}
|
||||
|
||||
const stack_frames = switch (zig_builtin.mode) {
|
||||
@ -249,34 +339,52 @@ const stack_frames = switch (zig_builtin.mode) {
|
||||
pub fn main() !void {
|
||||
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){};
|
||||
defer _ = gpa_state.deinit();
|
||||
|
||||
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{
|
||||
.config = undefined,
|
||||
.config_path = null,
|
||||
};
|
||||
defer if (config.config_path) |path| allocator.free(path);
|
||||
|
||||
switch (try parseArgs(allocator, &config)) {
|
||||
const result = try parseArgs(allocator);
|
||||
defer if (result.config_path) |path| allocator.free(path);
|
||||
defer if (result.replay_session_path) |path| allocator.free(path);
|
||||
switch (result.action) {
|
||||
.proceed => {},
|
||||
.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 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) {
|
||||
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(
|
||||
allocator,
|
||||
&config.config,
|
||||
config.config_path,
|
||||
record_file != null,
|
||||
replay_file != null,
|
||||
);
|
||||
defer server.deinit();
|
||||
|
||||
try loop(&server);
|
||||
try loop(&server, record_file, replay_file);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const ast = @import("ast.zig");
|
||||
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`
|
||||
pub fn countCodeUnits(text: []const u8, encoding: Encoding) usize {
|
||||
switch (encoding) {
|
||||
.utf8 => return text.len,
|
||||
.utf16 => {
|
||||
.@"utf-8" => return text.len,
|
||||
.@"utf-16" => {
|
||||
var iter: std.unicode.Utf8Iterator = .{ .bytes = text, .i = 0 };
|
||||
|
||||
var utf16_len: usize = 0;
|
||||
@ -277,15 +277,15 @@ pub fn countCodeUnits(text: []const u8, encoding: Encoding) usize {
|
||||
}
|
||||
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`
|
||||
pub fn getNCodeUnitByteCount(text: []const u8, n: usize, encoding: Encoding) usize {
|
||||
switch (encoding) {
|
||||
.utf8 => return n,
|
||||
.utf16 => {
|
||||
.@"utf-8" => return n,
|
||||
.@"utf-16" => {
|
||||
if (n == 0) return 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;
|
||||
},
|
||||
.utf32 => {
|
||||
.@"utf-32" => {
|
||||
var i: usize = 0;
|
||||
var count: usize = 0;
|
||||
while (count != n) : (count += 1) {
|
||||
|
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const Ast = std.zig.Ast;
|
||||
const DocumentStore = @import("DocumentStore.zig");
|
||||
const analysis = @import("analysis.zig");
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
const log = std.log.scoped(.references);
|
||||
const ast = @import("ast.zig");
|
||||
@ -488,7 +488,7 @@ pub fn symbolReferences(
|
||||
}
|
||||
|
||||
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| {
|
||||
try dependencies.put(arena.allocator(), uri, {});
|
||||
|
324
src/requests.zig
324
src/requests.zig
@ -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,
|
||||
},
|
||||
};
|
@ -289,7 +289,7 @@ fn writeNodeTokens(builder: *Builder, maybe_node: ?Ast.Node.Index) WriteTokensEr
|
||||
const token_tags = tree.tokens.items(.tag);
|
||||
const node_data = tree.nodes.items(.data);
|
||||
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();
|
||||
|
||||
|
230
src/setup.zig
230
src/setup.zig
@ -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;
|
||||
}
|
@ -2,31 +2,29 @@ const std = @import("std");
|
||||
const analysis = @import("analysis.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
const DocumentStore = @import("DocumentStore.zig");
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const Ast = std.zig.Ast;
|
||||
const Token = std.zig.Token;
|
||||
const identifierFromPosition = @import("Server.zig").identifierFromPosition;
|
||||
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 {
|
||||
const ParameterInformation = types.SignatureInformation.ParameterInformation;
|
||||
|
||||
const tree = handle.tree;
|
||||
const token_starts = tree.tokens.items(.start);
|
||||
const alloc = arena.allocator();
|
||||
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 has_self_param = try analysis.hasSelfParam(arena, document_store, handle, proto);
|
||||
break :blk commas + @boolToInt(has_self_param);
|
||||
} else commas;
|
||||
|
||||
var params = std.ArrayListUnmanaged(ParameterInformation){};
|
||||
var params = std.ArrayListUnmanaged(types.ParameterInformation){};
|
||||
var param_it = proto.iterate(&tree);
|
||||
while (ast.nextFnParam(¶m_it)) |param| {
|
||||
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
|
||||
"";
|
||||
|
||||
@ -55,13 +53,19 @@ fn fnProtoToSignatureInfo(document_store: *DocumentStore, arena: *std.heap.Arena
|
||||
}
|
||||
const param_label = tree.source[param_label_start..param_label_end];
|
||||
try params.append(alloc, .{
|
||||
.label = param_label,
|
||||
.documentation = types.MarkupContent{ .value = param_comments },
|
||||
.label = .{ .string = param_label },
|
||||
.documentation = .{ .MarkupContent = .{
|
||||
.kind = .markdown,
|
||||
.value = param_comments,
|
||||
} },
|
||||
});
|
||||
}
|
||||
return types.SignatureInformation{
|
||||
.label = label,
|
||||
.documentation = types.MarkupContent{ .value = proto_comments },
|
||||
.documentation = .{ .MarkupContent = .{
|
||||
.kind = .markdown,
|
||||
.value = proto_comments,
|
||||
} },
|
||||
.parameters = params.items,
|
||||
.activeParameter = arg_idx,
|
||||
};
|
||||
@ -188,20 +192,18 @@ pub fn getSignatureInfo(document_store: *DocumentStore, arena: *std.heap.ArenaAl
|
||||
for (data.builtins) |builtin| {
|
||||
if (std.mem.eql(u8, builtin.name, tree.tokenSlice(expr_last_token))) {
|
||||
const param_infos = try alloc.alloc(
|
||||
types.SignatureInformation.ParameterInformation,
|
||||
types.ParameterInformation,
|
||||
builtin.arguments.len,
|
||||
);
|
||||
for (param_infos) |*info, i| {
|
||||
info.* = .{
|
||||
.label = builtin.arguments[i],
|
||||
.label = .{ .string = builtin.arguments[i] },
|
||||
.documentation = null,
|
||||
};
|
||||
}
|
||||
return types.SignatureInformation{
|
||||
.label = builtin.signature,
|
||||
.documentation = .{
|
||||
.value = builtin.documentation,
|
||||
},
|
||||
.documentation = .{ .string = builtin.documentation },
|
||||
.parameters = param_infos,
|
||||
.activeParameter = paren_commas,
|
||||
};
|
||||
|
1
src/tres
Submodule
1
src/tres
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit fb23d644500ae5b93dd71b5a8406d0c83e8e4fbe
|
536
src/types.zig
536
src/types.zig
@ -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,
|
||||
};
|
@ -2,13 +2,13 @@
|
||||
// zigbot9001 to take advantage of zls' tools
|
||||
|
||||
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 requests = @import("requests.zig");
|
||||
pub const Config = @import("Config.zig");
|
||||
pub const Server = @import("Server.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 DocumentStore = @import("DocumentStore.zig");
|
||||
pub const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
|
||||
|
File diff suppressed because one or more lines are too long
@ -8,13 +8,12 @@ const ErrorBuilder = @import("../ErrorBuilder.zig");
|
||||
|
||||
const types = zls.types;
|
||||
const offsets = zls.offsets;
|
||||
const requests = zls.requests;
|
||||
|
||||
const allocator: std.mem.Allocator = std.testing.allocator;
|
||||
|
||||
const Completion = struct {
|
||||
label: []const u8,
|
||||
kind: types.CompletionItem.Kind,
|
||||
kind: types.CompletionItemKind,
|
||||
detail: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
@ -412,16 +411,13 @@ fn testCompletion(source: []const u8, expected_completions: []const Completion)
|
||||
|
||||
try ctx.requestDidOpen(test_uri, text);
|
||||
|
||||
const request = requests.Completion{
|
||||
.params = .{
|
||||
const params = types.CompletionParams{
|
||||
.textDocument = .{ .uri = test_uri },
|
||||
.position = offsets.indexToPosition(source, cursor_idx, ctx.server.offset_encoding),
|
||||
},
|
||||
};
|
||||
|
||||
@setEvalBranchQuota(2000);
|
||||
const response = try ctx.requestGetResponse(?types.CompletionList, "textDocument/completion", request);
|
||||
defer response.deinit();
|
||||
@setEvalBranchQuota(5000);
|
||||
const response = try ctx.requestGetResponse(?types.CompletionList, "textDocument/completion", params);
|
||||
|
||||
const completion_list: types.CompletionList = response.result orelse {
|
||||
std.debug.print("Server returned `null` as the result\n", .{});
|
||||
@ -462,11 +458,11 @@ fn testCompletion(source: []const u8, expected_completions: []const Completion)
|
||||
unreachable;
|
||||
};
|
||||
|
||||
if (expected_completion.kind != actual_completion.kind) {
|
||||
try error_builder.msgAtIndex("label '{s}' should be of kind '{s}' but was '{s}'!", cursor_idx, .err, .{
|
||||
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, .{
|
||||
label,
|
||||
@tagName(expected_completion.kind),
|
||||
@tagName(actual_completion.kind),
|
||||
if (actual_completion.kind) |kind| @tagName(kind) else null,
|
||||
});
|
||||
return error.InvalidCompletionKind;
|
||||
}
|
||||
@ -500,10 +496,16 @@ fn extractCompletionLabels(items: anytype) error{ DuplicateCompletionLabel, OutO
|
||||
errdefer set.deinit(allocator);
|
||||
try set.ensureTotalCapacity(allocator, items.len);
|
||||
for (items) |item| {
|
||||
switch (item.kind) {
|
||||
const maybe_kind = switch (@typeInfo(@TypeOf(item.kind))) {
|
||||
.Optional => item.kind,
|
||||
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;
|
||||
}
|
||||
return set;
|
||||
|
@ -2,10 +2,11 @@ const std = @import("std");
|
||||
const zls = @import("zls");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const tres = @import("tres");
|
||||
|
||||
const Context = @import("../context.zig").Context;
|
||||
|
||||
const types = zls.types;
|
||||
const requests = zls.requests;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
defer response.deinit();
|
||||
const response = try ctx.requestGetResponse(?[]types.FoldingRange, "textDocument/foldingRange", params);
|
||||
|
||||
var actual = std.ArrayList(u8).init(allocator);
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,6 @@ const ErrorBuilder = @import("../ErrorBuilder.zig");
|
||||
|
||||
const types = zls.types;
|
||||
const offsets = zls.offsets;
|
||||
const requests = zls.requests;
|
||||
|
||||
const allocator: std.mem.Allocator = std.testing.allocator;
|
||||
|
||||
@ -83,7 +82,7 @@ fn testInlayHints(source: []const u8) !void {
|
||||
|
||||
const range = types.Range{
|
||||
.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 {
|
||||
@ -92,15 +91,12 @@ fn testInlayHints(source: []const u8) !void {
|
||||
kind: types.InlayHintKind,
|
||||
};
|
||||
|
||||
const request = requests.InlayHint{
|
||||
.params = .{
|
||||
const params = types.InlayHintParams{
|
||||
.textDocument = .{ .uri = test_uri },
|
||||
.range = range,
|
||||
},
|
||||
};
|
||||
|
||||
const response = try ctx.requestGetResponse(?[]InlayHint, "textDocument/inlayHint", request);
|
||||
defer response.deinit();
|
||||
const response = try ctx.requestGetResponse(?[]InlayHint, "textDocument/inlayHint", params);
|
||||
|
||||
const hints: []InlayHint = response.result orelse {
|
||||
std.debug.print("Server returned `null` as the result\n", .{});
|
||||
@ -124,7 +120,7 @@ fn testInlayHints(source: []const u8) !void {
|
||||
for (hints) |hint| {
|
||||
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)) {
|
||||
try error_builder.msgAtLoc("expected label `{s}` here but got `{s}`!", new_loc, .err, .{ expected_label, actual_label });
|
||||
|
@ -7,7 +7,6 @@ const Context = @import("../context.zig").Context;
|
||||
const ErrorBuilder = @import("../ErrorBuilder.zig");
|
||||
|
||||
const types = zls.types;
|
||||
const requests = zls.requests;
|
||||
const offsets = zls.offsets;
|
||||
|
||||
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_loc_middle = var_loc.start + (var_loc.end - var_loc.start) / 2;
|
||||
|
||||
const request = requests.References{
|
||||
.params = .{
|
||||
const params = types.ReferenceParams{
|
||||
.textDocument = .{ .uri = file_uri },
|
||||
.position = offsets.indexToPosition(source, var_loc_middle, ctx.server.offset_encoding),
|
||||
.context = .{ .includeDeclaration = true },
|
||||
},
|
||||
};
|
||||
|
||||
const response = try ctx.requestGetResponse(?[]types.Location, "textDocument/references", request);
|
||||
defer response.deinit();
|
||||
const response = try ctx.requestGetResponse(?[]types.Location, "textDocument/references", params);
|
||||
|
||||
const locations: []types.Location = response.result orelse {
|
||||
std.debug.print("Server returned `null` as the result\n", .{});
|
||||
|
@ -38,20 +38,19 @@ fn testSelectionRange(source: []const u8, want: []const []const u8) !void {
|
||||
|
||||
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 {
|
||||
range: types.Range,
|
||||
parent: ?*@This(),
|
||||
parent: ?*@This() = null,
|
||||
};
|
||||
|
||||
const request = requests.SelectionRange{ .params = .{
|
||||
const params = types.SelectionRangeParams{
|
||||
.textDocument = .{ .uri = test_uri },
|
||||
.positions = &[_]types.Position{position},
|
||||
} };
|
||||
};
|
||||
|
||||
const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", request);
|
||||
defer response.deinit();
|
||||
const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", params);
|
||||
|
||||
const selectionRanges: []SelectionRange = response.result orelse {
|
||||
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];
|
||||
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;
|
||||
it = r.parent;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ const builtin = @import("builtin");
|
||||
|
||||
const Context = @import("../context.zig").Context;
|
||||
|
||||
const requests = zls.requests;
|
||||
const types = zls.types;
|
||||
|
||||
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();
|
||||
defer ctx.deinit();
|
||||
|
||||
const open_document = requests.OpenDocument{
|
||||
.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);
|
||||
try ctx.requestDidOpen(file_uri, source);
|
||||
|
||||
const Response = struct {
|
||||
data: []const u32,
|
||||
|
@ -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 position32: types.Position = .{ .line = line, .character = characters[2] };
|
||||
|
||||
try std.testing.expectEqual(position8, offsets.indexToPosition(text, index, .utf8));
|
||||
try std.testing.expectEqual(position16, offsets.indexToPosition(text, index, .utf16));
|
||||
try std.testing.expectEqual(position32, offsets.indexToPosition(text, index, .utf32));
|
||||
try std.testing.expectEqual(position8, offsets.indexToPosition(text, index, .@"utf-8"));
|
||||
try std.testing.expectEqual(position16, offsets.indexToPosition(text, index, .@"utf-16"));
|
||||
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, position16, .utf16));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position32, .utf32));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position8, .@"utf-8"));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position16, .@"utf-16"));
|
||||
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 {
|
||||
@ -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 {
|
||||
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);
|
||||
}
|
||||
@ -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 {
|
||||
const position: types.Position = .{ .line = line, .character = character };
|
||||
|
||||
const position8 = offsets.convertPositionEncoding(text, position, .utf8, .utf8);
|
||||
const position16 = offsets.convertPositionEncoding(text, position, .utf8, .utf16);
|
||||
const position32 = offsets.convertPositionEncoding(text, position, .utf8, .utf32);
|
||||
const position8 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-8");
|
||||
const position16 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-16");
|
||||
const position32 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-32");
|
||||
|
||||
try std.testing.expectEqual(line, position8.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 {
|
||||
try std.testing.expectEqual(counts[0], offsets.countCodeUnits(text, .utf8));
|
||||
try std.testing.expectEqual(counts[1], offsets.countCodeUnits(text, .utf16));
|
||||
try std.testing.expectEqual(counts[2], offsets.countCodeUnits(text, .utf32));
|
||||
try std.testing.expectEqual(counts[0], offsets.countCodeUnits(text, .@"utf-8"));
|
||||
try std.testing.expectEqual(counts[1], offsets.countCodeUnits(text, .@"utf-16"));
|
||||
try std.testing.expectEqual(counts[2], offsets.countCodeUnits(text, .@"utf-32"));
|
||||
}
|
||||
|
||||
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[1], .utf16));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[2], .utf32));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[0], .@"utf-8"));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[1], .@"utf-16"));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[2], .@"utf-32"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user