diff --git a/src/DocumentStore.zig b/src/DocumentStore.zig index 821bdb6d3..f7bc14384 100644 --- a/src/DocumentStore.zig +++ b/src/DocumentStore.zig @@ -23,8 +23,8 @@ config: Config, lock: std.Thread.RwLock = .{}, thread_pool: if (builtin.single_threaded) void else *std.Thread.Pool, handles: std.StringArrayHashMapUnmanaged(*Handle) = .empty, -build_files: std.StringArrayHashMapUnmanaged(*BuildFile) = .empty, -cimports: std.AutoArrayHashMapUnmanaged(Hash, translate_c.Result) = .empty, +build_files: if (supports_build_system) std.StringArrayHashMapUnmanaged(*BuildFile) else void = if (supports_build_system) .empty else {}, +cimports: if (supports_build_system) std.AutoArrayHashMapUnmanaged(Hash, translate_c.Result) else void = if (supports_build_system) .empty else {}, diagnostics_collection: *DiagnosticsCollection, builds_in_progress: std.atomic.Value(i32) = .init(0), transport: ?lsp.AnyTransport = null, @@ -41,6 +41,8 @@ pub const Hash = [Hasher.mac_length]u8; pub const max_document_size = std.math.maxInt(u32); +pub const supports_build_system = std.process.can_spawn; + pub fn computeHash(bytes: []const u8) Hash { var hasher: Hasher = .init(&@splat(0)); hasher.update(bytes); @@ -316,6 +318,7 @@ pub const Handle = struct { /// `DocumentStore.build_files` is guaranteed to contain this Uri. /// Uri memory managed by its build_file pub fn getAssociatedBuildFileUri(self: *Handle, document_store: *DocumentStore) error{OutOfMemory}!?Uri { + comptime std.debug.assert(supports_build_system); switch (try self.getAssociatedBuildFileUri2(document_store)) { .none, .unresolved, @@ -336,6 +339,8 @@ pub const Handle = struct { /// The associated build file (build.zig) has been successfully resolved. resolved: *BuildFile, } { + comptime std.debug.assert(supports_build_system); + self.impl.lock.lock(); defer self.impl.lock.unlock(); @@ -634,16 +639,19 @@ pub fn deinit(self: *DocumentStore) void { } self.handles.deinit(self.allocator); - for (self.build_files.values()) |build_file| { - build_file.deinit(self.allocator); - self.allocator.destroy(build_file); - } - self.build_files.deinit(self.allocator); + if (supports_build_system) { + for (self.build_files.values()) |build_file| { + build_file.deinit(self.allocator); + self.allocator.destroy(build_file); + } + self.build_files.deinit(self.allocator); - for (self.cimports.values()) |*result| { - result.deinit(self.allocator); + for (self.cimports.values()) |*result| { + result.deinit(self.allocator); + } + self.cimports.deinit(self.allocator); } - self.cimports.deinit(self.allocator); + self.* = undefined; } @@ -718,6 +726,7 @@ pub fn getOrLoadHandle(self: *DocumentStore, uri: Uri) ?*Handle { /// **Thread safe** takes a shared lock /// This function does not protect against data races from modifying the BuildFile pub fn getBuildFile(self: *DocumentStore, uri: Uri) ?*BuildFile { + comptime std.debug.assert(supports_build_system); self.lock.lockShared(); defer self.lock.unlockShared(); return self.build_files.get(uri); @@ -727,6 +736,8 @@ pub fn getBuildFile(self: *DocumentStore, uri: Uri) ?*BuildFile { /// **Thread safe** takes an exclusive lock /// This function does not protect against data races from modifying the BuildFile fn getOrLoadBuildFile(self: *DocumentStore, uri: Uri) ?*BuildFile { + comptime std.debug.assert(supports_build_system); + if (self.getBuildFile(uri)) |build_file| return build_file; const new_build_file: *BuildFile = blk: { @@ -754,9 +765,7 @@ fn getOrLoadBuildFile(self: *DocumentStore, uri: Uri) ?*BuildFile { // this code path is only reached when the build file is new - if (std.process.can_spawn) { - self.invalidateBuildFile(new_build_file.uri); - } + self.invalidateBuildFile(new_build_file.uri); return new_build_file; } @@ -817,8 +826,10 @@ pub fn closeDocument(self: *DocumentStore, uri: Uri) void { defer self.lock.unlock(); self.garbageCollectionImports() catch {}; - self.garbageCollectionCImports() catch {}; - self.garbageCollectionBuildFiles() catch {}; + if (supports_build_system) { + self.garbageCollectionCImports() catch {}; + self.garbageCollectionBuildFiles() catch {}; + } } /// Takes ownership of `new_text` which has to be allocated with this DocumentStore's allocator. @@ -864,7 +875,7 @@ pub fn refreshDocumentFromFileSystem(self: *DocumentStore, uri: Uri) !bool { /// Invalidates a build files. /// **Thread safe** takes a shared lock pub fn invalidateBuildFile(self: *DocumentStore, build_file_uri: Uri) void { - comptime std.debug.assert(std.process.can_spawn); + comptime std.debug.assert(supports_build_system); if (self.config.zig_exe_path == null) return; if (self.config.build_runner_path == null) return; @@ -1464,7 +1475,9 @@ fn createDocument(self: *DocumentStore, uri: Uri, text: [:0]const u8, open: bool _ = handle.setOpen(open); - if (isBuildFile(handle.uri) and !isInStd(handle.uri)) { + if (!supports_build_system) { + // nothing to do + } else if (isBuildFile(handle.uri) and !isInStd(handle.uri)) { _ = self.getOrLoadBuildFile(handle.uri); } else if (!isBuiltinFile(handle.uri) and !isInStd(handle.uri)) blk: { const potential_build_files = self.collectPotentialBuildFiles(uri) catch { @@ -1614,6 +1627,8 @@ fn collectDependenciesInternal( const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); + if (!supports_build_system) return; + { if (lock) store.lock.lockShared(); defer if (lock) store.lock.unlockShared(); @@ -1656,6 +1671,8 @@ pub fn collectIncludeDirs( handle: *Handle, include_dirs: *std.ArrayListUnmanaged([]const u8), ) !bool { + comptime std.debug.assert(supports_build_system); + var arena_allocator: std.heap.ArenaAllocator = .init(allocator); defer arena_allocator.deinit(); @@ -1695,6 +1712,8 @@ pub fn collectCMacros( handle: *Handle, c_macros: *std.ArrayListUnmanaged([]const u8), ) !bool { + comptime std.debug.assert(supports_build_system); + const collected_all = switch (try handle.getAssociatedBuildFileUri2(store)) { .none => true, .unresolved => false, @@ -1719,10 +1738,11 @@ pub fn collectCMacros( /// returned memory is owned by DocumentStore /// **Thread safe** takes an exclusive lock pub fn resolveCImport(self: *DocumentStore, handle: *Handle, node: Ast.Node.Index) error{OutOfMemory}!?Uri { + comptime std.debug.assert(supports_build_system); + const tracy_zone = tracy.trace(@src()); defer tracy_zone.end(); - if (!std.process.can_spawn) return null; if (self.config.zig_exe_path == null) return null; if (self.config.zig_lib_dir == null) return null; if (self.config.global_cache_dir == null) return null; @@ -1891,10 +1911,12 @@ pub fn uriFromImportStr(self: *DocumentStore, allocator: std.mem.Allocator, hand return try URI.fromPath(allocator, std_path); } else if (std.mem.eql(u8, import_str, "builtin")) { - if (try handle.getAssociatedBuildFileUri(self)) |build_file_uri| { - const build_file = self.getBuildFile(build_file_uri).?; - if (build_file.builtin_uri) |builtin_uri| { - return try allocator.dupe(u8, builtin_uri); + if (supports_build_system) { + if (try handle.getAssociatedBuildFileUri(self)) |build_file_uri| { + const build_file = self.getBuildFile(build_file_uri).?; + if (build_file.builtin_uri) |builtin_uri| { + return try allocator.dupe(u8, builtin_uri); + } } } if (self.config.builtin_path) |builtin_path| { @@ -1902,6 +1924,8 @@ pub fn uriFromImportStr(self: *DocumentStore, allocator: std.mem.Allocator, hand } return null; } else if (!std.mem.endsWith(u8, import_str, ".zig")) { + if (!supports_build_system) return null; + if (try handle.getAssociatedBuildFileUri(self)) |build_file_uri| blk: { const build_file = self.getBuildFile(build_file_uri).?; const build_config = build_file.tryLockConfig() orelse break :blk; diff --git a/src/Server.zig b/src/Server.zig index ca26f3064..eec394498 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -527,6 +527,7 @@ fn initializeHandler(server: *Server, arena: std.mem.Allocator, request: types.I } } + // TODO Instead of checking `is_test`, possible config paths should be provided by the main function. if (!zig_builtin.is_test) { var maybe_config_result = if (server.config_path) |config_path| configuration.loadFromFile(server.allocator, config_path) @@ -877,9 +878,12 @@ fn removeWorkspace(server: *Server, uri: types.URI) void { fn didChangeWatchedFilesHandler(server: *Server, arena: std.mem.Allocator, notification: types.DidChangeWatchedFilesParams) Error!void { var updated_files: usize = 0; for (notification.changes) |change| { - const file_path = Uri.parse(arena, change.uri) catch |err| { - log.err("failed to parse URI '{s}': {}", .{ change.uri, err }); - continue; + const file_path = Uri.parse(arena, change.uri) catch |err| switch (err) { + error.UnsupportedScheme => continue, + else => { + log.err("failed to parse URI '{s}': {}", .{ change.uri, err }); + continue; + }, }; const file_extension = std.fs.path.extension(file_path); if (!std.mem.eql(u8, file_extension, ".zig") and !std.mem.eql(u8, file_extension, ".zon")) continue; @@ -1079,7 +1083,7 @@ pub fn updateConfiguration( } } - if (new_zig_exe_path or new_zig_lib_path) { + if (DocumentStore.supports_build_system and (new_zig_exe_path or new_zig_lib_path)) { for (server.document_store.cimports.values()) |*result| { result.deinit(server.document_store.allocator); } @@ -1148,14 +1152,6 @@ pub fn updateConfiguration( } } - if (server.config.prefer_ast_check_as_child_process) { - if (!std.process.can_spawn) { - log.info("'prefer_ast_check_as_child_process' is ignored because the '{s}' operating system can't spawn child processes", .{@tagName(zig_builtin.target.os.tag)}); - } else if (server.status == .initialized and server.config.zig_exe_path == null) { - log.warn("'prefer_ast_check_as_child_process' is ignored because Zig could not be found", .{}); - } - } - if (server.config.enable_build_on_save orelse false) { if (!BuildOnSaveSupport.isSupportedComptime()) { // This message is not very helpful but it relatively uncommon to happen anyway. diff --git a/src/analysis.zig b/src/analysis.zig index 488722439..1220d4d83 100644 --- a/src/analysis.zig +++ b/src/analysis.zig @@ -2186,6 +2186,7 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e } if (std.mem.eql(u8, call_name, "@cImport")) { + if (!DocumentStore.supports_build_system) return null; const cimport_uri = (try analyser.store.resolveCImport(handle, node)) orelse return null; const new_handle = analyser.store.getOrLoadHandle(cimport_uri) orelse return null; diff --git a/src/configuration.zig b/src/configuration.zig index 33fcd508e..0c4bdb385 100644 --- a/src/configuration.zig +++ b/src/configuration.zig @@ -10,19 +10,21 @@ const Config = @import("Config.zig"); const logger = std.log.scoped(.config); -pub fn getLocalConfigPath(allocator: std.mem.Allocator) known_folders.Error!?[]const u8 { +fn getLocalConfigPath(allocator: std.mem.Allocator) known_folders.Error!?[]const u8 { const folder_path = try known_folders.getPath(allocator, .local_configuration) orelse return null; defer allocator.free(folder_path); return try std.fs.path.join(allocator, &.{ folder_path, "zls.json" }); } -pub fn getGlobalConfigPath(allocator: std.mem.Allocator) known_folders.Error!?[]const u8 { +fn getGlobalConfigPath(allocator: std.mem.Allocator) known_folders.Error!?[]const u8 { const folder_path = try known_folders.getPath(allocator, .global_configuration) orelse return null; defer allocator.free(folder_path); return try std.fs.path.join(allocator, &.{ folder_path, "zls.json" }); } pub fn load(allocator: std.mem.Allocator) error{OutOfMemory}!LoadConfigResult { + if (builtin.target.os.tag == .wasi) return .not_found; + const local_config_path = getLocalConfigPath(allocator) catch |err| blk: { logger.warn("failed to resolve local configuration path: {}", .{err}); break :blk null; diff --git a/src/features/completions.zig b/src/features/completions.zig index 0a9b0b1e7..1130e1511 100644 --- a/src/features/completions.zig +++ b/src/features/completions.zig @@ -744,6 +744,7 @@ fn completeFileSystemStringLiteral(builder: *Builder, pos_context: Analyser.Posi if (std.fs.path.isAbsolute(completing) and pos_context != .import_string_literal) { try search_paths.append(builder.arena, completing); } else if (pos_context == .cinclude_string_literal) { + if (!DocumentStore.supports_build_system) return; _ = store.collectIncludeDirs(builder.arena, builder.orig_handle, &search_paths) catch |err| { log.err("failed to resolve include paths: {}", .{err}); return; @@ -804,31 +805,35 @@ fn completeFileSystemStringLiteral(builder: *Builder, pos_context: Analyser.Posi } if (completing.len == 0 and pos_context == .import_string_literal) { - if (try builder.orig_handle.getAssociatedBuildFileUri(store)) |uri| blk: { - const build_file = store.getBuildFile(uri).?; - const build_config = build_file.tryLockConfig() orelse break :blk; - defer build_file.unlockConfig(); - - try completions.ensureUnusedCapacity(builder.arena, build_config.packages.len); - for (build_config.packages) |pkg| { - completions.putAssumeCapacity(.{ - .label = pkg.name, - .kind = .Module, - .detail = pkg.path, - }, {}); - } - } else if (DocumentStore.isBuildFile(builder.orig_handle.uri)) blk: { - const build_file = store.getBuildFile(builder.orig_handle.uri) orelse break :blk; - const build_config = build_file.tryLockConfig() orelse break :blk; - defer build_file.unlockConfig(); - - try completions.ensureUnusedCapacity(builder.arena, build_config.deps_build_roots.len); - for (build_config.deps_build_roots) |dbr| { - completions.putAssumeCapacity(.{ - .label = dbr.name, - .kind = .Module, - .detail = dbr.path, - }, {}); + no_modules: { + if (!DocumentStore.supports_build_system) break :no_modules; + + if (try builder.orig_handle.getAssociatedBuildFileUri(store)) |uri| { + const build_file = store.getBuildFile(uri).?; + const build_config = build_file.tryLockConfig() orelse break :no_modules; + defer build_file.unlockConfig(); + + try completions.ensureUnusedCapacity(builder.arena, build_config.packages.len); + for (build_config.packages) |pkg| { + completions.putAssumeCapacity(.{ + .label = pkg.name, + .kind = .Module, + .detail = pkg.path, + }, {}); + } + } else if (DocumentStore.isBuildFile(builder.orig_handle.uri)) { + const build_file = store.getBuildFile(builder.orig_handle.uri) orelse break :no_modules; + const build_config = build_file.tryLockConfig() orelse break :no_modules; + defer build_file.unlockConfig(); + + try completions.ensureUnusedCapacity(builder.arena, build_config.deps_build_roots.len); + for (build_config.deps_build_roots) |dbr| { + completions.putAssumeCapacity(.{ + .label = dbr.name, + .kind = .Module, + .detail = dbr.path, + }, {}); + } } } diff --git a/src/features/goto.zig b/src/features/goto.zig index 6850ddd90..8215f5087 100644 --- a/src/features/goto.zig +++ b/src/features/goto.zig @@ -129,6 +129,8 @@ fn gotoDefinitionBuiltin( const name_loc = offsets.tokenIndexToLoc(handle.tree.source, loc.start); const name = offsets.locToSlice(handle.tree.source, name_loc); if (std.mem.eql(u8, name, "@cImport")) { + if (!DocumentStore.supports_build_system) return null; + const tree = handle.tree; const index = for (handle.cimports.items(.node), 0..) |cimport_node, index| { const main_token = tree.nodeMainToken(cimport_node); @@ -205,6 +207,8 @@ fn gotoDefinitionString( .cinclude_string_literal => try URI.fromPath( arena, blk: { + if (!DocumentStore.supports_build_system) return null; + if (std.fs.path.isAbsolute(import_str)) break :blk import_str; var include_dirs: std.ArrayListUnmanaged([]const u8) = .empty; _ = document_store.collectIncludeDirs(arena, handle, &include_dirs) catch |err| { diff --git a/src/main.zig b/src/main.zig index a0af2c754..890ab2474 100644 --- a/src/main.zig +++ b/src/main.zig @@ -100,6 +100,7 @@ fn logFn( } fn defaultLogFilePath(allocator: std.mem.Allocator) std.mem.Allocator.Error!?[]const u8 { + if (zig_builtin.target.os.tag == .wasi) return null; const cache_path = known_folders.getPath(allocator, .cache) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.ParseError => return null, @@ -282,7 +283,7 @@ fn parseArgs(allocator: std.mem.Allocator) ParseArgsError!ParseArgsResult { } } - if (std.io.getStdIn().isTty()) { + if (zig_builtin.target.os.tag != .wasi and std.io.getStdIn().isTty()) { log.warn("ZLS is not a CLI tool, it communicates over the Language Server Protocol.", .{}); log.warn("Did you mean to run 'zls --help'?", .{}); log.warn("", .{}); diff --git a/src/uri.zig b/src/uri.zig index bd4072026..1188fd269 100644 --- a/src/uri.zig +++ b/src/uri.zig @@ -69,9 +69,9 @@ test fromPath { /// Parses a Uri and returns the unescaped path /// Caller owns the returned memory -pub fn parse(allocator: std.mem.Allocator, str: []const u8) (std.Uri.ParseError || error{OutOfMemory})![]u8 { +pub fn parse(allocator: std.mem.Allocator, str: []const u8) (std.Uri.ParseError || error{ UnsupportedScheme, OutOfMemory })![]u8 { var uri = try std.Uri.parse(str); - if (!std.mem.eql(u8, uri.scheme, "file")) return error.InvalidFormat; + if (!std.mem.eql(u8, uri.scheme, "file")) return error.UnsupportedScheme; if (builtin.os.tag == .windows and uri.path.percent_encoded.len != 0 and uri.path.percent_encoded[0] == '/') { uri.path.percent_encoded = uri.path.percent_encoded[1..]; }