Use zig-lsp-codegen (#850)

* add lsp.zig

* change references from types.zig to lsp.zig

* remove types.zig and requests.zig

* add tres as a submodule

* transition codebase from types.zig to lsp.zig

* update lsp.zig

* completely overhaul message handler

* fix memory errors

* partially transition tests to lsp.zig

* update lsp.zig

* more test fixes

* disable failing tests

* fix message handling bugs

* fix remaining tests

* access correct union in diff.applyTextEdits

* more message handler fixes

* run zig fmt

* update tres submodule

* fix memory access to freed memory

* simplify initialize_msg for testing

* check if publishDiagnostics is supported
This commit is contained in:
Techatrix 2022-12-27 06:47:57 +00:00 committed by GitHub
parent 941882371c
commit 61c0981294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 9015 additions and 1881 deletions

3
.gitmodules vendored
View File

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

View File

@ -92,6 +92,8 @@ pub fn build(b: *std.build.Builder) !void {
const known_folders_path = b.option([]const u8, "known-folders", "Path to known-folders package (default: " ++ KNOWN_FOLDERS_DEFAULT_PATH ++ ")") orelse KNOWN_FOLDERS_DEFAULT_PATH;
exe.addPackage(.{ .name = "known-folders", .source = .{ .path = known_folders_path } });
exe.addPackage(.{ .name = "tres", .source = .{ .path = "src/tres/tres.zig" } });
if (enable_tracy) {
const client_cpp = "src/tracy/TracyClient.cpp";
@ -146,6 +148,7 @@ pub fn build(b: *std.build.Builder) !void {
}
tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items });
tests.addPackage(.{ .name = "tres", .source = .{ .path = "src/tres/tres.zig" } });
tests.setBuildMode(.Debug);
tests.setTarget(target);
test_step.dependOn(&tests.step);

View File

@ -1,7 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const types = @import("types.zig");
const requests = @import("requests.zig");
const types = @import("lsp.zig");
const URI = @import("uri.zig");
const analysis = @import("analysis.zig");
const offsets = @import("offsets.zig");

View File

@ -1,18 +1,19 @@
const std = @import("std");
const RequestHeader = struct {
const Header = @This();
content_length: usize,
/// null implies "application/vscode-jsonrpc; charset=utf-8"
content_type: ?[]const u8,
content_type: ?[]const u8 = null,
pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
if (self.content_type) |ct| allocator.free(ct);
}
};
pub fn readRequestHeader(allocator: std.mem.Allocator, instream: anytype) !RequestHeader {
var r = RequestHeader{
// Caller owns returned memory.
pub fn parse(allocator: std.mem.Allocator, reader: anytype) !Header {
var r = Header{
.content_length = undefined,
.content_type = null,
};
@ -20,7 +21,7 @@ pub fn readRequestHeader(allocator: std.mem.Allocator, instream: anytype) !Reque
var has_content_length = false;
while (true) {
const header = try instream.readUntilDelimiterAlloc(allocator, '\n', 0x100);
const header = try reader.readUntilDelimiterAlloc(allocator, '\n', 0x100);
defer allocator.free(header);
if (header.len == 0 or header[header.len - 1] != '\r') return error.MissingCarriageReturn;
if (header.len == 1) break;
@ -41,3 +42,18 @@ pub fn readRequestHeader(allocator: std.mem.Allocator, instream: anytype) !Reque
return r;
}
pub fn format(
header: Header,
comptime unused_fmt_string: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
_ = options;
std.debug.assert(unused_fmt_string.len == 0);
try writer.print("Content-Length: {}\r\n", .{header.content_length});
if (header.content_type) |content_type| {
try writer.print("Content-Type: {s}\r\n", .{content_type});
}
try writer.writeAll("\r\n");
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
const std = @import("std");
const DocumentStore = @import("DocumentStore.zig");
const Ast = std.zig.Ast;
const types = @import("types.zig");
const types = @import("lsp.zig");
const offsets = @import("offsets.zig");
const log = std.log.scoped(.analysis);
const ast = @import("ast.zig");
@ -19,7 +19,7 @@ pub fn deinit() void {
}
/// Gets a declaration's doc comments. Caller owns returned memory.
pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, format: types.MarkupContent.Kind) !?[]const u8 {
pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, format: types.MarkupKind) !?[]const u8 {
const base = tree.nodes.items(.main_token)[node];
const base_kind = tree.nodes.items(.tag)[node];
const tokens = tree.tokens.items(.tag);
@ -68,7 +68,7 @@ pub fn getDocCommentTokenIndex(tokens: []const std.zig.Token.Tag, base_token: As
} else idx + 1;
}
pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: Ast.TokenIndex, format: types.MarkupContent.Kind, container_doc: bool) ![]const u8 {
pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: Ast.TokenIndex, format: types.MarkupKind, container_doc: bool) ![]const u8 {
var lines = std.ArrayList([]const u8).init(allocator);
defer lines.deinit();
const tokens = tree.tokens.items(.tag);
@ -81,7 +81,7 @@ pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments:
} else break;
}
return try std.mem.join(allocator, if (format == .Markdown) " \n" else "\n", lines.items);
return try std.mem.join(allocator, if (format == .markdown) " \n" else "\n", lines.items);
}
/// Gets a function's keyword, name, arguments and return value.
@ -2417,11 +2417,17 @@ pub const DocumentScope = struct {
}
self.scopes.deinit(allocator);
for (self.error_completions.entries.items(.key)) |item| {
if (item.documentation) |doc| allocator.free(doc.value);
switch (item.documentation orelse continue) {
.string => |str| allocator.free(str),
.MarkupContent => |content| allocator.free(content.value),
}
}
self.error_completions.deinit(allocator);
for (self.enum_completions.entries.items(.key)) |item| {
if (item.documentation) |doc| allocator.free(doc.value);
switch (item.documentation orelse continue) {
.string => |str| allocator.free(str),
.MarkupContent => |content| allocator.free(content.value),
}
}
self.enum_completions.deinit(allocator);
}
@ -2556,13 +2562,18 @@ fn makeInnerScope(allocator: std.mem.Allocator, context: ScopeContext, node_idx:
if (container_field) |_| {
if (!std.mem.eql(u8, name, "_")) {
var doc = if (try getDocComments(allocator, tree, decl, .Markdown)) |docs|
types.MarkupContent{ .kind = .Markdown, .value = docs }
else
null;
var gop_res = try context.enums.getOrPut(allocator, .{ .label = name, .kind = .Constant, .insertText = name, .insertTextFormat = .PlainText, .documentation = doc });
const Documentation = @TypeOf(@as(types.CompletionItem, undefined).documentation);
var doc: Documentation = if (try getDocComments(allocator, tree, decl, .markdown)) |docs| .{ .MarkupContent = types.MarkupContent{ .kind = .markdown, .value = docs } } else null;
var gop_res = try context.enums.getOrPut(allocator, .{
.label = name,
.kind = .Constant,
.insertText = name,
.insertTextFormat = .PlainText,
.documentation = doc,
});
if (gop_res.found_existing) {
if (doc) |d| allocator.free(d.value);
if (doc) |d| allocator.free(d.MarkupContent.value);
}
}
}

View File

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

View File

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

View File

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

View File

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

7852
src/lsp.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ const Config = @import("Config.zig");
const configuration = @import("configuration.zig");
const Server = @import("Server.zig");
const setup = @import("setup.zig");
const readRequestHeader = @import("header.zig").readRequestHeader;
const Header = @import("Header.zig");
const logger = std.log.scoped(.main);
@ -35,20 +35,34 @@ pub fn log(
}
fn loop(server: *Server) !void {
var reader = std.io.getStdIn().reader();
const reader = std.io.getStdIn().reader();
const std_out = std.io.getStdOut().writer();
var buffered_writer = std.io.bufferedWriter(std_out);
const writer = buffered_writer.writer();
while (true) {
const headers = readRequestHeader(server.allocator, reader) catch |err| {
logger.err("{s}; exiting!", .{@errorName(err)});
return;
};
const buffer = try server.allocator.alloc(u8, headers.content_length);
defer server.allocator.free(buffer);
var arena = std.heap.ArenaAllocator.init(server.allocator);
defer arena.deinit();
try reader.readNoEof(buffer);
// write server -> client messages
for (server.outgoing_messages.items) |outgoing_message| {
const header = Header{ .content_length = outgoing_message.len };
try writer.print("{}{s}", .{ header, outgoing_message });
try buffered_writer.flush();
}
for (server.outgoing_messages.items) |outgoing_message| {
server.allocator.free(outgoing_message);
}
server.outgoing_messages.clearRetainingCapacity();
const writer = std.io.getStdOut().writer();
try server.processJsonRpc(writer, buffer);
// read and handle client -> server message
const header = try Header.parse(arena.allocator(), reader);
const json_message = try arena.allocator().alloc(u8, header.content_length);
try reader.readNoEof(json_message);
server.processJsonRpc(&arena, json_message);
}
}

View File

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

View File

@ -2,7 +2,7 @@ const std = @import("std");
const Ast = std.zig.Ast;
const DocumentStore = @import("DocumentStore.zig");
const analysis = @import("analysis.zig");
const types = @import("types.zig");
const types = @import("lsp.zig");
const offsets = @import("offsets.zig");
const log = std.log.scoped(.references);
const ast = @import("ast.zig");

View File

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

View File

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

1
src/tres Submodule

@ -0,0 +1 @@
Subproject commit 16774b94efa61757a5302a690837dfb8cf750a11

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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