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)
|
/// 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,
|
enable_import_embedfile_argument_completions: bool = false,
|
||||||
|
|
||||||
/// zig library path
|
/// Zig library path
|
||||||
zig_lib_path: ?[]const u8 = null,
|
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.
|
/// May be used to find a lib path if none is provided.
|
||||||
zig_exe_path: ?[]const u8 = null,
|
zig_exe_path: ?[]const u8 = null,
|
||||||
|
|
||||||
@ -36,10 +36,11 @@ operator_completions: bool = true,
|
|||||||
include_at_in_builtins: bool = false,
|
include_at_in_builtins: bool = false,
|
||||||
|
|
||||||
/// The detail field of completions is truncated to be no longer than this (in bytes).
|
/// 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.
|
/// Skips references to std. This will improve lookup speeds.
|
||||||
/// Going to definition however will continue to work
|
/// Going to definition however will continue to work
|
||||||
skip_std_references: bool = false,
|
skip_std_references: bool = false,
|
||||||
|
|
||||||
|
/// Path to "builtin;" useful for debugging, automatically set if let null
|
||||||
builtin_path: ?[]const u8 = 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;
|
||||||
|
}
|
||||||
|
}
|
140
src/main.zig
140
src/main.zig
@ -91,6 +91,7 @@ const ClientCapabilities = struct {
|
|||||||
hover_supports_md: bool = false,
|
hover_supports_md: bool = false,
|
||||||
completion_doc_supports_md: bool = false,
|
completion_doc_supports_md: bool = false,
|
||||||
label_details_support: bool = false,
|
label_details_support: bool = false,
|
||||||
|
supports_configuration: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
var client_capabilities = ClientCapabilities{};
|
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 (req.params.capabilities.workspace) |workspace| {
|
||||||
if (workspace.configuration.value) {
|
client_capabilities.supports_configuration = workspace.configuration.value;
|
||||||
try send(arena, types.Request{
|
if (workspace.didChangeConfiguration != null and workspace.didChangeConfiguration.?.dynamicRegistration.value) {
|
||||||
.method = "workspace/configuration",
|
try registerCapability(arena, "workspace/didChangeConfiguration");
|
||||||
.params = .{
|
|
||||||
.ConfigurationParams = .{
|
|
||||||
.items = &[_]types.ConfigurationParams.ConfigurationItem{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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)});
|
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;
|
var keep_running = true;
|
||||||
fn shutdownHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, config: *const Config) !void {
|
fn shutdownHandler(arena: *std.heap.ArenaAllocator, id: types.RequestId, config: *const Config) !void {
|
||||||
_ = config;
|
_ = 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());
|
const tracy_zone = tracy.trace(@src());
|
||||||
defer tracy_zone.end();
|
defer tracy_zone.end();
|
||||||
|
|
||||||
_ = arena;
|
_ = arena;
|
||||||
_ = id;
|
_ = id;
|
||||||
inline for (std.meta.fields(Config)) |field| {
|
if (maybe_req.Object.get("params").?.Object.get("settings").? == .Object) {
|
||||||
if (@field(req.params.settings, field.name)) |value| {
|
const req = try requests.fromDynamicTree(arena, requests.Configuration, maybe_req);
|
||||||
logger.debug("setting configuration option '{s}' to '{any}'", .{ field.name, value });
|
inline for (std.meta.fields(Config)) |field| {
|
||||||
@field(config, field.name) = value;
|
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 {
|
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 },
|
||||||
} 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);
|
std.debug.assert(tree.root.Object.get("method") != null);
|
||||||
const method = tree.root.Object.get("method").?.String;
|
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 = .{
|
const method_map = .{
|
||||||
.{"initialized"},
|
.{ "initialized", void, initializedHandler },
|
||||||
.{"$/cancelRequest"},
|
.{"$/cancelRequest"},
|
||||||
.{"textDocument/willSave"},
|
.{"textDocument/willSave"},
|
||||||
.{ "initialize", requests.Initialize, initializeHandler },
|
.{ "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/formatting", requests.Formatting, formattingHandler },
|
||||||
.{ "textDocument/rename", requests.Rename, renameHandler },
|
.{ "textDocument/rename", requests.Rename, renameHandler },
|
||||||
.{ "textDocument/references", requests.References, referencesHandler },
|
.{ "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.
|
// Hack to avoid `return`ing in the inline for, which causes bugs.
|
||||||
|
@ -148,6 +148,9 @@ pub const Initialize = struct {
|
|||||||
pub const ClientCapabilities = struct {
|
pub const ClientCapabilities = struct {
|
||||||
workspace: ?struct {
|
workspace: ?struct {
|
||||||
configuration: Default(bool, false),
|
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),
|
workspaceFolders: Default(bool, false),
|
||||||
},
|
},
|
||||||
textDocument: ?struct {
|
textDocument: ?struct {
|
||||||
|
@ -44,6 +44,7 @@ pub const ResponseParams = union(enum) {
|
|||||||
WorkspaceEdit: WorkspaceEdit,
|
WorkspaceEdit: WorkspaceEdit,
|
||||||
InitializeResult: InitializeResult,
|
InitializeResult: InitializeResult,
|
||||||
ConfigurationParams: ConfigurationParams,
|
ConfigurationParams: ConfigurationParams,
|
||||||
|
RegistrationParams: RegistrationParams,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// JSONRPC notifications
|
/// JSONRPC notifications
|
||||||
@ -77,6 +78,7 @@ pub const Response = struct {
|
|||||||
|
|
||||||
pub const Request = struct {
|
pub const Request = struct {
|
||||||
jsonrpc: string = "2.0",
|
jsonrpc: string = "2.0",
|
||||||
|
id: RequestId,
|
||||||
method: []const u8,
|
method: []const u8,
|
||||||
params: ?ResponseParams,
|
params: ?ResponseParams,
|
||||||
};
|
};
|
||||||
@ -400,3 +402,14 @@ pub const ConfigurationParams = struct {
|
|||||||
section: ?[]const u8,
|
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 rest = response_bytes[json_fmt.len..];
|
||||||
const id_end = std.mem.indexOfScalar(u8, rest, ',') orelse return error.InvalidResponse;
|
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) {
|
if (id != self.request_id) {
|
||||||
continue;
|
continue;
|
||||||
@ -83,6 +83,7 @@ const Server = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extractError(msg: []const u8) !void {
|
fn extractError(msg: []const u8) !void {
|
||||||
const log_request =
|
const log_request =
|
||||||
\\"method":"window/logMessage","params":{"type":
|
\\"method":"window/logMessage","params":{"type":
|
||||||
|
Loading…
Reference in New Issue
Block a user