Merge pull request #62 from alexnask/master
Added workspace folder and local configuration folder config support
This commit is contained in:
		
						commit
						7939dc5185
					
				
							
								
								
									
										1
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/main.yml
									
									
									
									
										vendored
									
									
								
							@ -19,6 +19,7 @@ jobs:
 | 
			
		||||
        wget --quiet --output-document=- $ZIG | tar Jx
 | 
			
		||||
        mv zig-linux-x86_64-* zig
 | 
			
		||||
        echo zig version $(./zig/zig version)
 | 
			
		||||
        git submodule update --init --recursive
 | 
			
		||||
    - name: build
 | 
			
		||||
      run: |
 | 
			
		||||
        export PATH=./zig:$PATH
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
[submodule "src/known-folders"]
 | 
			
		||||
	path = src/known-folders
 | 
			
		||||
	url = https://github.com/ziglibs/known-folders
 | 
			
		||||
@ -37,7 +37,12 @@ Then, you can use the `zls` executable in an editor of your choice that has a Zi
 | 
			
		||||
 | 
			
		||||
### Configuration Options
 | 
			
		||||
 | 
			
		||||
You can configure zls by providing a zls.json file in the same directory as the executable.  
 | 
			
		||||
You can configure zls by providing a zls.json file.  
 | 
			
		||||
zls will look for a zls.json configuration file in multiple locations with the following priority:  
 | 
			
		||||
- In the folders open in your workspace (this applies for files in those folders)  
 | 
			
		||||
- In the local configuration folder of your OS (as provided by [known-folders](https://github.com/ziglibs/known-folders#folder-list))  
 | 
			
		||||
- In the same directory as the executable  
 | 
			
		||||
 | 
			
		||||
The following options are currently available.  
 | 
			
		||||
 | 
			
		||||
| Option | Type | Default value | What it Does |
 | 
			
		||||
@ -60,6 +65,8 @@ Install the `zls-vscode` extension from [here](https://github.com/zigtools/zls-v
 | 
			
		||||
  - Uses data provided by `src/data` to perform builtin autocompletion
 | 
			
		||||
- [`zig-lsp` by @xackus](https://github.com/xackus/zig-lsp)
 | 
			
		||||
  - Inspiration for `zls`
 | 
			
		||||
- [`known-folders` by @ziglibs](https://github.com/ziglibs/known-folders)
 | 
			
		||||
  - Provides API to acces known folders on Linux, Windows and Mac OS
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
MIT
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Configuration options for zls.
 | 
			
		||||
 | 
			
		||||
/// Whether to enable snippet completions
 | 
			
		||||
enable_snippets: bool = true,
 | 
			
		||||
enable_snippets: bool = false,
 | 
			
		||||
 | 
			
		||||
/// zig library path
 | 
			
		||||
zig_lib_path: ?[]const u8 = null,
 | 
			
		||||
 | 
			
		||||
@ -22,29 +22,11 @@ pub const Handle = struct {
 | 
			
		||||
 | 
			
		||||
allocator: *std.mem.Allocator,
 | 
			
		||||
handles: std.StringHashMap(*Handle),
 | 
			
		||||
std_uri: ?[]const u8,
 | 
			
		||||
 | 
			
		||||
pub fn init(self: *DocumentStore, allocator: *std.mem.Allocator, zig_lib_path: ?[]const u8) !void {
 | 
			
		||||
pub fn init(self: *DocumentStore, allocator: *std.mem.Allocator) !void {
 | 
			
		||||
    self.allocator = allocator;
 | 
			
		||||
    self.handles = std.StringHashMap(*Handle).init(allocator);
 | 
			
		||||
    errdefer self.handles.deinit();
 | 
			
		||||
 | 
			
		||||
    if (zig_lib_path) |zpath| {
 | 
			
		||||
        const std_path = std.fs.path.resolve(allocator, &[_][]const u8{
 | 
			
		||||
            zpath, "./std/std.zig",
 | 
			
		||||
        }) catch |err| block: {
 | 
			
		||||
            std.debug.warn("Failed to resolve zig std library path, error: {}\n", .{err});
 | 
			
		||||
            self.std_uri = null;
 | 
			
		||||
            return;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        defer allocator.free(std_path);
 | 
			
		||||
        // Get the std_path as a URI, so we can just append to it!
 | 
			
		||||
        self.std_uri = try URI.fromPath(allocator, std_path);
 | 
			
		||||
        std.debug.warn("Standard library base uri: {}\n", .{self.std_uri});
 | 
			
		||||
    } else {
 | 
			
		||||
        self.std_uri = null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// This function asserts the document is not open yet and takes ownership
 | 
			
		||||
@ -120,7 +102,7 @@ pub fn getHandle(self: *DocumentStore, uri: []const u8) ?*Handle {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if the document text is now sane, move it to sane_text if so.
 | 
			
		||||
fn removeOldImports(self: *DocumentStore, handle: *Handle) !void {
 | 
			
		||||
fn removeOldImports(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]const u8) !void {
 | 
			
		||||
    std.debug.warn("New text for document {}\n", .{handle.uri()});
 | 
			
		||||
    // TODO: Better algorithm or data structure?
 | 
			
		||||
    // Removing the imports is costly since they live in an array list
 | 
			
		||||
@ -143,8 +125,9 @@ fn removeOldImports(self: *DocumentStore, handle: *Handle) !void {
 | 
			
		||||
        ex.* = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const std_uri = try stdUriFromLibPath(&arena.allocator, zig_lib_path);
 | 
			
		||||
    for (import_strs.items) |str| {
 | 
			
		||||
        const uri = (try uriFromImportStr(self, &arena.allocator, handle.*, str)) orelse continue;
 | 
			
		||||
        const uri = (try uriFromImportStr(self, &arena.allocator, handle.*, str, std_uri)) orelse continue;
 | 
			
		||||
 | 
			
		||||
        var idx: usize = 0;
 | 
			
		||||
        exists_loop: while (idx < still_exist.len) : (idx += 1) {
 | 
			
		||||
@ -172,7 +155,12 @@ fn removeOldImports(self: *DocumentStore, handle: *Handle) !void {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn applyChanges(self: *DocumentStore, handle: *Handle, content_changes: std.json.Array) !void {
 | 
			
		||||
pub fn applyChanges(
 | 
			
		||||
    self: *DocumentStore,
 | 
			
		||||
    handle: *Handle,
 | 
			
		||||
    content_changes: std.json.Array,
 | 
			
		||||
    zig_lib_path: ?[]const u8,
 | 
			
		||||
) !void {
 | 
			
		||||
    const document = &handle.document;
 | 
			
		||||
 | 
			
		||||
    for (content_changes.items) |change| {
 | 
			
		||||
@ -225,12 +213,18 @@ pub fn applyChanges(self: *DocumentStore, handle: *Handle, content_changes: std.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try self.removeOldImports(handle);
 | 
			
		||||
    try self.removeOldImports(handle, zig_lib_path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn uriFromImportStr(store: *DocumentStore, allocator: *std.mem.Allocator, handle: Handle, import_str: []const u8) !?[]const u8 {
 | 
			
		||||
fn uriFromImportStr(
 | 
			
		||||
    store: *DocumentStore,
 | 
			
		||||
    allocator: *std.mem.Allocator,
 | 
			
		||||
    handle: Handle,
 | 
			
		||||
    import_str: []const u8,
 | 
			
		||||
    std_uri: ?[]const u8,
 | 
			
		||||
) !?[]const u8 {
 | 
			
		||||
    return if (std.mem.eql(u8, import_str, "std"))
 | 
			
		||||
        if (store.std_uri) |std_root_uri| try std.mem.dupe(allocator, u8, std_root_uri) else {
 | 
			
		||||
        if (std_uri) |uri| try std.mem.dupe(allocator, u8, uri) else {
 | 
			
		||||
            std.debug.warn("Cannot resolve std library import, path is null.\n", .{});
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
@ -259,6 +253,7 @@ pub const AnalysisContext = struct {
 | 
			
		||||
    tree: *std.zig.ast.Tree,
 | 
			
		||||
    scope_nodes: []*std.zig.ast.Node,
 | 
			
		||||
    last_this_node: *std.zig.ast.Node,
 | 
			
		||||
    std_uri: ?[]const u8,
 | 
			
		||||
 | 
			
		||||
    fn refreshScopeNodes(self: *AnalysisContext) !void {
 | 
			
		||||
        var scope_nodes = std.ArrayList(*std.zig.ast.Node).init(&self.arena.allocator);
 | 
			
		||||
@ -269,7 +264,13 @@ pub const AnalysisContext = struct {
 | 
			
		||||
 | 
			
		||||
    pub fn onImport(self: *AnalysisContext, import_str: []const u8) !?*std.zig.ast.Node {
 | 
			
		||||
        const allocator = self.store.allocator;
 | 
			
		||||
        const final_uri = (try uriFromImportStr(self.store, self.store.allocator, self.handle.*, import_str)) orelse return null;
 | 
			
		||||
        const final_uri = (try uriFromImportStr(
 | 
			
		||||
            self.store,
 | 
			
		||||
            self.store.allocator,
 | 
			
		||||
            self.handle.*,
 | 
			
		||||
            import_str,
 | 
			
		||||
            self.std_uri,
 | 
			
		||||
        )) orelse return null;
 | 
			
		||||
 | 
			
		||||
        std.debug.warn("Import final URI: {}\n", .{final_uri});
 | 
			
		||||
        var consumed_final_uri = false;
 | 
			
		||||
@ -351,6 +352,7 @@ pub const AnalysisContext = struct {
 | 
			
		||||
            .tree = tree,
 | 
			
		||||
            .scope_nodes = self.scope_nodes,
 | 
			
		||||
            .last_this_node = &tree.root_node.base,
 | 
			
		||||
            .std_uri = self.std_uri,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -369,12 +371,36 @@ pub const AnalysisContext = struct {
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub fn analysisContext(self: *DocumentStore, handle: *Handle, arena: *std.heap.ArenaAllocator, position: types.Position) !AnalysisContext {
 | 
			
		||||
fn stdUriFromLibPath(allocator: *std.mem.Allocator, zig_lib_path: ?[]const u8) !?[]const u8 {
 | 
			
		||||
    if (zig_lib_path) |zpath| {
 | 
			
		||||
        const std_path = std.fs.path.resolve(allocator, &[_][]const u8{
 | 
			
		||||
            zpath, "./std/std.zig",
 | 
			
		||||
        }) catch |err| block: {
 | 
			
		||||
            std.debug.warn("Failed to resolve zig std library path, error: {}\n", .{err});
 | 
			
		||||
            return null;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        defer allocator.free(std_path);
 | 
			
		||||
        // Get the std_path as a URI, so we can just append to it!
 | 
			
		||||
        return try URI.fromPath(allocator, std_path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn analysisContext(
 | 
			
		||||
    self: *DocumentStore,
 | 
			
		||||
    handle: *Handle,
 | 
			
		||||
    arena: *std.heap.ArenaAllocator,
 | 
			
		||||
    position: types.Position,
 | 
			
		||||
    zig_lib_path: ?[]const u8,
 | 
			
		||||
) !AnalysisContext {
 | 
			
		||||
    const tree = try handle.tree(self.allocator);
 | 
			
		||||
 | 
			
		||||
    var scope_nodes = std.ArrayList(*std.zig.ast.Node).init(&arena.allocator);
 | 
			
		||||
    try analysis.declsFromIndex(&scope_nodes, tree, try handle.document.positionToIndex(position));
 | 
			
		||||
 | 
			
		||||
    const std_uri = try stdUriFromLibPath(&arena.allocator, zig_lib_path);
 | 
			
		||||
    return AnalysisContext{
 | 
			
		||||
        .store = self,
 | 
			
		||||
        .handle = handle,
 | 
			
		||||
@ -382,6 +408,7 @@ pub fn analysisContext(self: *DocumentStore, handle: *Handle, arena: *std.heap.A
 | 
			
		||||
        .tree = tree,
 | 
			
		||||
        .scope_nodes = scope_nodes.items,
 | 
			
		||||
        .last_this_node = &tree.root_node.base,
 | 
			
		||||
        .std_uri = std_uri,
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -400,7 +427,4 @@ pub fn deinit(self: *DocumentStore) void {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    self.handles.deinit();
 | 
			
		||||
    if (self.std_uri) |uri| {
 | 
			
		||||
        self.allocator.free(uri);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								src/known-folders
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										1
									
								
								src/known-folders
									
									
									
									
									
										Submodule
									
								
							@ -0,0 +1 @@
 | 
			
		||||
Subproject commit 42a32b0241a5aaeaa09d7edeceefc5384b4eb466
 | 
			
		||||
							
								
								
									
										182
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										182
									
								
								src/main.zig
									
									
									
									
									
								
							@ -8,6 +8,7 @@ const readRequestHeader = @import("header.zig").readRequestHeader;
 | 
			
		||||
const data = @import("data/" ++ build_options.data_version ++ ".zig");
 | 
			
		||||
const types = @import("types.zig");
 | 
			
		||||
const analysis = @import("analysis.zig");
 | 
			
		||||
const URI = @import("uri.zig");
 | 
			
		||||
 | 
			
		||||
// Code is largely based off of https://github.com/andersfr/zig-lsp/blob/master/server.zig
 | 
			
		||||
 | 
			
		||||
@ -15,9 +16,10 @@ var stdout: std.io.BufferedOutStream(4096, std.fs.File.OutStream) = undefined;
 | 
			
		||||
var allocator: *std.mem.Allocator = undefined;
 | 
			
		||||
 | 
			
		||||
var document_store: DocumentStore = undefined;
 | 
			
		||||
var workspace_folder_configs: std.StringHashMap(?Config) = undefined;
 | 
			
		||||
 | 
			
		||||
const initialize_response =
 | 
			
		||||
    \\,"result":{"capabilities":{"signatureHelpProvider":{"triggerCharacters":["(",","]},"textDocumentSync":1,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":","@"]},"documentHighlightProvider":false,"codeActionProvider":false,"declarationProvider":true,"definitionProvider":true,"typeDefinitionProvider":true,"workspace":{"workspaceFolders":{"supported":true}}}}}
 | 
			
		||||
    \\,"result":{"capabilities":{"signatureHelpProvider":{"triggerCharacters":["(",","]},"textDocumentSync":1,"completionProvider":{"resolveProvider":false,"triggerCharacters":[".",":","@"]},"documentHighlightProvider":false,"codeActionProvider":false,"declarationProvider":true,"definitionProvider":true,"typeDefinitionProvider":true,"workspace":{"workspaceFolders":{"supported":true,"changeNotifications":true}}}}}
 | 
			
		||||
;
 | 
			
		||||
 | 
			
		||||
const not_implemented_response =
 | 
			
		||||
@ -319,11 +321,17 @@ fn gotoDefinitionGlobal(id: i64, pos_index: usize, handle: DocumentStore.Handle)
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn gotoDefinitionFieldAccess(id: i64, handle: *DocumentStore.Handle, position: types.Position, line_start_idx: usize) !void {
 | 
			
		||||
fn gotoDefinitionFieldAccess(
 | 
			
		||||
    id: i64,
 | 
			
		||||
    handle: *DocumentStore.Handle,
 | 
			
		||||
    position: types.Position,
 | 
			
		||||
    line_start_idx: usize,
 | 
			
		||||
    config: Config,
 | 
			
		||||
) !void {
 | 
			
		||||
    var arena = std.heap.ArenaAllocator.init(allocator);
 | 
			
		||||
    defer arena.deinit();
 | 
			
		||||
 | 
			
		||||
    var analysis_ctx = try document_store.analysisContext(handle, &arena, position);
 | 
			
		||||
    var analysis_ctx = try document_store.analysisContext(handle, &arena, position, config.zig_lib_path);
 | 
			
		||||
    defer analysis_ctx.deinit();
 | 
			
		||||
 | 
			
		||||
    const pos_index = try handle.document.positionToIndex(position);
 | 
			
		||||
@ -365,7 +373,7 @@ fn completeGlobal(id: i64, pos_index: usize, handle: *DocumentStore.Handle, conf
 | 
			
		||||
    var analysis_ctx = try document_store.analysisContext(handle, &arena, types.Position{
 | 
			
		||||
        .line = 0,
 | 
			
		||||
        .character = 0,
 | 
			
		||||
    });
 | 
			
		||||
    }, config.zig_lib_path);
 | 
			
		||||
    defer analysis_ctx.deinit();
 | 
			
		||||
 | 
			
		||||
    var decl_nodes = std.ArrayList(*std.zig.ast.Node).init(&arena.allocator);
 | 
			
		||||
@ -390,7 +398,7 @@ fn completeFieldAccess(id: i64, handle: *DocumentStore.Handle, position: types.P
 | 
			
		||||
    var arena = std.heap.ArenaAllocator.init(allocator);
 | 
			
		||||
    defer arena.deinit();
 | 
			
		||||
 | 
			
		||||
    var analysis_ctx = try document_store.analysisContext(handle, &arena, position);
 | 
			
		||||
    var analysis_ctx = try document_store.analysisContext(handle, &arena, position, config.zig_lib_path);
 | 
			
		||||
    defer analysis_ctx.deinit();
 | 
			
		||||
 | 
			
		||||
    var completions = std.ArrayList(types.CompletionItem).init(&arena.allocator);
 | 
			
		||||
@ -573,27 +581,121 @@ fn documentPositionContext(doc: types.TextDocument, pos_index: usize) PositionCo
 | 
			
		||||
    return context;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn loadConfig(folder_path: []const u8) ?Config {
 | 
			
		||||
    var folder = std.fs.cwd().openDir(folder_path, .{}) catch return null;
 | 
			
		||||
    defer folder.close();
 | 
			
		||||
 | 
			
		||||
    const conf_file = folder.openFile("zls.json", .{}) catch return null;
 | 
			
		||||
    defer conf_file.close();
 | 
			
		||||
 | 
			
		||||
    // Max 1MB
 | 
			
		||||
    const file_buf = conf_file.inStream().readAllAlloc(allocator, 0x1000000) catch return null;
 | 
			
		||||
    defer allocator.free(file_buf);
 | 
			
		||||
 | 
			
		||||
    // TODO: Better errors? Doesn't seem like std.json can provide us positions or context.
 | 
			
		||||
    var config = std.json.parse(Config, &std.json.TokenStream.init(file_buf), std.json.ParseOptions{ .allocator = allocator }) catch |err| {
 | 
			
		||||
        std.debug.warn("Error while parsing configuration file: {}\nUsing default config.\n", .{err});
 | 
			
		||||
        return null;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (config.zig_lib_path) |zig_lib_path| {
 | 
			
		||||
        if (!std.fs.path.isAbsolute(zig_lib_path)) {
 | 
			
		||||
            std.debug.warn("zig library path is not absolute, defaulting to null.\n", .{});
 | 
			
		||||
            allocator.free(zig_lib_path);
 | 
			
		||||
            config.zig_lib_path = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return config;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn loadWorkspaceConfigs() !void {
 | 
			
		||||
    var folder_config_it = workspace_folder_configs.iterator();
 | 
			
		||||
    while (folder_config_it.next()) |entry| {
 | 
			
		||||
        if (entry.value) |_| continue;
 | 
			
		||||
 | 
			
		||||
        const folder_path = try URI.parse(allocator, entry.key);
 | 
			
		||||
        defer allocator.free(folder_path);
 | 
			
		||||
 | 
			
		||||
        entry.value = loadConfig(folder_path);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn configFromUriOr(uri: []const u8, default: Config) Config {
 | 
			
		||||
    var folder_config_it = workspace_folder_configs.iterator();
 | 
			
		||||
    while (folder_config_it.next()) |entry| {
 | 
			
		||||
        if (std.mem.startsWith(u8, uri, entry.key)) {
 | 
			
		||||
            return entry.value orelse default;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return default;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !void {
 | 
			
		||||
    var tree = try parser.parse(json);
 | 
			
		||||
    defer tree.deinit();
 | 
			
		||||
 | 
			
		||||
    const root = tree.root;
 | 
			
		||||
 | 
			
		||||
    std.debug.assert(root.Object.getValue("method") != null);
 | 
			
		||||
 | 
			
		||||
    const method = root.Object.getValue("method").?.String;
 | 
			
		||||
    const id = if (root.Object.getValue("id")) |id| id.Integer else 0;
 | 
			
		||||
    if (id == 1337 and (root.Object.getValue("method") == null or std.mem.eql(u8, root.Object.getValue("method").?.String, ""))) {
 | 
			
		||||
        const result = (root.Object.getValue("result") orelse return).Array;
 | 
			
		||||
 | 
			
		||||
        for (result.items) |workspace_folder| {
 | 
			
		||||
            const duped_uri = try std.mem.dupe(allocator, u8, workspace_folder.Object.getValue("uri").?.String);
 | 
			
		||||
            try workspace_folder_configs.putNoClobber(duped_uri, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try loadWorkspaceConfigs();
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std.debug.assert(root.Object.getValue("method") != null);
 | 
			
		||||
    const method = root.Object.getValue("method").?.String;
 | 
			
		||||
    const params = root.Object.getValue("params").?.Object;
 | 
			
		||||
 | 
			
		||||
    // Core
 | 
			
		||||
    if (std.mem.eql(u8, method, "initialize")) {
 | 
			
		||||
        try respondGeneric(id, initialize_response);
 | 
			
		||||
    } else if (std.mem.eql(u8, method, "initialized")) {
 | 
			
		||||
        // noop
 | 
			
		||||
        // Send the workspaceFolders request
 | 
			
		||||
        try send(types.Request{
 | 
			
		||||
            .id = .{ .Integer = 1337 },
 | 
			
		||||
            .method = "workspace/workspaceFolders",
 | 
			
		||||
            .params = {},
 | 
			
		||||
        });
 | 
			
		||||
    } else if (std.mem.eql(u8, method, "$/cancelRequest")) {
 | 
			
		||||
        // noop
 | 
			
		||||
    }
 | 
			
		||||
    // Workspace folder changes
 | 
			
		||||
    else if (std.mem.eql(u8, method, "workspace/didChangeWorkspaceFolders")) {
 | 
			
		||||
        const event = params.getValue("event").?.Object;
 | 
			
		||||
        const added = event.getValue("added").?.Array;
 | 
			
		||||
        const removed = event.getValue("removed").?.Array;
 | 
			
		||||
 | 
			
		||||
        for (removed.items) |rem| {
 | 
			
		||||
            const uri = rem.Object.getValue("uri").?.String;
 | 
			
		||||
            if (workspace_folder_configs.remove(uri)) |entry| {
 | 
			
		||||
                allocator.free(entry.key);
 | 
			
		||||
                if (entry.value) |c| {
 | 
			
		||||
                    std.json.parseFree(Config, c, std.json.ParseOptions{ .allocator = allocator });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (added.items) |add| {
 | 
			
		||||
            const duped_uri = try std.mem.dupe(allocator, u8, add.Object.getValue("uri").?.String);
 | 
			
		||||
            if (try workspace_folder_configs.put(duped_uri, null)) |old| {
 | 
			
		||||
                allocator.free(old.key);
 | 
			
		||||
                if (old.value) |c| {
 | 
			
		||||
                    std.json.parseFree(Config, c, std.json.ParseOptions{ .allocator = allocator });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try loadWorkspaceConfigs();
 | 
			
		||||
    }
 | 
			
		||||
    // File changes
 | 
			
		||||
    else if (std.mem.eql(u8, method, "textDocument/didOpen")) {
 | 
			
		||||
        const document = params.getValue("textDocument").?.Object;
 | 
			
		||||
@ -601,7 +703,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v
 | 
			
		||||
        const text = document.getValue("text").?.String;
 | 
			
		||||
 | 
			
		||||
        const handle = try document_store.openDocument(uri, text);
 | 
			
		||||
        try publishDiagnostics(handle.*, config);
 | 
			
		||||
        try publishDiagnostics(handle.*, configFromUriOr(uri, config));
 | 
			
		||||
    } else if (std.mem.eql(u8, method, "textDocument/didChange")) {
 | 
			
		||||
        const text_document = params.getValue("textDocument").?.Object;
 | 
			
		||||
        const uri = text_document.getValue("uri").?.String;
 | 
			
		||||
@ -612,8 +714,9 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v
 | 
			
		||||
            return;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        try document_store.applyChanges(handle, content_changes);
 | 
			
		||||
        try publishDiagnostics(handle.*, config);
 | 
			
		||||
        const local_config = configFromUriOr(uri, config);
 | 
			
		||||
        try document_store.applyChanges(handle, content_changes, local_config.zig_lib_path);
 | 
			
		||||
        try publishDiagnostics(handle.*, local_config);
 | 
			
		||||
    } else if (std.mem.eql(u8, method, "textDocument/didSave")) {
 | 
			
		||||
        // noop
 | 
			
		||||
    } else if (std.mem.eql(u8, method, "textDocument/didClose")) {
 | 
			
		||||
@ -641,18 +744,19 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v
 | 
			
		||||
            const pos_index = try handle.document.positionToIndex(pos);
 | 
			
		||||
            const pos_context = documentPositionContext(handle.document, pos_index);
 | 
			
		||||
 | 
			
		||||
            const this_config = configFromUriOr(uri, config);
 | 
			
		||||
            switch (pos_context) {
 | 
			
		||||
                .builtin => try send(types.Response{
 | 
			
		||||
                    .id = .{ .Integer = id },
 | 
			
		||||
                    .result = .{
 | 
			
		||||
                        .CompletionList = .{
 | 
			
		||||
                            .isIncomplete = false,
 | 
			
		||||
                            .items = builtin_completions[@boolToInt(config.enable_snippets)][0..],
 | 
			
		||||
                            .items = builtin_completions[@boolToInt(this_config.enable_snippets)][0..],
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                }),
 | 
			
		||||
                .var_access, .empty => try completeGlobal(id, pos_index, handle, config),
 | 
			
		||||
                .field_access => |start_idx| try completeFieldAccess(id, handle, pos, start_idx, config),
 | 
			
		||||
                .var_access, .empty => try completeGlobal(id, pos_index, handle, this_config),
 | 
			
		||||
                .field_access => |start_idx| try completeFieldAccess(id, handle, pos, start_idx, this_config),
 | 
			
		||||
                else => try respondGeneric(id, no_completions_response),
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
@ -685,7 +789,13 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v
 | 
			
		||||
 | 
			
		||||
            switch (pos_context) {
 | 
			
		||||
                .var_access => try gotoDefinitionGlobal(id, pos_index, handle.*),
 | 
			
		||||
                .field_access => |start_idx| try gotoDefinitionFieldAccess(id, handle, pos, start_idx),
 | 
			
		||||
                .field_access => |start_idx| try gotoDefinitionFieldAccess(
 | 
			
		||||
                    id,
 | 
			
		||||
                    handle,
 | 
			
		||||
                    pos,
 | 
			
		||||
                    start_idx,
 | 
			
		||||
                    configFromUriOr(uri, config),
 | 
			
		||||
                ),
 | 
			
		||||
                else => try respondGeneric(id, null_result_response),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -723,39 +833,31 @@ pub fn main() anyerror!void {
 | 
			
		||||
    var config = Config{};
 | 
			
		||||
    defer std.json.parseFree(Config, config, config_parse_options);
 | 
			
		||||
 | 
			
		||||
    // TODO: Investigate using std.fs.Watch to detect writes to the config and reload it.
 | 
			
		||||
    config_read: {
 | 
			
		||||
        const known_folders = @import("known-folders/known-folders.zig");
 | 
			
		||||
        const res = try known_folders.getPath(allocator, .local_configuration);
 | 
			
		||||
        if (res) |local_config_path| {
 | 
			
		||||
            defer allocator.free(local_config_path);
 | 
			
		||||
            if (loadConfig(local_config_path)) |conf| {
 | 
			
		||||
                config = conf;
 | 
			
		||||
                break :config_read;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var exec_dir_bytes: [std.fs.MAX_PATH_BYTES]u8 = undefined;
 | 
			
		||||
        const exec_dir_path = std.fs.selfExeDirPath(&exec_dir_bytes) catch break :config_read;
 | 
			
		||||
 | 
			
		||||
        var exec_dir = std.fs.cwd().openDir(exec_dir_path, .{}) catch break :config_read;
 | 
			
		||||
        defer exec_dir.close();
 | 
			
		||||
 | 
			
		||||
        const conf_file = exec_dir.openFile("zls.json", .{}) catch break :config_read;
 | 
			
		||||
        defer conf_file.close();
 | 
			
		||||
 | 
			
		||||
        // Max 1MB
 | 
			
		||||
        const file_buf = conf_file.inStream().readAllAlloc(allocator, 0x1000000) catch break :config_read;
 | 
			
		||||
        defer allocator.free(file_buf);
 | 
			
		||||
 | 
			
		||||
        // TODO: Better errors? Doesn't seem like std.json can provide us positions or context.
 | 
			
		||||
        config = std.json.parse(Config, &std.json.TokenStream.init(file_buf), config_parse_options) catch |err| {
 | 
			
		||||
            std.debug.warn("Error while parsing configuration file: {}\nUsing default config.\n", .{err});
 | 
			
		||||
            break :config_read;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (config.zig_lib_path) |zig_lib_path| {
 | 
			
		||||
        if (!std.fs.path.isAbsolute(zig_lib_path)) {
 | 
			
		||||
            std.debug.warn("zig library path is not absolute, defaulting to null.\n", .{});
 | 
			
		||||
            allocator.free(zig_lib_path);
 | 
			
		||||
            config.zig_lib_path = null;
 | 
			
		||||
        if (loadConfig(exec_dir_path)) |conf| {
 | 
			
		||||
            config = conf;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try document_store.init(allocator, config.zig_lib_path);
 | 
			
		||||
    try document_store.init(allocator);
 | 
			
		||||
    defer document_store.deinit();
 | 
			
		||||
 | 
			
		||||
    workspace_folder_configs = std.StringHashMap(?Config).init(allocator);
 | 
			
		||||
    defer workspace_folder_configs.deinit();
 | 
			
		||||
 | 
			
		||||
    // This JSON parser is passed to processJsonRpc and reset.
 | 
			
		||||
    var json_parser = std.json.Parser.init(allocator, false);
 | 
			
		||||
    defer json_parser.deinit();
 | 
			
		||||
 | 
			
		||||
@ -40,9 +40,7 @@ pub const RequestId = union(enum) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Params of a request
 | 
			
		||||
pub const RequestParams = union(enum) {
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
pub const RequestParams = void;
 | 
			
		||||
 | 
			
		||||
pub const NotificationParams = union(enum) {
 | 
			
		||||
    LogMessageParams: LogMessageParams,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user