Merge pull request #520 from zigtools/finish_configuration

Workspace configuration server request model implemented!
This commit is contained in:
Auguste Rame 2022-07-11 14:37:41 -04:00 committed by GitHub
commit d431565312
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 190 additions and 21 deletions

View File

@ -9,10 +9,10 @@ enable_unused_variable_warnings: bool = false,
/// Whether to enable import/embedFile argument completions (NOTE: these are triggered manually as updating the autotrigger characters may cause issues)
enable_import_embedfile_argument_completions: bool = false,
/// zig library path
/// Zig library path
zig_lib_path: ?[]const u8 = null,
/// zig executable path used to run the custom build runner.
/// Zig executable path used to run the custom build runner.
/// May be used to find a lib path if none is provided.
zig_exe_path: ?[]const u8 = null,
@ -36,10 +36,11 @@ operator_completions: bool = true,
include_at_in_builtins: bool = false,
/// The detail field of completions is truncated to be no longer than this (in bytes).
max_detail_length: usize = 1024 * 1024,
max_detail_length: usize = 1048576,
/// Skips references to std. This will improve lookup speeds.
/// Going to definition however will continue to work
skip_std_references: bool = false,
/// Path to "builtin;" useful for debugging, automatically set if let null
builtin_path: ?[]const u8 = null,

View File

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

View File

@ -91,6 +91,7 @@ const ClientCapabilities = struct {
hover_supports_md: bool = false,
completion_doc_supports_md: bool = false,
label_details_support: bool = false,
supports_configuration: bool = false,
};
var client_capabilities = ClientCapabilities{};
@ -1741,15 +1742,9 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
});
if (req.params.capabilities.workspace) |workspace| {
if (workspace.configuration.value) {
try send(arena, types.Request{
.method = "workspace/configuration",
.params = .{
.ConfigurationParams = .{
.items = &[_]types.ConfigurationParams.ConfigurationItem{},
},
},
});
client_capabilities.supports_configuration = workspace.configuration.value;
if (workspace.didChangeConfiguration != null and workspace.didChangeConfiguration.?.dynamicRegistration.value) {
try registerCapability(arena, "workspace/didChangeConfiguration");
}
}
@ -1758,6 +1753,68 @@ fn initializeHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req:
logger.info("Using offset encoding: {s}", .{std.meta.tagName(offset_encoding)});
}
fn registerCapability(arena: *std.heap.ArenaAllocator, method: []const u8) !void {
// NOTE: stage1 moment occurs if we dont do it like this :(
// long live stage2's not broken anon structs
logger.debug("Dynamically registering method '{s}'", .{method});
const id = try std.fmt.allocPrint(arena.allocator(), "register-{s}", .{method});
const reg = types.RegistrationParams.Registration{
.id = id,
.method = method,
};
const registrations = [1]types.RegistrationParams.Registration{reg};
const params = types.RegistrationParams{
.registrations = &registrations,
};
const respp = types.ResponseParams{
.RegistrationParams = params,
};
const req = types.Request{
.id = .{ .String = id },
.method = "client/registerCapability",
.params = respp,
};
try send(arena, req);
}
fn requestConfiguration(arena: *std.heap.ArenaAllocator) !void {
const configuration_items = comptime confi: {
var comp_confi: [std.meta.fields(Config).len]types.ConfigurationParams.ConfigurationItem = undefined;
inline for (std.meta.fields(Config)) |field, index| {
comp_confi[index] = .{
.scopeUri = "zls",
.section = "zls." ++ field.name,
};
}
break :confi comp_confi;
};
logger.info("Requesting configuration!", .{});
try send(arena, types.Request{
.id = .{ .String = "i_haz_configuration" },
.method = "workspace/configuration",
.params = .{
.ConfigurationParams = .{
.items = &configuration_items,
},
},
});
}
fn initializedHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, config: *const Config) !void {
_ = id;
_ = config;
if (client_capabilities.supports_configuration)
try requestConfiguration(arena);
}
var keep_running = true;
fn shutdownHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, config: *const Config) !void {
_ = config;
@ -2121,18 +2178,22 @@ fn renameHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requ
}
}
fn didChangeConfigurationHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.Configuration, config: *Config) !void {
fn didChangeConfigurationHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, maybe_req: std.json.Value, config: *Config) !void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
_ = arena;
_ = id;
inline for (std.meta.fields(Config)) |field| {
if (@field(req.params.settings, field.name)) |value| {
logger.debug("setting configuration option '{s}' to '{any}'", .{ field.name, value });
@field(config, field.name) = value;
if (maybe_req.Object.get("params").?.Object.get("settings").? == .Object) {
const req = try requests.fromDynamicTree(arena, requests.Configuration, maybe_req);
inline for (std.meta.fields(Config)) |field| {
if (@field(req.params.settings, field.name)) |value| {
logger.debug("setting configuration option '{s}' to '{any}'", .{ field.name, value });
@field(config, field.name) = value;
}
}
}
} else if (client_capabilities.supports_configuration)
try requestConfiguration(arena);
}
fn referencesHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, req: requests.References, config: *const Config) !void {
@ -2179,6 +2240,51 @@ fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, jso
else => types.RequestId{ .Integer = 0 },
} else types.RequestId{ .Integer = 0 };
if (id == .String and std.mem.startsWith(u8, id.String, "register"))
return;
if (id == .String and std.mem.eql(u8, id.String, "i_haz_configuration")) {
logger.info("Setting configuration...", .{});
// NOTE: Does this work with other editors?
// Yes, String ids are officially supported by LSP
// but not sure how standard this "standard" really is
const result = tree.root.Object.get("result").?.Array;
inline for (std.meta.fields(Config)) |field, index| {
const value = result.items[index];
const ft = if (@typeInfo(field.field_type) == .Optional)
@typeInfo(field.field_type).Optional.child
else
field.field_type;
const ti = @typeInfo(ft);
if (value != .Null) {
const new_value: field.field_type = switch (ft) {
[]const u8 => switch (value) {
.String => |s| s,
else => @panic("Invalid configuration value"), // TODO: Handle this
},
else => switch (ti) {
.Int => switch (value) {
.Integer => |s| std.math.cast(ft, s) orelse @panic("Invalid configuration value"),
else => @panic("Invalid configuration value"), // TODO: Handle this
},
.Bool => switch (value) {
.Bool => |b| b,
else => @panic("Invalid configuration value"), // TODO: Handle this
},
else => @compileError("Not implemented for " ++ @typeName(ft)),
},
};
logger.debug("setting configuration option '{s}' to '{any}'", .{ field.name, new_value });
@field(config, field.name) = new_value;
}
}
return;
}
std.debug.assert(tree.root.Object.get("method") != null);
const method = tree.root.Object.get("method").?.String;
@ -2189,7 +2295,7 @@ fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, jso
}
const method_map = .{
.{"initialized"},
.{ "initialized", void, initializedHandler },
.{"$/cancelRequest"},
.{"textDocument/willSave"},
.{ "initialize", requests.Initialize, initializeHandler },
@ -2210,7 +2316,7 @@ fn processJsonRpc(arena: *std.heap.ArenaAllocator, parser: *std.json.Parser, jso
.{ "textDocument/formatting", requests.Formatting, formattingHandler },
.{ "textDocument/rename", requests.Rename, renameHandler },
.{ "textDocument/references", requests.References, referencesHandler },
.{ "workspace/didChangeConfiguration", requests.Configuration, didChangeConfigurationHandler },
.{ "workspace/didChangeConfiguration", std.json.Value, didChangeConfigurationHandler },
};
// Hack to avoid `return`ing in the inline for, which causes bugs.

View File

@ -148,6 +148,9 @@ 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 {

View File

@ -44,6 +44,7 @@ pub const ResponseParams = union(enum) {
WorkspaceEdit: WorkspaceEdit,
InitializeResult: InitializeResult,
ConfigurationParams: ConfigurationParams,
RegistrationParams: RegistrationParams,
};
/// JSONRPC notifications
@ -77,6 +78,7 @@ pub const Response = struct {
pub const Request = struct {
jsonrpc: string = "2.0",
id: RequestId,
method: []const u8,
params: ?ResponseParams,
};
@ -400,3 +402,14 @@ pub const ConfigurationParams = struct {
section: ?[]const u8,
};
};
pub const RegistrationParams = struct {
registrations: []const Registration,
pub const Registration = struct {
id: string,
method: string,
// registerOptions?: LSPAny;
};
};

View File

@ -66,7 +66,7 @@ const Server = struct {
const rest = response_bytes[json_fmt.len..];
const id_end = std.mem.indexOfScalar(u8, rest, ',') orelse return error.InvalidResponse;
const id = try std.fmt.parseInt(u32, rest[0..id_end], 10);
const id = std.fmt.parseInt(u32, rest[0..id_end], 10) catch continue;
if (id != self.request_id) {
continue;
@ -83,6 +83,7 @@ const Server = struct {
}
}
}
fn extractError(msg: []const u8) !void {
const log_request =
\\"method":"window/logMessage","params":{"type":