diff --git a/README.md b/README.md index f152560..312d49c 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ The following options are currently available. | --- | --- | --- | --- | | `enable_snippets` | `bool` | `false` | Enables snippet completion, set to false for compatibility with language clients that do not support snippets (such as ale). | | `zig_lib_path` | `?[]const u8` | `null` | zig library path, e.g. `/path/to/zig/lib/zig`, used to analyze std library imports. | +| `zig_exe_path` | `?[]const u8` | `null` | zig executable path, e.g. `/path/to/zig/bin/zig`, used to run the custom build runner. If `null`, zig is looked up in `PATH`. Will be used to infer the zig standard library path if none is provided. | | `warn_style` | `bool` | `false` | Enables warnings for style *guideline* mismatches | | `build_runner_path` | `?[]const u8` | `null` | Path to the build_runner.zig file provided by zls. This option must be present in one of the global configuration files to have any effect. `null` is equivalent to `${executable_directory}/build_runner.zig` | diff --git a/src/config.zig b/src/config.zig index c209875..2df19cd 100644 --- a/src/config.zig +++ b/src/config.zig @@ -6,6 +6,10 @@ enable_snippets: bool = false, /// zig library path zig_lib_path: ?[]const u8 = null, +/// 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, + /// Whether to pay attention to style issues. This is opt-in since the style /// guide explicitly states that the style info provided is a guideline only. warn_style: bool = false, diff --git a/src/document_store.zig b/src/document_store.zig index 5032b08..c1c1568 100644 --- a/src/document_store.zig +++ b/src/document_store.zig @@ -74,7 +74,7 @@ pub const TagStore = struct { allocator: *std.mem.Allocator, handles: std.StringHashMap(*Handle), -has_zig: bool, +zig_exe_path: ?[]const u8, build_files: std.ArrayListUnmanaged(*BuildFile), build_runner_path: []const u8, @@ -84,12 +84,12 @@ enum_completions: TagStore, pub fn init( self: *DocumentStore, allocator: *std.mem.Allocator, - has_zig: bool, + zig_exe_path: ?[]const u8, build_runner_path: []const u8, ) !void { self.allocator = allocator; self.handles = std.StringHashMap(*Handle).init(allocator); - self.has_zig = has_zig; + self.zig_exe_path = zig_exe_path; self.build_files = .{}; self.build_runner_path = build_runner_path; self.error_completions = TagStore.init(allocator); @@ -100,12 +100,14 @@ const LoadPackagesContext = struct { build_file: *BuildFile, allocator: *std.mem.Allocator, build_runner_path: []const u8, + zig_exe_path: []const u8, }; fn loadPackages(context: LoadPackagesContext) !void { const allocator = context.allocator; const build_file = context.build_file; const build_runner_path = context.build_runner_path; + const zig_exe_path = context.zig_exe_path; const directory_path = try URI.parse(allocator, build_file.uri[0 .. build_file.uri.len - "build.zig".len]); defer allocator.free(directory_path); @@ -134,7 +136,7 @@ fn loadPackages(context: LoadPackagesContext) !void { const zig_run_result = try std.ChildProcess.exec(.{ .allocator = allocator, - .argv = &[_][]const u8{ "zig", "run", "build_runner.zig" }, + .argv = &[_][]const u8{ zig_exe_path, "run", "build_runner.zig" }, .cwd = directory_path, }); @@ -204,7 +206,7 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand // TODO: Better logic for detecting std or subdirectories? const in_std = std.mem.indexOf(u8, uri, "/std/") != null; - if (self.has_zig and std.mem.endsWith(u8, uri, "/build.zig") and !in_std) { + if (self.zig_exe_path != null and std.mem.endsWith(u8, uri, "/build.zig") and !in_std) { std.debug.warn("Document is a build file, extracting packages...\n", .{}); // This is a build file. var build_file = try self.allocator.create(BuildFile); @@ -225,10 +227,11 @@ fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Hand .build_file = build_file, .allocator = self.allocator, .build_runner_path = self.build_runner_path, + .zig_exe_path = self.zig_exe_path.?, }) catch |err| { std.debug.warn("Failed to load packages of build file {} (error: {})\n", .{ build_file.uri, err }); }; - } else if (self.has_zig and !in_std) associate_build_file: { + } else if (self.zig_exe_path != null and !in_std) associate_build_file: { // Look into build files to see if we already have one that fits for (self.build_files.items) |build_file| { const build_file_base_uri = build_file.uri[0 .. std.mem.lastIndexOfScalar(u8, build_file.uri, '/').? + 1]; @@ -471,6 +474,7 @@ pub fn applyChanges( .build_file = build_file, .allocator = self.allocator, .build_runner_path = self.build_runner_path, + .zig_exe_path = self.zig_exe_path.?, }) catch |err| { std.debug.warn("Failed to load packages of build file {} (error: {})\n", .{ build_file.uri, err }); }; diff --git a/src/main.zig b/src/main.zig index 6502f3c..fdb80a1 100644 --- a/src/main.zig +++ b/src/main.zig @@ -533,9 +533,7 @@ fn documentSymbol(id: i64, handle: *DocumentStore.Handle) !void { try send(types.Response{ .id = .{ .Integer = id }, - .result = .{ - .DocumentSymbols = try analysis.getDocumentSymbols(&arena.allocator, handle.tree) - }, + .result = .{ .DocumentSymbols = try analysis.getDocumentSymbols(&arena.allocator, handle.tree) }, }); } @@ -760,7 +758,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v }), .var_access, .empty => try completeGlobal(id, pos_index, handle, this_config), .field_access => |range| try completeFieldAccess(id, handle, pos, range, this_config), - .global_error_set => try send(types.Response{ + .global_error_set => try send(types.Response{ .id = .{ .Integer = id }, .result = .{ .CompletionList = .{ @@ -769,7 +767,7 @@ fn processJsonRpc(parser: *std.json.Parser, json: []const u8, config: Config) !v }, }, }), - .enum_literal => try send(types.Response{ + .enum_literal => try send(types.Response{ .id = .{ .Integer = id }, .result = .{ .CompletionList = .{ @@ -930,11 +928,19 @@ pub fn main() anyerror!void { } // Find the zig executable in PATH - var has_zig = false; + var zig_exe_path: ?[]const u8 = null; + defer if (zig_exe_path) |exe_path| allocator.free(exe_path); - // TODO: Should we just spawn a child process that calls "zig version" or something - // and check that way? find_zig: { + if (config.zig_exe_path) |exe_path| { + if (std.fs.path.isAbsolute(exe_path)) { + zig_exe_path = try std.mem.dupe(allocator, u8, exe_path); + break :find_zig; + } + + std.debug.warn("zig path `{}` is not absolute, will look in path\n", .{exe_path}); + } + const env_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) { error.EnvironmentVariableNotFound => { std.debug.warn("Could not get PATH.\n", .{}); @@ -957,21 +963,32 @@ pub fn main() anyerror!void { defer allocator.free(full_path); var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - const zig_path = std.os.realpath(full_path, &buf) catch continue; - std.debug.warn("Found zig in PATH: {}\n", .{zig_path}); - has_zig = true; + zig_exe_path = std.os.realpath(full_path, &buf) catch continue; + std.debug.warn("Found zig in PATH: {}\n", .{zig_exe_path}); break :find_zig; } } + if (zig_exe_path) |exe_path| { + std.debug.warn("Using zig executable {}\n", .{exe_path}); + if (config.zig_lib_path == null) { + // Set the lib path relative to the executable path. + config.zig_lib_path = try std.fs.path.resolve(allocator, &[_][]const u8{ + std.fs.path.dirname(exe_path).?, "./lib/zig", + }); + + std.debug.warn("Resolved standard library from executable: {}\n", .{config.zig_lib_path}); + } + } + if (config.build_runner_path) |build_runner_path| { - try document_store.init(allocator, has_zig, try std.mem.dupe(allocator, u8, build_runner_path)); + try document_store.init(allocator, zig_exe_path, try std.mem.dupe(allocator, u8, build_runner_path)); } else { var exe_dir_bytes: [std.fs.MAX_PATH_BYTES]u8 = undefined; const exe_dir_path = try std.fs.selfExeDirPath(&exe_dir_bytes); const build_runner_path = try std.fs.path.resolve(allocator, &[_][]const u8{ exe_dir_path, "build_runner.zig" }); - try document_store.init(allocator, has_zig, build_runner_path); + try document_store.init(allocator, zig_exe_path, build_runner_path); } defer document_store.deinit();