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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										140
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										140
									
								
								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;
 | 
			
		||||
    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.
 | 
			
		||||
 | 
			
		||||
@ -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