Merge branch 'master' into intern-pool

This commit is contained in:
Techatrix 2023-01-20 22:23:44 +01:00
commit ba42fd2bb9
28 changed files with 5055 additions and 2927 deletions

View File

@ -10,6 +10,10 @@ on:
schedule: schedule:
- cron: "0 0 * * *" - cron: "0 0 * * *"
workflow_dispatch: workflow_dispatch:
inputs:
bypass_tracy_and_artifacts:
type: boolean
description: Bypass Tracy and artifact builds (much faster)
jobs: jobs:
build: build:
@ -33,14 +37,14 @@ jobs:
run: zig build run: zig build
- name: Build with Tracy - name: Build with Tracy
if: ${{ matrix.os != 'macos-latest' }} if: ${{ matrix.os != 'macos-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
run: zig build -Denable_tracy -Denable_tracy_allocation run: zig build -Denable_tracy -Denable_tracy_allocation
- name: Run Tests - name: Run Tests
run: zig build test run: zig build test
- name: Build artifacts - name: Build artifacts
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
run: | run: |
declare -a targets=("x86_64-windows" "x86_64-linux" "x86_64-macos" "x86-windows" "x86-linux" "aarch64-linux" "aarch64-macos") declare -a targets=("x86_64-windows" "x86_64-linux" "x86_64-macos" "x86-windows" "x86-linux" "aarch64-linux" "aarch64-macos")
mkdir -p "artifacts/" mkdir -p "artifacts/"
@ -60,49 +64,49 @@ jobs:
done done
- name: Upload x86_64-windows artifact - name: Upload x86_64-windows artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: zls-x86_64-windows name: zls-x86_64-windows
path: artifacts/x86_64-windows/ path: artifacts/x86_64-windows/
- name: Upload x86_64-linux artifact - name: Upload x86_64-linux artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: zls-x86_64-linux name: zls-x86_64-linux
path: artifacts/x86_64-linux/ path: artifacts/x86_64-linux/
- name: Upload x86_64-macos artifact - name: Upload x86_64-macos artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: zls-x86_64-macos name: zls-x86_64-macos
path: artifacts/x86_64-macos/ path: artifacts/x86_64-macos/
- name: Upload x86-windows artifact - name: Upload x86-windows artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: zls-x86-windows name: zls-x86-windows
path: artifacts/x86-windows/ path: artifacts/x86-windows/
- name: Upload x86-linux artifact - name: Upload x86-linux artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: zls-x86-linux name: zls-x86-linux
path: artifacts/x86-linux/ path: artifacts/x86-linux/
- name: Upload aarch64-linux artifact - name: Upload aarch64-linux artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: zls-aarch64-linux name: zls-aarch64-linux
path: artifacts/aarch64-linux/ path: artifacts/aarch64-linux/
- name: Upload aarch64-macos artifact - name: Upload aarch64-macos artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' && github.event.inputs.bypass_tracy_and_artifacts != 'true' }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: zls-aarch64-macos name: zls-aarch64-macos
@ -118,3 +122,19 @@ jobs:
REMOTE_HOST: ${{ secrets.WEBSITE_DEPLOY_HOST }} REMOTE_HOST: ${{ secrets.WEBSITE_DEPLOY_HOST }}
REMOTE_USER: ${{ secrets.WEBSITE_DEPLOY_USER }} REMOTE_USER: ${{ secrets.WEBSITE_DEPLOY_USER }}
TARGET: ${{ secrets.WEBSITE_DEPLOY_FOLDER }} TARGET: ${{ secrets.WEBSITE_DEPLOY_FOLDER }}
- name: Instruct fuzzing server to pull latest zls
if: ${{ matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/master' && github.repository_owner == 'zigtools' }}
uses: appleboy/ssh-action@v0.1.7
with:
host: fuzzing.zigtools.org
username: ${{ secrets.FUZZING_SSH_USERNAME }}
key: ${{ secrets.FUZZING_SSH_PRIVKEY }}
script: |
systemctl stop fuzzing
systemctl stop fuzzing-web
cd /root/sus
./script/setup.sh
systemctl start fuzzing
sleep 5s
systemctl start fuzzing-web

View File

@ -44,17 +44,8 @@ zig build -Drelease-safe
#### Updating Data Files #### Updating Data Files
There is a `generate-data.py` in the `src/data` folder, run this file to update data files. Run `zig build gen -- --generate-version-data master` to update data files. This command will require an internet connection.
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. You can replace `master` with a specific zig version like `0.10.0`. Which version ZLS uses can be configured by passing the `-Ddata_version` parameter when building ZLS.
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 ### Configuration Options

View File

@ -5,10 +5,13 @@ const shared = @import("src/shared.zig");
const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 }; const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 };
pub fn build(b: *std.build.Builder) !void { pub fn build(b: *std.build.Builder) !void {
const current_zig = builtin.zig_version; comptime {
const min_zig = std.SemanticVersion.parse("0.11.0-dev.874+40ed6ae84") catch return; // Changes to builtin.Type API const current_zig = builtin.zig_version;
if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); const min_zig = std.SemanticVersion.parse("0.11.0-dev.874+40ed6ae84") catch return; // Changes to builtin.Type API
if (current_zig.order(min_zig) == .lt) {
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
}
}
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const mode = b.standardReleaseOptions(); const mode = b.standardReleaseOptions();
@ -108,6 +111,12 @@ pub fn build(b: *std.build.Builder) !void {
const tres_path = b.option([]const u8, "tres", "Path to tres package (default: " ++ TRES_DEFAULT_PATH ++ ")") orelse TRES_DEFAULT_PATH; 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 } }); exe.addPackage(.{ .name = "tres", .source = .{ .path = tres_path } });
const check_submodules_step = CheckSubmodulesStep.init(b, &.{
known_folders_path,
tres_path,
});
b.getInstallStep().dependOn(&check_submodules_step.step);
if (enable_tracy) { if (enable_tracy) {
const client_cpp = "src/tracy/TracyClient.cpp"; const client_cpp = "src/tracy/TracyClient.cpp";
@ -133,26 +142,30 @@ pub fn build(b: *std.build.Builder) !void {
exe.install(); exe.install();
const gen_exe = b.addExecutable("zls_gen", "src/config_gen/config_gen.zig"); const gen_exe = b.addExecutable("zls_gen", "src/config_gen/config_gen.zig");
gen_exe.addPackage(.{ .name = "tres", .source = .{ .path = "src/tres/tres.zig" } }); gen_exe.addPackage(.{ .name = "tres", .source = .{ .path = tres_path } });
const gen_cmd = gen_exe.run(); const gen_cmd = gen_exe.run();
gen_cmd.addArgs(&.{ gen_cmd.addArgs(&.{
b.fmt("{s}/src/Config.zig", .{b.build_root}), b.pathJoin(&.{ b.build_root, "src", "Config.zig" }),
b.fmt("{s}/schema.json", .{b.build_root}), b.pathJoin(&.{ b.build_root, "schema.json" }),
b.fmt("{s}/README.md", .{b.build_root}), b.pathJoin(&.{ b.build_root, "README.md" }),
b.pathJoin(&.{ b.build_root, "src", "data" }),
}); });
if (b.args) |args| gen_cmd.addArgs(args);
if (b.option([]const u8, "vscode-config-path", "Output path to vscode-config")) |path| {
gen_cmd.addArg(b.pathFromRoot(path));
}
const gen_step = b.step("gen", "Regenerate config files"); const gen_step = b.step("gen", "Regenerate config files");
gen_step.dependOn(&check_submodules_step.step);
gen_step.dependOn(&gen_cmd.step); gen_step.dependOn(&gen_cmd.step);
const test_step = b.step("test", "Run all the tests"); const test_step = b.step("test", "Run all the tests");
test_step.dependOn(b.getInstallStep()); test_step.dependOn(b.getInstallStep());
var tests = b.addTest("tests/tests.zig"); var tests = b.addTest("tests/tests.zig");
tests.setFilter(b.option(
[]const u8,
"test-filter",
"Skip tests that do not match filter",
));
if (coverage) { if (coverage) {
const src_dir = b.pathJoin(&.{ b.build_root, "src" }); const src_dir = b.pathJoin(&.{ b.build_root, "src" });
@ -177,3 +190,35 @@ pub fn build(b: *std.build.Builder) !void {
src_tests.setTarget(target); src_tests.setTarget(target);
test_step.dependOn(&src_tests.step); test_step.dependOn(&src_tests.step);
} }
const CheckSubmodulesStep = struct {
step: std.build.Step,
builder: *std.build.Builder,
submodules: []const []const u8,
pub fn init(builder: *std.build.Builder, submodules: []const []const u8) *CheckSubmodulesStep {
var self = builder.allocator.create(CheckSubmodulesStep) catch unreachable;
self.* = CheckSubmodulesStep{
.builder = builder,
.step = std.build.Step.init(.custom, "Check Submodules", builder.allocator, make),
.submodules = builder.allocator.dupe([]const u8, submodules) catch unreachable,
};
return self;
}
fn make(step: *std.build.Step) anyerror!void {
const self = @fieldParentPtr(CheckSubmodulesStep, "step", step);
for (self.submodules) |path| {
const access = std.fs.accessAbsolute(self.builder.pathFromRoot(path), .{});
if (access == error.FileNotFound) {
std.debug.print(
\\Did you clone ZLS with `git clone --recurse-submodules https://github.com/zigtools/zls`?
\\If not you can fix this with `git submodule update --init --recursive`.
\\
\\
, .{});
break;
}
}
}
};

View File

@ -1,7 +1,7 @@
//! DO NOT EDIT //! DO NOT EDIT
//! Configuration options for zls. //! Configuration options for zls.
//! If you want to add a config option edit //! If you want to add a config option edit
//! src/config_gen/config.zig and run `zig build gen` //! src/config_gen/config.json and run `zig build gen`
//! GENERATED BY src/config_gen/config_gen.zig //! GENERATED BY src/config_gen/config_gen.zig
/// Enables snippet completions when the client also supports them /// Enables snippet completions when the client also supports them

View File

@ -224,7 +224,7 @@ pub fn applySave(self: *DocumentStore, handle: *const Handle) !void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
if (isBuildFile(handle.uri)) { if (std.process.can_spawn and isBuildFile(handle.uri)) {
const build_file = self.build_files.getPtr(handle.uri).?; const build_file = self.build_files.getPtr(handle.uri).?;
const build_config = loadBuildConfiguration(self.allocator, build_file.*, self.config.*) catch |err| { const build_config = loadBuildConfiguration(self.allocator, build_file.*, self.config.*) catch |err| {
@ -431,6 +431,7 @@ fn loadBuildConfiguration(
const zig_run_result = try std.ChildProcess.exec(.{ const zig_run_result = try std.ChildProcess.exec(.{
.allocator = arena_allocator, .allocator = arena_allocator,
.argv = args.items, .argv = args.items,
.cwd = try std.fs.path.resolve(arena_allocator, &.{ config.zig_exe_path.?, "../" }),
}); });
defer { defer {
@ -647,7 +648,9 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
handle.import_uris = try self.collectImportUris(handle); handle.import_uris = try self.collectImportUris(handle);
handle.cimports = try self.collectCIncludes(handle); handle.cimports = try self.collectCIncludes(handle);
if (self.config.zig_exe_path != null and isBuildFile(handle.uri) and !isInStd(handle.uri)) { if (!std.process.can_spawn or self.config.zig_exe_path == null) return handle;
if (isBuildFile(handle.uri) and !isInStd(handle.uri)) {
const gop = try self.build_files.getOrPut(self.allocator, uri); const gop = try self.build_files.getOrPut(self.allocator, uri);
errdefer |err| { errdefer |err| {
self.build_files.swapRemoveAt(gop.index); self.build_files.swapRemoveAt(gop.index);
@ -658,7 +661,7 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
gop.value_ptr.* = try self.createBuildFile(duped_uri); gop.value_ptr.* = try self.createBuildFile(duped_uri);
gop.key_ptr.* = gop.value_ptr.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: { } else if (!isBuiltinFile(handle.uri) and !isInStd(handle.uri)) blk: {
// log.debug("Going to walk down the tree towards: {s}", .{uri}); // log.debug("Going to walk down the tree towards: {s}", .{uri});
// walk down the tree towards the uri. When we hit build.zig files // walk down the tree towards the uri. When we hit build.zig files
@ -691,7 +694,7 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]u8, open: bool) erro
handle.associated_build_file = gop.key_ptr.*; handle.associated_build_file = gop.key_ptr.*;
break; break;
} else if (handle.associated_build_file == null) { } else if (handle.associated_build_file == null) {
handle.associated_build_file = build_file_uri; handle.associated_build_file = gop.key_ptr.*;
} }
} }
} }
@ -831,6 +834,8 @@ pub fn resolveCImport(self: *DocumentStore, handle: Handle, node: Ast.Node.Index
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
if (!std.process.can_spawn) return null;
const index = std.mem.indexOfScalar(Ast.Node.Index, handle.cimports.items(.node), node).?; const index = std.mem.indexOfScalar(Ast.Node.Index, handle.cimports.items(.node), node).?;
const hash: Hash = handle.cimports.items(.hash)[index]; const hash: Hash = handle.cimports.items(.hash)[index];
@ -921,7 +926,7 @@ pub fn uriFromImportStr(self: *const DocumentStore, allocator: std.mem.Allocator
} }
} }
fn tagStoreCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle, comptime name: []const u8) ![]types.CompletionItem { fn tagStoreCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle, comptime name: []const u8) error{OutOfMemory}![]types.CompletionItem {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -944,11 +949,11 @@ fn tagStoreCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle
return result_set.entries.items(.key); return result_set.entries.items(.key);
} }
pub fn errorCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle) ![]types.CompletionItem { pub fn errorCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle) error{OutOfMemory}![]types.CompletionItem {
return try self.tagStoreCompletionItems(arena, handle, "error_completions"); return try self.tagStoreCompletionItems(arena, handle, "error_completions");
} }
pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle) ![]types.CompletionItem { pub fn enumCompletionItems(self: DocumentStore, arena: std.mem.Allocator, handle: Handle) error{OutOfMemory}![]types.CompletionItem {
return try self.tagStoreCompletionItems(arena, handle, "enum_completions"); return try self.tagStoreCompletionItems(arena, handle, "enum_completions");
} }

View File

@ -34,7 +34,7 @@ config: *Config,
allocator: std.mem.Allocator = undefined, allocator: std.mem.Allocator = undefined,
arena: *std.heap.ArenaAllocator = undefined, arena: *std.heap.ArenaAllocator = undefined,
document_store: DocumentStore = undefined, document_store: DocumentStore = undefined,
builtin_completions: std.ArrayListUnmanaged(types.CompletionItem), builtin_completions: ?std.ArrayListUnmanaged(types.CompletionItem),
client_capabilities: ClientCapabilities = .{}, client_capabilities: ClientCapabilities = .{},
outgoing_messages: std.ArrayListUnmanaged([]const u8) = .{}, outgoing_messages: std.ArrayListUnmanaged([]const u8) = .{},
recording_enabled: bool, recording_enabled: bool,
@ -49,6 +49,10 @@ status: enum {
initialized, initialized,
/// the server has been shutdown and can't handle any more requests /// the server has been shutdown and can't handle any more requests
shutdown, shutdown,
/// the server is received a `exit` notification and has been shutdown
exiting_success,
/// the server is received a `exit` notification but has not been shutdown
exiting_failure,
}, },
// Code was based off of https://github.com/andersfr/zig-lsp/blob/master/server.zig // Code was based off of https://github.com/andersfr/zig-lsp/blob/master/server.zig
@ -66,8 +70,7 @@ const ClientCapabilities = packed struct {
supports_configuration: bool = false, supports_configuration: bool = false,
}; };
/// TODO remove anyerror pub const Error = std.mem.Allocator.Error || error{
pub const Error = anyerror || std.mem.Allocator.Error || error{
ParseError, ParseError,
InvalidRequest, InvalidRequest,
MethodNotFound, MethodNotFound,
@ -138,7 +141,7 @@ fn sendInternal(
maybe_err: ?types.ResponseError, maybe_err: ?types.ResponseError,
extra_name: []const u8, extra_name: []const u8,
extra: anytype, extra: anytype,
) !void { ) error{OutOfMemory}!void {
var buffer = std.ArrayListUnmanaged(u8){}; var buffer = std.ArrayListUnmanaged(u8){};
var writer = buffer.writer(server.allocator); var writer = buffer.writer(server.allocator);
try writer.writeAll( try writer.writeAll(
@ -185,13 +188,19 @@ fn showMessage(
args: anytype, args: anytype,
) void { ) void {
const message = std.fmt.allocPrint(server.arena.allocator(), fmt, args) catch return; const message = std.fmt.allocPrint(server.arena.allocator(), fmt, args) catch return;
switch (message_type) {
.Error => log.err("{s}", .{message}),
.Warning => log.warn("{s}", .{message}),
.Info => log.info("{s}", .{message}),
.Log => log.debug("{s}", .{message}),
}
server.sendNotification("window/showMessage", types.ShowMessageParams{ server.sendNotification("window/showMessage", types.ShowMessageParams{
.type = message_type, .type = message_type,
.message = message, .message = message,
}); });
} }
fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) !types.PublishDiagnosticsParams { fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) error{OutOfMemory}!types.PublishDiagnosticsParams {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -205,7 +214,7 @@ fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) !types.Pub
for (tree.errors) |err| { for (tree.errors) |err| {
var mem_buffer: [256]u8 = undefined; var mem_buffer: [256]u8 = undefined;
var fbs = std.io.fixedBufferStream(&mem_buffer); var fbs = std.io.fixedBufferStream(&mem_buffer);
try tree.renderError(err, fbs.writer()); tree.renderError(err, fbs.writer()) catch if (std.debug.runtime_safety) unreachable else continue; // if an error occurs here increase buffer size
try diagnostics.append(allocator, .{ try diagnostics.append(allocator, .{
.range = offsets.tokenToRange(tree, err.token, server.offset_encoding), .range = offsets.tokenToRange(tree, err.token, server.offset_encoding),
@ -218,7 +227,9 @@ fn generateDiagnostics(server: *Server, handle: DocumentStore.Handle) !types.Pub
} }
if (server.config.enable_ast_check_diagnostics and tree.errors.len == 0) { if (server.config.enable_ast_check_diagnostics and tree.errors.len == 0) {
try getAstCheckDiagnostics(server, handle, &diagnostics); getAstCheckDiagnostics(server, handle, &diagnostics) catch |err| {
log.err("failed to run ast-check: {}", .{err});
};
} }
if (server.config.warn_style) { if (server.config.warn_style) {
@ -485,12 +496,14 @@ fn getAutofixMode(server: *Server) enum {
} }
/// caller owns returned memory. /// caller owns returned memory.
fn autofix(server: *Server, allocator: std.mem.Allocator, handle: *const DocumentStore.Handle) !std.ArrayListUnmanaged(types.TextEdit) { fn autofix(server: *Server, allocator: std.mem.Allocator, handle: *const DocumentStore.Handle) error{OutOfMemory}!std.ArrayListUnmanaged(types.TextEdit) {
if (!server.config.enable_ast_check_diagnostics) return .{}; if (!server.config.enable_ast_check_diagnostics) return .{};
if (handle.tree.errors.len != 0) return .{}; if (handle.tree.errors.len != 0) return .{};
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){}; var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
try getAstCheckDiagnostics(server, handle.*, &diagnostics); getAstCheckDiagnostics(server, handle.*, &diagnostics) catch |err| {
log.err("failed to run ast-check: {}", .{err});
};
var builder = code_actions.Builder{ var builder = code_actions.Builder{
.arena = server.arena, .arena = server.arena,
@ -720,16 +733,14 @@ fn nodeToCompletion(
.container_field_init, .container_field_init,
=> { => {
const field = ast.containerField(tree, node).?; const field = ast.containerField(tree, node).?;
if (!field.ast.tuple_like) { try list.append(allocator, .{
try list.append(allocator, .{ .label = handle.tree.tokenSlice(field.ast.main_token),
.label = handle.tree.tokenSlice(field.ast.main_token), .kind = if (field.ast.tuple_like) .Enum else .Field,
.kind = .Field, .documentation = doc,
.documentation = doc, .detail = analysis.getContainerFieldSignature(handle.tree, field),
.detail = analysis.getContainerFieldSignature(handle.tree, field), .insertText = tree.tokenSlice(field.ast.main_token),
.insertText = tree.tokenSlice(field.ast.main_token), .insertTextFormat = .PlainText,
.insertTextFormat = .PlainText, });
});
}
}, },
.array_type, .array_type,
.array_type_sentinel, .array_type_sentinel,
@ -908,7 +919,13 @@ fn hoverSymbol(server: *Server, decl_handle: analysis.DeclWithHandle) error{OutO
const end = offsets.tokenToLoc(tree, last_token).end; const end = offsets.tokenToLoc(tree, last_token).end;
break :def tree.source[start..end]; break :def tree.source[start..end];
}, },
.pointer_payload, .array_payload, .array_index, .switch_payload, .label_decl => tree.tokenSlice(decl_handle.nameToken()), .pointer_payload,
.array_payload,
.array_index,
.switch_payload,
.label_decl,
.error_token,
=> tree.tokenSlice(decl_handle.nameToken()),
}; };
var bound_type_params = analysis.BoundTypeParams{}; var bound_type_params = analysis.BoundTypeParams{};
@ -1078,7 +1095,7 @@ fn getSymbolFieldAccess(
handle: *const DocumentStore.Handle, handle: *const DocumentStore.Handle,
source_index: usize, source_index: usize,
loc: offsets.Loc, loc: offsets.Loc,
) !?analysis.DeclWithHandle { ) error{OutOfMemory}!?analysis.DeclWithHandle {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1159,7 +1176,7 @@ const DeclToCompletionContext = struct {
parent_is_type_val: ?bool = null, parent_is_type_val: ?bool = null,
}; };
fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) !void { fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.DeclWithHandle) error{OutOfMemory}!void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1212,6 +1229,17 @@ fn declToCompletion(context: DeclToCompletionContext, decl_handle: analysis.Decl
.insertTextFormat = .PlainText, .insertTextFormat = .PlainText,
}); });
}, },
.error_token => {
const name = tree.tokenSlice(decl_handle.decl.error_token);
try context.completions.append(allocator, .{
.label = name,
.kind = .Constant,
.detail = try std.fmt.allocPrint(allocator, "error.{s}", .{name}),
.insertText = name,
.insertTextFormat = .PlainText,
});
},
} }
} }
@ -1219,7 +1247,7 @@ fn completeLabel(
server: *Server, server: *Server,
pos_index: usize, pos_index: usize,
handle: *const DocumentStore.Handle, handle: *const DocumentStore.Handle,
) ![]types.CompletionItem { ) error{OutOfMemory}![]types.CompletionItem {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1259,7 +1287,50 @@ fn populateSnippedCompletions(
} }
} }
fn completeGlobal(server: *Server, pos_index: usize, handle: *const DocumentStore.Handle) ![]types.CompletionItem { fn completeBuiltin(server: *Server) error{OutOfMemory}!?[]types.CompletionItem {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
const allocator = server.arena.allocator();
const builtin_completions = if (server.builtin_completions) |completions|
return completions.items
else blk: {
server.builtin_completions = try std.ArrayListUnmanaged(types.CompletionItem).initCapacity(server.allocator, data.builtins.len);
break :blk &server.builtin_completions.?;
};
for (data.builtins) |builtin| {
const insert_text = if (server.config.enable_snippets) builtin.snippet else builtin.name;
builtin_completions.appendAssumeCapacity(.{
.label = builtin.name,
.kind = .Function,
.filterText = builtin.name[1..],
.detail = builtin.signature,
.insertText = if (server.config.include_at_in_builtins) insert_text else insert_text[1..],
.insertTextFormat = if (server.config.enable_snippets) .Snippet else .PlainText,
.documentation = .{
.MarkupContent = .{
.kind = .markdown,
.value = builtin.documentation,
},
},
});
}
var completions = try allocator.alloc(types.CompletionItem, builtin_completions.items.len);
if (server.client_capabilities.label_details_support) {
for (builtin_completions.items) |item, i| {
completions[i] = item;
try formatDetailledLabel(&completions[i], allocator);
}
}
return completions;
}
fn completeGlobal(server: *Server, pos_index: usize, handle: *const DocumentStore.Handle) error{OutOfMemory}![]types.CompletionItem {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1282,7 +1353,7 @@ fn completeGlobal(server: *Server, pos_index: usize, handle: *const DocumentStor
return completions.toOwnedSlice(server.arena.allocator()); return completions.toOwnedSlice(server.arena.allocator());
} }
fn completeFieldAccess(server: *Server, handle: *const DocumentStore.Handle, source_index: usize, loc: offsets.Loc) !?[]types.CompletionItem { fn completeFieldAccess(server: *Server, handle: *const DocumentStore.Handle, source_index: usize, loc: offsets.Loc) error{OutOfMemory}!?[]types.CompletionItem {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1304,7 +1375,7 @@ fn completeFieldAccess(server: *Server, handle: *const DocumentStore.Handle, sou
return try completions.toOwnedSlice(allocator); return try completions.toOwnedSlice(allocator);
} }
fn formatDetailledLabel(item: *types.CompletionItem, alloc: std.mem.Allocator) !void { fn formatDetailledLabel(item: *types.CompletionItem, arena: std.mem.Allocator) error{OutOfMemory}!void {
// NOTE: this is not ideal, we should build a detailled label like we do for label/detail // NOTE: this is not ideal, we should build a detailled label like we do for label/detail
// because this implementation is very loose, nothing is formated properly so we need to clean // because this implementation is very loose, nothing is formated properly so we need to clean
// things a little bit, wich is quite messy // things a little bit, wich is quite messy
@ -1315,7 +1386,7 @@ fn formatDetailledLabel(item: *types.CompletionItem, alloc: std.mem.Allocator) !
return; return;
var detailLen: usize = item.detail.?.len; var detailLen: usize = item.detail.?.len;
var it: []u8 = try alloc.alloc(u8, detailLen); var it: []u8 = try arena.alloc(u8, detailLen);
detailLen -= std.mem.replace(u8, item.detail.?, " ", " ", it) * 3; detailLen -= std.mem.replace(u8, item.detail.?, " ", " ", it) * 3;
it = it[0..detailLen]; it = it[0..detailLen];
@ -1470,7 +1541,7 @@ fn formatDetailledLabel(item: *types.CompletionItem, alloc: std.mem.Allocator) !
// logger.info("labelDetails: {s} :: {s}", .{item.labelDetails.?.detail, item.labelDetails.?.description}); // logger.info("labelDetails: {s} :: {s}", .{item.labelDetails.?.detail, item.labelDetails.?.description});
} }
fn completeError(server: *Server, handle: *const DocumentStore.Handle) ![]types.CompletionItem { fn completeError(server: *Server, handle: *const DocumentStore.Handle) error{OutOfMemory}![]types.CompletionItem {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1505,7 +1576,7 @@ fn kindToSortScore(kind: types.CompletionItemKind) ?[]const u8 {
}; };
} }
fn completeDot(server: *Server, handle: *const DocumentStore.Handle) ![]types.CompletionItem { fn completeDot(server: *Server, handle: *const DocumentStore.Handle) error{OutOfMemory}![]types.CompletionItem {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1570,7 +1641,7 @@ fn completeFileSystemStringLiteral(allocator: std.mem.Allocator, store: *const D
return completions.toOwnedSlice(allocator); return completions.toOwnedSlice(allocator);
} }
fn initializeHandler(server: *Server, request: types.InitializeParams) !types.InitializeResult { fn initializeHandler(server: *Server, request: types.InitializeParams) Error!types.InitializeResult {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1661,14 +1732,6 @@ fn initializeHandler(server: *Server, request: types.InitializeParams) !types.In
} }
} }
// NOTE: everything is initialized, we got the client capabilities
// so we can now format the prebuilt builtins items for labelDetails
if (server.client_capabilities.label_details_support) {
for (server.builtin_completions.items) |*item| {
try formatDetailledLabel(item, std.heap.page_allocator);
}
}
if (request.capabilities.workspace) |workspace| { if (request.capabilities.workspace) |workspace| {
server.client_capabilities.supports_apply_edits = workspace.applyEdit orelse false; server.client_capabilities.supports_apply_edits = workspace.applyEdit orelse false;
server.client_capabilities.supports_configuration = workspace.configuration orelse false; server.client_capabilities.supports_configuration = workspace.configuration orelse false;
@ -1686,17 +1749,38 @@ fn initializeHandler(server: *Server, request: types.InitializeParams) !types.In
server.status = .initializing; server.status = .initializing;
if (server.config.zig_exe_path) |exe_path| blk: { if (server.config.zig_exe_path) |exe_path| blk: {
if (!std.process.can_spawn) break :blk;
// TODO avoid having to call getZigEnv twice // TODO avoid having to call getZigEnv twice
// once in init and here // once in init and here
const env = configuration.getZigEnv(server.allocator, exe_path) orelse break :blk; const env = configuration.getZigEnv(server.allocator, exe_path) orelse break :blk;
defer std.json.parseFree(configuration.Env, env, .{ .allocator = server.allocator }); defer std.json.parseFree(configuration.Env, env, .{ .allocator = server.allocator });
const zig_exe_version = std.SemanticVersion.parse(env.version) catch break :blk; const zig_version = std.SemanticVersion.parse(env.version) catch break :blk;
const zls_version = comptime std.SemanticVersion.parse(build_options.version) catch unreachable;
if (zig_builtin.zig_version.order(zig_exe_version) == .gt) { const zig_version_simple = std.SemanticVersion{
server.showMessage(.Warning, .major = zig_version.major,
\\ZLS was built with Zig {}, but your Zig version is {s}. Update Zig to avoid unexpected behavior. .minor = zig_version.minor,
, .{ zig_builtin.zig_version, env.version }); .patch = 0,
};
const zls_version_simple = std.SemanticVersion{
.major = zls_version.major,
.minor = zls_version.minor,
.patch = 0,
};
switch (zig_version_simple.order(zls_version_simple)) {
.lt => {
server.showMessage(.Warning,
\\Zig `{}` is older than ZLS `{}`. Update Zig to avoid unexpected behavior.
, .{ zig_version, zls_version });
},
.eq => {},
.gt => {
server.showMessage(.Warning,
\\Zig `{}` is newer than ZLS `{}`. Update ZLS to avoid unexpected behavior.
, .{ zig_version, zls_version });
},
} }
} else { } else {
server.showMessage(.Warning, server.showMessage(.Warning,
@ -1786,7 +1870,7 @@ fn initializeHandler(server: *Server, request: types.InitializeParams) !types.In
}; };
} }
fn initializedHandler(server: *Server, notification: types.InitializedParams) !void { fn initializedHandler(server: *Server, notification: types.InitializedParams) Error!void {
_ = notification; _ = notification;
if (server.status != .initializing) { if (server.status != .initializing) {
@ -1799,32 +1883,28 @@ fn initializedHandler(server: *Server, notification: types.InitializedParams) !v
try server.requestConfiguration(); try server.requestConfiguration();
} }
fn shutdownHandler(server: *Server, _: void) !?void { fn shutdownHandler(server: *Server, _: void) Error!?void {
defer server.status = .shutdown;
if (server.status != .initialized) return error.InvalidRequest; // received a shutdown request but the server is not initialized! if (server.status != .initialized) return error.InvalidRequest; // received a shutdown request but the server is not initialized!
// Technically we should deinitialize first and send possible errors to the client
return null; return null;
} }
fn exitHandler(server: *Server, _: void) noreturn { fn exitHandler(server: *Server, _: void) Error!void {
log.info("Server exiting...", .{}); server.status = switch (server.status) {
// Technically we should deinitialize first and send possible errors to the client .initialized => .exiting_failure,
.shutdown => .exiting_success,
const error_code: u8 = switch (server.status) { else => unreachable,
.uninitialized, .shutdown => 0,
else => 1,
}; };
std.os.exit(error_code);
} }
fn cancelRequestHandler(server: *Server, request: types.CancelParams) !void { fn cancelRequestHandler(server: *Server, request: types.CancelParams) Error!void {
_ = server; _ = server;
_ = request; _ = request;
// TODO implement $/cancelRequest // TODO implement $/cancelRequest
} }
fn registerCapability(server: *Server, method: []const u8) !void { fn registerCapability(server: *Server, method: []const u8) Error!void {
const allocator = server.arena.allocator(); const allocator = server.arena.allocator();
const id = try std.fmt.allocPrint(allocator, "register-{s}", .{method}); const id = try std.fmt.allocPrint(allocator, "register-{s}", .{method});
@ -1843,7 +1923,7 @@ fn registerCapability(server: *Server, method: []const u8) !void {
); );
} }
fn requestConfiguration(server: *Server) !void { fn requestConfiguration(server: *Server) Error!void {
if (server.recording_enabled) { if (server.recording_enabled) {
log.info("workspace/configuration are disabled during a recording session!", .{}); log.info("workspace/configuration are disabled during a recording session!", .{});
return; return;
@ -1930,19 +2010,20 @@ fn handleConfiguration(server: *Server, json: std.json.Value) error{OutOfMemory}
}; };
} }
fn openDocumentHandler(server: *Server, notification: types.DidOpenTextDocumentParams) !void { fn openDocumentHandler(server: *Server, notification: types.DidOpenTextDocumentParams) Error!void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
const handle = try server.document_store.openDocument(notification.textDocument.uri, notification.textDocument.text); const handle = try server.document_store.openDocument(notification.textDocument.uri, notification.textDocument.text);
if (server.client_capabilities.supports_publish_diagnostics) { if (server.client_capabilities.supports_publish_diagnostics) blk: {
if (!std.process.can_spawn) break :blk;
const diagnostics = try server.generateDiagnostics(handle); const diagnostics = try server.generateDiagnostics(handle);
server.sendNotification("textDocument/publishDiagnostics", diagnostics); server.sendNotification("textDocument/publishDiagnostics", diagnostics);
} }
} }
fn changeDocumentHandler(server: *Server, notification: types.DidChangeTextDocumentParams) !void { fn changeDocumentHandler(server: *Server, notification: types.DidChangeTextDocumentParams) Error!void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1952,13 +2033,14 @@ fn changeDocumentHandler(server: *Server, notification: types.DidChangeTextDocum
try server.document_store.refreshDocument(handle.uri, new_text); try server.document_store.refreshDocument(handle.uri, new_text);
if (server.client_capabilities.supports_publish_diagnostics) { if (server.client_capabilities.supports_publish_diagnostics) blk: {
if (!std.process.can_spawn) break :blk;
const diagnostics = try server.generateDiagnostics(handle.*); const diagnostics = try server.generateDiagnostics(handle.*);
server.sendNotification("textDocument/publishDiagnostics", diagnostics); server.sendNotification("textDocument/publishDiagnostics", diagnostics);
} }
} }
fn saveDocumentHandler(server: *Server, notification: types.DidSaveTextDocumentParams) !void { fn saveDocumentHandler(server: *Server, notification: types.DidSaveTextDocumentParams) Error!void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -1968,7 +2050,7 @@ fn saveDocumentHandler(server: *Server, notification: types.DidSaveTextDocumentP
const handle = server.document_store.getHandle(uri) orelse return; const handle = server.document_store.getHandle(uri) orelse return;
try server.document_store.applySave(handle); try server.document_store.applySave(handle);
if (server.getAutofixMode() == .on_save) { if (std.process.can_spawn and server.getAutofixMode() == .on_save) {
var text_edits = try server.autofix(allocator, handle); var text_edits = try server.autofix(allocator, handle);
var workspace_edit = types.WorkspaceEdit{ .changes = .{} }; var workspace_edit = types.WorkspaceEdit{ .changes = .{} };
@ -1992,7 +2074,7 @@ fn closeDocumentHandler(server: *Server, notification: types.DidCloseTextDocumen
server.document_store.closeDocument(notification.textDocument.uri); server.document_store.closeDocument(notification.textDocument.uri);
} }
fn willSaveWaitUntilHandler(server: *Server, request: types.WillSaveTextDocumentParams) !?[]types.TextEdit { fn willSaveWaitUntilHandler(server: *Server, request: types.WillSaveTextDocumentParams) Error!?[]types.TextEdit {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2002,12 +2084,13 @@ fn willSaveWaitUntilHandler(server: *Server, request: types.WillSaveTextDocument
const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null; const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null;
if (!std.process.can_spawn) return null;
var text_edits = try server.autofix(allocator, handle); var text_edits = try server.autofix(allocator, handle);
return try text_edits.toOwnedSlice(allocator); return try text_edits.toOwnedSlice(allocator);
} }
fn semanticTokensFullHandler(server: *Server, request: types.SemanticTokensParams) !?types.SemanticTokens { fn semanticTokensFullHandler(server: *Server, request: types.SemanticTokensParams) Error!?types.SemanticTokens {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2020,7 +2103,7 @@ fn semanticTokensFullHandler(server: *Server, request: types.SemanticTokensParam
return .{ .data = token_array }; return .{ .data = token_array };
} }
fn completionHandler(server: *Server, request: types.CompletionParams) !?types.CompletionList { fn completionHandler(server: *Server, request: types.CompletionParams) Error!?types.CompletionList {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2037,7 +2120,7 @@ fn completionHandler(server: *Server, request: types.CompletionParams) !?types.C
const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.text, source_index); const pos_context = try analysis.getPositionContext(server.arena.allocator(), handle.text, source_index);
const maybe_completions = switch (pos_context) { const maybe_completions = switch (pos_context) {
.builtin => server.builtin_completions.items, .builtin => try server.completeBuiltin(),
.var_access, .empty => try server.completeGlobal(source_index, handle), .var_access, .empty => try server.completeGlobal(source_index, handle),
.field_access => |loc| try server.completeFieldAccess(handle, source_index, loc), .field_access => |loc| try server.completeFieldAccess(handle, source_index, loc),
.global_error_set => try server.completeError(handle), .global_error_set => try server.completeError(handle),
@ -2048,7 +2131,10 @@ fn completionHandler(server: *Server, request: types.CompletionParams) !?types.C
const completing = offsets.locToSlice(handle.tree.source, loc); const completing = offsets.locToSlice(handle.tree.source, loc);
const is_import = pos_context == .import_string_literal; const is_import = pos_context == .import_string_literal;
break :blk try completeFileSystemStringLiteral(server.arena.allocator(), &server.document_store, handle, completing, is_import); break :blk completeFileSystemStringLiteral(server.arena.allocator(), &server.document_store, handle, completing, is_import) catch |err| {
log.err("failed to get file system completions: {}", .{err});
return null;
};
}, },
else => null, else => null,
}; };
@ -2074,7 +2160,7 @@ fn completionHandler(server: *Server, request: types.CompletionParams) !?types.C
return .{ .isIncomplete = false, .items = completions }; return .{ .isIncomplete = false, .items = completions };
} }
fn signatureHelpHandler(server: *Server, request: types.SignatureHelpParams) !?types.SignatureHelp { fn signatureHelpHandler(server: *Server, request: types.SignatureHelpParams) Error!?types.SignatureHelp {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2103,7 +2189,7 @@ fn signatureHelpHandler(server: *Server, request: types.SignatureHelpParams) !?t
}; };
} }
fn gotoHandler(server: *Server, request: types.TextDocumentPositionParams, resolve_alias: bool) !?types.Location { fn gotoHandler(server: *Server, request: types.TextDocumentPositionParams, resolve_alias: bool) Error!?types.Location {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2126,7 +2212,7 @@ fn gotoHandler(server: *Server, request: types.TextDocumentPositionParams, resol
fn gotoDefinitionHandler( fn gotoDefinitionHandler(
server: *Server, server: *Server,
request: types.TextDocumentPositionParams, request: types.TextDocumentPositionParams,
) !?types.Location { ) Error!?types.Location {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2136,14 +2222,14 @@ fn gotoDefinitionHandler(
fn gotoDeclarationHandler( fn gotoDeclarationHandler(
server: *Server, server: *Server,
request: types.TextDocumentPositionParams, request: types.TextDocumentPositionParams,
) !?types.Location { ) Error!?types.Location {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
return try server.gotoHandler(request, false); return try server.gotoHandler(request, false);
} }
fn hoverHandler(server: *Server, request: types.HoverParams) !?types.Hover { fn hoverHandler(server: *Server, request: types.HoverParams) Error!?types.Hover {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2163,7 +2249,8 @@ fn hoverHandler(server: *Server, request: types.HoverParams) !?types.Hover {
}; };
// TODO: Figure out a better solution for comptime interpreter diags // TODO: Figure out a better solution for comptime interpreter diags
if (server.client_capabilities.supports_publish_diagnostics) { if (server.client_capabilities.supports_publish_diagnostics) blk: {
if (!std.process.can_spawn) break :blk;
const diagnostics = try server.generateDiagnostics(handle.*); const diagnostics = try server.generateDiagnostics(handle.*);
server.sendNotification("textDocument/publishDiagnostics", diagnostics); server.sendNotification("textDocument/publishDiagnostics", diagnostics);
} }
@ -2171,7 +2258,7 @@ fn hoverHandler(server: *Server, request: types.HoverParams) !?types.Hover {
return response; return response;
} }
fn documentSymbolsHandler(server: *Server, request: types.DocumentSymbolParams) !?[]types.DocumentSymbol { fn documentSymbolsHandler(server: *Server, request: types.DocumentSymbolParams) Error!?[]types.DocumentSymbol {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2180,7 +2267,7 @@ fn documentSymbolsHandler(server: *Server, request: types.DocumentSymbolParams)
return try analysis.getDocumentSymbols(server.arena.allocator(), handle.tree, server.offset_encoding); return try analysis.getDocumentSymbols(server.arena.allocator(), handle.tree, server.offset_encoding);
} }
fn formattingHandler(server: *Server, request: types.DocumentFormattingParams) !?[]types.TextEdit { fn formattingHandler(server: *Server, request: types.DocumentFormattingParams) Error!?[]types.TextEdit {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2188,35 +2275,25 @@ fn formattingHandler(server: *Server, request: types.DocumentFormattingParams) !
if (handle.tree.errors.len != 0) return null; if (handle.tree.errors.len != 0) return null;
const formatted = try handle.tree.render(server.allocator); const allocator = server.arena.allocator();
defer server.allocator.free(formatted);
const formatted = try handle.tree.render(allocator);
if (std.mem.eql(u8, handle.text, formatted)) return null; if (std.mem.eql(u8, handle.text, formatted)) return null;
// avoid computing diffs if the output is small if (formatted.len <= 512) {
const maybe_edits = if (formatted.len <= 512) null else diff.edits(server.arena.allocator(), handle.text, formatted) catch null; var text_edits = try allocator.alloc(types.TextEdit, 1);
text_edits[0] = .{
const edits = maybe_edits orelse {
// if edits have been computed we replace the entire file with the formatted text
return &[1]types.TextEdit{.{
.range = offsets.locToRange(handle.text, .{ .start = 0, .end = handle.text.len }, server.offset_encoding), .range = offsets.locToRange(handle.text, .{ .start = 0, .end = handle.text.len }, server.offset_encoding),
.newText = formatted, .newText = formatted,
}}; };
}; return text_edits;
// Convert from `[]diff.Edit` to `[]types.TextEdit`
var text_edits = try std.ArrayListUnmanaged(types.TextEdit).initCapacity(server.arena.allocator(), edits.items.len);
for (edits.items) |edit| {
text_edits.appendAssumeCapacity(.{
.range = edit.range,
.newText = edit.newText.items,
});
} }
return text_edits.items; return if (diff.edits(allocator, handle.text, formatted)) |text_edits| text_edits.items else |_| null;
} }
fn didChangeConfigurationHandler(server: *Server, request: configuration.DidChangeConfigurationParams) !void { fn didChangeConfigurationHandler(server: *Server, request: configuration.DidChangeConfigurationParams) Error!void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2236,13 +2313,15 @@ fn didChangeConfigurationHandler(server: *Server, request: configuration.DidChan
} }
} }
try configuration.configChanged(server.config, server.allocator, null); configuration.configChanged(server.config, server.allocator, null) catch |err| {
log.err("failed to update config: {}", .{err});
};
} else if (server.client_capabilities.supports_configuration) { } else if (server.client_capabilities.supports_configuration) {
try server.requestConfiguration(); try server.requestConfiguration();
} }
} }
fn renameHandler(server: *Server, request: types.RenameParams) !?types.WorkspaceEdit { fn renameHandler(server: *Server, request: types.RenameParams) Error!?types.WorkspaceEdit {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2250,7 +2329,7 @@ fn renameHandler(server: *Server, request: types.RenameParams) !?types.Workspace
return if (response) |rep| rep.rename else null; return if (response) |rep| rep.rename else null;
} }
fn referencesHandler(server: *Server, request: types.ReferenceParams) !?[]types.Location { fn referencesHandler(server: *Server, request: types.ReferenceParams) Error!?[]types.Location {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2258,7 +2337,7 @@ fn referencesHandler(server: *Server, request: types.ReferenceParams) !?[]types.
return if (response) |rep| rep.references else null; return if (response) |rep| rep.references else null;
} }
fn documentHighlightHandler(server: *Server, request: types.DocumentHighlightParams) !?[]types.DocumentHighlight { fn documentHighlightHandler(server: *Server, request: types.DocumentHighlightParams) Error!?[]types.DocumentHighlight {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2294,7 +2373,7 @@ const GeneralReferencesResponse = union {
highlight: []types.DocumentHighlight, highlight: []types.DocumentHighlight,
}; };
fn generalReferencesHandler(server: *Server, request: GeneralReferencesRequest) !?GeneralReferencesResponse { fn generalReferencesHandler(server: *Server, request: GeneralReferencesRequest) Error!?GeneralReferencesResponse {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2379,7 +2458,7 @@ fn isPositionBefore(lhs: types.Position, rhs: types.Position) bool {
} }
} }
fn inlayHintHandler(server: *Server, request: types.InlayHintParams) !?[]types.InlayHint { fn inlayHintHandler(server: *Server, request: types.InlayHintParams) Error!?[]types.InlayHint {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2421,7 +2500,7 @@ fn inlayHintHandler(server: *Server, request: types.InlayHintParams) !?[]types.I
return visible_hints; return visible_hints;
} }
fn codeActionHandler(server: *Server, request: types.CodeActionParams) !?[]types.CodeAction { fn codeActionHandler(server: *Server, request: types.CodeActionParams) Error!?[]types.CodeAction {
const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null; const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null;
var builder = code_actions.Builder{ var builder = code_actions.Builder{
@ -2433,8 +2512,12 @@ fn codeActionHandler(server: *Server, request: types.CodeActionParams) !?[]types
// as of right now, only ast-check errors may get a code action // as of right now, only ast-check errors may get a code action
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){}; var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
if (server.config.enable_ast_check_diagnostics and handle.tree.errors.len == 0) { if (server.config.enable_ast_check_diagnostics and handle.tree.errors.len == 0) blk: {
try getAstCheckDiagnostics(server, handle.*, &diagnostics); if (!std.process.can_spawn) break :blk;
getAstCheckDiagnostics(server, handle.*, &diagnostics) catch |err| {
log.err("failed to run ast-check: {}", .{err});
return error.InternalError;
};
} }
var actions = std.ArrayListUnmanaged(types.CodeAction){}; var actions = std.ArrayListUnmanaged(types.CodeAction){};
@ -2445,7 +2528,7 @@ fn codeActionHandler(server: *Server, request: types.CodeActionParams) !?[]types
return actions.items; return actions.items;
} }
fn foldingRangeHandler(server: *Server, request: types.FoldingRangeParams) !?[]types.FoldingRange { fn foldingRangeHandler(server: *Server, request: types.FoldingRangeParams) Error!?[]types.FoldingRange {
const Token = std.zig.Token; const Token = std.zig.Token;
const Node = Ast.Node; const Node = Ast.Node;
const allocator = server.arena.allocator(); const allocator = server.arena.allocator();
@ -2708,7 +2791,7 @@ pub const SelectionRange = struct {
parent: ?*SelectionRange, parent: ?*SelectionRange,
}; };
fn selectionRangeHandler(server: *Server, request: types.SelectionRangeParams) !?[]*SelectionRange { fn selectionRangeHandler(server: *Server, request: types.SelectionRangeParams) Error!?[]*SelectionRange {
const allocator = server.arena.allocator(); const allocator = server.arena.allocator();
const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null; const handle = server.document_store.getHandle(request.textDocument.uri) orelse return null;
@ -2898,26 +2981,23 @@ pub fn processJsonRpc(
var parser = std.json.Parser.init(server.arena.allocator(), false); var parser = std.json.Parser.init(server.arena.allocator(), false);
defer parser.deinit(); defer parser.deinit();
var tree = parser.parse(json) catch { var tree = parser.parse(json) catch |err| {
log.err("failed to parse message!", .{}); log.err("failed to parse message: {}", .{err});
return; // maybe panic? return; // maybe panic?
}; };
defer tree.deinit(); defer tree.deinit();
const message = Message.fromJsonValueTree(tree) catch { const message = Message.fromJsonValueTree(tree) catch |err| {
log.err("failed to parse message!", .{}); log.err("failed to parse message: {}", .{err});
return; // maybe panic? return; // maybe panic?
}; };
server.processMessage(message) catch |err| { server.processMessage(message) catch |err| switch (message) {
log.err("got {} while processing message!", .{err}); // TODO include message information .RequestMessage => |request| server.sendResponseError(request.id, .{
switch (message) { .code = @errorToInt(err),
.RequestMessage => |request| server.sendResponseError(request.id, .{ .message = @errorName(err),
.code = @errorToInt(err), }),
.message = @errorName(err), else => {},
}),
else => {},
}
}; };
} }
@ -2969,12 +3049,15 @@ fn processMessage(server: *Server, message: Message) Error!void {
return error.InvalidRequest; // server received a request after shutdown! return error.InvalidRequest; // server received a request after shutdown!
}, },
.exiting_success,
.exiting_failure,
=> unreachable,
} }
const start_time = std.time.milliTimestamp(); const start_time = std.time.milliTimestamp();
defer { defer {
// makes `zig build test` look nice // makes `zig build test` look nice
if (!zig_builtin.is_test and !std.mem.eql(u8, method, "shutdown")) { if (!zig_builtin.is_test) {
const end_time = std.time.milliTimestamp(); const end_time = std.time.milliTimestamp();
log.debug("Took {}ms to process method {s}", .{ end_time - start_time, method }); log.debug("Took {}ms to process method {s}", .{ end_time - start_time, method });
} }
@ -3031,7 +3114,13 @@ fn processMessage(server: *Server, message: Message) Error!void {
const ParamsType = handler_info.params[1].type.?; // TODO add error message on null const ParamsType = handler_info.params[1].type.?; // TODO add error message on null
const params: ParamsType = tres.parse(ParamsType, message.params().?, server.arena.allocator()) catch return error.InternalError; const params: ParamsType = tres.parse(ParamsType, message.params().?, server.arena.allocator()) catch return error.InternalError;
const response = handler(server, params) catch return error.InternalError; const response = handler(server, params) catch |err| {
log.err("got {} error while handling {s}", .{ err, method });
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
return error.InternalError;
};
if (@TypeOf(response) == void) return; if (@TypeOf(response) == void) return;
@ -3070,32 +3159,11 @@ pub fn init(
}; };
errdefer document_store.deinit(); errdefer document_store.deinit();
var builtin_completions = try std.ArrayListUnmanaged(types.CompletionItem).initCapacity(allocator, data.builtins.len);
errdefer builtin_completions.deinit(allocator);
for (data.builtins) |builtin| {
const insert_text = if (config.enable_snippets) builtin.snippet else builtin.name;
builtin_completions.appendAssumeCapacity(.{
.label = builtin.name,
.kind = .Function,
.filterText = builtin.name[1..],
.detail = builtin.signature,
.insertText = if (config.include_at_in_builtins) insert_text else insert_text[1..],
.insertTextFormat = if (config.enable_snippets) .Snippet else .PlainText,
.documentation = .{
.MarkupContent = .{
.kind = .markdown,
.value = builtin.documentation,
},
},
});
}
return Server{ return Server{
.config = config, .config = config,
.allocator = allocator, .allocator = allocator,
.document_store = document_store, .document_store = document_store,
.builtin_completions = builtin_completions, .builtin_completions = null,
.recording_enabled = recording_enabled, .recording_enabled = recording_enabled,
.replay_enabled = replay_enabled, .replay_enabled = replay_enabled,
.status = .uninitialized, .status = .uninitialized,
@ -3106,7 +3174,7 @@ pub fn deinit(server: *Server) void {
server.document_store.deinit(); server.document_store.deinit();
analysis.deinit(); analysis.deinit();
server.builtin_completions.deinit(server.allocator); if (server.builtin_completions) |*completions| completions.deinit(server.allocator);
for (server.outgoing_messages.items) |message| { for (server.outgoing_messages.items) |message| {
server.allocator.free(message); server.allocator.free(message);

View File

@ -3,6 +3,7 @@ const DocumentStore = @import("DocumentStore.zig");
const Ast = std.zig.Ast; const Ast = std.zig.Ast;
const types = @import("lsp.zig"); const types = @import("lsp.zig");
const offsets = @import("offsets.zig"); const offsets = @import("offsets.zig");
const URI = @import("uri.zig");
const log = std.log.scoped(.analysis); const log = std.log.scoped(.analysis);
const ast = @import("ast.zig"); const ast = @import("ast.zig");
const ComptimeInterpreter = @import("ComptimeInterpreter.zig"); const ComptimeInterpreter = @import("ComptimeInterpreter.zig");
@ -292,9 +293,12 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex {
}, },
// containers // containers
.container_field, .container_field_init, .container_field_align => { .container_field,
.container_field_init,
.container_field_align,
=> {
const field = ast.containerField(tree, node).?.ast; const field = ast.containerField(tree, node).?.ast;
return if (field.tuple_like) null else field.main_token; return field.main_token;
}, },
.identifier => main_token, .identifier => main_token,
@ -975,6 +979,30 @@ pub fn resolveTypeOfNodeInternal(store: *DocumentStore, arena: *std.heap.ArenaAl
return resolved_type; return resolved_type;
} }
if (std.mem.eql(u8, call_name, "@typeInfo")) {
const zig_lib_path = try URI.fromPath(arena.allocator(), store.config.zig_lib_path orelse return null);
const builtin_uri = URI.pathRelative(arena.allocator(), zig_lib_path, "/std/builtin.zig") catch |err| switch (err) {
error.OutOfMemory => |e| return e,
else => return null,
};
const new_handle = store.getOrLoadHandle(builtin_uri) orelse return null;
const root_scope = new_handle.document_scope.scopes.items[0];
const decl = root_scope.decls.get("Type") orelse return null;
if (decl != .ast_node) return null;
const var_decl = ast.varDecl(new_handle.tree, decl.ast_node) orelse return null;
return TypeWithHandle{
.type = .{
.data = .{ .other = var_decl.ast.init_node },
.is_type_val = false,
},
.handle = new_handle,
};
}
if (std.mem.eql(u8, call_name, "@import")) { if (std.mem.eql(u8, call_name, "@import")) {
if (params.len == 0) return null; if (params.len == 0) return null;
const import_param = params[0]; const import_param = params[0];
@ -1693,7 +1721,7 @@ pub fn getPositionContext(allocator: std.mem.Allocator, text: []const u8, doc_in
return if (tok.tag == .identifier) PositionContext{ .var_access = tok.loc } else .empty; return if (tok.tag == .identifier) PositionContext{ .var_access = tok.loc } else .empty;
} }
fn addOutlineNodes(allocator: std.mem.Allocator, tree: Ast, child: Ast.Node.Index, context: *GetDocumentSymbolsContext) anyerror!void { fn addOutlineNodes(allocator: std.mem.Allocator, tree: Ast, child: Ast.Node.Index, context: *GetDocumentSymbolsContext) error{OutOfMemory}!void {
switch (tree.nodes.items(.tag)[child]) { switch (tree.nodes.items(.tag)[child]) {
.string_literal, .string_literal,
.number_literal, .number_literal,
@ -1838,7 +1866,7 @@ const GetDocumentSymbolsContext = struct {
encoding: offsets.Encoding, encoding: offsets.Encoding,
}; };
fn getDocumentSymbolsInternal(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, context: *GetDocumentSymbolsContext) anyerror!void { fn getDocumentSymbolsInternal(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, context: *GetDocumentSymbolsContext) error{OutOfMemory}!void {
const name = getDeclName(tree, node) orelse return; const name = getDeclName(tree, node) orelse return;
if (name.len == 0) if (name.len == 0)
return; return;
@ -1950,6 +1978,8 @@ pub const Declaration = union(enum) {
label: Ast.TokenIndex, label: Ast.TokenIndex,
block: Ast.Node.Index, block: Ast.Node.Index,
}, },
/// always an identifier
error_token: Ast.Node.Index,
}; };
pub const DeclWithHandle = struct { pub const DeclWithHandle = struct {
@ -1966,6 +1996,7 @@ pub const DeclWithHandle = struct {
.array_index => |ai| ai, .array_index => |ai| ai,
.switch_payload => |sp| sp.node, .switch_payload => |sp| sp.node,
.label_decl => |ld| ld.label, .label_decl => |ld| ld.label,
.error_token => |et| et,
}; };
} }
@ -2065,6 +2096,7 @@ pub const DeclWithHandle = struct {
} }
return null; return null;
}, },
.error_token => return null,
}; };
} }
}; };
@ -2408,6 +2440,7 @@ pub const DocumentScope = struct {
} }
self.scopes.deinit(allocator); self.scopes.deinit(allocator);
for (self.error_completions.entries.items(.key)) |item| { for (self.error_completions.entries.items(.key)) |item| {
if (item.detail) |detail| allocator.free(detail);
switch (item.documentation orelse continue) { switch (item.documentation orelse continue) {
.string => |str| allocator.free(str), .string => |str| allocator.free(str),
.MarkupContent => |content| allocator.free(content.value), .MarkupContent => |content| allocator.free(content.value),
@ -2415,6 +2448,7 @@ pub const DocumentScope = struct {
} }
self.error_completions.deinit(allocator); self.error_completions.deinit(allocator);
for (self.enum_completions.entries.items(.key)) |item| { for (self.enum_completions.entries.items(.key)) |item| {
if (item.detail) |detail| allocator.free(detail);
switch (item.documentation orelse continue) { switch (item.documentation orelse continue) {
.string => |str| allocator.free(str), .string => |str| allocator.free(str),
.MarkupContent => |content| allocator.free(content.value), .MarkupContent => |content| allocator.free(content.value),
@ -2488,9 +2522,6 @@ fn makeInnerScope(allocator: std.mem.Allocator, context: ScopeContext, node_idx:
const main_tokens = tree.nodes.items(.main_token); const main_tokens = tree.nodes.items(.main_token);
const node_tag = tags[node_idx]; const node_tag = tags[node_idx];
var buf: [2]Ast.Node.Index = undefined;
const ast_decls = ast.declMembers(tree, node_idx, &buf);
var scope = try scopes.addOne(allocator); var scope = try scopes.addOne(allocator);
scope.* = .{ scope.* = .{
.loc = offsets.nodeToLoc(tree, node_idx), .loc = offsets.nodeToLoc(tree, node_idx),
@ -2503,26 +2534,26 @@ fn makeInnerScope(allocator: std.mem.Allocator, context: ScopeContext, node_idx:
var i = main_tokens[node_idx]; var i = main_tokens[node_idx];
while (i < data[node_idx].rhs) : (i += 1) { while (i < data[node_idx].rhs) : (i += 1) {
if (token_tags[i] == .identifier) { if (token_tags[i] == .identifier) {
try context.errors.put(allocator, .{ const name = offsets.tokenToSlice(tree, i);
.label = tree.tokenSlice(i), if (try scopes.items[scope_idx].decls.fetchPut(allocator, name, .{ .error_token = i })) |_| {
// TODO Record a redefinition error.
}
const gop = try context.errors.getOrPut(allocator, .{
.label = name,
.kind = .Constant, .kind = .Constant,
.insertText = tree.tokenSlice(i), //.detail =
.insertText = name,
.insertTextFormat = .PlainText, .insertTextFormat = .PlainText,
}, {}); });
if (!gop.found_existing) {
gop.key_ptr.detail = try std.fmt.allocPrint(allocator, "error.{s}", .{name});
}
} }
} }
} }
var buffer: [2]Ast.Node.Index = undefined; var buf: [2]Ast.Node.Index = undefined;
const container_decl = ast.containerDecl(tree, node_idx, &buffer); const ast_decls = ast.declMembers(tree, node_idx, &buf);
// Only tagged unions and enums should pass this
const can_have_enum_completions = if (container_decl) |container| blk: {
const kind = token_tags[container.ast.main_token];
break :blk kind != .keyword_struct and
(kind != .keyword_union or container.ast.enum_token != null or container.ast.arg != 0);
} else false;
for (ast_decls) |decl| { for (ast_decls) |decl| {
if (tags[decl] == .@"usingnamespace") { if (tags[decl] == .@"usingnamespace") {
try scopes.items[scope_idx].uses.append(allocator, decl); try scopes.items[scope_idx].uses.append(allocator, decl);
@ -2541,31 +2572,23 @@ fn makeInnerScope(allocator: std.mem.Allocator, context: ScopeContext, node_idx:
// TODO Record a redefinition error. // TODO Record a redefinition error.
} }
if (!can_have_enum_completions) var buffer: [2]Ast.Node.Index = undefined;
continue; const container_decl = ast.containerDecl(tree, node_idx, &buffer) orelse continue;
const container_field = switch (tags[decl]) { if (container_decl.ast.enum_token != null) {
.container_field => tree.containerField(decl), if (std.mem.eql(u8, name, "_")) return;
.container_field_align => tree.containerFieldAlign(decl), const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation);
.container_field_init => tree.containerFieldInit(decl),
else => null,
};
if (container_field) |_| { var doc: Documentation = if (try getDocComments(allocator, tree, decl, .markdown)) |docs| .{ .MarkupContent = types.MarkupContent{ .kind = .markdown, .value = docs } } else null;
if (!std.mem.eql(u8, name, "_")) { var gop_res = try context.enums.getOrPut(allocator, .{
const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation); .label = name,
.kind = .Constant,
var doc: Documentation = if (try getDocComments(allocator, tree, decl, .markdown)) |docs| .{ .MarkupContent = types.MarkupContent{ .kind = .markdown, .value = docs } } else null; .insertText = name,
var gop_res = try context.enums.getOrPut(allocator, .{ .insertTextFormat = .PlainText,
.label = name, .documentation = doc,
.kind = .Constant, });
.insertText = name, if (gop_res.found_existing) {
.insertTextFormat = .PlainText, if (doc) |d| allocator.free(d.MarkupContent.value);
.documentation = doc,
});
if (gop_res.found_existing) {
if (doc) |d| allocator.free(d.MarkupContent.value);
}
} }
} }
} }

View File

@ -4,7 +4,7 @@
"name": "enable_snippets", "name": "enable_snippets",
"description": "Enables snippet completions when the client also supports them", "description": "Enables snippet completions when the client also supports them",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
}, },
{ {
"name": "enable_ast_check_diagnostics", "name": "enable_ast_check_diagnostics",
@ -16,13 +16,13 @@
"name": "enable_autofix", "name": "enable_autofix",
"description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.", "description": "Whether to automatically fix errors on save. Currently supports adding and removing discards.",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
}, },
{ {
"name": "enable_import_embedfile_argument_completions", "name": "enable_import_embedfile_argument_completions",
"description": "Whether to enable import/embedFile argument completions", "description": "Whether to enable import/embedFile argument completions",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
}, },
{ {
"name": "enable_semantic_tokens", "name": "enable_semantic_tokens",
@ -34,7 +34,7 @@
"name": "enable_inlay_hints", "name": "enable_inlay_hints",
"description": "Enables inlay hint support when the client also supports it", "description": "Enables inlay hint support when the client also supports it",
"type": "bool", "type": "bool",
"default": "true", "default": "true"
}, },
{ {
"name": "inlay_hints_show_builtin", "name": "inlay_hints_show_builtin",

View File

@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const zig_builtin = @import("builtin");
const tres = @import("tres"); const tres = @import("tres");
const ConfigOption = struct { const ConfigOption = struct {
@ -54,7 +55,7 @@ fn generateConfigFile(allocator: std.mem.Allocator, config: Config, path: []cons
\\//! DO NOT EDIT \\//! DO NOT EDIT
\\//! Configuration options for zls. \\//! Configuration options for zls.
\\//! If you want to add a config option edit \\//! If you want to add a config option edit
\\//! src/config_gen/config.zig and run `zig build gen` \\//! src/config_gen/config.json and run `zig build gen`
\\//! GENERATED BY src/config_gen/config_gen.zig \\//! GENERATED BY src/config_gen/config_gen.zig
\\ \\
); );
@ -229,18 +230,728 @@ fn generateVSCodeConfigFile(allocator: std.mem.Allocator, config: Config, path:
try buffered_writer.flush(); try buffered_writer.flush();
} }
/// Tokenizer for a langref.html.in file
/// example file: https://raw.githubusercontent.com/ziglang/zig/master/doc/langref.html.in
/// this is a modified version from https://github.com/ziglang/zig/blob/master/doc/docgen.zig
const Tokenizer = struct {
buffer: []const u8,
index: usize = 0,
state: State = .Start,
const State = enum {
Start,
LBracket,
Hash,
TagName,
Eof,
};
const Token = struct {
id: Id,
start: usize,
end: usize,
const Id = enum {
Invalid,
Content,
BracketOpen,
TagContent,
Separator,
BracketClose,
Eof,
};
};
fn next(self: *Tokenizer) Token {
var result = Token{
.id = .Eof,
.start = self.index,
.end = undefined,
};
while (self.index < self.buffer.len) : (self.index += 1) {
const c = self.buffer[self.index];
switch (self.state) {
.Start => switch (c) {
'{' => {
self.state = .LBracket;
},
else => {
result.id = .Content;
},
},
.LBracket => switch (c) {
'#' => {
if (result.id != .Eof) {
self.index -= 1;
self.state = .Start;
break;
} else {
result.id = .BracketOpen;
self.index += 1;
self.state = .TagName;
break;
}
},
else => {
result.id = .Content;
self.state = .Start;
},
},
.TagName => switch (c) {
'|' => {
if (result.id != .Eof) {
break;
} else {
result.id = .Separator;
self.index += 1;
break;
}
},
'#' => {
self.state = .Hash;
},
else => {
result.id = .TagContent;
},
},
.Hash => switch (c) {
'}' => {
if (result.id != .Eof) {
self.index -= 1;
self.state = .TagName;
break;
} else {
result.id = .BracketClose;
self.index += 1;
self.state = .Start;
break;
}
},
else => {
result.id = .TagContent;
self.state = .TagName;
},
},
.Eof => unreachable,
}
} else {
switch (self.state) {
.Start,
.LBracket,
.Eof,
=> {},
else => {
result.id = .Invalid;
},
}
self.state = .Eof;
}
result.end = self.index;
return result;
}
};
const Builtin = struct {
name: []const u8,
signature: []const u8,
documentation: std.ArrayListUnmanaged(u8),
};
/// parses a `langref.html.in` file and extracts builtins from this section: `https://ziglang.org/documentation/master/#Builtin-Functions`
/// the documentation field contains poorly formated html
fn collectBuiltinData(allocator: std.mem.Allocator, version: []const u8, langref_file: []const u8) error{OutOfMemory}![]Builtin {
var tokenizer = Tokenizer{ .buffer = langref_file };
const State = enum {
/// searching for this line:
/// {#header_open|Builtin Functions|2col#}
searching,
/// skippig builtin functions description:
/// Builtin functions are provided by the compiler and are prefixed ...
prefix,
/// every entry begins with this:
/// {#syntax#}@addrSpaceCast(comptime addrspace: std.builtin.AddressSpace, ptr: anytype) anytype{#endsyntax#}
builtin_begin,
/// iterate over documentation
builtin_content,
};
var state: State = .searching;
var builtins = std.ArrayListUnmanaged(Builtin){};
errdefer {
for (builtins.items) |*builtin| {
builtin.documentation.deinit(allocator);
}
builtins.deinit(allocator);
}
var depth: u32 = undefined;
while (true) {
const token = tokenizer.next();
switch (token.id) {
.Content => {
switch (state) {
.builtin_content => {
try builtins.items[builtins.items.len - 1].documentation.appendSlice(allocator, tokenizer.buffer[token.start..token.end]);
},
else => continue,
}
},
.BracketOpen => {
const tag_token = tokenizer.next();
std.debug.assert(tag_token.id == .TagContent);
const tag_name = tokenizer.buffer[tag_token.start..tag_token.end];
if (std.mem.eql(u8, tag_name, "header_open")) {
std.debug.assert(tokenizer.next().id == .Separator);
const content_token = tokenizer.next();
std.debug.assert(tag_token.id == .TagContent);
const content_name = tokenizer.buffer[content_token.start..content_token.end];
switch (state) {
.searching => {
if (std.mem.eql(u8, content_name, "Builtin Functions")) {
state = .prefix;
depth = 0;
}
},
.prefix, .builtin_begin => {
state = .builtin_begin;
try builtins.append(allocator, .{
.name = content_name,
.signature = "",
.documentation = .{},
});
},
.builtin_content => unreachable,
}
if (state != .searching) {
depth += 1;
}
while (true) {
const bracket_tok = tokenizer.next();
switch (bracket_tok.id) {
.BracketClose => break,
.Separator, .TagContent => continue,
else => unreachable,
}
}
} else if (std.mem.eql(u8, tag_name, "header_close")) {
std.debug.assert(tokenizer.next().id == .BracketClose);
if (state == .builtin_content) {
state = .builtin_begin;
}
if (state != .searching) {
depth -= 1;
if (depth == 0) break;
}
} else if (state != .searching and std.mem.eql(u8, tag_name, "syntax")) {
std.debug.assert(tokenizer.next().id == .BracketClose);
const content_tag = tokenizer.next();
std.debug.assert(content_tag.id == .Content);
const content_name = tokenizer.buffer[content_tag.start..content_tag.end];
std.debug.assert(tokenizer.next().id == .BracketOpen);
const end_syntax_tag = tokenizer.next();
std.debug.assert(end_syntax_tag.id == .TagContent);
const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end];
std.debug.assert(std.mem.eql(u8, end_tag_name, "endsyntax"));
std.debug.assert(tokenizer.next().id == .BracketClose);
switch (state) {
.builtin_begin => {
builtins.items[builtins.items.len - 1].signature = content_name;
state = .builtin_content;
},
.builtin_content => {
const writer = builtins.items[builtins.items.len - 1].documentation.writer(allocator);
try writeMarkdownCode(content_name, "zig", writer);
},
else => {},
}
} else if (state != .searching and std.mem.eql(u8, tag_name, "syntax_block")) {
std.debug.assert(tokenizer.next().id == .Separator);
const source_type_tag = tokenizer.next();
std.debug.assert(tag_token.id == .TagContent);
const source_type = tokenizer.buffer[source_type_tag.start..source_type_tag.end];
switch (tokenizer.next().id) {
.Separator => {
std.debug.assert(tokenizer.next().id == .TagContent);
std.debug.assert(tokenizer.next().id == .BracketClose);
},
.BracketClose => {},
else => unreachable,
}
var content_token = tokenizer.next();
std.debug.assert(content_token.id == .Content);
const content = tokenizer.buffer[content_token.start..content_token.end];
const writer = builtins.items[builtins.items.len - 1].documentation.writer(allocator);
try writeMarkdownCode(content, source_type, writer);
std.debug.assert(tokenizer.next().id == .BracketOpen);
const end_code_token = tokenizer.next();
std.debug.assert(tag_token.id == .TagContent);
const end_code_name = tokenizer.buffer[end_code_token.start..end_code_token.end];
std.debug.assert(std.mem.eql(u8, end_code_name, "end_syntax_block"));
std.debug.assert(tokenizer.next().id == .BracketClose);
} else if (state != .searching and std.mem.eql(u8, tag_name, "link")) {
std.debug.assert(tokenizer.next().id == .Separator);
const name_token = tokenizer.next();
std.debug.assert(name_token.id == .TagContent);
const name = tokenizer.buffer[name_token.start..name_token.end];
const url_name = switch (tokenizer.next().id) {
.Separator => blk: {
const url_name_token = tokenizer.next();
std.debug.assert(url_name_token.id == .TagContent);
const url_name = tokenizer.buffer[url_name_token.start..url_name_token.end];
std.debug.assert(tokenizer.next().id == .BracketClose);
break :blk url_name;
},
.BracketClose => name,
else => unreachable,
};
const spaceless_url_name = try std.mem.replaceOwned(u8, allocator, url_name, " ", "-");
defer allocator.free(spaceless_url_name);
const writer = builtins.items[builtins.items.len - 1].documentation.writer(allocator);
try writer.print("[{s}](https://ziglang.org/documentation/{s}/#{s})", .{
name,
version,
std.mem.trimLeft(u8, spaceless_url_name, "@"),
});
} else if (state != .searching and std.mem.eql(u8, tag_name, "code_begin")) {
std.debug.assert(tokenizer.next().id == .Separator);
std.debug.assert(tokenizer.next().id == .TagContent);
switch (tokenizer.next().id) {
.Separator => {
std.debug.assert(tokenizer.next().id == .TagContent);
std.debug.assert(tokenizer.next().id == .BracketClose);
},
.BracketClose => {},
else => unreachable,
}
while (true) {
const content_token = tokenizer.next();
std.debug.assert(content_token.id == .Content);
const content = tokenizer.buffer[content_token.start..content_token.end];
std.debug.assert(tokenizer.next().id == .BracketOpen);
const end_code_token = tokenizer.next();
std.debug.assert(end_code_token.id == .TagContent);
const end_tag_name = tokenizer.buffer[end_code_token.start..end_code_token.end];
if (std.mem.eql(u8, end_tag_name, "code_end")) {
std.debug.assert(tokenizer.next().id == .BracketClose);
const writer = builtins.items[builtins.items.len - 1].documentation.writer(allocator);
try writeMarkdownCode(content, "zig", writer);
break;
}
std.debug.assert(tokenizer.next().id == .BracketClose);
}
} else {
while (true) {
switch (tokenizer.next().id) {
.Eof => unreachable,
.BracketClose => break,
else => continue,
}
}
}
},
else => unreachable,
}
}
return try builtins.toOwnedSlice(allocator);
}
/// single line: \`{content}\`
/// multi line:
/// \`\`\`{source_type}
/// {content}
/// \`\`\`
fn writeMarkdownCode(content: []const u8, source_type: []const u8, writer: anytype) @TypeOf(writer).Error!void {
const trimmed_content = std.mem.trim(u8, content, " \n");
const is_multiline = std.mem.indexOfScalar(u8, trimmed_content, '\n') != null;
if (is_multiline) {
var line_it = std.mem.tokenize(u8, trimmed_content, "\n");
try writer.print("\n```{s}", .{source_type});
while (line_it.next()) |line| {
try writer.print("\n{s}", .{line});
}
try writer.writeAll("\n```");
} else {
try writer.print("`{s}`", .{trimmed_content});
}
}
fn writeLine(str: []const u8, single_line: bool, writer: anytype) @TypeOf(writer).Error!void {
const trimmed_content = std.mem.trim(u8, str, &std.ascii.whitespace);
if (trimmed_content.len == 0) return;
if (single_line) {
var line_it = std.mem.split(u8, trimmed_content, "\n");
while (line_it.next()) |line| {
try writer.print("{s} ", .{std.mem.trim(u8, line, &std.ascii.whitespace)});
}
} else {
try writer.writeAll(trimmed_content);
}
try writer.writeByte('\n');
}
/// converts text with various html tags into markdown
/// supported tags:
/// - `<p>`
/// - `<pre>`
/// - `<em>`
/// - `<ul>` and `<li>`
/// - `<a>`
/// - `<code>`
fn writeMarkdownFromHtml(html: []const u8, writer: anytype) !void {
return writeMarkdownFromHtmlInternal(html, false, 0, writer);
}
/// this is kind of a hacky solution. A cleaner solution would be to implement using a xml/html parser.
fn writeMarkdownFromHtmlInternal(html: []const u8, single_line: bool, depth: u32, writer: anytype) !void {
var index: usize = 0;
while (std.mem.indexOfScalarPos(u8, html, index, '<')) |tag_start_index| {
const tags: []const []const u8 = &.{ "pre", "p", "em", "ul", "li", "a", "code" };
const opening_tags: []const []const u8 = &.{ "<pre>", "<p>", "<em>", "<ul>", "<li>", "<a>", "<code>" };
const closing_tags: []const []const u8 = &.{ "</pre>", "</p>", "</em>", "</ul>", "</li>", "</a>", "</code>" };
const tag_index = for (tags) |tag_name, i| {
if (std.mem.startsWith(u8, html[tag_start_index + 1 ..], tag_name)) break i;
} else {
index += 1;
continue;
};
try writeLine(html[index..tag_start_index], single_line, writer);
const tag_name = tags[tag_index];
const opening_tag_name = opening_tags[tag_index];
const closing_tag_name = closing_tags[tag_index];
// std.debug.print("tag: '{s}'\n", .{tag_name});
const content_start = 1 + (std.mem.indexOfScalarPos(u8, html, tag_start_index + 1 + tag_name.len, '>') orelse return error.InvalidTag);
index = content_start;
const content_end = while (std.mem.indexOfScalarPos(u8, html, index, '<')) |end| {
if (std.mem.startsWith(u8, html[end..], closing_tag_name)) break end;
if (std.mem.startsWith(u8, html[end..], opening_tag_name)) {
index = std.mem.indexOfPos(u8, html, end + opening_tag_name.len, closing_tag_name) orelse return error.MissingEndTag;
index += closing_tag_name.len;
continue;
}
index += 1;
} else html.len;
const content = html[content_start..content_end];
index = @min(html.len, content_end + closing_tag_name.len);
// std.debug.print("content: {s}\n", .{content});
if (std.mem.eql(u8, tag_name, "p")) {
try writeMarkdownFromHtmlInternal(content, true, depth, writer);
try writer.writeByte('\n');
} else if (std.mem.eql(u8, tag_name, "pre")) {
try writeMarkdownFromHtmlInternal(content, false, depth, writer);
} else if (std.mem.eql(u8, tag_name, "em")) {
try writer.print("**{s}** ", .{content});
} else if (std.mem.eql(u8, tag_name, "ul")) {
try writeMarkdownFromHtmlInternal(content, false, depth + 1, writer);
} else if (std.mem.eql(u8, tag_name, "li")) {
try writer.writeByteNTimes(' ', 1 + (depth -| 1) * 2);
try writer.writeAll("- ");
try writeMarkdownFromHtmlInternal(content, true, depth, writer);
} else if (std.mem.eql(u8, tag_name, "a")) {
const href_part = std.mem.trimLeft(u8, html[tag_start_index + 2 .. content_start - 1], " ");
std.debug.assert(std.mem.startsWith(u8, href_part, "href=\""));
std.debug.assert(href_part[href_part.len - 1] == '\"');
const url = href_part["href=\"".len .. href_part.len - 1];
try writer.print("[{s}]({s})", .{ content, std.mem.trimLeft(u8, url, "@") });
} else if (std.mem.eql(u8, tag_name, "code")) {
try writeMarkdownCode(content, "zig", writer);
} else return error.UnsupportedTag;
}
try writeLine(html[index..], single_line, writer);
}
/// takes in a signature like this: `@intToEnum(comptime DestType: type, integer: anytype) DestType`
/// and outputs its arguments: `comptime DestType: type`, `integer: anytype`
fn extractArgumentsFromSignature(allocator: std.mem.Allocator, signature: []const u8) error{OutOfMemory}![][]const u8 {
var arguments = std.ArrayListUnmanaged([]const u8){};
defer arguments.deinit(allocator);
var argument_start: usize = 0;
var index: usize = 0;
while (std.mem.indexOfAnyPos(u8, signature, index, ",()")) |token_index| {
if (signature[token_index] == '(') {
argument_start = index;
index = 1 + std.mem.indexOfScalarPos(u8, signature, token_index + 1, ')').?;
continue;
}
const argument = std.mem.trim(u8, signature[argument_start..token_index], &std.ascii.whitespace);
if (argument.len != 0) try arguments.append(allocator, argument);
if (signature[token_index] == ')') break;
argument_start = token_index + 1;
index = token_index + 1;
}
return arguments.toOwnedSlice(allocator);
}
/// takes in a signature like this: `@intToEnum(comptime DestType: type, integer: anytype) DestType`
/// and outputs a snippet: `@intToEnum(${1:comptime DestType: type}, ${2:integer: anytype})`
fn extractSnippetFromSignature(allocator: std.mem.Allocator, signature: []const u8) error{OutOfMemory}![]const u8 {
var snippet = std.ArrayListUnmanaged(u8){};
defer snippet.deinit(allocator);
var writer = snippet.writer(allocator);
const start_index = 1 + std.mem.indexOfScalar(u8, signature, '(').?;
try writer.writeAll(signature[0..start_index]);
var argument_start: usize = start_index;
var index: usize = start_index;
var i: u32 = 1;
while (std.mem.indexOfAnyPos(u8, signature, index, ",()")) |token_index| {
if (signature[token_index] == '(') {
argument_start = index;
index = 1 + std.mem.indexOfScalarPos(u8, signature, token_index + 1, ')').?;
continue;
}
const argument = std.mem.trim(u8, signature[argument_start..token_index], &std.ascii.whitespace);
if (argument.len != 0) {
if (i != 1) try writer.writeAll(", ");
try writer.print("${{{d}:{s}}}", .{ i, argument });
}
if (signature[token_index] == ')') break;
argument_start = token_index + 1;
index = token_index + 1;
i += 1;
}
try writer.writeByte(')');
return snippet.toOwnedSlice(allocator);
}
/// Generates data files from the Zig language Reference (https://ziglang.org/documentation/master/)
/// An output example would `zls/src/master.zig`
fn generateVersionDataFile(allocator: std.mem.Allocator, version: []const u8, path: []const u8) !void {
const url = try std.fmt.allocPrint(allocator, "https://raw.githubusercontent.com/ziglang/zig/{s}/doc/langref.html.in", .{version});
defer allocator.free(url);
const response = try httpGET(allocator, try std.Uri.parse(url));
switch (response) {
.ok => {},
.other => |status| {
const error_name = status.phrase() orelse @tagName(status.class());
std.log.err("failed to download {s}: {s}", .{ url, error_name });
return error.DownloadFailed;
},
}
defer allocator.free(response.ok);
const response_bytes = response.ok;
// const response_bytes: []const u8 = @embedFile("langref.html.in");
var builtins = try collectBuiltinData(allocator, version, response_bytes);
defer {
for (builtins) |*builtin| {
builtin.documentation.deinit(allocator);
}
allocator.free(builtins);
}
var builtin_file = try std.fs.createFileAbsolute(path, .{});
defer builtin_file.close();
var buffered_writer = std.io.bufferedWriter(builtin_file.writer());
var writer = buffered_writer.writer();
try writer.print(
\\//! DO NOT EDIT
\\//! If you want to update this file run:
\\//! `zig build gen -- --generate-version-data {s}` (requires an internet connection)
\\//! GENERATED BY src/config_gen/config_gen.zig
\\
\\const Builtin = struct {{
\\ name: []const u8,
\\ signature: []const u8,
\\ snippet: []const u8,
\\ documentation: []const u8,
\\ arguments: []const []const u8,
\\}};
\\
\\pub const builtins = [_]Builtin{{
\\
, .{version});
for (builtins) |builtin| {
const signature = try std.mem.replaceOwned(u8, allocator, builtin.signature, "\n", "");
defer allocator.free(signature);
const snippet = try extractSnippetFromSignature(allocator, signature);
defer allocator.free(snippet);
var arguments = try extractArgumentsFromSignature(allocator, signature[builtin.name.len + 1 ..]);
defer allocator.free(arguments);
try writer.print(
\\ .{{
\\ .name = "{}",
\\ .signature = "{}",
\\ .snippet = "{}",
\\
, .{
std.zig.fmtEscapes(builtin.name),
std.zig.fmtEscapes(signature),
std.zig.fmtEscapes(snippet),
});
const html = builtin.documentation.items["</pre>".len..];
var markdown = std.ArrayListUnmanaged(u8){};
defer markdown.deinit(allocator);
try writeMarkdownFromHtml(html, markdown.writer(allocator));
try writer.writeAll(" .documentation =\n");
var line_it = std.mem.split(u8, std.mem.trim(u8, markdown.items, "\n"), "\n");
while (line_it.next()) |line| {
try writer.print(" \\\\{s}\n", .{std.mem.trimRight(u8, line, " ")});
}
try writer.writeAll(
\\ ,
\\ .arguments = &.{
);
if (arguments.len != 0) {
try writer.writeByte('\n');
for (arguments) |arg| {
try writer.print(" \"{}\",\n", .{std.zig.fmtEscapes(arg)});
}
try writer.writeByteNTimes(' ', 8);
}
try writer.writeAll(
\\},
\\ },
\\
);
}
try writer.writeAll(
\\};
\\
\\// DO NOT EDIT
\\
);
try buffered_writer.flush();
}
const Response = union(enum) {
ok: []const u8,
other: std.http.Status,
};
fn httpGET(allocator: std.mem.Allocator, uri: std.Uri) !Response {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit(allocator);
try client.ca_bundle.rescan(allocator);
var request = try client.request(uri, .{}, .{});
defer request.deinit();
var output = std.ArrayListUnmanaged(u8){};
defer output.deinit(allocator);
var buffer: [1024]u8 = undefined;
while (true) {
const size = try request.read(&buffer);
if (size == 0) break;
try output.appendSlice(allocator, buffer[0..size]);
}
if (request.response.headers.status != .ok) {
return .{
.other = request.response.headers.status,
};
}
return .{ .ok = try output.toOwnedSlice(allocator) };
}
pub fn main() !void { pub fn main() !void {
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(!general_purpose_allocator.deinit());
var gpa = general_purpose_allocator.allocator(); var gpa = general_purpose_allocator.allocator();
var arg_it = try std.process.argsWithAllocator(gpa); var stderr = std.io.getStdErr().writer();
defer arg_it.deinit();
_ = arg_it.next() orelse @panic(""); var args_it = try std.process.argsWithAllocator(gpa);
const config_path = arg_it.next() orelse @panic("first argument must be path to Config.zig"); defer args_it.deinit();
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"); _ = args_it.next() orelse @panic("");
const maybe_vscode_config_path = arg_it.next(); const config_path = args_it.next() orelse @panic("first argument must be path to Config.zig");
const schema_path = args_it.next() orelse @panic("second argument must be path to schema.json");
const readme_path = args_it.next() orelse @panic("third argument must be path to README.md");
const data_path = args_it.next() orelse @panic("fourth argument must be path to data directory");
var maybe_vscode_config_path: ?[]const u8 = null;
var maybe_data_file_version: ?[]const u8 = null;
var maybe_data_file_path: ?[]const u8 = null;
while (args_it.next()) |argname| {
if (std.mem.eql(u8, argname, "--help")) {
try stderr.writeAll(
\\ Usage: zig build gen -- [command]
\\
\\ Commands:
\\
\\ --help Prints this message
\\ --vscode-config-path [path] Output zls-vscode configurations
\\ --generate-version-data [version] Output version data file (see src/data/master.zig)
\\ --generate-version-data-path [path] Override default data file path (default: src/data/*.zig)
\\
);
} else if (std.mem.eql(u8, argname, "--vscode-config-path")) {
maybe_vscode_config_path = args_it.next() orelse {
try stderr.print("Expected output path after --vscode-config-path argument.\n", .{});
return;
};
} else if (std.mem.eql(u8, argname, "--generate-version-data")) {
maybe_data_file_version = args_it.next() orelse {
try stderr.print("Expected version after --generate-version-data argument.\n", .{});
return;
};
const is_valid_version = blk: {
if (std.mem.eql(u8, maybe_data_file_version.?, "master")) break :blk true;
_ = std.SemanticVersion.parse(maybe_data_file_version.?) catch break :blk false;
break :blk true;
};
if (!is_valid_version) {
try stderr.print("'{s}' is not a valid argument after --generate-version-data.\n", .{maybe_data_file_version.?});
return;
}
} else if (std.mem.eql(u8, argname, "--generate-version-data-path")) {
maybe_data_file_path = args_it.next() orelse {
try stderr.print("Expected output path after --generate-version-data-path argument.\n", .{});
return;
};
} else {
try stderr.print("Unrecognized argument '{s}'.\n", .{argname});
return;
}
}
const parse_options = std.json.ParseOptions{ const parse_options = std.json.ParseOptions{
.allocator = gpa, .allocator = gpa,
@ -257,13 +968,22 @@ pub fn main() !void {
try generateVSCodeConfigFile(gpa, config, vscode_config_path); try generateVSCodeConfigFile(gpa, config, vscode_config_path);
} }
if (builtin.os.tag == .windows) { if (maybe_data_file_version) |data_version| {
const path = if (maybe_data_file_path) |path| path else blk: {
const file_name = try std.fmt.allocPrint(gpa, "{s}.zig", .{data_version});
defer gpa.free(file_name);
break :blk try std.fs.path.join(gpa, &.{ data_path, file_name });
};
defer if (maybe_data_file_path == null) gpa.free(path);
try generateVersionDataFile(gpa, data_version, path);
}
if (zig_builtin.os.tag == .windows) {
std.log.warn("Running on windows may result in CRLF and LF mismatch", .{}); std.log.warn("Running on windows may result in CRLF and LF mismatch", .{});
} }
try std.io.getStdOut().writeAll( try stderr.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 \\Changing configuration options may also require editing the `package.json` from zls-vscode at https://github.com/zigtools/zls-vscode/blob/master/package.json
\\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` \\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`
\\ \\

View File

@ -55,6 +55,7 @@ pub fn loadFromFolder(allocator: std.mem.Allocator, folder_path: []const u8) ?Co
/// Invoke this once all config values have been changed. /// Invoke this once all config values have been changed.
pub fn configChanged(config: *Config, allocator: std.mem.Allocator, builtin_creation_dir: ?[]const u8) !void { pub fn configChanged(config: *Config, allocator: std.mem.Allocator, builtin_creation_dir: ?[]const u8) !void {
if (!std.process.can_spawn) return;
// Find the zig executable in PATH // Find the zig executable in PATH
find_zig: { find_zig: {
if (config.zig_exe_path) |exe_path| { if (config.zig_exe_path) |exe_path| {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,107 +0,0 @@
// Run this in a Chrome developer console.
const builtins = $$("a#toc-Builtin-Functions+ul > li").map(element => {
const anchor = element.querySelector("a").getAttribute("href");
const code = $(`${anchor}+pre > code`).textContent.replace(/(\r\n|\n|\r)/gm, "");
var curr_paragraph = $(`${anchor}+pre+p`);
var doc = "";
var first = true;
while (curr_paragraph.nodeName == "P" || curr_paragraph.nodeName == "PRE") {
if (curr_paragraph.innerHTML == "See also:")
break;
if (!first) {
doc += "\n";
} else {
first = false;
}
if (curr_paragraph.nodeName == "PRE") {
doc += "```zig\n";
curr_paragraph.childNodes[0].childNodes.forEach(elem => {
doc += elem.textContent;
});
doc += "\n```";
} else {
curr_paragraph.childNodes.forEach(elem => {
if (elem.nodeName == "CODE") {
console.log(elem.innerHTML);
doc += "`" + elem.innerHTML.replaceAll(/<span .+?>(.+?)<\/span>/gm, "$1") + "`";
} else
doc += elem.textContent.replace(/(\s\s+)/gm, " ");
});
}
curr_paragraph = curr_paragraph.nextElementSibling;
}
return { "name": anchor.substring(1), "code": code, "documentation": doc };
});
// Take output and paste into a .zig file
console.log(
`const Builtin = struct {
name: []const u8,
signature: []const u8,
snippet: []const u8,
documentation: []const u8,
arguments: []const []const u8,
};
pub const builtins = [_]Builtin{` +
'\n' + builtins.map(builtin => {
// Make a snippet
const first_paren_idx = builtin.code.indexOf('(');
var snippet = builtin.code.substr(0, first_paren_idx + 1);
var rest = builtin.code.substr(first_paren_idx + 1);
var args = [];
if (rest[0] == ')') {
snippet += ')';
} else {
snippet += "${1:"
args.push("");
var arg_idx = 2;
var paren_depth = 1;
var skip_space = false;
for (const char of rest) {
if (char == '(') {
paren_depth += 1;
} else if (char == ')') {
paren_depth -= 1;
if (paren_depth == 0) {
snippet += "})";
break;
}
} else if (char == '"') {
snippet += "\\\"";
args[args.length - 1] += "\\\"";
continue;
} else if (char == ',' && paren_depth == 1) {
snippet += "}, ${" + arg_idx + ':';
arg_idx += 1;
args.push("");
skip_space = true;
continue;
} else if (char == ' ' && skip_space) {
continue;
}
snippet += char;
args[args.length - 1] += char;
skip_space = false;
}
}
return ` .{
.name = "@${builtin.name}",
.signature = "${builtin.code.replaceAll('"', "\\\"")}",
.snippet = "${snippet}",
.documentation =
\\\\${builtin.documentation.split('\n').join("\n \\\\")}
,
.arguments = &.{${args.map(x => "\n \"" + x + "\"").join(",") + ((args.length > 0) ? ",\n " : "")}},
},`;
}).join('\n') + "\n};\n"
);

View File

@ -1,105 +0,0 @@
#!/usr/bin/env python3
import urllib.request
import re
import minify_html
zig_version = 'master'
def fix_ul(s):
l = s.split('<li>')
l.insert(0, '')
return '\n - '.join(l)
url = f'https://raw.githubusercontent.com/ziglang/zig/{zig_version}/doc/langref.html.in'
res = urllib.request.urlopen(url)
page = res.read().decode('utf-8')
print('''const Builtin = struct {
name: []const u8,
signature: []const u8,
snippet: []const u8,
documentation: []const u8,
arguments: []const []const u8,
};
pub const builtins = [_]Builtin{''')
pattern = r'{#header_open\|(@\S+?)#}(.+?){#header_close#}'
for match in re.finditer(pattern, page, re.M | re.S):
blk = match[2].strip(' \n')
name = match[1]
signature = re.search(r'<pre>{#syntax#}(.+?){#endsyntax#}</pre>', blk,
re.M | re.S)[1].replace('\n ', '').replace('"', '\\"')
snippet = name
if f'{name}()' in signature:
params = None
snippet += '()'
else:
params = []
i = signature.index('(') + 1
level = 1
j = i
while i < len(signature):
if signature[i] == '(':
level += 1
elif signature[i] == ')':
level -= 1
if signature[i] == ',' and level == 1:
params.append(signature[j:i])
j = i + 2
if level == 0:
break
i += 1
params.append(signature[j:i])
snippet += '(${'
i = 1
for param in params:
snippet += f'{i}:{param}}}, ${{'
i += 1
snippet = snippet[:-4] + ')'
docs = re.sub(r'{#see_also\|[^#]+#}', '', blk)
docs = re.sub(
r' {#code_begin\|(obj|syntax|(test(\|(call|truncate))?))#}\n', ' <pre>{#syntax#}', docs)
docs = re.sub(
r' {#code_begin\|test_(err|safety)\|[^#]+#}\n', ' <pre>{#syntax#}', docs)
docs = docs.replace(' {#code_release_fast#}\n', '')
docs = docs.replace(' {#code_end#}', '{#endsyntax#}</pre>')
docs = docs.replace('\n{#endsyntax#}</pre>', '{#endsyntax#}</pre>')
docs = minify_html.minify(docs)
prefix = '</pre><p>'
docs = docs[docs.index(prefix)+len(prefix):]
docs = docs.replace('<p>', '\n\n')
docs = re.sub(r'{#(end)?syntax#}', '`', docs)
# @cDefine
docs = re.sub(r'<pre><code[^>]+>([^<]+)</code></pre>', '`\\1`', docs)
docs = re.sub(r'</?code>', '`', docs)
docs = docs.replace('<pre>`', '\n\n```zig\n')
docs = docs.replace('`</pre>', '\n```')
# @setFloatMode
docs = docs.replace('```<', '```\n<')
# @TypeOf
docs = re.sub(r'</?em>', '*', docs)
docs = re.sub(r'<a href=([^>]+)>([^<]+)</a>', '[\\2](\\1)', docs)
docs = re.sub(r'{#link\|([^|#]+)\|([^|#]+)#}',
lambda m: f'[{m[1]}](https://ziglang.org/documentation/{zig_version}/#{m[2].replace(" ","-")})', docs)
docs = re.sub(
r'{#link\|([^|#]+)#}', lambda m: f'[{m[1]}](https://ziglang.org/documentation/{zig_version}/#{m[1].replace(" ","-").replace("@","")})', docs)
docs = re.sub(r'<ul><li>(.+?)</ul>', lambda m: fix_ul(m[1]), docs)
print(' .{')
print(f' .name = "{name}",')
print(f' .signature = "{signature}",')
print(f' .snippet = "{snippet}",')
print(' .documentation =')
for line in docs.splitlines():
print(r' \\' + line)
print(' ,')
if params is None:
print(' .arguments = &.{},')
else:
print(' .arguments = &.{')
for param in params:
print(f' "{param}",')
print(' },')
print(' },')
print('};')

View File

@ -1,3 +1,8 @@
//! DO NOT EDIT
//! If you want to update this file run:
//! `zig build gen -- --generate-version-data master` (requires an internet connection)
//! GENERATED BY src/config_gen/config_gen.zig
const Builtin = struct { const Builtin = struct {
name: []const u8, name: []const u8,
signature: []const u8, signature: []const u8,
@ -58,8 +63,7 @@ pub const builtins = [_]Builtin{
\\ assert(*u32 == *align(@alignOf(u32)) u32); \\ assert(*u32 == *align(@alignOf(u32)) u32);
\\} \\}
\\``` \\```
\\ \\The result is a target-specific compile time constant. It is guaranteed to be less than or equal to [@sizeOf(T)](https://ziglang.org/documentation/master/#sizeOf).
\\The result is a target-specific compile time constant. It is guaranteed to be less than or equal to [@sizeOf(T)](https://ziglang.org/documentation/master/#@sizeOf).
, ,
.arguments = &.{ .arguments = &.{
"comptime T: type", "comptime T: type",
@ -102,15 +106,16 @@ pub const builtins = [_]Builtin{
\\`T` must be a pointer, a `bool`, a float, an integer or an enum. \\`T` must be a pointer, a `bool`, a float, an integer or an enum.
\\ \\
\\Supported operations: \\Supported operations:
\\ - `.Xchg` - stores the operand unmodified. Supports enums, integers and floats. \\
\\ - `.Add` - for integers, twos complement wraparound addition. Also supports [Floats](https://ziglang.org/documentation/master/#Floats). \\ - `.Xchg` - stores the operand unmodified. Supports enums, integers and floats.
\\ - `.Sub` - for integers, twos complement wraparound subtraction. Also supports [Floats](https://ziglang.org/documentation/master/#Floats). \\ - `.Add` - for integers, twos complement wraparound addition. Also supports [Floats](https://ziglang.org/documentation/master/#Floats).
\\ - `.And` - bitwise and \\ - `.Sub` - for integers, twos complement wraparound subtraction. Also supports [Floats](https://ziglang.org/documentation/master/#Floats).
\\ - `.Nand` - bitwise nand \\ - `.And` - bitwise and
\\ - `.Or` - bitwise or \\ - `.Nand` - bitwise nand
\\ - `.Xor` - bitwise xor \\ - `.Or` - bitwise or
\\ - `.Max` - stores the operand if it is larger. Supports integers and floats. \\ - `.Xor` - bitwise xor
\\ - `.Min` - stores the operand if it is smaller. Supports integers and floats. \\ - `.Max` - stores the operand if it is larger. Supports integers and floats.
\\ - `.Min` - stores the operand if it is smaller. Supports integers and floats.
, ,
.arguments = &.{ .arguments = &.{
"comptime T: type", "comptime T: type",
@ -148,9 +153,9 @@ pub const builtins = [_]Builtin{
\\Asserts that `@typeInfo(DestType) != .Pointer`. Use `@ptrCast` or `@intToPtr` if you need this. \\Asserts that `@typeInfo(DestType) != .Pointer`. Use `@ptrCast` or `@intToPtr` if you need this.
\\ \\
\\Can be used for these things for example: \\Can be used for these things for example:
\\ - Convert `f32` to `u32` bits
\\ - Convert `i32` to `u32` preserving twos complement
\\ \\
\\ - Convert `f32` to `u32` bits
\\ - Convert `i32` to `u32` preserving twos complement
\\Works at compile-time if `value` is known at compile time. It's a compile error to bitcast a value of undefined layout; this means that, besides the restriction from types which possess dedicated casting builtins (enums, pointers, error sets), bare structs, error unions, slices, optionals, and any other type without a well-defined memory layout, also cannot be used in this operation. \\Works at compile-time if `value` is known at compile time. It's a compile error to bitcast a value of undefined layout; this means that, besides the restriction from types which possess dedicated casting builtins (enums, pointers, error sets), bare structs, error unions, slices, optionals, and any other type without a well-defined memory layout, also cannot be used in this operation.
, ,
.arguments = &.{ .arguments = &.{
@ -278,17 +283,43 @@ pub const builtins = [_]Builtin{
\\ \\
\\```zig \\```zig
\\const expect = @import("std").testing.expect; \\const expect = @import("std").testing.expect;
\\
\\test "noinline function call" { \\test "noinline function call" {
\\ try expect(@call(.auto, add, .{3, 9}) == 12); \\ try expect(@call(.auto, add, .{3, 9}) == 12);
\\} \\}
\\
\\fn add(a: i32, b: i32) i32 { \\fn add(a: i32, b: i32) i32 {
\\ return a + b; \\ return a + b;
\\} \\}
\\``` \\```
\\`@call` allows more flexibility than normal function call syntax does. The `CallModifier` enum is reproduced here:
\\ \\
\\`@call` allows more flexibility than normal function call syntax does. The `CallModifier` enum is reproduced here:</p> {#syntax_block|zig|builtin.CallModifier struct#} pub const CallModifier = enum { /// Equivalent to function call syntax. auto, /// Equivalent to async keyword used with function call syntax. async_kw, /// Prevents tail call optimization. This guarantees that the return /// address will point to the callsite, as opposed to the callsite's /// callsite. If the call is otherwise required to be tail-called /// or inlined, a compile error is emitted instead. never_tail, /// Guarantees that the call will not be inlined. If the call is /// otherwise required to be inlined, a compile error is emitted instead. never_inline, /// Asserts that the function call will not suspend. This allows a /// non-async function to call an async function. no_async, /// Guarantees that the call will be generated with tail call optimization. /// If this is not possible, a compile error is emitted instead. always_tail, /// Guarantees that the call will inlined at the callsite. /// If this is not possible, a compile error is emitted instead. always_inline, /// Evaluates the call at compile-time. If the call cannot be completed at /// compile-time, a compile error is emitted instead. compile_time, }; {#end_syntax_block#} \\```zig
\\pub const CallModifier = enum {
\\ /// Equivalent to function call syntax.
\\ auto,
\\ /// Equivalent to async keyword used with function call syntax.
\\ async_kw,
\\ /// Prevents tail call optimization. This guarantees that the return
\\ /// address will point to the callsite, as opposed to the callsite's
\\ /// callsite. If the call is otherwise required to be tail-called
\\ /// or inlined, a compile error is emitted instead.
\\ never_tail,
\\ /// Guarantees that the call will not be inlined. If the call is
\\ /// otherwise required to be inlined, a compile error is emitted instead.
\\ never_inline,
\\ /// Asserts that the function call will not suspend. This allows a
\\ /// non-async function to call an async function.
\\ no_async,
\\ /// Guarantees that the call will be generated with tail call optimization.
\\ /// If this is not possible, a compile error is emitted instead.
\\ always_tail,
\\ /// Guarantees that the call will inlined at the callsite.
\\ /// If this is not possible, a compile error is emitted instead.
\\ always_inline,
\\ /// Evaluates the call at compile-time. If the call cannot be completed at
\\ /// compile-time, a compile error is emitted instead.
\\ compile_time,
\\};
\\```
, ,
.arguments = &.{ .arguments = &.{
"modifier: std.builtin.CallModifier", "modifier: std.builtin.CallModifier",
@ -303,15 +334,14 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\This function can only occur inside `@cImport`. \\This function can only occur inside `@cImport`.
\\ \\
\\This appends `#define $name $value` to the `@cImport` temporary buffer. \\This appends
\\`#define $name $value`to the `@cImport` temporary buffer.
\\ \\
\\To define without a value, like this:`#define _GNU_SOURCE` \\To define without a value, like this:
\\ \\
\\Use the void value, like this: \\`#define _GNU_SOURCE`Use the void value, like this:
\\ \\
\\```zig \\`@cDefine("_GNU_SOURCE", {})`
\\@cDefine("_GNU_SOURCE", {})
\\```
, ,
.arguments = &.{ .arguments = &.{
"comptime name: []u8", "comptime name: []u8",
@ -330,8 +360,9 @@ pub const builtins = [_]Builtin{
\\Usually you should only have one `@cImport` in your entire application, because it saves the compiler from invoking clang multiple times, and prevents inline functions from being duplicated. \\Usually you should only have one `@cImport` in your entire application, because it saves the compiler from invoking clang multiple times, and prevents inline functions from being duplicated.
\\ \\
\\Reasons for having multiple `@cImport` expressions would be: \\Reasons for having multiple `@cImport` expressions would be:
\\ - To avoid a symbol collision, for example if foo.h and bar.h both `#define CONNECTION_COUNT` \\
\\ - To analyze the C code with different preprocessor defines \\ - To avoid a symbol collision, for example if foo.h and bar.h both
\\`#define CONNECTION_COUNT` - To analyze the C code with different preprocessor defines
, ,
.arguments = &.{ .arguments = &.{
"expression", "expression",
@ -344,7 +375,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\This function can only occur inside `@cImport`. \\This function can only occur inside `@cImport`.
\\ \\
\\This appends `#include <$path>\n` to the `c_import` temporary buffer. \\This appends
\\`#include &lt;$path&gt;\n`to the `c_import` temporary buffer.
, ,
.arguments = &.{ .arguments = &.{
"comptime path: []u8", "comptime path: []u8",
@ -387,7 +419,6 @@ pub const builtins = [_]Builtin{
\\ } \\ }
\\} \\}
\\``` \\```
\\
\\If you are using cmpxchg in a loop, [@cmpxchgWeak](https://ziglang.org/documentation/master/#cmpxchgWeak) is the better choice, because it can be implemented more efficiently in machine instructions. \\If you are using cmpxchg in a loop, [@cmpxchgWeak](https://ziglang.org/documentation/master/#cmpxchgWeak) is the better choice, because it can be implemented more efficiently in machine instructions.
\\ \\
\\`T` must be a pointer, a `bool`, a float, an integer or an enum. \\`T` must be a pointer, a `bool`, a float, an integer or an enum.
@ -408,8 +439,19 @@ pub const builtins = [_]Builtin{
.signature = "@cmpxchgWeak(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T", .signature = "@cmpxchgWeak(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T",
.snippet = "@cmpxchgWeak(${1:comptime T: type}, ${2:ptr: *T}, ${3:expected_value: T}, ${4:new_value: T}, ${5:success_order: AtomicOrder}, ${6:fail_order: AtomicOrder})", .snippet = "@cmpxchgWeak(${1:comptime T: type}, ${2:ptr: *T}, ${3:expected_value: T}, ${4:new_value: T}, ${5:success_order: AtomicOrder}, ${6:fail_order: AtomicOrder})",
.documentation = .documentation =
\\This function performs a weak atomic compare exchange operation. It's the equivalent of this code, except atomic:</p> {#syntax_block|zig|cmpxchgWeakButNotAtomic#} fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T { const old_value = ptr.*; if (old_value == expected_value and usuallyTrueButSometimesFalse()) { ptr.* = new_value; return null; } else { return old_value; } } {#end_syntax_block#} \\This function performs a weak atomic compare exchange operation. It's the equivalent of this code, except atomic:
\\ \\
\\```zig
\\fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T {
\\ const old_value = ptr.*;
\\ if (old_value == expected_value and usuallyTrueButSometimesFalse()) {
\\ ptr.* = new_value;
\\ return null;
\\ } else {
\\ return old_value;
\\ }
\\}
\\```
\\If you are using cmpxchg in a loop, the sporadic failure will be no problem, and `cmpxchgWeak` is the better choice, because it can be implemented more efficiently in machine instructions. However if you need a stronger guarantee, use [@cmpxchgStrong](https://ziglang.org/documentation/master/#cmpxchgStrong). \\If you are using cmpxchg in a loop, the sporadic failure will be no problem, and `cmpxchgWeak` is the better choice, because it can be implemented more efficiently in machine instructions. However if you need a stronger guarantee, use [@cmpxchgStrong](https://ziglang.org/documentation/master/#cmpxchgStrong).
\\ \\
\\`T` must be a pointer, a `bool`, a float, an integer or an enum. \\`T` must be a pointer, a `bool`, a float, an integer or an enum.
@ -451,22 +493,30 @@ pub const builtins = [_]Builtin{
\\ \\
\\```zig \\```zig
\\const print = @import("std").debug.print; \\const print = @import("std").debug.print;
\\
\\const num1 = blk: { \\const num1 = blk: {
\\ var val1: i32 = 99; \\ var val1: i32 = 99;
\\ @compileLog("comptime val1 = ", val1); \\ @compileLog("comptime val1 = ", val1);
\\ val1 = val1 + 1; \\ val1 = val1 + 1;
\\ break :blk val1; \\ break :blk val1;
\\}; \\};
\\
\\test "main" { \\test "main" {
\\ @compileLog("comptime in main"); \\ @compileLog("comptime in main");
\\
\\ print("Runtime in main, num1 = {}.\n", .{num1}); \\ print("Runtime in main, num1 = {}.\n", .{num1});
\\} \\}
\\``` \\```
\\If all `@compileLog` calls are removed or not encountered by analysis, the program compiles successfully and the generated executable prints:
\\ \\
\\If all `@compileLog` calls are removed or not encountered by analysis, the program compiles successfully and the generated executable prints:</p> {#code_begin|test|without_compileLog#} const print = @import("std").debug.print; const num1 = blk: { var val1: i32 = 99; val1 = val1 + 1; break :blk val1; }; test "main" { print("Runtime in main, num1 = {}.\n", .{num1}); }` \\```zig
\\const print = @import("std").debug.print;
\\const num1 = blk: {
\\ var val1: i32 = 99;
\\ val1 = val1 + 1;
\\ break :blk val1;
\\};
\\test "main" {
\\ print("Runtime in main, num1 = {}.\n", .{num1});
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"args: ...", "args: ...",
@ -498,7 +548,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\This function can only occur inside `@cImport`. \\This function can only occur inside `@cImport`.
\\ \\
\\This appends `#undef $name` to the `@cImport` temporary buffer. \\This appends
\\`#undef $name`to the `@cImport` temporary buffer.
, ,
.arguments = &.{ .arguments = &.{
"comptime name: []u8", "comptime name: []u8",
@ -553,9 +604,9 @@ pub const builtins = [_]Builtin{
.snippet = "@divExact(${1:numerator: T}, ${2:denominator: T})", .snippet = "@divExact(${1:numerator: T}, ${2:denominator: T})",
.documentation = .documentation =
\\Exact division. Caller guarantees `denominator != 0` and `@divTrunc(numerator, denominator) * denominator == numerator`. \\Exact division. Caller guarantees `denominator != 0` and `@divTrunc(numerator, denominator) * denominator == numerator`.
\\ - `@divExact(6, 3) == 2`
\\ - `@divExact(a, b) * b == a`
\\ \\
\\ - `@divExact(6, 3) == 2`
\\ - `@divExact(a, b) * b == a`
\\For a function that returns a possible error code, use `@import("std").math.divExact`. \\For a function that returns a possible error code, use `@import("std").math.divExact`.
, ,
.arguments = &.{ .arguments = &.{
@ -569,9 +620,9 @@ pub const builtins = [_]Builtin{
.snippet = "@divFloor(${1:numerator: T}, ${2:denominator: T})", .snippet = "@divFloor(${1:numerator: T}, ${2:denominator: T})",
.documentation = .documentation =
\\Floored division. Rounds toward negative infinity. For unsigned integers it is the same as `numerator / denominator`. Caller guarantees `denominator != 0` and `!(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)`. \\Floored division. Rounds toward negative infinity. For unsigned integers it is the same as `numerator / denominator`. Caller guarantees `denominator != 0` and `!(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)`.
\\ - `@divFloor(-5, 3) == -2`
\\ - `(@divFloor(a, b) * b) + @mod(a, b) == a`
\\ \\
\\ - `@divFloor(-5, 3) == -2`
\\ - `(@divFloor(a, b) * b) + @mod(a, b) == a`
\\For a function that returns a possible error code, use `@import("std").math.divFloor`. \\For a function that returns a possible error code, use `@import("std").math.divFloor`.
, ,
.arguments = &.{ .arguments = &.{
@ -585,9 +636,9 @@ pub const builtins = [_]Builtin{
.snippet = "@divTrunc(${1:numerator: T}, ${2:denominator: T})", .snippet = "@divTrunc(${1:numerator: T}, ${2:denominator: T})",
.documentation = .documentation =
\\Truncated division. Rounds toward zero. For unsigned integers it is the same as `numerator / denominator`. Caller guarantees `denominator != 0` and `!(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)`. \\Truncated division. Rounds toward zero. For unsigned integers it is the same as `numerator / denominator`. Caller guarantees `denominator != 0` and `!(@typeInfo(T) == .Int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)`.
\\ - `@divTrunc(-5, 3) == -1`
\\ - `(@divTrunc(a, b) * b) + @rem(a, b) == a`
\\ \\
\\ - `@divTrunc(-5, 3) == -1`
\\ - `(@divTrunc(a, b) * b) + @rem(a, b) == a`
\\For a function that returns a possible error code, use `@import("std").math.divTrunc`. \\For a function that returns a possible error code, use `@import("std").math.divTrunc`.
, ,
.arguments = &.{ .arguments = &.{
@ -649,10 +700,10 @@ pub const builtins = [_]Builtin{
.snippet = "@errorToInt(${1:err: anytype})", .snippet = "@errorToInt(${1:err: anytype})",
.documentation = .documentation =
\\Supports the following types: \\Supports the following types:
\\ - [The Global Error Set](https://ziglang.org/documentation/master/#The-Global-Error-Set)
\\ - [Error Set Type](https://ziglang.org/documentation/master/#Error-Set-Type)
\\ - [Error Union Type](https://ziglang.org/documentation/master/#Error-Union-Type)
\\ \\
\\ - [The Global Error Set](https://ziglang.org/documentation/master/#The-Global-Error-Set)
\\ - [Error Set Type](https://ziglang.org/documentation/master/#Error-Set-Type)
\\ - [Error Union Type](https://ziglang.org/documentation/master/#Error-Union-Type)
\\Converts an error to the integer representation of an error. \\Converts an error to the integer representation of an error.
\\ \\
\\It is generally recommended to avoid this cast, as the integer representation of an error is not stable across source code changes. \\It is generally recommended to avoid this cast, as the integer representation of an error is not stable across source code changes.
@ -680,37 +731,28 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Creates a symbol in the output object file. \\Creates a symbol in the output object file.
\\ \\
\\`declaration` must be one of two things: \\`declaration`must be one of two things:
\\ - An identifier (`x`) identifying a [function](https://ziglang.org/documentation/master/#Functions) or a [variable](https://ziglang.org/documentation/master/#Container-Level-Variables).
\\ - Field access (`x.y`) looking up a [function](https://ziglang.org/documentation/master/#Functions) or a [variable](https://ziglang.org/documentation/master/#Container-Level-Variables).
\\ \\
\\This builtin can be called from a [comptime](https://ziglang.org/documentation/master/#comptime) block to conditionally export symbols. When `declaration` is a function with the C calling convention and `options.linkage` is `Strong`, this is equivalent to the `export` keyword used on a function: \\ - An identifier (`x`) identifying a [function](https://ziglang.org/documentation/master/#Functions) or a [variable](https://ziglang.org/documentation/master/#Container-Level-Variables).
\\ - Field access (`x.y`) looking up a [function](https://ziglang.org/documentation/master/#Functions) or a [variable](https://ziglang.org/documentation/master/#Container-Level-Variables).
\\This builtin can be called from a [comptime](https://ziglang.org/documentation/master/#comptime) block to conditionally export symbols. When
\\`declaration`is a function with the C calling convention and `options.linkage` is `Strong`, this is equivalent to the `export` keyword used on a function:
\\ \\
\\```zig \\```zig
\\comptime { \\comptime {
\\ @export(internalName, .{ .name = "foo", .linkage = .Strong }); \\ @export(internalName, .{ .name = "foo", .linkage = .Strong });
\\} \\}
\\
\\fn internalName() callconv(.C) void {} \\fn internalName() callconv(.C) void {}
\\``` \\```
\\
\\This is equivalent to: \\This is equivalent to:
\\ \\
\\```zig \\`export fn foo() void {}`
\\export fn foo() void {}
\\```
\\
\\Note that even when using `export`, the `@"foo"` syntax for [identifiers](https://ziglang.org/documentation/master/#Identifiers) can be used to choose any string for the symbol name: \\Note that even when using `export`, the `@"foo"` syntax for [identifiers](https://ziglang.org/documentation/master/#Identifiers) can be used to choose any string for the symbol name:
\\ \\
\\```zig \\`export fn @"A function name that is a complete sentence."() void {}`
\\export fn @"A function name that is a complete sentence."() void {}
\\```
\\
\\When looking at the resulting object, you can see the symbol is used verbatim: \\When looking at the resulting object, you can see the symbol is used verbatim:
\\ \\
\\```zig \\`00000000000001f0 T A function name that is a complete sentence.`
\\00000000000001f0 T A function name that is a complete sentence.
\\```
, ,
.arguments = &.{ .arguments = &.{
"declaration", "declaration",
@ -747,7 +789,30 @@ pub const builtins = [_]Builtin{
.signature = "@field(lhs: anytype, comptime field_name: []const u8) (field)", .signature = "@field(lhs: anytype, comptime field_name: []const u8) (field)",
.snippet = "@field(${1:lhs: anytype}, ${2:comptime field_name: []const u8})", .snippet = "@field(${1:lhs: anytype}, ${2:comptime field_name: []const u8})",
.documentation = .documentation =
\\Performs field access by a compile-time string. Works on both fields and declarations.</p> {#code_begin|test|field_decl_access_by_string#} const std = @import("std"); const Point = struct { x: u32, y: u32, pub var z: u32 = 1; }; test "field access by string" { const expect = std.testing.expect; var p = Point{ .x = 0, .y = 0 }; @field(p, "x") = 4; @field(p, "y") = @field(p, "x") + 1; try expect(@field(p, "x") == 4); try expect(@field(p, "y") == 5); } test "decl access by string" { const expect = std.testing.expect; try expect(@field(Point, "z") == 1); @field(Point, "z") = 2; try expect(@field(Point, "z") == 2); }` \\Performs field access by a compile-time string. Works on both fields and declarations.
\\
\\```zig
\\const std = @import("std");
\\const Point = struct {
\\ x: u32,
\\ y: u32,
\\ pub var z: u32 = 1;
\\};
\\test "field access by string" {
\\ const expect = std.testing.expect;
\\ var p = Point{ .x = 0, .y = 0 };
\\ @field(p, "x") = 4;
\\ @field(p, "y") = @field(p, "x") + 1;
\\ try expect(@field(p, "x") == 4);
\\ try expect(@field(p, "y") == 5);
\\}
\\test "decl access by string" {
\\ const expect = std.testing.expect;
\\ try expect(@field(Point, "z") == 1);
\\ @field(Point, "z") = 2;
\\ try expect(@field(Point, "z") == 2);
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"lhs: anytype", "lhs: anytype",
@ -756,7 +821,7 @@ pub const builtins = [_]Builtin{
}, },
.{ .{
.name = "@fieldParentPtr", .name = "@fieldParentPtr",
.signature = "@fieldParentPtr(comptime ParentType: type, comptime field_name: []const u8, field_ptr: *T) *ParentType", .signature = "@fieldParentPtr(comptime ParentType: type, comptime field_name: []const u8, field_ptr: *T) *ParentType",
.snippet = "@fieldParentPtr(${1:comptime ParentType: type}, ${2:comptime field_name: []const u8}, ${3:field_ptr: *T})", .snippet = "@fieldParentPtr(${1:comptime ParentType: type}, ${2:comptime field_name: []const u8}, ${3:field_ptr: *T})",
.documentation = .documentation =
\\Given a pointer to a field, returns the base pointer of a struct. \\Given a pointer to a field, returns the base pointer of a struct.
@ -811,7 +876,27 @@ pub const builtins = [_]Builtin{
.signature = "@hasDecl(comptime Container: type, comptime name: []const u8) bool", .signature = "@hasDecl(comptime Container: type, comptime name: []const u8) bool",
.snippet = "@hasDecl(${1:comptime Container: type}, ${2:comptime name: []const u8})", .snippet = "@hasDecl(${1:comptime Container: type}, ${2:comptime name: []const u8})",
.documentation = .documentation =
\\Returns whether or not a [container](https://ziglang.org/documentation/master/#Containers) has a declaration matching `name`.</p> {#code_begin|test|hasDecl#} const std = @import("std"); const expect = std.testing.expect; const Foo = struct { nope: i32, pub var blah = "xxx"; const hi = 1; }; test "@hasDecl" { try expect(@hasDecl(Foo, "blah")); // Even though `hi` is private, @hasDecl returns true because this test is // in the same file scope as Foo. It would return false if Foo was declared // in a different file. try expect(@hasDecl(Foo, "hi")); // @hasDecl is for declarations; not fields. try expect(!@hasDecl(Foo, "nope")); try expect(!@hasDecl(Foo, "nope1234")); }` \\Returns whether or not a [container](https://ziglang.org/documentation/master/#Containers) has a declaration matching `name`.
\\
\\```zig
\\const std = @import("std");
\\const expect = std.testing.expect;
\\const Foo = struct {
\\ nope: i32,
\\ pub var blah = "xxx";
\\ const hi = 1;
\\};
\\test "@hasDecl" {
\\ try expect(@hasDecl(Foo, "blah"));
\\ // Even though `hi` is private, @hasDecl returns true because this test is
\\ // in the same file scope as Foo. It would return false if Foo was declared
\\ // in a different file.
\\ try expect(@hasDecl(Foo, "hi"));
\\ // @hasDecl is for declarations; not fields.
\\ try expect(!@hasDecl(Foo, "nope"));
\\ try expect(!@hasDecl(Foo, "nope1234"));
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"comptime Container: type", "comptime Container: type",
@ -848,9 +933,12 @@ pub const builtins = [_]Builtin{
\\`path` can be a relative path or it can be the name of a package. If it is a relative path, it is relative to the file that contains the `@import` function call. \\`path` can be a relative path or it can be the name of a package. If it is a relative path, it is relative to the file that contains the `@import` function call.
\\ \\
\\The following packages are always available: \\The following packages are always available:
\\ - `@import("std")` - Zig Standard Library \\
\\ - `@import("builtin")` - Target-specific information. The command `zig build-exe --show-builtin` outputs the source to stdout for reference. \\ - `@import("std")` - Zig Standard Library
\\ - `@import("root")` - Points to the root source file. This is usually `src/main.zig` but it depends on what file is chosen to be built. \\ - `@import("builtin")` - Target-specific information. The command
\\`zig build-exe --show-builtin`outputs the source to stdout for reference.
\\ - `@import("root")` - Points to the root source file. This is usually
\\`src/main.zig`but it depends on what file is chosen to be built.
, ,
.arguments = &.{ .arguments = &.{
"comptime path: []u8", "comptime path: []u8",
@ -870,7 +958,6 @@ pub const builtins = [_]Builtin{
\\ _ = b; \\ _ = b;
\\} \\}
\\``` \\```
\\
\\To truncate the significant bits of a number out of range of the destination type, use [@truncate](https://ziglang.org/documentation/master/#truncate). \\To truncate the significant bits of a number out of range of the destination type, use [@truncate](https://ziglang.org/documentation/master/#truncate).
\\ \\
\\If `T` is `comptime_int`, then this is semantically equivalent to [Type Coercion](https://ziglang.org/documentation/master/#Type-Coercion). \\If `T` is `comptime_int`, then this is semantically equivalent to [Type Coercion](https://ziglang.org/documentation/master/#Type-Coercion).
@ -897,7 +984,7 @@ pub const builtins = [_]Builtin{
.{ .{
.name = "@intToError", .name = "@intToError",
.signature = "@intToError(value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8)) anyerror", .signature = "@intToError(value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8)) anyerror",
.snippet = "@intToError(${1:value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8)})", .snippet = "@intToError(${1:value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8})",
.documentation = .documentation =
\\Converts from the integer representation of an error into [The Global Error Set](https://ziglang.org/documentation/master/#The-Global-Error-Set) type. \\Converts from the integer representation of an error into [The Global Error Set](https://ziglang.org/documentation/master/#The-Global-Error-Set) type.
\\ \\
@ -906,7 +993,7 @@ pub const builtins = [_]Builtin{
\\Attempting to convert an integer that does not correspond to any error results in safety-protected [Undefined Behavior](https://ziglang.org/documentation/master/#Undefined-Behavior). \\Attempting to convert an integer that does not correspond to any error results in safety-protected [Undefined Behavior](https://ziglang.org/documentation/master/#Undefined-Behavior).
, ,
.arguments = &.{ .arguments = &.{
"value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8)", "value: std.meta.Int(.unsigned, @sizeOf(anyerror) * 8",
}, },
}, },
.{ .{
@ -958,10 +1045,7 @@ pub const builtins = [_]Builtin{
\\ \\
\\This function is a low level intrinsic with no safety mechanisms. Most code should not use this function, instead using something like this: \\This function is a low level intrinsic with no safety mechanisms. Most code should not use this function, instead using something like this:
\\ \\
\\```zig \\`for (source[0..byte_count]) |b, i| dest[i] = b;`
\\for (source[0..byte_count]) |b, i| dest[i] = b;
\\```
\\
\\The optimizer is intelligent enough to turn the above snippet into a memcpy. \\The optimizer is intelligent enough to turn the above snippet into a memcpy.
\\ \\
\\There is also a standard library function for this: \\There is also a standard library function for this:
@ -986,10 +1070,7 @@ pub const builtins = [_]Builtin{
\\ \\
\\This function is a low level intrinsic with no safety mechanisms. Most code should not use this function, instead using something like this: \\This function is a low level intrinsic with no safety mechanisms. Most code should not use this function, instead using something like this:
\\ \\
\\```zig \\`for (dest[0..byte_count]) |*b| b.* = c;`
\\for (dest[0..byte_count]) |*b| b.* = c;
\\```
\\
\\The optimizer is intelligent enough to turn the above snippet into a memset. \\The optimizer is intelligent enough to turn the above snippet into a memset.
\\ \\
\\There is also a standard library function for this: \\There is also a standard library function for this:
@ -1039,7 +1120,19 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\This function increases the size of the Wasm memory identified by `index` by `delta` in units of unsigned number of Wasm pages. Note that each Wasm page is 64KB in size. On success, returns previous memory size; on failure, if the allocation fails, returns -1. \\This function increases the size of the Wasm memory identified by `index` by `delta` in units of unsigned number of Wasm pages. Note that each Wasm page is 64KB in size. On success, returns previous memory size; on failure, if the allocation fails, returns -1.
\\ \\
\\This function is a low level intrinsic with no safety mechanisms usually useful for allocator designers targeting Wasm. So unless you are writing a new allocator from scratch, you should use something like `@import("std").heap.WasmPageAllocator`.</p> {#code_begin|test|wasmMemoryGrow#} const std = @import("std"); const native_arch = @import("builtin").target.cpu.arch; const expect = std.testing.expect; test "@wasmMemoryGrow" { if (native_arch != .wasm32) return error.SkipZigTest; var prev = @wasmMemorySize(0); try expect(prev == @wasmMemoryGrow(0, 1)); try expect(prev + 1 == @wasmMemorySize(0)); }` \\This function is a low level intrinsic with no safety mechanisms usually useful for allocator designers targeting Wasm. So unless you are writing a new allocator from scratch, you should use something like `@import("std").heap.WasmPageAllocator`.
\\
\\```zig
\\const std = @import("std");
\\const native_arch = @import("builtin").target.cpu.arch;
\\const expect = std.testing.expect;
\\test "@wasmMemoryGrow" {
\\ if (native_arch != .wasm32) return error.SkipZigTest;
\\ var prev = @wasmMemorySize(0);
\\ try expect(prev == @wasmMemoryGrow(0, 1));
\\ try expect(prev + 1 == @wasmMemorySize(0));
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"index: u32", "index: u32",
@ -1052,9 +1145,9 @@ pub const builtins = [_]Builtin{
.snippet = "@mod(${1:numerator: T}, ${2:denominator: T})", .snippet = "@mod(${1:numerator: T}, ${2:denominator: T})",
.documentation = .documentation =
\\Modulus division. For unsigned integers this is the same as `numerator % denominator`. Caller guarantees `denominator > 0`, otherwise the operation will result in a [Remainder Division by Zero](https://ziglang.org/documentation/master/#Remainder-Division-by-Zero) when runtime safety checks are enabled. \\Modulus division. For unsigned integers this is the same as `numerator % denominator`. Caller guarantees `denominator > 0`, otherwise the operation will result in a [Remainder Division by Zero](https://ziglang.org/documentation/master/#Remainder-Division-by-Zero) when runtime safety checks are enabled.
\\ - `@mod(-5, 3) == 1`
\\ - `(@divFloor(a, b) * b) + @mod(a, b) == a`
\\ \\
\\ - `@mod(-5, 3) == 1`
\\ - `(@divFloor(a, b) * b) + @mod(a, b) == a`
\\For a function that returns an error code, see `@import("std").math.mod`. \\For a function that returns an error code, see `@import("std").math.mod`.
, ,
.arguments = &.{ .arguments = &.{
@ -1082,8 +1175,9 @@ pub const builtins = [_]Builtin{
\\Invokes the panic handler function. By default the panic handler function calls the public `panic` function exposed in the root source file, or if there is not one specified, the `std.builtin.default_panic` function from `std/builtin.zig`. \\Invokes the panic handler function. By default the panic handler function calls the public `panic` function exposed in the root source file, or if there is not one specified, the `std.builtin.default_panic` function from `std/builtin.zig`.
\\ \\
\\Generally it is better to use `@import("std").debug.panic`. However, `@panic` can be useful for 2 scenarios: \\Generally it is better to use `@import("std").debug.panic`. However, `@panic` can be useful for 2 scenarios:
\\ - From library code, calling the programmer's panic function if they exposed one in the root source file. \\
\\ - When mixing C and Zig code, calling the canonical panic implementation across multiple .o files. \\ - From library code, calling the programmer's panic function if they exposed one in the root source file.
\\ - When mixing C and Zig code, calling the canonical panic implementation across multiple .o files.
, ,
.arguments = &.{ .arguments = &.{
"message: []const u8", "message: []const u8",
@ -1115,7 +1209,32 @@ pub const builtins = [_]Builtin{
\\ \\
\\The `ptr` argument may be any pointer type and determines the memory address to prefetch. This function does not dereference the pointer, it is perfectly legal to pass a pointer to invalid memory to this function and no illegal behavior will result. \\The `ptr` argument may be any pointer type and determines the memory address to prefetch. This function does not dereference the pointer, it is perfectly legal to pass a pointer to invalid memory to this function and no illegal behavior will result.
\\ \\
\\The `options` argument is the following struct:</p> {#code_begin|syntax|builtin#} /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const PrefetchOptions = struct { /// Whether the prefetch should prepare for a read or a write. rw: Rw = .read, /// 0 means no temporal locality. That is, the data can be immediately /// dropped from the cache after it is accessed. /// /// 3 means high temporal locality. That is, the data should be kept in /// the cache as it is likely to be accessed again soon. locality: u2 = 3, /// The cache that the prefetch should be preformed on. cache: Cache = .data, pub const Rw = enum { read, write, }; pub const Cache = enum { instruction, data, }; };` \\The `options` argument is the following struct:
\\
\\```zig
\\/// This data structure is used by the Zig language code generation and
\\/// therefore must be kept in sync with the compiler implementation.
\\pub const PrefetchOptions = struct {
\\ /// Whether the prefetch should prepare for a read or a write.
\\ rw: Rw = .read,
\\ /// 0 means no temporal locality. That is, the data can be immediately
\\ /// dropped from the cache after it is accessed.
\\ ///
\\ /// 3 means high temporal locality. That is, the data should be kept in
\\ /// the cache as it is likely to be accessed again soon.
\\ locality: u2 = 3,
\\ /// The cache that the prefetch should be preformed on.
\\ cache: Cache = .data,
\\ pub const Rw = enum {
\\ read,
\\ write,
\\ };
\\ pub const Cache = enum {
\\ instruction,
\\ data,
\\ };
\\};
\\```
, ,
.arguments = &.{ .arguments = &.{
"ptr: anytype", "ptr: anytype",
@ -1155,9 +1274,9 @@ pub const builtins = [_]Builtin{
.snippet = "@rem(${1:numerator: T}, ${2:denominator: T})", .snippet = "@rem(${1:numerator: T}, ${2:denominator: T})",
.documentation = .documentation =
\\Remainder division. For unsigned integers this is the same as `numerator % denominator`. Caller guarantees `denominator > 0`, otherwise the operation will result in a [Remainder Division by Zero](https://ziglang.org/documentation/master/#Remainder-Division-by-Zero) when runtime safety checks are enabled. \\Remainder division. For unsigned integers this is the same as `numerator % denominator`. Caller guarantees `denominator > 0`, otherwise the operation will result in a [Remainder Division by Zero](https://ziglang.org/documentation/master/#Remainder-Division-by-Zero) when runtime safety checks are enabled.
\\ - `@rem(-5, 3) == -2`
\\ - `(@divTrunc(a, b) * b) + @rem(a, b) == a`
\\ \\
\\ - `@rem(-5, 3) == -2`
\\ - `(@divTrunc(a, b) * b) + @rem(a, b) == a`
\\For a function that returns an error code, see `@import("std").math.rem`. \\For a function that returns an error code, see `@import("std").math.rem`.
, ,
.arguments = &.{ .arguments = &.{
@ -1225,16 +1344,16 @@ pub const builtins = [_]Builtin{
\\ \\
\\Example: \\Example:
\\ \\
\\```zig \\1001) : (i += 1) {}
\\test "foo" {
\\ comptime {
\\ var i = 0;
\\ while (i < 1001) : (i += 1) {}
\\ } \\ }
\\} \\}
\\``` \\```
\\Now we use `@setEvalBranchQuota`:
\\ \\
\\Now we use `@setEvalBranchQuota`:</p> {#code_begin|test|setEvalBranchQuota#} test "foo" { comptime { @setEvalBranchQuota(1001); var i = 0; while (i < 1001) : (i += 1) {} } }` \\1001) : (i += 1) {}
\\ }
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"comptime new_quota: u32", "comptime new_quota: u32",
@ -1253,16 +1372,16 @@ pub const builtins = [_]Builtin{
\\ Optimized, \\ Optimized,
\\}; \\};
\\``` \\```
\\ \\ - `Strict` (default) - Floating point operations follow strict IEEE compliance.
\\ - `Strict` (default) - Floating point operations follow strict IEEE compliance. \\ - `Optimized` - Floating point operations may do all of the following:
\\ - `Optimized` - Floating point operations may do all of the following: <ul> \\ - Assume the arguments and result are not NaN. Optimizations are required to retain defined behavior over NaNs, but the value of the result is undefined.
\\ - Assume the arguments and result are not NaN. Optimizations are required to retain defined behavior over NaNs, but the value of the result is undefined. \\ - Assume the arguments and result are not +/-Inf. Optimizations are required to retain defined behavior over +/-Inf, but the value of the result is undefined.
\\ - Assume the arguments and result are not +/-Inf. Optimizations are required to retain defined behavior over +/-Inf, but the value of the result is undefined. \\ - Treat the sign of a zero argument or result as insignificant.
\\ - Treat the sign of a zero argument or result as insignificant. \\ - Use the reciprocal of an argument rather than perform division.
\\ - Use the reciprocal of an argument rather than perform division. \\ - Perform floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-add).
\\ - Perform floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-add). \\ - Perform algebraically equivalent transformations that may change results in floating point (e.g. reassociate).
\\ - Perform algebraically equivalent transformations that may change results in floating point (e.g. reassociate). This is equivalent to `-ffast-math` in GCC.</ul> \\This is equivalent to
\\ \\`-ffast-math`in GCC.
\\The floating point mode is inherited by child scopes, and can be overridden in any scope. You can set the floating point mode in a struct or module scope by using a comptime block. \\The floating point mode is inherited by child scopes, and can be overridden in any scope. You can set the floating point mode in a struct or module scope by using a comptime block.
, ,
.arguments = &.{ .arguments = &.{
@ -1288,7 +1407,6 @@ pub const builtins = [_]Builtin{
\\ @setRuntimeSafety(true); \\ @setRuntimeSafety(true);
\\ var x: u8 = 255; \\ var x: u8 = 255;
\\ x += 1; \\ x += 1;
\\
\\ { \\ {
\\ // The value can be overridden at any scope. So here integer overflow \\ // The value can be overridden at any scope. So here integer overflow
\\ // would not be caught in any build mode. \\ // would not be caught in any build mode.
@ -1299,8 +1417,9 @@ pub const builtins = [_]Builtin{
\\ } \\ }
\\} \\}
\\``` \\```
\\ \\Note: it is
\\Note: it is [planned](https://github.com/ziglang/zig/issues/978) to replace `@setRuntimeSafety` with `@optimizeFor` \\[planned](https://github.com/ziglang/zig/issues/978)to replace `@setRuntimeSafety` with
\\`@optimizeFor`
, ,
.arguments = &.{ .arguments = &.{
"comptime safety_on: bool", "comptime safety_on: bool",
@ -1311,7 +1430,7 @@ pub const builtins = [_]Builtin{
.signature = "@shlExact(value: T, shift_amt: Log2T) T", .signature = "@shlExact(value: T, shift_amt: Log2T) T",
.snippet = "@shlExact(${1:value: T}, ${2:shift_amt: Log2T})", .snippet = "@shlExact(${1:value: T}, ${2:shift_amt: Log2T})",
.documentation = .documentation =
\\Performs the left shift operation (`<<`). For unsigned integers, the result is [undefined](https://ziglang.org/documentation/master/#undefined) if any 1 bits are shifted out. For signed integers, the result is [undefined](https://ziglang.org/documentation/master/#undefined) if any bits that disagree with the resultant sign bit are shifted out. \\`). For unsigned integers, the result is [undefined](https://ziglang.org/documentation/master/#undefined) if any 1 bits are shifted out. For signed integers, the result is [undefined](https://ziglang.org/documentation/master/#undefined) if any bits that disagree with the resultant sign bit are shifted out.
\\ \\
\\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(T).Int.bits)` bits. This is because `shift_amt >= @typeInfo(T).Int.bits` is undefined behavior.
, ,
@ -1325,7 +1444,7 @@ pub const builtins = [_]Builtin{
.signature = "@shlWithOverflow(a: anytype, shift_amt: Log2T) struct { @TypeOf(a), u1 }", .signature = "@shlWithOverflow(a: anytype, shift_amt: Log2T) struct { @TypeOf(a), u1 }",
.snippet = "@shlWithOverflow(${1:a: anytype}, ${2:shift_amt: Log2T})", .snippet = "@shlWithOverflow(${1:a: anytype}, ${2:shift_amt: Log2T})",
.documentation = .documentation =
\\Performs `a << b` and returns a tuple with the result and a possible overflow bit. \\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(@TypeOf(a)).Int.bits)` bits. This is because `shift_amt >= @typeInfo(@TypeOf(a)).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.
, ,
@ -1363,7 +1482,25 @@ pub const builtins = [_]Builtin{
\\ \\
\\If `a` or `b` is `undefined`, it is equivalent to a vector of all `undefined` with the same length as the other vector. If both vectors are `undefined`, `@shuffle` returns a vector with all elements `undefined`. \\If `a` or `b` is `undefined`, it is equivalent to a vector of all `undefined` with the same length as the other vector. If both vectors are `undefined`, `@shuffle` returns a vector with all elements `undefined`.
\\ \\
\\`E` must be an [integer](https://ziglang.org/documentation/master/#Integers), [float](https://ziglang.org/documentation/master/#Floats), [pointer](https://ziglang.org/documentation/master/#Pointers), or `bool`. The mask may be any vector length, and its length determines the result length.</p> {#code_begin|test|vector_shuffle#} const std = @import("std"); const expect = std.testing.expect; test "vector @shuffle" { const a = @Vector(7, u8){ 'o', 'l', 'h', 'e', 'r', 'z', 'w' }; const b = @Vector(4, u8){ 'w', 'd', '!', 'x' }; // To shuffle within a single vector, pass undefined as the second argument. // Notice that we can re-order, duplicate, or omit elements of the input vector const mask1 = @Vector(5, i32){ 2, 3, 1, 1, 0 }; const res1: @Vector(5, u8) = @shuffle(u8, a, undefined, mask1); try expect(std.mem.eql(u8, &@as([5]u8, res1), "hello")); // Combining two vectors const mask2 = @Vector(6, i32){ -1, 0, 4, 1, -2, -3 }; const res2: @Vector(6, u8) = @shuffle(u8, a, b, mask2); try expect(std.mem.eql(u8, &@as([6]u8, res2), "world!")); }` \\`E` must be an [integer](https://ziglang.org/documentation/master/#Integers), [float](https://ziglang.org/documentation/master/#Floats), [pointer](https://ziglang.org/documentation/master/#Pointers), or `bool`. The mask may be any vector length, and its length determines the result length.
\\
\\```zig
\\const std = @import("std");
\\const expect = std.testing.expect;
\\test "vector @shuffle" {
\\ const a = @Vector(7, u8){ 'o', 'l', 'h', 'e', 'r', 'z', 'w' };
\\ const b = @Vector(4, u8){ 'w', 'd', '!', 'x' };
\\ // To shuffle within a single vector, pass undefined as the second argument.
\\ // Notice that we can re-order, duplicate, or omit elements of the input vector
\\ const mask1 = @Vector(5, i32){ 2, 3, 1, 1, 0 };
\\ const res1: @Vector(5, u8) = @shuffle(u8, a, undefined, mask1);
\\ try expect(std.mem.eql(u8, &@as([5]u8, res1), "hello"));
\\ // Combining two vectors
\\ const mask2 = @Vector(6, i32){ -1, 0, 4, 1, -2, -3 };
\\ const res2: @Vector(6, u8) = @shuffle(u8, a, b, mask2);
\\ try expect(std.mem.eql(u8, &@as([6]u8, res2), "world!"));
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"comptime E: type", "comptime E: type",
@ -1392,8 +1529,18 @@ pub const builtins = [_]Builtin{
.signature = "@splat(comptime len: u32, scalar: anytype) @Vector(len, @TypeOf(scalar))", .signature = "@splat(comptime len: u32, scalar: anytype) @Vector(len, @TypeOf(scalar))",
.snippet = "@splat(${1:comptime len: u32}, ${2:scalar: anytype})", .snippet = "@splat(${1:comptime len: u32}, ${2:scalar: anytype})",
.documentation = .documentation =
\\Produces a vector of length `len` where each element is the value `scalar`:</p> {#code_begin|test|vector_splat#} const std = @import("std"); const expect = std.testing.expect; test "vector @splat" { const scalar: u32 = 5; const result = @splat(4, scalar); comptime try expect(@TypeOf(result) == @Vector(4, u32)); try expect(std.mem.eql(u32, &@as([4]u32, result), &[_]u32{ 5, 5, 5, 5 })); }` \\Produces a vector of length `len` where each element is the value `scalar`:
\\ \\
\\```zig
\\const std = @import("std");
\\const expect = std.testing.expect;
\\test "vector @splat" {
\\ const scalar: u32 = 5;
\\ const result = @splat(4, scalar);
\\ comptime try expect(@TypeOf(result) == @Vector(4, u32));
\\ try expect(std.mem.eql(u32, &@as([4]u32, result), &[_]u32{ 5, 5, 5, 5 }));
\\}
\\```
\\`scalar` must be an [integer](https://ziglang.org/documentation/master/#Integers), [bool](https://ziglang.org/documentation/master/#Primitive-Types), [float](https://ziglang.org/documentation/master/#Floats), or [pointer](https://ziglang.org/documentation/master/#Pointers). \\`scalar` must be an [integer](https://ziglang.org/documentation/master/#Integers), [bool](https://ziglang.org/documentation/master/#Primitive-Types), [float](https://ziglang.org/documentation/master/#Floats), or [pointer](https://ziglang.org/documentation/master/#Pointers).
, ,
.arguments = &.{ .arguments = &.{
@ -1406,14 +1553,29 @@ pub const builtins = [_]Builtin{
.signature = "@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E", .signature = "@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E",
.snippet = "@reduce(${1:comptime op: std.builtin.ReduceOp}, ${2:value: anytype})", .snippet = "@reduce(${1:comptime op: std.builtin.ReduceOp}, ${2:value: anytype})",
.documentation = .documentation =
\\Transforms a [vector](https://ziglang.org/documentation/master/#Vectors) into a scalar value (of type `E`) by performing a sequential horizontal reduction of its elements using the specified operator `op`. \\Transforms a [vector](https://ziglang.org/documentation/master/#Vectors) into a scalar value (of type
\\`E`) by performing a sequential horizontal reduction of its elements using the specified operator `op`.
\\ \\
\\Not every operator is available for every vector element type: \\Not every operator is available for every vector element type:
\\ - Every operator is available for [integer](https://ziglang.org/documentation/master/#Integers) vectors.
\\ - `.And`, `.Or`, `.Xor` are additionally available for `bool` vectors,
\\ - `.Min`, `.Max`, `.Add`, `.Mul` are additionally available for [floating point](https://ziglang.org/documentation/master/#Floats) vectors,
\\ \\
\\Note that `.Add` and `.Mul` reductions on integral types are wrapping; when applied on floating point types the operation associativity is preserved, unless the float mode is set to `Optimized`.</p> {#code_begin|test|vector_reduce#} const std = @import("std"); const expect = std.testing.expect; test "vector @reduce" { const value = @Vector(4, i32){ 1, -1, 1, -1 }; const result = value > @splat(4, @as(i32, 0)); // result is { true, false, true, false }; comptime try expect(@TypeOf(result) == @Vector(4, bool)); const is_all_true = @reduce(.And, result); comptime try expect(@TypeOf(is_all_true) == bool); try expect(is_all_true == false); }` \\ - Every operator is available for [integer](https://ziglang.org/documentation/master/#Integers) vectors.
\\ - `.And`, `.Or`, `.Xor` are additionally available for `bool` vectors,
\\ - `.Min`, `.Max`, `.Add`, `.Mul` are additionally available for [floating point](https://ziglang.org/documentation/master/#Floats) vectors,
\\Note that `.Add` and `.Mul` reductions on integral types are wrapping; when applied on floating point types the operation associativity is preserved, unless the float mode is set to `Optimized`.
\\
\\```zig
\\const std = @import("std");
\\const expect = std.testing.expect;
\\test "vector @reduce" {
\\ const value = @Vector(4, i32){ 1, -1, 1, -1 };
\\ const result = value > @splat(4, @as(i32, 0));
\\ // result is { true, false, true, false };
\\ comptime try expect(@TypeOf(result) == @Vector(4, bool));
\\ const is_all_true = @reduce(.And, result);
\\ comptime try expect(@TypeOf(is_all_true) == bool);
\\ try expect(is_all_true == false);
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"comptime op: std.builtin.ReduceOp", "comptime op: std.builtin.ReduceOp",
@ -1425,7 +1587,22 @@ pub const builtins = [_]Builtin{
.signature = "@src() std.builtin.SourceLocation", .signature = "@src() std.builtin.SourceLocation",
.snippet = "@src()", .snippet = "@src()",
.documentation = .documentation =
\\Returns a `SourceLocation` struct representing the function's name and location in the source code. This must be called in a function.</p> {#code_begin|test|source_location#} const std = @import("std"); const expect = std.testing.expect; test "@src" { try doTheTest(); } fn doTheTest() !void { const src = @src(); try expect(src.line == 9); try expect(src.column == 17); try expect(std.mem.endsWith(u8, src.fn_name, "doTheTest")); try expect(std.mem.endsWith(u8, src.file, "source_location.zig")); }` \\Returns a `SourceLocation` struct representing the function's name and location in the source code. This must be called in a function.
\\
\\```zig
\\const std = @import("std");
\\const expect = std.testing.expect;
\\test "@src" {
\\ try doTheTest();
\\}
\\fn doTheTest() !void {
\\ const src = @src();
\\ try expect(src.line == 9);
\\ try expect(src.column == 17);
\\ try expect(std.mem.endsWith(u8, src.fn_name, "doTheTest"));
\\ try expect(std.mem.endsWith(u8, src.file, "source_location.zig"));
\\}
\\```
, ,
.arguments = &.{}, .arguments = &.{},
}, },
@ -1436,7 +1613,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Performs the square root of a floating point number. Uses a dedicated hardware instruction when available. \\Performs the square root of a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1449,7 +1627,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Sine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. \\Sine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1462,7 +1641,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Cosine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. \\Cosine trigonometric function on a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1475,7 +1655,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Tangent trigonometric function on a floating point number. Uses a dedicated hardware instruction when available. \\Tangent trigonometric function on a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1488,7 +1669,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Base-e exponential function on a floating point number. Uses a dedicated hardware instruction when available. \\Base-e exponential function on a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1501,7 +1683,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Base-2 exponential function on a floating point number. Uses a dedicated hardware instruction when available. \\Base-2 exponential function on a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1514,7 +1697,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Returns the natural logarithm of a floating point number. Uses a dedicated hardware instruction when available. \\Returns the natural logarithm of a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1527,7 +1711,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Returns the logarithm to the base 2 of a floating point number. Uses a dedicated hardware instruction when available. \\Returns the logarithm to the base 2 of a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1540,7 +1725,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Returns the logarithm to the base 10 of a floating point number. Uses a dedicated hardware instruction when available. \\Returns the logarithm to the base 10 of a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1553,7 +1739,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Returns the absolute value of a floating point number. Uses a dedicated hardware instruction when available. \\Returns the absolute value of a floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1566,7 +1753,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Returns the largest integral value not greater than the given floating point number. Uses a dedicated hardware instruction when available. \\Returns the largest integral value not greater than the given floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1579,7 +1767,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Returns the smallest integral value not less than the given floating point number. Uses a dedicated hardware instruction when available. \\Returns the smallest integral value not less than the given floating point number. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1592,7 +1781,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Rounds the given floating point number to an integer, towards zero. Uses a dedicated hardware instruction when available. \\Rounds the given floating point number to an integer, towards zero. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1605,7 +1795,8 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\Rounds the given floating point number to an integer, away from zero. Uses a dedicated hardware instruction when available. \\Rounds the given floating point number to an integer, away from zero. Uses a dedicated hardware instruction when available.
\\ \\
\\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that [some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026). \\Supports [Floats](https://ziglang.org/documentation/master/#Floats) and [Vectors](https://ziglang.org/documentation/master/#Vectors) of floats, with the caveat that
\\[some float operations are not yet implemented for all float types](https://github.com/ziglang/zig/issues/4026).
, ,
.arguments = &.{ .arguments = &.{
"value: anytype", "value: anytype",
@ -1641,8 +1832,26 @@ pub const builtins = [_]Builtin{
.signature = "@This() type", .signature = "@This() type",
.snippet = "@This()", .snippet = "@This()",
.documentation = .documentation =
\\Returns the innermost struct, enum, or union that this function call is inside. This can be useful for an anonymous struct that needs to refer to itself:</p> {#code_begin|test|this_innermost#} const std = @import("std"); const expect = std.testing.expect; test "@This()" { var items = [_]i32{ 1, 2, 3, 4 }; const list = List(i32){ .items = items[0..] }; try expect(list.length() == 4); } fn List(comptime T: type) type { return struct { const Self = @This(); items: []T, fn length(self: Self) usize { return self.items.len; } }; }` \\Returns the innermost struct, enum, or union that this function call is inside. This can be useful for an anonymous struct that needs to refer to itself:
\\ \\
\\```zig
\\const std = @import("std");
\\const expect = std.testing.expect;
\\test "@This()" {
\\ var items = [_]i32{ 1, 2, 3, 4 };
\\ const list = List(i32){ .items = items[0..] };
\\ try expect(list.length() == 4);
\\}
\\fn List(comptime T: type) type {
\\ return struct {
\\ const Self = @This();
\\ items: []T,
\\ fn length(self: Self) usize {
\\ return self.items.len;
\\ }
\\ };
\\}
\\```
\\When `@This()` is used at file scope, it returns a reference to the struct that corresponds to the current file. \\When `@This()` is used at file scope, it returns a reference to the struct that corresponds to the current file.
, ,
.arguments = &.{}, .arguments = &.{},
@ -1661,14 +1870,12 @@ pub const builtins = [_]Builtin{
\\```zig \\```zig
\\const std = @import("std"); \\const std = @import("std");
\\const expect = std.testing.expect; \\const expect = std.testing.expect;
\\
\\test "integer truncation" { \\test "integer truncation" {
\\ var a: u16 = 0xabcd; \\ var a: u16 = 0xabcd;
\\ var b: u8 = @truncate(u8, a); \\ var b: u8 = @truncate(u8, a);
\\ try expect(b == 0xcd); \\ try expect(b == 0xcd);
\\} \\}
\\``` \\```
\\
\\Use [@intCast](https://ziglang.org/documentation/master/#intCast) to convert numbers guaranteed to fit the destination type. \\Use [@intCast](https://ziglang.org/documentation/master/#intCast) to convert numbers guaranteed to fit the destination type.
, ,
.arguments = &.{ .arguments = &.{
@ -1684,29 +1891,29 @@ pub const builtins = [_]Builtin{
\\This function is the inverse of [@typeInfo](https://ziglang.org/documentation/master/#typeInfo). It reifies type information into a `type`. \\This function is the inverse of [@typeInfo](https://ziglang.org/documentation/master/#typeInfo). It reifies type information into a `type`.
\\ \\
\\It is available for the following types: \\It is available for the following types:
\\ - `type`
\\ - `noreturn`
\\ - `void`
\\ - `bool`
\\ - [Integers](https://ziglang.org/documentation/master/#Integers) - The maximum bit count for an integer type is `65535`.
\\ - [Floats](https://ziglang.org/documentation/master/#Floats)
\\ - [Pointers](https://ziglang.org/documentation/master/#Pointers)
\\ - `comptime_int`
\\ - `comptime_float`
\\ - `@TypeOf(undefined)`
\\ - `@TypeOf(null)`
\\ - [Arrays](https://ziglang.org/documentation/master/#Arrays)
\\ - [Optionals](https://ziglang.org/documentation/master/#Optionals)
\\ - [Error Set Type](https://ziglang.org/documentation/master/#Error-Set-Type)
\\ - [Error Union Type](https://ziglang.org/documentation/master/#Error-Union-Type)
\\ - [Vectors](https://ziglang.org/documentation/master/#Vectors)
\\ - [opaque](https://ziglang.org/documentation/master/#opaque)
\\ - `anyframe`
\\ - [struct](https://ziglang.org/documentation/master/#struct)
\\ - [enum](https://ziglang.org/documentation/master/#enum)
\\ - [Enum Literals](https://ziglang.org/documentation/master/#Enum-Literals)
\\ - [union](https://ziglang.org/documentation/master/#union)
\\ \\
\\ - `type`
\\ - `noreturn`
\\ - `void`
\\ - `bool`
\\ - [Integers](https://ziglang.org/documentation/master/#Integers) - The maximum bit count for an integer type is `65535`.
\\ - [Floats](https://ziglang.org/documentation/master/#Floats)
\\ - [Pointers](https://ziglang.org/documentation/master/#Pointers)
\\ - `comptime_int`
\\ - `comptime_float`
\\ - `@TypeOf(undefined)`
\\ - `@TypeOf(null)`
\\ - [Arrays](https://ziglang.org/documentation/master/#Arrays)
\\ - [Optionals](https://ziglang.org/documentation/master/#Optionals)
\\ - [Error Set Type](https://ziglang.org/documentation/master/#Error-Set-Type)
\\ - [Error Union Type](https://ziglang.org/documentation/master/#Error-Union-Type)
\\ - [Vectors](https://ziglang.org/documentation/master/#Vectors)
\\ - [opaque](https://ziglang.org/documentation/master/#opaque)
\\ - `anyframe`
\\ - [struct](https://ziglang.org/documentation/master/#struct)
\\ - [enum](https://ziglang.org/documentation/master/#enum)
\\ - [Enum Literals](https://ziglang.org/documentation/master/#Enum-Literals)
\\ - [union](https://ziglang.org/documentation/master/#union)
\\`@Type` is not available for [Functions](https://ziglang.org/documentation/master/#Functions). \\`@Type` is not available for [Functions](https://ziglang.org/documentation/master/#Functions).
, ,
.arguments = &.{ .arguments = &.{
@ -1746,7 +1953,23 @@ pub const builtins = [_]Builtin{
.documentation = .documentation =
\\`@TypeOf` is a special builtin function that takes any (nonzero) number of expressions as parameters and returns the type of the result, using [Peer Type Resolution](https://ziglang.org/documentation/master/#Peer-Type-Resolution). \\`@TypeOf` is a special builtin function that takes any (nonzero) number of expressions as parameters and returns the type of the result, using [Peer Type Resolution](https://ziglang.org/documentation/master/#Peer-Type-Resolution).
\\ \\
\\The expressions are evaluated, however they are guaranteed to have no *runtime* side-effects:</p> {#code_begin|test|no_runtime_side_effects#} const std = @import("std"); const expect = std.testing.expect; test "no runtime side effects" { var data: i32 = 0; const T = @TypeOf(foo(i32, &data)); comptime try expect(T == i32); try expect(data == 0); } fn foo(comptime T: type, ptr: *T) T { ptr.* += 1; return ptr.*; }` \\The expressions are evaluated, however they are guaranteed to have no
\\**runtime** side-effects:
\\
\\```zig
\\const std = @import("std");
\\const expect = std.testing.expect;
\\test "no runtime side effects" {
\\ var data: i32 = 0;
\\ const T = @TypeOf(foo(i32, &data));
\\ comptime try expect(T == i32);
\\ try expect(data == 0);
\\}
\\fn foo(comptime T: type, ptr: *T) T {
\\ ptr.* += 1;
\\ return ptr.*;
\\}
\\```
, ,
.arguments = &.{ .arguments = &.{
"...", "...",
@ -1780,3 +2003,5 @@ pub const builtins = [_]Builtin{
}, },
}, },
}; };
// DO NOT EDIT

View File

@ -4,13 +4,6 @@ const offsets = @import("offsets.zig");
pub const Error = error{ OutOfMemory, InvalidRange }; pub const Error = error{ OutOfMemory, InvalidRange };
// This is essentially the same as `types.TextEdit`, but we use an
// ArrayList(u8) here to be able to clean up the memory later on
pub const Edit = struct {
range: types.Range,
newText: std.ArrayListUnmanaged(u8),
};
// Whether the `Change` is an addition, deletion, or no change from the // Whether the `Change` is an addition, deletion, or no change from the
// original string to the new string // original string to the new string
const Operation = enum { Deletion, Addition, Nothing }; const Operation = enum { Deletion, Addition, Nothing };
@ -28,7 +21,7 @@ pub fn edits(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
a: []const u8, a: []const u8,
b: []const u8, b: []const u8,
) Error!std.ArrayListUnmanaged(Edit) { ) Error!std.ArrayListUnmanaged(types.TextEdit) {
// Given the input strings A and B, we skip over the first N characters // Given the input strings A and B, we skip over the first N characters
// where A[0..N] == B[0..N]. We want to trim the start (and end) of the // where A[0..N] == B[0..N]. We want to trim the start (and end) of the
// strings that have the same text. This decreases the size of the LCS // strings that have the same text. This decreases the size of the LCS
@ -186,7 +179,7 @@ pub fn get_changes(
a_trim: []const u8, a_trim: []const u8,
b_trim: []const u8, b_trim: []const u8,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
) Error!std.ArrayListUnmanaged(Edit) { ) Error!std.ArrayListUnmanaged(types.TextEdit) {
// First we get a list of changes between strings at the character level: // First we get a list of changes between strings at the character level:
// "addition", "deletion", and "no change" for each character // "addition", "deletion", and "no change" for each character
var changes = try std.ArrayListUnmanaged(Change).initCapacity(allocator, a_trim.len); var changes = try std.ArrayListUnmanaged(Change).initCapacity(allocator, a_trim.len);
@ -235,14 +228,20 @@ pub fn get_changes(
std.mem.reverse([]Change, groups.items); std.mem.reverse([]Change, groups.items);
for (groups.items) |group| std.mem.reverse(Change, group); for (groups.items) |group| std.mem.reverse(Change, group);
var edit_results = std.ArrayListUnmanaged(Edit){}; var edit_results = try std.ArrayListUnmanaged(types.TextEdit).initCapacity(allocator, groups.items.len);
errdefer edit_results.deinit(allocator); errdefer {
for (edit_results.items) |edit| {
allocator.free(edit.newText);
}
edit_results.deinit(allocator);
}
// Convert our grouped changes into `Edit`s // Convert our grouped changes into `Edit`s
for (groups.items) |group| { for (groups.items) |group| {
var range_start = group[0].pos; var range_start = group[0].pos;
var range_len: usize = 0; var range_len: usize = 0;
var newText = std.ArrayListUnmanaged(u8){}; var newText = std.ArrayListUnmanaged(u8){};
errdefer newText.deinit(allocator);
for (group) |ch| { for (group) |ch| {
switch (ch.operation) { switch (ch.operation) {
.Addition => try newText.append(allocator, ch.value.?), .Addition => try newText.append(allocator, ch.value.?),
@ -256,9 +255,9 @@ pub fn get_changes(
a_trim_offset + range_start + range_len, a_trim_offset + range_start + range_len,
); );
a_lines.reset(); a_lines.reset();
try edit_results.append(allocator, Edit{ edit_results.appendAssumeCapacity(.{
.range = range, .range = range,
.newText = newText, .newText = try newText.toOwnedSlice(allocator),
}); });
} }

@ -1 +1 @@
Subproject commit 24845b0103e611c108d6bc334231c464e699742c Subproject commit 6b37490ac7285133bf09441850b8102c9728a776

View File

@ -708,7 +708,29 @@ pub const CodeActionKind = enum {
_ = maybe_allocator; _ = maybe_allocator;
if (json_value != .String) return error.InvalidEnumTag; if (json_value != .String) return error.InvalidEnumTag;
if (json_value.String.len == 0) return .empty; if (json_value.String.len == 0) return .empty;
return std.meta.stringToEnum(@This(), json_value.String) orelse return error.InvalidEnumTag; if (std.meta.stringToEnum(@This(), json_value.String)) |val| return val;
// Some clients (nvim) may report these by the enumeration names rather than the
// actual strings, so let's check those names here
const aliases = [_]struct { []const u8, CodeActionKind }{
.{ "Empty", .empty },
.{ "QuickFix", .quickfix },
.{ "Refactor", .refactor },
.{ "RefactorExtract", .@"refactor.extract" },
.{ "RefactorInline", .@"refactor.inline" },
.{ "RefactorRewrite", .@"refactor.rewrite" },
.{ "Source", .source },
.{ "SourceOrganizeImports", .@"source.organizeImports" },
.{ "SourceFixAll", .@"source.fixAll" },
};
for (aliases) |alias| {
if (std.mem.eql(u8, json_value.String, alias[0])) {
return alias[1];
}
}
return error.InvalidEnumTag;
} }
}; };

View File

@ -11,28 +11,30 @@ const debug = @import("debug.zig");
const logger = std.log.scoped(.main); const logger = std.log.scoped(.main);
// Always set this to debug to make std.log call into our handler, then control the runtime
// value in the definition below.
pub const log_level = .debug;
var actual_log_level: std.log.Level = switch (zig_builtin.mode) { var actual_log_level: std.log.Level = switch (zig_builtin.mode) {
.Debug => .debug, .Debug => .debug,
else => @intToEnum(std.log.Level, @enumToInt(build_options.log_level)), // temporary fix to build failing on release-safe due to a Zig bug else => @intToEnum(std.log.Level, @enumToInt(build_options.log_level)), // temporary fix to build failing on release-safe due to a Zig bug
}; };
pub fn log( pub const std_options = struct {
comptime level: std.log.Level, // Always set this to debug to make std.log call into our handler, then control the runtime
comptime scope: @TypeOf(.EnumLiteral), // value in the definition below.
comptime format: []const u8, pub const log_level = .debug;
args: anytype,
) void {
if (@enumToInt(level) > @enumToInt(actual_log_level)) return;
const level_txt = comptime level.asText(); pub fn logFn(
comptime level: std.log.Level,
comptime scope: @TypeOf(.EnumLiteral),
comptime format: []const u8,
args: anytype,
) void {
if (@enumToInt(level) > @enumToInt(actual_log_level)) return;
std.debug.print("{s:<5}: ({s:^6}): ", .{ level_txt, @tagName(scope) }); const level_txt = comptime level.asText();
std.debug.print(format ++ "\n", args);
} std.debug.print("{s:<5}: ({s:^6}): ", .{ level_txt, @tagName(scope) });
std.debug.print(format ++ "\n", args);
}
};
fn loop( fn loop(
server: *Server, server: *Server,
@ -48,9 +50,18 @@ fn loop(
var buffered_writer = std.io.bufferedWriter(std_out); var buffered_writer = std.io.bufferedWriter(std_out);
const writer = buffered_writer.writer(); const writer = buffered_writer.writer();
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
while (true) { while (true) {
var arena = std.heap.ArenaAllocator.init(server.allocator); defer {
defer arena.deinit(); // Mom, can we have garbage collection?
// No, we already have garbage collection at home.
// at home:
if (arena.queryCapacity() > 128 * 1024) {
_ = arena.reset(.free_all);
}
}
// write server -> client messages // write server -> client messages
for (server.outgoing_messages.items) |outgoing_message| { for (server.outgoing_messages.items) |outgoing_message| {
@ -76,6 +87,8 @@ fn loop(
} }
server.processJsonRpc(&arena, json_message); server.processJsonRpc(&arena, json_message);
if (server.status == .exiting_success or server.status == .exiting_failure) return;
} }
} }
@ -338,7 +351,7 @@ const stack_frames = switch (zig_builtin.mode) {
pub fn main() !void { pub fn main() !void {
var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){}; var gpa_state = std.heap.GeneralPurposeAllocator(.{ .stack_trace_frames = stack_frames }){};
defer _ = gpa_state.deinit(); defer std.debug.assert(!gpa_state.deinit());
var tracy_state = if (tracy.enable_allocation) tracy.tracyAllocator(gpa_state.allocator()) else void{}; var tracy_state = if (tracy.enable_allocation) tracy.tracyAllocator(gpa_state.allocator()) else void{};
const inner_allocator: std.mem.Allocator = if (tracy.enable_allocation) tracy_state.allocator() else gpa_state.allocator(); const inner_allocator: std.mem.Allocator = if (tracy.enable_allocation) tracy_state.allocator() else gpa_state.allocator();
@ -387,4 +400,8 @@ pub fn main() !void {
defer server.deinit(); defer server.deinit();
try loop(&server, record_file, replay_file); try loop(&server, record_file, replay_file);
if (server.status == .exiting_failure) {
std.process.exit(1);
}
} }

View File

@ -534,6 +534,7 @@ pub fn symbolReferences(
log.warn("Could not find param decl's function", .{}); log.warn("Could not find param decl's function", .{});
}, },
.label_decl => unreachable, // handled separately by labelReferences .label_decl => unreachable, // handled separately by labelReferences
.error_token => {},
} }
return builder.locations; return builder.locations;

View File

@ -268,19 +268,16 @@ test "completion - union" {
} }
test "completion - enum" { test "completion - enum" {
// TODO: Fix try testCompletion(
return error.SkipZigTest; \\const E = enum {
// try testCompletion( \\ alpha,
// \\const E = enum { \\ beta,
// \\ alpha, \\};
// \\ beta, \\const foo = E.<cursor>
// \\}; , &.{
// \\const foo = E.<cursor> .{ .label = "alpha", .kind = .Enum },
// , &.{ .{ .label = "beta", .kind = .Enum },
// // TODO kind should be Enum });
// .{ .label = "alpha", .kind = .Field },
// .{ .label = "beta", .kind = .Field },
// });
} }
test "completion - error union" { test "completion - error union" {
@ -291,21 +288,20 @@ test "completion - error union" {
\\}; \\};
\\const baz = error.<cursor> \\const baz = error.<cursor>
, &.{ , &.{
.{ .label = "Foo", .kind = .Constant }, .{ .label = "Foo", .kind = .Constant, .detail = "error.Foo" },
.{ .label = "Bar", .kind = .Constant }, .{ .label = "Bar", .kind = .Constant, .detail = "error.Bar" },
}); });
// TODO implement completion for error unions try testCompletion(
// try testCompletion( \\const E = error {
// \\const E = error { \\ foo,
// \\ foo, \\ bar,
// \\ bar, \\};
// \\}; \\const baz = E.<cursor>
// \\const baz = E.<cursor> , &.{
// , &.{ .{ .label = "foo", .kind = .Constant, .detail = "error.foo" },
// .{ .label = "foo", .kind = .Constant }, .{ .label = "bar", .kind = .Constant, .detail = "error.bar" },
// .{ .label = "bar", .kind = .Constant }, });
// });
try testCompletion( try testCompletion(
\\const S = struct { alpha: u32 }; \\const S = struct { alpha: u32 };

View File

@ -0,0 +1,76 @@
const std = @import("std");
const mem = std.mem;
const zls = @import("zls");
const builtin = @import("builtin");
const helper = @import("../helper.zig");
const Context = @import("../context.zig").Context;
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;
test "definition - smoke" {
try testDefinition(
\\fn main() void { f<>oo(); }
\\fn <def>foo</def>() void {}
);
}
test "definition - cursor is at the end of an identifier" {
try testDefinition(
\\fn main() void { foo<>(); }
\\fn <def>foo</def>() void {}
);
}
test "definition - cursor is at the start of an identifier" {
testDefinition(
\\fn main() void { <>foo(); }
\\fn <def>foo</def>() void {}
) catch |err| switch (err) {
error.UnresolvedDefinition => {
// TODO: #891
},
else => return err,
};
}
fn testDefinition(source: []const u8) !void {
var phr = try helper.collectClearPlaceholders(allocator, source);
defer phr.deinit(allocator);
var cursor: offsets.Loc = .{ .start = 0, .end = 0 };
var def_start: offsets.Loc = .{ .start = 0, .end = 0 };
var def_end: offsets.Loc = .{ .start = 0, .end = 0 };
for (phr.locations.items(.old)) |loc, i| {
if (mem.eql(u8, source[loc.start..loc.end], "<>")) cursor = phr.locations.items(.new)[i];
if (mem.eql(u8, source[loc.start..loc.end], "<def>")) def_start = phr.locations.items(.new)[i];
if (mem.eql(u8, source[loc.start..loc.end], "</def>")) def_end = phr.locations.items(.new)[i];
}
const cursor_lsp = offsets.locToRange(phr.new_source, cursor, .@"utf-16").start;
const def_range_lsp = offsets.locToRange(phr.new_source, .{ .start = def_start.end, .end = def_end.start }, .@"utf-16");
var ctx = try Context.init();
defer ctx.deinit();
const test_uri: []const u8 = switch (builtin.os.tag) {
.windows => "file:///C:\\test.zig",
else => "file:///test.zig",
};
try ctx.requestDidOpen(test_uri, phr.new_source);
const params = types.TextDocumentPositionParams{
.textDocument = .{ .uri = test_uri },
.position = cursor_lsp,
};
const response = try ctx.requestGetResponse(?types.Location, "textDocument/definition", params);
const result = response.result orelse return error.UnresolvedDefinition;
try std.testing.expectEqual(def_range_lsp, result.range);
}

View File

@ -11,6 +11,7 @@ comptime {
// LSP features // LSP features
_ = @import("lsp_features/completion.zig"); _ = @import("lsp_features/completion.zig");
_ = @import("lsp_features/definition.zig");
_ = @import("lsp_features/folding_range.zig"); _ = @import("lsp_features/folding_range.zig");
_ = @import("lsp_features/inlay_hints.zig"); _ = @import("lsp_features/inlay_hints.zig");
_ = @import("lsp_features/references.zig"); _ = @import("lsp_features/references.zig");