Merge pull request #520 from zigtools/finish_configuration
Workspace configuration server request model implemented!
This commit is contained in:
commit
d431565312
@ -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,
|
||||
|
45
src/data/generate-vscode-config.js
Normal file
45
src/data/generate-vscode-config.js
Normal 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;
|
||||
}
|
||||
}
|
130
src/main.zig
130
src/main.zig
@ -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 = ®istrations,
|
||||
};
|
||||
|
||||
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;
|
||||
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.
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
@ -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":
|
||||
|
Loading…
Reference in New Issue
Block a user