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:
parent
941882371c
commit
61c0981294
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -4,3 +4,6 @@
|
||||
[submodule "src/tracy"]
|
||||
path = src/tracy
|
||||
url = https://github.com/wolfpld/tracy
|
||||
[submodule "src/tres"]
|
||||
path = src/tres
|
||||
url = https://github.com/ziglibs/tres.git
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -1,18 +1,19 @@
|
||||
const std = @import("std");
|
||||
|
||||
const RequestHeader = struct {
|
||||
content_length: usize,
|
||||
const Header = @This();
|
||||
|
||||
/// null implies "application/vscode-jsonrpc; charset=utf-8"
|
||||
content_type: ?[]const u8,
|
||||
content_length: usize,
|
||||
|
||||
pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
|
||||
if (self.content_type) |ct| allocator.free(ct);
|
||||
}
|
||||
};
|
||||
/// null implies "application/vscode-jsonrpc; charset=utf-8"
|
||||
content_type: ?[]const u8 = null,
|
||||
|
||||
pub fn readRequestHeader(allocator: std.mem.Allocator, instream: anytype) !RequestHeader {
|
||||
var r = RequestHeader{
|
||||
pub fn deinit(self: @This(), allocator: std.mem.Allocator) void {
|
||||
if (self.content_type) |ct| allocator.free(ct);
|
||||
}
|
||||
|
||||
// Caller owns returned memory.
|
||||
pub fn parse(allocator: std.mem.Allocator, 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");
|
||||
}
|
1675
src/Server.zig
1675
src/Server.zig
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
||||
const std = @import("std");
|
||||
const DocumentStore = @import("DocumentStore.zig");
|
||||
const Ast = std.zig.Ast;
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
const log = std.log.scoped(.analysis);
|
||||
const ast = @import("ast.zig");
|
||||
@ -19,7 +19,7 @@ pub fn deinit() void {
|
||||
}
|
||||
|
||||
/// Gets a declaration's doc comments. Caller owns returned memory.
|
||||
pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, format: types.MarkupContent.Kind) !?[]const u8 {
|
||||
pub fn getDocComments(allocator: std.mem.Allocator, tree: Ast, node: Ast.Node.Index, format: types.MarkupKind) !?[]const u8 {
|
||||
const base = tree.nodes.items(.main_token)[node];
|
||||
const base_kind = tree.nodes.items(.tag)[node];
|
||||
const tokens = tree.tokens.items(.tag);
|
||||
@ -68,7 +68,7 @@ pub fn getDocCommentTokenIndex(tokens: []const std.zig.Token.Tag, base_token: As
|
||||
} else idx + 1;
|
||||
}
|
||||
|
||||
pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: Ast.TokenIndex, format: types.MarkupContent.Kind, container_doc: bool) ![]const u8 {
|
||||
pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments: Ast.TokenIndex, format: types.MarkupKind, container_doc: bool) ![]const u8 {
|
||||
var lines = std.ArrayList([]const u8).init(allocator);
|
||||
defer lines.deinit();
|
||||
const tokens = tree.tokens.items(.tag);
|
||||
@ -81,7 +81,7 @@ pub fn collectDocComments(allocator: std.mem.Allocator, tree: Ast, doc_comments:
|
||||
} else break;
|
||||
}
|
||||
|
||||
return try std.mem.join(allocator, if (format == .Markdown) " \n" else "\n", lines.items);
|
||||
return try std.mem.join(allocator, if (format == .markdown) " \n" else "\n", lines.items);
|
||||
}
|
||||
|
||||
/// Gets a function's keyword, name, arguments and return value.
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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, ""),
|
||||
|
@ -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,
|
||||
};
|
||||
|
||||
|
13
src/diff.zig
13
src/diff.zig
@ -1,6 +1,5 @@
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
const requests = @import("requests.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
|
||||
pub const Error = error{ OutOfMemory, InvalidRange };
|
||||
@ -357,14 +356,14 @@ fn char_pos_to_range(
|
||||
pub fn applyTextEdits(
|
||||
allocator: std.mem.Allocator,
|
||||
text: []const u8,
|
||||
content_changes: []const requests.TextDocumentContentChangeEvent,
|
||||
content_changes: []const types.TextDocumentContentChangeEvent,
|
||||
encoding: offsets.Encoding,
|
||||
) ![:0]const u8 {
|
||||
var last_full_text_change: ?usize = null;
|
||||
var i: usize = content_changes.len;
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
if (content_changes[i].range == null) {
|
||||
if (content_changes[i] == .literal_1) {
|
||||
last_full_text_change = i;
|
||||
continue;
|
||||
}
|
||||
@ -373,16 +372,16 @@ pub fn applyTextEdits(
|
||||
var text_array = std.ArrayListUnmanaged(u8){};
|
||||
errdefer text_array.deinit(allocator);
|
||||
|
||||
try text_array.appendSlice(allocator, if (last_full_text_change) |index| content_changes[index].text else text);
|
||||
try text_array.appendSlice(allocator, if (last_full_text_change) |index| content_changes[index].literal_1.text else text);
|
||||
|
||||
// don't even bother applying changes before a full text change
|
||||
const changes = content_changes[if (last_full_text_change) |index| index + 1 else 0..];
|
||||
|
||||
for (changes) |item| {
|
||||
const range = item.range.?; // every element is guaranteed to have `range` set
|
||||
const range = item.literal_0.range;
|
||||
|
||||
const loc = offsets.rangeToLoc(text_array.items, range, encoding);
|
||||
try text_array.replaceRange(allocator, loc.start, loc.end - loc.start, item.text);
|
||||
try text_array.replaceRange(allocator, loc.start, loc.end - loc.start, item.literal_0.text);
|
||||
}
|
||||
|
||||
return try text_array.toOwnedSliceSentinel(allocator, 0);
|
||||
|
@ -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
7852
src/lsp.zig
Normal file
File diff suppressed because it is too large
Load Diff
36
src/main.zig
36
src/main.zig
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const ast = @import("ast.zig");
|
||||
const Ast = std.zig.Ast;
|
||||
|
||||
@ -263,8 +263,8 @@ pub fn advancePosition(text: []const u8, position: types.Position, from_index: u
|
||||
/// returns the number of code units in `text`
|
||||
pub fn countCodeUnits(text: []const u8, encoding: Encoding) usize {
|
||||
switch (encoding) {
|
||||
.utf8 => return text.len,
|
||||
.utf16 => {
|
||||
.@"utf-8" => return text.len,
|
||||
.@"utf-16" => {
|
||||
var iter: std.unicode.Utf8Iterator = .{ .bytes = text, .i = 0 };
|
||||
|
||||
var utf16_len: usize = 0;
|
||||
@ -277,15 +277,15 @@ pub fn countCodeUnits(text: []const u8, encoding: Encoding) usize {
|
||||
}
|
||||
return utf16_len;
|
||||
},
|
||||
.utf32 => return std.unicode.utf8CountCodepoints(text) catch unreachable,
|
||||
.@"utf-32" => return std.unicode.utf8CountCodepoints(text) catch unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns the number of (utf-8 code units / bytes) that represent `n` code units in `text`
|
||||
pub fn getNCodeUnitByteCount(text: []const u8, n: usize, encoding: Encoding) usize {
|
||||
switch (encoding) {
|
||||
.utf8 => return n,
|
||||
.utf16 => {
|
||||
.@"utf-8" => return n,
|
||||
.@"utf-16" => {
|
||||
if (n == 0) return 0;
|
||||
var iter: std.unicode.Utf8Iterator = .{ .bytes = text, .i = 0 };
|
||||
|
||||
@ -300,7 +300,7 @@ pub fn getNCodeUnitByteCount(text: []const u8, n: usize, encoding: Encoding) usi
|
||||
}
|
||||
return iter.i;
|
||||
},
|
||||
.utf32 => {
|
||||
.@"utf-32" => {
|
||||
var i: usize = 0;
|
||||
var count: usize = 0;
|
||||
while (count != n) : (count += 1) {
|
||||
|
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const Ast = std.zig.Ast;
|
||||
const DocumentStore = @import("DocumentStore.zig");
|
||||
const analysis = @import("analysis.zig");
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
const log = std.log.scoped(.references);
|
||||
const ast = @import("ast.zig");
|
||||
|
324
src/requests.zig
324
src/requests.zig
@ -1,324 +0,0 @@
|
||||
//! This file contains request types zls handles.
|
||||
//! Note that the parameter types may be incomplete.
|
||||
//! We only define what we actually use.
|
||||
|
||||
const std = @import("std");
|
||||
const types = @import("types.zig");
|
||||
|
||||
/// Only check for the field's existence.
|
||||
const Exists = struct {
|
||||
exists: bool,
|
||||
};
|
||||
|
||||
fn Default(comptime T: type, comptime default_value: T) type {
|
||||
return struct {
|
||||
pub const value_type = T;
|
||||
pub const default = default_value;
|
||||
value: T,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ErrorUnwrappedReturnOf(comptime func: anytype) type {
|
||||
return switch (@typeInfo(@TypeOf(func))) {
|
||||
.Fn, .BoundFn => |fn_info| switch (@typeInfo(fn_info.return_type.?)) {
|
||||
.ErrorUnion => |err_union| err_union.payload,
|
||||
else => |T| return T,
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn Transform(comptime Original: type, comptime transform_fn: anytype) type {
|
||||
return struct {
|
||||
pub const original_type = Original;
|
||||
pub const transform = transform_fn;
|
||||
|
||||
value: ErrorUnwrappedReturnOf(transform_fn),
|
||||
};
|
||||
}
|
||||
|
||||
fn fromDynamicTreeInternal(arena: *std.heap.ArenaAllocator, value: std.json.Value, out: anytype) error{ MalformedJson, OutOfMemory }!void {
|
||||
const T = comptime std.meta.Child(@TypeOf(out));
|
||||
|
||||
if (comptime std.meta.trait.is(.Struct)(T)) {
|
||||
if (value != .Object) return error.MalformedJson;
|
||||
|
||||
var err = false;
|
||||
inline for (std.meta.fields(T)) |field| {
|
||||
const is_exists = field.type == Exists;
|
||||
|
||||
const is_optional = comptime std.meta.trait.is(.Optional)(field.type);
|
||||
const actual_type = if (is_optional) std.meta.Child(field.type) else field.type;
|
||||
|
||||
const is_struct = comptime std.meta.trait.is(.Struct)(actual_type);
|
||||
const is_default = comptime if (is_struct) std.meta.trait.hasDecls(actual_type, .{ "default", "value_type" }) else false;
|
||||
const is_transform = comptime if (is_struct) std.meta.trait.hasDecls(actual_type, .{ "original_type", "transform" }) else false;
|
||||
|
||||
if (value.Object.get(field.name)) |json_field| {
|
||||
if (is_exists) {
|
||||
@field(out, field.name) = Exists{ .exists = true };
|
||||
} else if (is_transform) {
|
||||
var original_value: actual_type.original_type = undefined;
|
||||
try fromDynamicTreeInternal(arena, json_field, &original_value);
|
||||
@field(out, field.name) = actual_type{
|
||||
.value = actual_type.transform(original_value) catch
|
||||
return error.MalformedJson,
|
||||
};
|
||||
} else if (is_default) {
|
||||
try fromDynamicTreeInternal(arena, json_field, &@field(out, field.name).value);
|
||||
} else if (is_optional) {
|
||||
if (json_field == .Null) {
|
||||
@field(out, field.name) = null;
|
||||
} else {
|
||||
var actual_value: actual_type = undefined;
|
||||
try fromDynamicTreeInternal(arena, json_field, &actual_value);
|
||||
@field(out, field.name) = actual_value;
|
||||
}
|
||||
} else {
|
||||
try fromDynamicTreeInternal(arena, json_field, &@field(out, field.name));
|
||||
}
|
||||
} else {
|
||||
if (is_exists) {
|
||||
@field(out, field.name) = Exists{ .exists = false };
|
||||
} else if (is_optional) {
|
||||
@field(out, field.name) = null;
|
||||
} else if (is_default) {
|
||||
@field(out, field.name) = actual_type{ .value = actual_type.default };
|
||||
} else {
|
||||
err = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (err) return error.MalformedJson;
|
||||
} else if (comptime (std.meta.trait.isSlice(T) and T != []const u8)) {
|
||||
if (value != .Array) return error.MalformedJson;
|
||||
const Child = std.meta.Child(T);
|
||||
|
||||
if (value.Array.items.len == 0) {
|
||||
out.* = &[0]Child{};
|
||||
} else {
|
||||
var slice = try arena.allocator().alloc(Child, value.Array.items.len);
|
||||
for (value.Array.items) |arr_item, idx| {
|
||||
try fromDynamicTreeInternal(arena, arr_item, &slice[idx]);
|
||||
}
|
||||
out.* = slice;
|
||||
}
|
||||
} else if (T == std.json.Value) {
|
||||
out.* = value;
|
||||
} else if (comptime std.meta.trait.is(.Enum)(T)) {
|
||||
const info = @typeInfo(T).Enum;
|
||||
const TagType = info.tag_type;
|
||||
if (value != .Integer) return error.MalformedJson;
|
||||
out.* = std.meta.intToEnum(
|
||||
T,
|
||||
std.math.cast(TagType, value.Integer) orelse return error.MalformedJson,
|
||||
) catch return error.MalformedJson;
|
||||
} else if (comptime std.meta.trait.is(.Int)(T)) {
|
||||
if (value != .Integer) return error.MalformedJson;
|
||||
out.* = std.math.cast(T, value.Integer) orelse return error.MalformedJson;
|
||||
} else switch (T) {
|
||||
bool => {
|
||||
if (value != .Bool) return error.MalformedJson;
|
||||
out.* = value.Bool;
|
||||
},
|
||||
f64 => {
|
||||
if (value != .Float) return error.MalformedJson;
|
||||
out.* = value.Float;
|
||||
},
|
||||
[]const u8 => {
|
||||
if (value != .String) return error.MalformedJson;
|
||||
out.* = value.String;
|
||||
},
|
||||
else => @compileError("Invalid type " ++ @typeName(T)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fromDynamicTree(arena: *std.heap.ArenaAllocator, comptime T: type, value: std.json.Value) error{ MalformedJson, OutOfMemory }!T {
|
||||
var out: T = undefined;
|
||||
try fromDynamicTreeInternal(arena, value, &out);
|
||||
return out;
|
||||
}
|
||||
|
||||
const MaybeStringArray = Default([]const []const u8, &.{});
|
||||
|
||||
pub const Initialize = struct {
|
||||
pub const ClientCapabilities = struct {
|
||||
workspace: ?struct {
|
||||
configuration: Default(bool, false),
|
||||
didChangeConfiguration: ?struct {
|
||||
dynamicRegistration: Default(bool, false), // NOTE: Should this be true? Seems like this critical feature should be nearly universal
|
||||
},
|
||||
workspaceFolders: Default(bool, false),
|
||||
},
|
||||
textDocument: ?struct {
|
||||
synchronization: ?struct {
|
||||
willSave: Default(bool, false),
|
||||
willSaveWaitUntil: Default(bool, false),
|
||||
didSave: Default(bool, false),
|
||||
},
|
||||
semanticTokens: Exists,
|
||||
inlayHint: Exists,
|
||||
hover: ?struct {
|
||||
contentFormat: MaybeStringArray,
|
||||
},
|
||||
completion: ?struct {
|
||||
completionItem: ?struct {
|
||||
snippetSupport: Default(bool, false),
|
||||
labelDetailsSupport: Default(bool, false),
|
||||
documentationFormat: MaybeStringArray,
|
||||
},
|
||||
},
|
||||
documentHighlight: Exists,
|
||||
},
|
||||
general: ?struct {
|
||||
positionEncodings: MaybeStringArray,
|
||||
},
|
||||
};
|
||||
|
||||
pub const ClientInfo = struct {
|
||||
name: []const u8,
|
||||
version: ?[]const u8,
|
||||
};
|
||||
|
||||
params: struct {
|
||||
clientInfo: ?ClientInfo,
|
||||
capabilities: ClientCapabilities,
|
||||
workspaceFolders: ?[]const types.WorkspaceFolder,
|
||||
},
|
||||
};
|
||||
|
||||
pub const WorkspaceFoldersChange = struct {
|
||||
params: struct {
|
||||
event: struct {
|
||||
added: []const types.WorkspaceFolder,
|
||||
removed: []const types.WorkspaceFolder,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub const OpenDocument = struct {
|
||||
params: struct {
|
||||
textDocument: struct {
|
||||
uri: []const u8,
|
||||
text: []const u8,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const TextDocumentIdentifier = struct {
|
||||
uri: []const u8,
|
||||
};
|
||||
|
||||
pub const ChangeDocument = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
contentChanges: []TextDocumentContentChangeEvent,
|
||||
},
|
||||
};
|
||||
|
||||
pub const TextDocumentContentChangeEvent = struct {
|
||||
range: ?types.Range,
|
||||
text: []const u8,
|
||||
};
|
||||
|
||||
const TextDocumentIdentifierRequest = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
},
|
||||
};
|
||||
|
||||
pub const SaveDocument = TextDocumentIdentifierRequest;
|
||||
pub const CloseDocument = TextDocumentIdentifierRequest;
|
||||
pub const SemanticTokensFull = TextDocumentIdentifierRequest;
|
||||
|
||||
const TextDocumentIdentifierPositionRequest = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
position: types.Position,
|
||||
},
|
||||
};
|
||||
|
||||
pub const SaveReason = enum(u32) {
|
||||
Manual = 1,
|
||||
AfterDelay = 2,
|
||||
FocusOut = 3,
|
||||
};
|
||||
|
||||
pub const WillSave = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
reason: SaveReason,
|
||||
},
|
||||
};
|
||||
|
||||
pub const SignatureHelp = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
position: types.Position,
|
||||
context: ?struct {
|
||||
triggerKind: enum(u32) {
|
||||
invoked = 1,
|
||||
trigger_character = 2,
|
||||
content_change = 3,
|
||||
},
|
||||
triggerCharacter: ?[]const u8,
|
||||
isRetrigger: bool,
|
||||
activeSignatureHelp: ?types.SignatureHelp,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub const Completion = TextDocumentIdentifierPositionRequest;
|
||||
pub const GotoDefinition = TextDocumentIdentifierPositionRequest;
|
||||
pub const GotoDeclaration = TextDocumentIdentifierPositionRequest;
|
||||
pub const Hover = TextDocumentIdentifierPositionRequest;
|
||||
pub const DocumentSymbols = TextDocumentIdentifierRequest;
|
||||
pub const Formatting = TextDocumentIdentifierRequest;
|
||||
pub const DocumentHighlight = TextDocumentIdentifierPositionRequest;
|
||||
pub const Rename = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
position: types.Position,
|
||||
newName: []const u8,
|
||||
},
|
||||
};
|
||||
|
||||
pub const References = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
position: types.Position,
|
||||
context: struct {
|
||||
includeDeclaration: bool,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub const InlayHint = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
range: types.Range,
|
||||
},
|
||||
};
|
||||
|
||||
pub const CodeAction = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
range: types.Range,
|
||||
context: struct {
|
||||
diagnostics: []types.Diagnostic,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub const FoldingRange = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
},
|
||||
};
|
||||
|
||||
pub const SelectionRange = struct {
|
||||
params: struct {
|
||||
textDocument: TextDocumentIdentifier,
|
||||
positions: []types.Position,
|
||||
},
|
||||
};
|
@ -2,31 +2,29 @@ const std = @import("std");
|
||||
const analysis = @import("analysis.zig");
|
||||
const offsets = @import("offsets.zig");
|
||||
const DocumentStore = @import("DocumentStore.zig");
|
||||
const types = @import("types.zig");
|
||||
const types = @import("lsp.zig");
|
||||
const Ast = std.zig.Ast;
|
||||
const Token = std.zig.Token;
|
||||
const identifierFromPosition = @import("Server.zig").identifierFromPosition;
|
||||
const ast = @import("ast.zig");
|
||||
|
||||
fn fnProtoToSignatureInfo(document_store: *DocumentStore, arena: *std.heap.ArenaAllocator, commas: u32, skip_self_param: bool, handle: *const DocumentStore.Handle, fn_node: Ast.Node.Index, proto: Ast.full.FnProto) !types.SignatureInformation {
|
||||
const ParameterInformation = types.SignatureInformation.ParameterInformation;
|
||||
|
||||
const tree = handle.tree;
|
||||
const token_starts = tree.tokens.items(.start);
|
||||
const alloc = arena.allocator();
|
||||
const label = analysis.getFunctionSignature(tree, proto);
|
||||
const proto_comments = (try analysis.getDocComments(alloc, tree, fn_node, .Markdown)) orelse "";
|
||||
const proto_comments = (try analysis.getDocComments(alloc, tree, fn_node, .markdown)) orelse "";
|
||||
|
||||
const arg_idx = if (skip_self_param) blk: {
|
||||
const has_self_param = try analysis.hasSelfParam(arena, document_store, handle, proto);
|
||||
break :blk commas + @boolToInt(has_self_param);
|
||||
} else commas;
|
||||
|
||||
var params = std.ArrayListUnmanaged(ParameterInformation){};
|
||||
var params = std.ArrayListUnmanaged(types.ParameterInformation){};
|
||||
var param_it = proto.iterate(&tree);
|
||||
while (ast.nextFnParam(¶m_it)) |param| {
|
||||
const param_comments = if (param.first_doc_comment) |dc|
|
||||
try analysis.collectDocComments(alloc, tree, dc, .Markdown, false)
|
||||
try analysis.collectDocComments(alloc, tree, dc, .markdown, false)
|
||||
else
|
||||
"";
|
||||
|
||||
@ -55,13 +53,19 @@ fn fnProtoToSignatureInfo(document_store: *DocumentStore, arena: *std.heap.Arena
|
||||
}
|
||||
const param_label = tree.source[param_label_start..param_label_end];
|
||||
try params.append(alloc, .{
|
||||
.label = param_label,
|
||||
.documentation = types.MarkupContent{ .value = param_comments },
|
||||
.label = .{ .string = param_label },
|
||||
.documentation = .{ .MarkupContent = .{
|
||||
.kind = .markdown,
|
||||
.value = param_comments,
|
||||
} },
|
||||
});
|
||||
}
|
||||
return types.SignatureInformation{
|
||||
.label = label,
|
||||
.documentation = types.MarkupContent{ .value = proto_comments },
|
||||
.documentation = .{ .MarkupContent = .{
|
||||
.kind = .markdown,
|
||||
.value = proto_comments,
|
||||
} },
|
||||
.parameters = params.items,
|
||||
.activeParameter = arg_idx,
|
||||
};
|
||||
@ -188,20 +192,18 @@ pub fn getSignatureInfo(document_store: *DocumentStore, arena: *std.heap.ArenaAl
|
||||
for (data.builtins) |builtin| {
|
||||
if (std.mem.eql(u8, builtin.name, tree.tokenSlice(expr_last_token))) {
|
||||
const param_infos = try alloc.alloc(
|
||||
types.SignatureInformation.ParameterInformation,
|
||||
types.ParameterInformation,
|
||||
builtin.arguments.len,
|
||||
);
|
||||
for (param_infos) |*info, i| {
|
||||
info.* = .{
|
||||
.label = builtin.arguments[i],
|
||||
.label = .{ .string = builtin.arguments[i] },
|
||||
.documentation = null,
|
||||
};
|
||||
}
|
||||
return types.SignatureInformation{
|
||||
.label = builtin.signature,
|
||||
.documentation = .{
|
||||
.value = builtin.documentation,
|
||||
},
|
||||
.documentation = .{ .string = builtin.documentation },
|
||||
.parameters = param_infos,
|
||||
.activeParameter = paren_commas,
|
||||
};
|
||||
|
1
src/tres
Submodule
1
src/tres
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 16774b94efa61757a5302a690837dfb8cf750a11
|
536
src/types.zig
536
src/types.zig
@ -1,536 +0,0 @@
|
||||
const std = @import("std");
|
||||
const string = []const u8;
|
||||
|
||||
// LSP types
|
||||
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/
|
||||
|
||||
pub const Position = struct {
|
||||
line: u32,
|
||||
character: u32,
|
||||
};
|
||||
|
||||
pub const Range = struct {
|
||||
start: Position,
|
||||
end: Position,
|
||||
};
|
||||
|
||||
pub const Location = struct {
|
||||
uri: string,
|
||||
range: Range,
|
||||
};
|
||||
|
||||
/// Id of a request
|
||||
pub const RequestId = union(enum) {
|
||||
String: string,
|
||||
Integer: i32,
|
||||
};
|
||||
|
||||
/// Params of a response (result)
|
||||
pub const ResponseParams = union(enum) {
|
||||
SignatureHelp: SignatureHelp,
|
||||
CompletionList: CompletionList,
|
||||
Location: Location,
|
||||
Hover: Hover,
|
||||
DocumentSymbols: []DocumentSymbol,
|
||||
SemanticTokensFull: SemanticTokens,
|
||||
InlayHint: []InlayHint,
|
||||
TextEdits: []TextEdit,
|
||||
Locations: []Location,
|
||||
WorkspaceEdit: WorkspaceEdit,
|
||||
InitializeResult: InitializeResult,
|
||||
ConfigurationParams: ConfigurationParams,
|
||||
RegistrationParams: RegistrationParams,
|
||||
DocumentHighlight: []DocumentHighlight,
|
||||
CodeAction: []CodeAction,
|
||||
ApplyEdit: ApplyWorkspaceEditParams,
|
||||
FoldingRange: []FoldingRange,
|
||||
SelectionRange: []*SelectionRange,
|
||||
};
|
||||
|
||||
pub const Response = struct {
|
||||
jsonrpc: string = "2.0",
|
||||
id: RequestId,
|
||||
result: ResponseParams,
|
||||
};
|
||||
|
||||
pub const Request = struct {
|
||||
jsonrpc: string = "2.0",
|
||||
id: RequestId,
|
||||
method: []const u8,
|
||||
params: ?ResponseParams,
|
||||
};
|
||||
|
||||
pub const ResponseError = struct {
|
||||
code: i32,
|
||||
message: string,
|
||||
data: std.json.Value,
|
||||
};
|
||||
|
||||
pub const ErrorCodes = enum(i32) {
|
||||
// Defined by JSON-RPC
|
||||
ParseError = -32700,
|
||||
InvalidRequest = -32600,
|
||||
MethodNotFound = -32601,
|
||||
InvalidParams = -32602,
|
||||
InternalError = -32603,
|
||||
|
||||
// JSON-RPC reserved error codes
|
||||
ServerNotInitialized = -32002,
|
||||
UnknownErrorCode = -3200,
|
||||
|
||||
// LSP reserved error codes
|
||||
RequestFailed = -32803,
|
||||
ServerCancelled = -32802,
|
||||
ContentModified = -32801,
|
||||
RequestCancelled = -32800,
|
||||
};
|
||||
|
||||
pub const Notification = struct {
|
||||
jsonrpc: string = "2.0",
|
||||
method: string,
|
||||
params: NotificationParams,
|
||||
};
|
||||
|
||||
pub const NotificationParams = union(enum) {
|
||||
LogMessage: struct {
|
||||
type: MessageType,
|
||||
message: string,
|
||||
},
|
||||
PublishDiagnostics: struct {
|
||||
uri: string,
|
||||
diagnostics: []Diagnostic,
|
||||
},
|
||||
ShowMessage: struct {
|
||||
type: MessageType,
|
||||
message: string,
|
||||
},
|
||||
};
|
||||
|
||||
/// Type of a debug message
|
||||
pub const MessageType = enum(i64) {
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Info = 3,
|
||||
Log = 4,
|
||||
|
||||
pub fn jsonStringify(value: MessageType, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
pub const DiagnosticSeverity = enum(i64) {
|
||||
Error = 1,
|
||||
Warning = 2,
|
||||
Information = 3,
|
||||
Hint = 4,
|
||||
|
||||
pub fn jsonStringify(value: DiagnosticSeverity, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
pub const DiagnosticRelatedInformation = struct {
|
||||
location: Location,
|
||||
message: string,
|
||||
};
|
||||
|
||||
pub const Diagnostic = struct {
|
||||
range: Range,
|
||||
severity: ?DiagnosticSeverity,
|
||||
code: ?string,
|
||||
source: ?string,
|
||||
message: string,
|
||||
relatedInformation: ?[]DiagnosticRelatedInformation = null,
|
||||
};
|
||||
|
||||
pub const WorkspaceEdit = struct {
|
||||
changes: std.StringHashMapUnmanaged(std.ArrayListUnmanaged(TextEdit)),
|
||||
|
||||
pub fn jsonStringify(self: WorkspaceEdit, options: std.json.StringifyOptions, writer: anytype) @TypeOf(writer).Error!void {
|
||||
try writer.writeAll("{\"changes\": {");
|
||||
var it = self.changes.iterator();
|
||||
var idx: usize = 0;
|
||||
while (it.next()) |entry| : (idx += 1) {
|
||||
if (idx != 0) try writer.writeAll(", ");
|
||||
|
||||
try writer.writeByte('"');
|
||||
try writer.writeAll(entry.key_ptr.*);
|
||||
try writer.writeAll("\":");
|
||||
try std.json.stringify(entry.value_ptr.items, options, writer);
|
||||
}
|
||||
try writer.writeAll("}}");
|
||||
}
|
||||
};
|
||||
|
||||
pub const TextEdit = struct {
|
||||
range: Range,
|
||||
newText: string,
|
||||
};
|
||||
|
||||
pub const MarkupContent = struct {
|
||||
pub const Kind = enum(u1) {
|
||||
PlainText = 0,
|
||||
Markdown = 1,
|
||||
|
||||
pub fn jsonStringify(value: Kind, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
const str = switch (value) {
|
||||
.PlainText => "plaintext",
|
||||
.Markdown => "markdown",
|
||||
};
|
||||
try std.json.stringify(str, options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
kind: Kind = .Markdown,
|
||||
value: string,
|
||||
};
|
||||
|
||||
pub const CompletionList = struct {
|
||||
isIncomplete: bool,
|
||||
items: []const CompletionItem,
|
||||
};
|
||||
|
||||
pub const InsertTextFormat = enum(i64) {
|
||||
PlainText = 1,
|
||||
Snippet = 2,
|
||||
|
||||
pub fn jsonStringify(value: InsertTextFormat, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Hover = struct {
|
||||
contents: MarkupContent,
|
||||
};
|
||||
|
||||
pub const SemanticTokens = struct {
|
||||
data: []const u32,
|
||||
};
|
||||
|
||||
pub const CompletionItem = struct {
|
||||
pub const Kind = enum(i64) {
|
||||
Text = 1,
|
||||
Method = 2,
|
||||
Function = 3,
|
||||
Constructor = 4,
|
||||
Field = 5,
|
||||
Variable = 6,
|
||||
Class = 7,
|
||||
Interface = 8,
|
||||
Module = 9,
|
||||
Property = 10,
|
||||
Unit = 11,
|
||||
Value = 12,
|
||||
Enum = 13,
|
||||
Keyword = 14,
|
||||
Snippet = 15,
|
||||
Color = 16,
|
||||
File = 17,
|
||||
Reference = 18,
|
||||
Folder = 19,
|
||||
EnumMember = 20,
|
||||
Constant = 21,
|
||||
Struct = 22,
|
||||
Event = 23,
|
||||
Operator = 24,
|
||||
TypeParameter = 25,
|
||||
|
||||
pub fn jsonStringify(value: Kind, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
label: string,
|
||||
labelDetails: ?CompletionItemLabelDetails = null,
|
||||
kind: Kind,
|
||||
detail: ?string = null,
|
||||
|
||||
sortText: ?string = null,
|
||||
filterText: ?string = null,
|
||||
insertText: ?string = null,
|
||||
|
||||
insertTextFormat: ?InsertTextFormat = .PlainText,
|
||||
documentation: ?MarkupContent = null,
|
||||
|
||||
// FIXME: i commented this out, because otherwise the vscode client complains about *ranges*
|
||||
// and breaks code completion entirely
|
||||
// see: https://github.com/zigtools/zls-vscode/pull/33
|
||||
// textEdit: ?TextEdit = null,
|
||||
};
|
||||
|
||||
pub const CompletionItemLabelDetails = struct {
|
||||
detail: ?string,
|
||||
description: ?string,
|
||||
sortText: ?string = null,
|
||||
};
|
||||
|
||||
pub const DocumentSymbol = struct {
|
||||
const Kind = enum(u32) {
|
||||
File = 1,
|
||||
Module = 2,
|
||||
Namespace = 3,
|
||||
Package = 4,
|
||||
Class = 5,
|
||||
Method = 6,
|
||||
Property = 7,
|
||||
Field = 8,
|
||||
Constructor = 9,
|
||||
Enum = 10,
|
||||
Interface = 11,
|
||||
Function = 12,
|
||||
Variable = 13,
|
||||
Constant = 14,
|
||||
String = 15,
|
||||
Number = 16,
|
||||
Boolean = 17,
|
||||
Array = 18,
|
||||
Object = 19,
|
||||
Key = 20,
|
||||
Null = 21,
|
||||
EnumMember = 22,
|
||||
Struct = 23,
|
||||
Event = 24,
|
||||
Operator = 25,
|
||||
TypeParameter = 26,
|
||||
|
||||
pub fn jsonStringify(value: Kind, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
name: string,
|
||||
detail: ?string = null,
|
||||
kind: Kind,
|
||||
deprecated: bool = false,
|
||||
range: Range,
|
||||
selectionRange: Range,
|
||||
children: []const DocumentSymbol = &[_]DocumentSymbol{},
|
||||
};
|
||||
|
||||
pub const WorkspaceFolder = struct {
|
||||
uri: string,
|
||||
name: string,
|
||||
};
|
||||
|
||||
pub const SignatureInformation = struct {
|
||||
pub const ParameterInformation = struct {
|
||||
// TODO Can also send a pair of encoded offsets
|
||||
label: string,
|
||||
documentation: ?MarkupContent,
|
||||
};
|
||||
|
||||
label: string,
|
||||
documentation: ?MarkupContent,
|
||||
parameters: ?[]const ParameterInformation,
|
||||
activeParameter: ?u32,
|
||||
};
|
||||
|
||||
pub const SignatureHelp = struct {
|
||||
signatures: ?[]const SignatureInformation,
|
||||
activeSignature: ?u32,
|
||||
activeParameter: ?u32,
|
||||
};
|
||||
|
||||
pub const InlayHint = struct {
|
||||
position: Position,
|
||||
label: string,
|
||||
kind: InlayHintKind,
|
||||
tooltip: MarkupContent,
|
||||
paddingLeft: bool,
|
||||
paddingRight: bool,
|
||||
|
||||
// appends a colon to the label and reduces the output size
|
||||
pub fn jsonStringify(value: InlayHint, options: std.json.StringifyOptions, writer: anytype) @TypeOf(writer).Error!void {
|
||||
try writer.writeAll("{\"position\":");
|
||||
try std.json.stringify(value.position, options, writer);
|
||||
try writer.writeAll(",\"label\":\"");
|
||||
try writer.writeAll(value.label);
|
||||
try writer.writeAll(":\",\"kind\":");
|
||||
try std.json.stringify(value.kind, options, writer);
|
||||
if (value.tooltip.value.len != 0) {
|
||||
try writer.writeAll(",\"tooltip\":");
|
||||
try std.json.stringify(value.tooltip, options, writer);
|
||||
}
|
||||
if (value.paddingLeft) try writer.writeAll(",\"paddingLeft\":true");
|
||||
if (value.paddingRight) try writer.writeAll(",\"paddingRight\":true");
|
||||
try writer.writeByte('}');
|
||||
}
|
||||
};
|
||||
|
||||
pub const InlayHintKind = enum(i64) {
|
||||
Type = 1,
|
||||
Parameter = 2,
|
||||
|
||||
pub fn jsonStringify(value: InlayHintKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
pub const CodeActionKind = enum {
|
||||
Empty,
|
||||
QuickFix,
|
||||
Refactor,
|
||||
RefactorExtract,
|
||||
RefactorInline,
|
||||
RefactorRewrite,
|
||||
Source,
|
||||
SourceOrganizeImports,
|
||||
SourceFixAll,
|
||||
|
||||
pub fn jsonStringify(value: CodeActionKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
const name = switch (value) {
|
||||
.Empty => "",
|
||||
.QuickFix => "quickfix",
|
||||
.Refactor => "refactor",
|
||||
.RefactorExtract => "refactor.extract",
|
||||
.RefactorInline => "refactor.inline",
|
||||
.RefactorRewrite => "refactor.rewrite",
|
||||
.Source => "source",
|
||||
.SourceOrganizeImports => "source.organizeImports",
|
||||
.SourceFixAll => "source.fixAll",
|
||||
};
|
||||
try std.json.stringify(name, options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
pub const CodeAction = struct {
|
||||
title: string,
|
||||
kind: CodeActionKind,
|
||||
// diagnostics: []Diagnostic,
|
||||
isPreferred: bool,
|
||||
edit: WorkspaceEdit,
|
||||
};
|
||||
|
||||
pub const ApplyWorkspaceEditParams = struct {
|
||||
label: string,
|
||||
edit: WorkspaceEdit,
|
||||
};
|
||||
|
||||
pub const PositionEncodingKind = enum {
|
||||
utf8,
|
||||
utf16,
|
||||
utf32,
|
||||
|
||||
pub fn jsonStringify(value: PositionEncodingKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
const str = switch (value) {
|
||||
.utf8 => "utf-8",
|
||||
.utf16 => "utf-16",
|
||||
.utf32 => "utf-32",
|
||||
};
|
||||
try std.json.stringify(str, options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
const TextDocumentSyncKind = enum(u32) {
|
||||
None = 0,
|
||||
Full = 1,
|
||||
Incremental = 2,
|
||||
|
||||
pub fn jsonStringify(value: @This(), options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
// Only includes options we set in our initialize result.
|
||||
const InitializeResult = struct {
|
||||
capabilities: struct {
|
||||
positionEncoding: PositionEncodingKind,
|
||||
signatureHelpProvider: struct {
|
||||
triggerCharacters: []const string,
|
||||
retriggerCharacters: []const string,
|
||||
},
|
||||
textDocumentSync: struct {
|
||||
openClose: bool,
|
||||
change: TextDocumentSyncKind,
|
||||
willSave: bool,
|
||||
willSaveWaitUntil: bool,
|
||||
save: bool,
|
||||
},
|
||||
renameProvider: bool,
|
||||
completionProvider: struct {
|
||||
resolveProvider: bool,
|
||||
triggerCharacters: []const string,
|
||||
completionItem: struct { labelDetailsSupport: bool },
|
||||
},
|
||||
documentHighlightProvider: bool,
|
||||
hoverProvider: bool,
|
||||
codeActionProvider: bool,
|
||||
declarationProvider: bool,
|
||||
definitionProvider: bool,
|
||||
typeDefinitionProvider: bool,
|
||||
implementationProvider: bool,
|
||||
referencesProvider: bool,
|
||||
documentSymbolProvider: bool,
|
||||
colorProvider: bool,
|
||||
documentFormattingProvider: bool,
|
||||
documentRangeFormattingProvider: bool,
|
||||
foldingRangeProvider: bool,
|
||||
selectionRangeProvider: bool,
|
||||
workspaceSymbolProvider: bool,
|
||||
rangeProvider: bool,
|
||||
documentProvider: bool,
|
||||
workspace: ?struct {
|
||||
workspaceFolders: ?struct {
|
||||
supported: bool,
|
||||
changeNotifications: bool,
|
||||
},
|
||||
},
|
||||
semanticTokensProvider: struct {
|
||||
full: bool,
|
||||
range: bool,
|
||||
legend: struct {
|
||||
tokenTypes: []const string,
|
||||
tokenModifiers: []const string,
|
||||
},
|
||||
},
|
||||
inlayHintProvider: bool,
|
||||
},
|
||||
serverInfo: struct {
|
||||
name: string,
|
||||
version: ?string = null,
|
||||
},
|
||||
};
|
||||
|
||||
pub const ConfigurationParams = struct {
|
||||
items: []const ConfigurationItem,
|
||||
|
||||
pub const ConfigurationItem = struct {
|
||||
section: ?[]const u8,
|
||||
};
|
||||
};
|
||||
|
||||
pub const RegistrationParams = struct {
|
||||
registrations: []const Registration,
|
||||
|
||||
pub const Registration = struct {
|
||||
id: string,
|
||||
method: string,
|
||||
|
||||
// registerOptions?: LSPAny;
|
||||
};
|
||||
};
|
||||
|
||||
pub const DocumentHighlightKind = enum(u8) {
|
||||
Text = 1,
|
||||
Read = 2,
|
||||
Write = 3,
|
||||
|
||||
pub fn jsonStringify(value: DocumentHighlightKind, options: std.json.StringifyOptions, out_stream: anytype) !void {
|
||||
try std.json.stringify(@enumToInt(value), options, out_stream);
|
||||
}
|
||||
};
|
||||
|
||||
pub const DocumentHighlight = struct {
|
||||
range: Range,
|
||||
kind: ?DocumentHighlightKind,
|
||||
};
|
||||
|
||||
pub const FoldingRange = struct {
|
||||
startLine: usize,
|
||||
endLine: usize,
|
||||
};
|
||||
|
||||
pub const SelectionRange = struct {
|
||||
range: Range,
|
||||
parent: ?*SelectionRange,
|
||||
};
|
@ -2,13 +2,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
@ -68,8 +68,8 @@ fn testConvertCInclude(cimport_source: []const u8, expected: []const u8) !void {
|
||||
=> {},
|
||||
else => continue,
|
||||
}
|
||||
|
||||
if(!std.mem.eql(u8, ast.tokenSlice(main_tokens[index]), "@cImport")) continue;
|
||||
|
||||
if (!std.mem.eql(u8, ast.tokenSlice(main_tokens[index]), "@cImport")) continue;
|
||||
|
||||
break :blk @intCast(Ast.Node.Index, index);
|
||||
}
|
||||
|
@ -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 = .{
|
||||
.textDocument = .{ .uri = test_uri },
|
||||
.position = offsets.indexToPosition(source, cursor_idx, ctx.server.offset_encoding),
|
||||
},
|
||||
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,9 +496,15 @@ fn extractCompletionLabels(items: anytype) error{ DuplicateCompletionLabel, OutO
|
||||
errdefer set.deinit(allocator);
|
||||
try set.ensureTotalCapacity(allocator, items.len);
|
||||
for (items) |item| {
|
||||
switch (item.kind) {
|
||||
.Keyword, .Snippet => continue,
|
||||
else => {},
|
||||
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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 = .{
|
||||
.textDocument = .{ .uri = test_uri },
|
||||
.range = range,
|
||||
},
|
||||
const params = types.InlayHintParams{
|
||||
.textDocument = .{ .uri = test_uri },
|
||||
.range = range,
|
||||
};
|
||||
|
||||
const response = try ctx.requestGetResponse(?[]InlayHint, "textDocument/inlayHint", request);
|
||||
defer response.deinit();
|
||||
const response = try ctx.requestGetResponse(?[]InlayHint, "textDocument/inlayHint", params);
|
||||
|
||||
const hints: []InlayHint = response.result orelse {
|
||||
std.debug.print("Server returned `null` as the result\n", .{});
|
||||
@ -124,7 +120,7 @@ fn testInlayHints(source: []const u8) !void {
|
||||
for (hints) |hint| {
|
||||
if (position.line != hint.position.line or position.character != hint.position.character) continue;
|
||||
|
||||
const actual_label = hint.label[0 .. hint.label.len - 1]; // exclude :
|
||||
const actual_label = hint.label[0..hint.label.len];
|
||||
|
||||
if (!std.mem.eql(u8, expected_label, actual_label)) {
|
||||
try error_builder.msgAtLoc("expected label `{s}` here but got `{s}`!", new_loc, .err, .{ expected_label, actual_label });
|
||||
|
@ -7,7 +7,6 @@ const Context = @import("../context.zig").Context;
|
||||
const ErrorBuilder = @import("../ErrorBuilder.zig");
|
||||
|
||||
const types = zls.types;
|
||||
const requests = zls.requests;
|
||||
const offsets = zls.offsets;
|
||||
|
||||
const allocator: std.mem.Allocator = std.testing.allocator;
|
||||
@ -113,16 +112,13 @@ fn testReferences(source: []const u8) !void {
|
||||
const var_name = offsets.locToSlice(source, var_loc);
|
||||
const var_loc_middle = var_loc.start + (var_loc.end - var_loc.start) / 2;
|
||||
|
||||
const request = requests.References{
|
||||
.params = .{
|
||||
.textDocument = .{ .uri = file_uri },
|
||||
.position = offsets.indexToPosition(source, var_loc_middle, ctx.server.offset_encoding),
|
||||
.context = .{ .includeDeclaration = true },
|
||||
},
|
||||
const params = types.ReferenceParams{
|
||||
.textDocument = .{ .uri = file_uri },
|
||||
.position = offsets.indexToPosition(source, var_loc_middle, ctx.server.offset_encoding),
|
||||
.context = .{ .includeDeclaration = true },
|
||||
};
|
||||
|
||||
const response = try ctx.requestGetResponse(?[]types.Location, "textDocument/references", request);
|
||||
defer response.deinit();
|
||||
const response = try ctx.requestGetResponse(?[]types.Location, "textDocument/references", params);
|
||||
|
||||
const locations: []types.Location = response.result orelse {
|
||||
std.debug.print("Server returned `null` as the result\n", .{});
|
||||
|
@ -38,20 +38,19 @@ fn testSelectionRange(source: []const u8, want: []const []const u8) !void {
|
||||
|
||||
try ctx.requestDidOpen(test_uri, phr.new_source);
|
||||
|
||||
const position = offsets.locToRange(phr.new_source, phr.locations.items(.new)[0], .utf16).start;
|
||||
const position = offsets.locToRange(phr.new_source, phr.locations.items(.new)[0], .@"utf-16").start;
|
||||
|
||||
const SelectionRange = struct {
|
||||
range: types.Range,
|
||||
parent: ?*@This(),
|
||||
parent: ?*@This() = null,
|
||||
};
|
||||
|
||||
const request = requests.SelectionRange{ .params = .{
|
||||
const params = types.SelectionRangeParams{
|
||||
.textDocument = .{ .uri = test_uri },
|
||||
.positions = &[_]types.Position{position},
|
||||
} };
|
||||
};
|
||||
|
||||
const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", request);
|
||||
defer response.deinit();
|
||||
const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", params);
|
||||
|
||||
const selectionRanges: []SelectionRange = response.result orelse {
|
||||
std.debug.print("Server returned `null` as the result\n", .{});
|
||||
@ -63,7 +62,7 @@ fn testSelectionRange(source: []const u8, want: []const []const u8) !void {
|
||||
|
||||
var it: ?*SelectionRange = &selectionRanges[0];
|
||||
while (it) |r| {
|
||||
const slice = offsets.rangeToSlice(phr.new_source, r.range, .utf16);
|
||||
const slice = offsets.rangeToSlice(phr.new_source, r.range, .@"utf-16");
|
||||
(try got.addOne()).* = slice;
|
||||
it = r.parent;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ const builtin = @import("builtin");
|
||||
|
||||
const Context = @import("../context.zig").Context;
|
||||
|
||||
const requests = zls.requests;
|
||||
const types = zls.types;
|
||||
|
||||
const allocator: std.mem.Allocator = std.testing.allocator;
|
||||
|
||||
@ -41,21 +41,7 @@ fn testSemanticTokens(source: []const u8, expected: []const u32) !void {
|
||||
var ctx = try Context.init();
|
||||
defer ctx.deinit();
|
||||
|
||||
const open_document = requests.OpenDocument{
|
||||
.params = .{
|
||||
.textDocument = .{
|
||||
.uri = file_uri,
|
||||
// .languageId = "zig",
|
||||
// .version = 420,
|
||||
.text = source,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const did_open_method = try std.json.stringifyAlloc(allocator, open_document.params, .{});
|
||||
defer allocator.free(did_open_method);
|
||||
|
||||
try ctx.request("textDocument/didOpen", did_open_method, null);
|
||||
try ctx.requestDidOpen(file_uri, source);
|
||||
|
||||
const Response = struct {
|
||||
data: []const u32,
|
||||
|
@ -107,13 +107,13 @@ fn testIndexPosition(text: []const u8, index: usize, line: u32, characters: [3]u
|
||||
const position16: types.Position = .{ .line = line, .character = characters[1] };
|
||||
const position32: types.Position = .{ .line = line, .character = characters[2] };
|
||||
|
||||
try std.testing.expectEqual(position8, offsets.indexToPosition(text, index, .utf8));
|
||||
try std.testing.expectEqual(position16, offsets.indexToPosition(text, index, .utf16));
|
||||
try std.testing.expectEqual(position32, offsets.indexToPosition(text, index, .utf32));
|
||||
try std.testing.expectEqual(position8, offsets.indexToPosition(text, index, .@"utf-8"));
|
||||
try std.testing.expectEqual(position16, offsets.indexToPosition(text, index, .@"utf-16"));
|
||||
try std.testing.expectEqual(position32, offsets.indexToPosition(text, index, .@"utf-32"));
|
||||
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position8, .utf8));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position16, .utf16));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position32, .utf32));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position8, .@"utf-8"));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position16, .@"utf-16"));
|
||||
try std.testing.expectEqual(index, offsets.positionToIndex(text, position32, .@"utf-32"));
|
||||
}
|
||||
|
||||
fn testTokenToLoc(text: [:0]const u8, token_index: std.zig.Ast.TokenIndex, start: usize, end: usize) !void {
|
||||
@ -135,7 +135,7 @@ fn testTokenIndexToLoc(text: [:0]const u8, index: usize, start: usize, end: usiz
|
||||
|
||||
fn testAdvancePosition(text: [:0]const u8, expected_line: u32, expected_character: u32, line: u32, character: u32, from: usize, to: usize) !void {
|
||||
const expected: types.Position = .{ .line = expected_line, .character = expected_character };
|
||||
const actual = offsets.advancePosition(text, .{ .line = line, .character = character }, from, to, .utf16);
|
||||
const actual = offsets.advancePosition(text, .{ .line = line, .character = character }, from, to, .@"utf-16");
|
||||
|
||||
try std.testing.expectEqual(expected, actual);
|
||||
}
|
||||
@ -143,9 +143,9 @@ fn testAdvancePosition(text: [:0]const u8, expected_line: u32, expected_characte
|
||||
fn testConvertPositionEncoding(text: [:0]const u8, line: u32, character: u32, new_characters: [3]u32) !void {
|
||||
const position: types.Position = .{ .line = line, .character = character };
|
||||
|
||||
const position8 = offsets.convertPositionEncoding(text, position, .utf8, .utf8);
|
||||
const position16 = offsets.convertPositionEncoding(text, position, .utf8, .utf16);
|
||||
const position32 = offsets.convertPositionEncoding(text, position, .utf8, .utf32);
|
||||
const position8 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-8");
|
||||
const position16 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-16");
|
||||
const position32 = offsets.convertPositionEncoding(text, position, .@"utf-8", .@"utf-32");
|
||||
|
||||
try std.testing.expectEqual(line, position8.line);
|
||||
try std.testing.expectEqual(line, position16.line);
|
||||
@ -157,13 +157,13 @@ fn testConvertPositionEncoding(text: [:0]const u8, line: u32, character: u32, ne
|
||||
}
|
||||
|
||||
fn testCountCodeUnits(text: []const u8, counts: [3]usize) !void {
|
||||
try std.testing.expectEqual(counts[0], offsets.countCodeUnits(text, .utf8));
|
||||
try std.testing.expectEqual(counts[1], offsets.countCodeUnits(text, .utf16));
|
||||
try std.testing.expectEqual(counts[2], offsets.countCodeUnits(text, .utf32));
|
||||
try std.testing.expectEqual(counts[0], offsets.countCodeUnits(text, .@"utf-8"));
|
||||
try std.testing.expectEqual(counts[1], offsets.countCodeUnits(text, .@"utf-16"));
|
||||
try std.testing.expectEqual(counts[2], offsets.countCodeUnits(text, .@"utf-32"));
|
||||
}
|
||||
|
||||
fn testGetNCodeUnitByteCount(text: []const u8, n: [3]usize) !void {
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[0], .utf8));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[1], .utf16));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[2], .utf32));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[0], .@"utf-8"));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[1], .@"utf-16"));
|
||||
try std.testing.expectEqual(n[0], offsets.getNCodeUnitByteCount(text, n[2], .@"utf-32"));
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user