2020-05-14 00:10:41 +01:00
|
|
|
const std = @import("std");
|
|
|
|
const types = @import("types.zig");
|
2020-05-14 02:54:05 +01:00
|
|
|
const URI = @import("uri.zig");
|
2020-05-14 12:51:07 +01:00
|
|
|
const analysis = @import("analysis.zig");
|
2020-07-03 00:31:28 +01:00
|
|
|
const offsets = @import("offsets.zig");
|
2020-08-14 11:41:34 +01:00
|
|
|
const log = std.log.scoped(.doc_store);
|
2020-05-14 00:10:41 +01:00
|
|
|
|
|
|
|
const DocumentStore = @This();
|
|
|
|
|
2020-05-25 15:24:44 +01:00
|
|
|
const BuildFile = struct {
|
|
|
|
const Pkg = struct {
|
|
|
|
name: []const u8,
|
|
|
|
uri: []const u8,
|
|
|
|
};
|
|
|
|
|
2020-05-25 18:42:58 +01:00
|
|
|
refs: usize,
|
2020-05-25 15:24:44 +01:00
|
|
|
uri: []const u8,
|
|
|
|
packages: std.ArrayListUnmanaged(Pkg),
|
|
|
|
};
|
|
|
|
|
2020-05-14 00:10:41 +01:00
|
|
|
pub const Handle = struct {
|
|
|
|
document: types.TextDocument,
|
|
|
|
count: usize,
|
2021-03-30 13:41:59 +01:00
|
|
|
/// Contains one entry for every import in the document
|
|
|
|
import_uris: []const []const u8,
|
|
|
|
/// Items in thsi array list come from `import_uris`
|
|
|
|
imports_used: std.ArrayListUnmanaged([]const u8),
|
2021-02-26 20:26:52 +00:00
|
|
|
tree: std.zig.ast.Tree,
|
2020-06-10 14:12:00 +01:00
|
|
|
document_scope: analysis.DocumentScope,
|
2020-05-14 00:10:41 +01:00
|
|
|
|
2020-05-25 15:24:44 +01:00
|
|
|
associated_build_file: ?*BuildFile,
|
|
|
|
is_build_file: ?*BuildFile,
|
|
|
|
|
2020-05-14 00:10:41 +01:00
|
|
|
pub fn uri(handle: Handle) []const u8 {
|
|
|
|
return handle.document.uri;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
allocator: *std.mem.Allocator,
|
2020-05-18 08:28:36 +01:00
|
|
|
handles: std.StringHashMap(*Handle),
|
2020-05-30 21:36:18 +01:00
|
|
|
zig_exe_path: ?[]const u8,
|
2020-05-25 15:24:44 +01:00
|
|
|
build_files: std.ArrayListUnmanaged(*BuildFile),
|
2020-05-25 17:33:08 +01:00
|
|
|
build_runner_path: []const u8,
|
2021-01-12 11:10:51 +00:00
|
|
|
build_runner_cache_path: []const u8,
|
2020-06-10 17:54:01 +01:00
|
|
|
std_uri: ?[]const u8,
|
2020-05-14 00:10:41 +01:00
|
|
|
|
2020-05-25 17:33:08 +01:00
|
|
|
pub fn init(
|
|
|
|
self: *DocumentStore,
|
|
|
|
allocator: *std.mem.Allocator,
|
2020-05-30 21:36:18 +01:00
|
|
|
zig_exe_path: ?[]const u8,
|
2020-05-25 17:33:08 +01:00
|
|
|
build_runner_path: []const u8,
|
2021-01-12 11:10:51 +00:00
|
|
|
build_runner_cache_path: []const u8,
|
2020-06-10 17:54:01 +01:00
|
|
|
zig_lib_path: ?[]const u8,
|
2020-05-25 17:33:08 +01:00
|
|
|
) !void {
|
2020-05-14 00:10:41 +01:00
|
|
|
self.allocator = allocator;
|
2020-05-18 08:28:36 +01:00
|
|
|
self.handles = std.StringHashMap(*Handle).init(allocator);
|
2020-05-30 21:36:18 +01:00
|
|
|
self.zig_exe_path = zig_exe_path;
|
2020-05-25 15:24:44 +01:00
|
|
|
self.build_files = .{};
|
2020-05-25 17:33:08 +01:00
|
|
|
self.build_runner_path = build_runner_path;
|
2021-01-12 11:10:51 +00:00
|
|
|
self.build_runner_cache_path = build_runner_cache_path;
|
2020-06-10 17:54:01 +01:00
|
|
|
self.std_uri = try stdUriFromLibPath(allocator, zig_lib_path);
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|
|
|
|
|
2020-05-25 17:33:08 +01:00
|
|
|
const LoadPackagesContext = struct {
|
|
|
|
build_file: *BuildFile,
|
|
|
|
allocator: *std.mem.Allocator,
|
|
|
|
build_runner_path: []const u8,
|
2021-01-12 11:10:51 +00:00
|
|
|
build_runner_cache_path: []const u8,
|
2020-05-30 21:36:18 +01:00
|
|
|
zig_exe_path: []const u8,
|
2020-05-25 17:33:08 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
fn loadPackages(context: LoadPackagesContext) !void {
|
|
|
|
const allocator = context.allocator;
|
|
|
|
const build_file = context.build_file;
|
|
|
|
const build_runner_path = context.build_runner_path;
|
2021-01-12 11:10:51 +00:00
|
|
|
const build_runner_cache_path = context.build_runner_cache_path;
|
2020-05-30 21:36:18 +01:00
|
|
|
const zig_exe_path = context.zig_exe_path;
|
2020-05-25 17:33:08 +01:00
|
|
|
|
2021-01-12 11:10:51 +00:00
|
|
|
const build_file_path = try URI.parse(allocator, build_file.uri);
|
|
|
|
defer allocator.free(build_file_path);
|
2021-01-31 10:24:55 +00:00
|
|
|
const directory_path = build_file_path[0 .. build_file_path.len - "build.zig".len];
|
2020-05-25 17:33:08 +01:00
|
|
|
|
|
|
|
const zig_run_result = try std.ChildProcess.exec(.{
|
2020-06-29 00:03:51 +01:00
|
|
|
.allocator = allocator,
|
2021-01-12 11:10:51 +00:00
|
|
|
.argv = &[_][]const u8{
|
|
|
|
zig_exe_path,
|
|
|
|
"run",
|
|
|
|
build_runner_path,
|
|
|
|
"--cache-dir",
|
|
|
|
build_runner_cache_path,
|
|
|
|
"--pkg-begin",
|
|
|
|
"@build@",
|
|
|
|
build_file_path,
|
|
|
|
"--pkg-end",
|
|
|
|
},
|
2020-05-25 17:33:08 +01:00
|
|
|
});
|
|
|
|
|
2020-06-29 00:03:51 +01:00
|
|
|
defer {
|
|
|
|
allocator.free(zig_run_result.stdout);
|
|
|
|
allocator.free(zig_run_result.stderr);
|
|
|
|
}
|
2020-05-25 17:33:08 +01:00
|
|
|
|
|
|
|
switch (zig_run_result.term) {
|
|
|
|
.Exited => |exit_code| {
|
|
|
|
if (exit_code == 0) {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Finished zig run for build file {s}", .{build_file.uri});
|
2020-05-25 17:33:08 +01:00
|
|
|
|
|
|
|
for (build_file.packages.items) |old_pkg| {
|
|
|
|
allocator.free(old_pkg.name);
|
|
|
|
allocator.free(old_pkg.uri);
|
|
|
|
}
|
|
|
|
|
2021-01-07 08:35:36 +00:00
|
|
|
build_file.packages.shrinkAndFree(allocator, 0);
|
2020-05-25 17:33:08 +01:00
|
|
|
var line_it = std.mem.split(zig_run_result.stdout, "\n");
|
|
|
|
while (line_it.next()) |line| {
|
|
|
|
if (std.mem.indexOfScalar(u8, line, '\x00')) |zero_byte_idx| {
|
|
|
|
const name = line[0..zero_byte_idx];
|
|
|
|
const rel_path = line[zero_byte_idx + 1 ..];
|
|
|
|
|
|
|
|
const pkg_abs_path = try std.fs.path.resolve(allocator, &[_][]const u8{ directory_path, rel_path });
|
|
|
|
defer allocator.free(pkg_abs_path);
|
|
|
|
|
|
|
|
const pkg_uri = try URI.fromPath(allocator, pkg_abs_path);
|
|
|
|
errdefer allocator.free(pkg_uri);
|
|
|
|
|
|
|
|
const duped_name = try std.mem.dupe(allocator, u8, name);
|
|
|
|
errdefer allocator.free(duped_name);
|
|
|
|
|
|
|
|
(try build_file.packages.addOne(allocator)).* = .{
|
|
|
|
.name = duped_name,
|
|
|
|
.uri = pkg_uri,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2020-05-27 11:41:50 +01:00
|
|
|
else => return error.RunFailed,
|
2020-05-25 17:33:08 +01:00
|
|
|
}
|
|
|
|
}
|
2020-05-25 15:24:44 +01:00
|
|
|
|
2020-05-14 15:22:15 +01:00
|
|
|
/// This function asserts the document is not open yet and takes ownership
|
2020-05-14 09:51:49 +01:00
|
|
|
/// of the uri and text passed in.
|
2020-05-25 17:33:08 +01:00
|
|
|
fn newDocument(self: *DocumentStore, uri: []const u8, text: []u8) anyerror!*Handle {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Opened document: {s}", .{uri});
|
2020-05-14 09:51:49 +01:00
|
|
|
|
2020-05-18 09:37:15 +01:00
|
|
|
var handle = try self.allocator.create(Handle);
|
|
|
|
errdefer self.allocator.destroy(handle);
|
|
|
|
|
2021-02-26 20:26:52 +00:00
|
|
|
var tree = try std.zig.parse(self.allocator, text);
|
|
|
|
errdefer tree.deinit(self.allocator);
|
2020-06-10 14:12:00 +01:00
|
|
|
|
2021-03-29 12:02:58 +01:00
|
|
|
var document_scope = try analysis.makeDocumentScope(self.allocator, tree);
|
2020-06-10 14:12:00 +01:00
|
|
|
errdefer document_scope.deinit(self.allocator);
|
|
|
|
|
2020-05-18 09:37:15 +01:00
|
|
|
handle.* = Handle{
|
2020-05-14 09:51:49 +01:00
|
|
|
.count = 1,
|
2021-03-30 13:41:59 +01:00
|
|
|
.import_uris = &.{},
|
|
|
|
.imports_used = .{},
|
2020-05-14 09:51:49 +01:00
|
|
|
.document = .{
|
|
|
|
.uri = uri,
|
|
|
|
.text = text,
|
|
|
|
.mem = text,
|
|
|
|
},
|
2020-06-10 14:12:00 +01:00
|
|
|
.tree = tree,
|
|
|
|
.document_scope = document_scope,
|
2020-05-25 15:24:44 +01:00
|
|
|
.associated_build_file = null,
|
|
|
|
.is_build_file = null,
|
2020-05-14 09:51:49 +01:00
|
|
|
};
|
2020-05-24 17:00:21 +01:00
|
|
|
|
2020-05-25 17:33:08 +01:00
|
|
|
// TODO: Better logic for detecting std or subdirectories?
|
2020-05-25 15:48:25 +01:00
|
|
|
const in_std = std.mem.indexOf(u8, uri, "/std/") != null;
|
2020-05-30 21:36:18 +01:00
|
|
|
if (self.zig_exe_path != null and std.mem.endsWith(u8, uri, "/build.zig") and !in_std) {
|
2020-11-15 19:32:27 +00:00
|
|
|
log.debug("Document is a build file, extracting packages...", .{});
|
2020-05-25 15:24:44 +01:00
|
|
|
// This is a build file.
|
|
|
|
var build_file = try self.allocator.create(BuildFile);
|
2020-05-25 15:48:25 +01:00
|
|
|
errdefer self.allocator.destroy(build_file);
|
2020-05-25 15:24:44 +01:00
|
|
|
|
|
|
|
build_file.* = .{
|
2020-05-25 18:42:58 +01:00
|
|
|
.refs = 1,
|
2020-05-25 15:24:44 +01:00
|
|
|
.uri = try std.mem.dupe(self.allocator, u8, uri),
|
|
|
|
.packages = .{},
|
|
|
|
};
|
|
|
|
|
2020-05-25 15:48:25 +01:00
|
|
|
try self.build_files.append(self.allocator, build_file);
|
2020-05-25 15:24:44 +01:00
|
|
|
handle.is_build_file = build_file;
|
2020-05-25 17:33:08 +01:00
|
|
|
|
|
|
|
// TODO: Do this in a separate thread?
|
|
|
|
// It can take quite long.
|
2020-05-25 18:21:53 +01:00
|
|
|
loadPackages(.{
|
2020-05-25 17:33:08 +01:00
|
|
|
.build_file = build_file,
|
|
|
|
.allocator = self.allocator,
|
|
|
|
.build_runner_path = self.build_runner_path,
|
2021-01-12 11:10:51 +00:00
|
|
|
.build_runner_cache_path = self.build_runner_cache_path,
|
2020-05-30 21:36:18 +01:00
|
|
|
.zig_exe_path = self.zig_exe_path.?,
|
2020-05-27 11:41:50 +01:00
|
|
|
}) catch |err| {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Failed to load packages of build file {s} (error: {})", .{ build_file.uri, err });
|
2020-05-25 18:21:53 +01:00
|
|
|
};
|
2021-01-31 10:24:55 +00:00
|
|
|
} else if (self.zig_exe_path != null and !in_std) {
|
2021-01-31 10:35:08 +00:00
|
|
|
// Look into build files and keep the one that lives closest to the document in the directory structure
|
|
|
|
var candidate: ?*BuildFile = null;
|
|
|
|
{
|
|
|
|
var uri_chars_matched: usize = 0;
|
|
|
|
for (self.build_files.items) |build_file| {
|
|
|
|
const build_file_base_uri = build_file.uri[0 .. std.mem.lastIndexOfScalar(u8, build_file.uri, '/').? + 1];
|
|
|
|
|
|
|
|
if (build_file_base_uri.len > uri_chars_matched and std.mem.startsWith(u8, uri, build_file_base_uri)) {
|
|
|
|
uri_chars_matched = build_file_base_uri.len;
|
|
|
|
candidate = build_file;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (candidate) |build_file| {
|
|
|
|
log.debug("Found a candidate associated build file: `{s}`", .{build_file.uri});
|
2020-05-25 15:24:44 +01:00
|
|
|
}
|
2021-01-31 10:35:08 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 10:24:55 +00:00
|
|
|
// Then, try to find the closest build file.
|
2020-05-25 15:24:44 +01:00
|
|
|
var curr_path = try URI.parse(self.allocator, uri);
|
|
|
|
defer self.allocator.free(curr_path);
|
|
|
|
while (true) {
|
2021-01-31 10:24:55 +00:00
|
|
|
if (curr_path.len == 0) break;
|
2020-05-25 15:24:44 +01:00
|
|
|
|
|
|
|
if (std.mem.lastIndexOfScalar(u8, curr_path[0 .. curr_path.len - 1], std.fs.path.sep)) |idx| {
|
|
|
|
// This includes the last separator
|
2020-05-25 17:33:08 +01:00
|
|
|
curr_path = curr_path[0 .. idx + 1];
|
2020-05-25 15:24:44 +01:00
|
|
|
|
2021-01-31 10:24:55 +00:00
|
|
|
// Try to open the folder, then the file.
|
2020-07-08 02:05:44 +01:00
|
|
|
var folder = std.fs.cwd().openDir(curr_path, .{}) catch |err| switch (err) {
|
2020-07-07 22:22:03 +01:00
|
|
|
error.FileNotFound => continue,
|
|
|
|
else => return err,
|
|
|
|
};
|
2020-06-28 11:58:51 +01:00
|
|
|
defer folder.close();
|
|
|
|
|
2021-01-31 10:24:55 +00:00
|
|
|
var build_file = folder.openFile("build.zig", .{}) catch |err| switch (err) {
|
2020-06-28 11:58:51 +01:00
|
|
|
error.FileNotFound, error.AccessDenied => continue,
|
|
|
|
else => return err,
|
|
|
|
};
|
2021-01-31 10:24:55 +00:00
|
|
|
defer build_file.close();
|
2020-05-25 15:24:44 +01:00
|
|
|
|
2021-01-31 10:24:55 +00:00
|
|
|
// Calculate build file's URI
|
2020-06-28 11:58:51 +01:00
|
|
|
var candidate_path = try std.mem.concat(self.allocator, u8, &[_][]const u8{ curr_path, "build.zig" });
|
|
|
|
defer self.allocator.free(candidate_path);
|
2020-05-25 15:24:44 +01:00
|
|
|
const build_file_uri = try URI.fromPath(self.allocator, candidate_path);
|
|
|
|
errdefer self.allocator.free(build_file_uri);
|
|
|
|
|
2021-01-31 10:24:55 +00:00
|
|
|
if (candidate) |candidate_build_file| {
|
|
|
|
// Check if it is the same as the current candidate we got from the existing build files.
|
|
|
|
// If it isn't, we need to read the file and make a new build file.
|
|
|
|
if (std.mem.eql(u8, candidate_build_file.uri, build_file_uri)) {
|
|
|
|
self.allocator.free(build_file_uri);
|
|
|
|
break;
|
|
|
|
}
|
2020-06-15 01:59:49 +01:00
|
|
|
}
|
2021-01-31 10:24:55 +00:00
|
|
|
|
|
|
|
// Read the build file, create a new document, set the candidate to the new build file.
|
|
|
|
const build_file_text = try build_file.readToEndAlloc(self.allocator, std.math.maxInt(usize));
|
|
|
|
errdefer self.allocator.free(build_file_text);
|
|
|
|
|
|
|
|
const build_file_handle = try self.newDocument(build_file_uri, build_file_text);
|
|
|
|
candidate = build_file_handle.is_build_file.?;
|
2020-05-25 15:24:44 +01:00
|
|
|
break;
|
2021-01-31 10:24:55 +00:00
|
|
|
} else break;
|
|
|
|
}
|
|
|
|
// Finally, associate the candidate build file, if any, to the new document.
|
|
|
|
if (candidate) |build_file| {
|
|
|
|
build_file.refs += 1;
|
|
|
|
handle.associated_build_file = build_file;
|
2021-01-31 10:35:08 +00:00
|
|
|
log.debug("Associated build file `{s}` to document `{s}`", .{ build_file.uri, handle.uri() });
|
2020-05-25 15:24:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-24 17:00:21 +01:00
|
|
|
try self.handles.putNoClobber(uri, handle);
|
|
|
|
return handle;
|
2020-05-14 09:51:49 +01:00
|
|
|
}
|
|
|
|
|
2020-05-14 00:10:41 +01:00
|
|
|
pub fn openDocument(self: *DocumentStore, uri: []const u8, text: []const u8) !*Handle {
|
2020-07-05 22:56:41 +01:00
|
|
|
if (self.handles.getEntry(uri)) |entry| {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Document already open: {s}, incrementing count", .{uri});
|
2020-05-14 00:10:41 +01:00
|
|
|
entry.value.count += 1;
|
2020-05-25 18:42:58 +01:00
|
|
|
if (entry.value.is_build_file) |build_file| {
|
|
|
|
build_file.refs += 1;
|
|
|
|
}
|
2020-11-15 19:32:27 +00:00
|
|
|
log.debug("New count: {}", .{entry.value.count});
|
2020-05-18 08:28:36 +01:00
|
|
|
return entry.value;
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const duped_text = try std.mem.dupe(self.allocator, u8, text);
|
|
|
|
errdefer self.allocator.free(duped_text);
|
|
|
|
const duped_uri = try std.mem.dupe(self.allocator, u8, uri);
|
|
|
|
errdefer self.allocator.free(duped_uri);
|
|
|
|
|
2020-05-17 15:39:04 +01:00
|
|
|
return try self.newDocument(duped_uri, duped_text);
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|
|
|
|
|
2020-05-26 11:30:28 +01:00
|
|
|
fn decrementBuildFileRefs(self: *DocumentStore, build_file: *BuildFile) void {
|
2020-05-25 18:42:58 +01:00
|
|
|
build_file.refs -= 1;
|
|
|
|
if (build_file.refs == 0) {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Freeing build file {s}", .{build_file.uri});
|
2020-05-25 18:42:58 +01:00
|
|
|
for (build_file.packages.items) |pkg| {
|
|
|
|
self.allocator.free(pkg.name);
|
|
|
|
self.allocator.free(pkg.uri);
|
|
|
|
}
|
|
|
|
build_file.packages.deinit(self.allocator);
|
|
|
|
|
|
|
|
// Decrement count of the document since one count comes
|
|
|
|
// from the build file existing.
|
2020-05-26 11:30:28 +01:00
|
|
|
self.decrementCount(build_file.uri);
|
2020-05-25 18:42:58 +01:00
|
|
|
self.allocator.free(build_file.uri);
|
|
|
|
|
|
|
|
// Remove the build file from the array list
|
|
|
|
_ = self.build_files.swapRemove(std.mem.indexOfScalar(*BuildFile, self.build_files.items, build_file).?);
|
|
|
|
self.allocator.destroy(build_file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-14 00:10:41 +01:00
|
|
|
fn decrementCount(self: *DocumentStore, uri: []const u8) void {
|
2020-07-05 22:56:41 +01:00
|
|
|
if (self.handles.getEntry(uri)) |entry| {
|
2020-06-18 18:52:54 +01:00
|
|
|
if (entry.value.count == 0) return;
|
2020-05-14 00:10:41 +01:00
|
|
|
entry.value.count -= 1;
|
2020-05-25 18:42:58 +01:00
|
|
|
|
2020-06-14 19:19:27 +01:00
|
|
|
if (entry.value.count > 0)
|
|
|
|
return;
|
|
|
|
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Freeing document: {s}", .{uri});
|
2020-06-14 19:19:27 +01:00
|
|
|
|
2020-05-26 11:30:28 +01:00
|
|
|
if (entry.value.associated_build_file) |build_file| {
|
|
|
|
self.decrementBuildFileRefs(build_file);
|
2020-05-25 18:42:58 +01:00
|
|
|
}
|
|
|
|
|
2020-06-14 19:19:27 +01:00
|
|
|
if (entry.value.is_build_file) |build_file| {
|
|
|
|
self.decrementBuildFileRefs(build_file);
|
|
|
|
}
|
2020-05-14 00:10:41 +01:00
|
|
|
|
2021-02-26 20:26:52 +00:00
|
|
|
entry.value.tree.deinit(self.allocator);
|
2020-05-14 00:10:41 +01:00
|
|
|
self.allocator.free(entry.value.document.mem);
|
|
|
|
|
2021-03-30 13:41:59 +01:00
|
|
|
for (entry.value.imports_used.items) |import_uri| {
|
2020-05-14 00:10:41 +01:00
|
|
|
self.decrementCount(import_uri);
|
2021-03-30 13:41:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (entry.value.import_uris) |import_uri| {
|
2020-05-14 00:10:41 +01:00
|
|
|
self.allocator.free(import_uri);
|
|
|
|
}
|
|
|
|
|
2020-07-08 02:05:44 +01:00
|
|
|
entry.value.document_scope.deinit(self.allocator);
|
2021-03-30 13:41:59 +01:00
|
|
|
entry.value.imports_used.deinit(self.allocator);
|
|
|
|
self.allocator.free(entry.value.import_uris);
|
2020-05-27 11:41:50 +01:00
|
|
|
self.allocator.destroy(entry.value);
|
2020-05-14 00:10:41 +01:00
|
|
|
const uri_key = entry.key;
|
|
|
|
self.handles.removeAssertDiscard(uri);
|
|
|
|
self.allocator.free(uri_key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn closeDocument(self: *DocumentStore, uri: []const u8) void {
|
|
|
|
self.decrementCount(uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn getHandle(self: *DocumentStore, uri: []const u8) ?*Handle {
|
2020-07-05 22:56:41 +01:00
|
|
|
return self.handles.get(uri);
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|
|
|
|
|
2020-05-24 17:00:21 +01:00
|
|
|
fn refreshDocument(self: *DocumentStore, handle: *Handle, zig_lib_path: ?[]const u8) !void {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("New text for document {s}", .{handle.uri()});
|
2021-02-26 20:26:52 +00:00
|
|
|
handle.tree.deinit(self.allocator);
|
2020-05-24 17:00:21 +01:00
|
|
|
handle.tree = try std.zig.parse(self.allocator, handle.document.text);
|
|
|
|
|
2020-06-10 14:12:00 +01:00
|
|
|
handle.document_scope.deinit(self.allocator);
|
|
|
|
handle.document_scope = try analysis.makeDocumentScope(self.allocator, handle.tree);
|
2021-03-30 13:41:59 +01:00
|
|
|
|
2021-03-29 10:21:39 +01:00
|
|
|
var new_imports = std.ArrayList([]const u8).init(self.allocator);
|
|
|
|
errdefer new_imports.deinit();
|
|
|
|
try analysis.collectImports(&new_imports, handle.tree);
|
|
|
|
|
|
|
|
// Convert to URIs
|
|
|
|
var i: usize = 0;
|
|
|
|
while (i < new_imports.items.len) {
|
|
|
|
if (try self.uriFromImportStr(self.allocator, handle.*, new_imports.items[i])) |uri| {
|
|
|
|
// The raw import strings are owned by the document and do not need to be freed here.
|
|
|
|
new_imports.items[i] = uri;
|
|
|
|
i += 1;
|
|
|
|
} else {
|
|
|
|
_ = new_imports.swapRemove(i);
|
|
|
|
}
|
2020-05-14 12:51:07 +01:00
|
|
|
}
|
|
|
|
|
2021-03-29 10:21:39 +01:00
|
|
|
const old_imports = handle.import_uris;
|
2021-03-30 13:41:59 +01:00
|
|
|
handle.import_uris = new_imports.toOwnedSlice();
|
|
|
|
defer {
|
|
|
|
for (old_imports) |uri| {
|
|
|
|
self.allocator.free(uri);
|
|
|
|
}
|
|
|
|
self.allocator.free(old_imports);
|
|
|
|
}
|
2020-05-14 12:51:07 +01:00
|
|
|
|
2021-03-30 13:41:59 +01:00
|
|
|
i = 0;
|
|
|
|
while (i < handle.imports_used.items.len) {
|
|
|
|
const old = handle.imports_used.items[i];
|
2021-03-29 10:21:39 +01:00
|
|
|
still_exists: {
|
2021-03-29 13:02:52 +01:00
|
|
|
for (new_imports.items) |new| {
|
2021-03-29 10:21:39 +01:00
|
|
|
if (std.mem.eql(u8, new, old)) {
|
|
|
|
break :still_exists;
|
|
|
|
}
|
2020-05-14 12:51:07 +01:00
|
|
|
}
|
2021-03-29 10:21:39 +01:00
|
|
|
log.debug("Import removed: {s}", .{old});
|
2021-03-29 13:02:52 +01:00
|
|
|
self.decrementCount(old);
|
2021-03-30 13:41:59 +01:00
|
|
|
_ = handle.imports_used.swapRemove(i);
|
|
|
|
continue;
|
2020-05-14 12:51:07 +01:00
|
|
|
}
|
2021-03-30 13:41:59 +01:00
|
|
|
i += 1;
|
2020-05-14 12:51:07 +01:00
|
|
|
}
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|
|
|
|
|
2020-06-03 09:32:05 +01:00
|
|
|
pub fn applySave(self: *DocumentStore, handle: *Handle) !void {
|
|
|
|
if (handle.is_build_file) |build_file| {
|
|
|
|
loadPackages(.{
|
|
|
|
.build_file = build_file,
|
|
|
|
.allocator = self.allocator,
|
|
|
|
.build_runner_path = self.build_runner_path,
|
2021-01-12 11:10:51 +00:00
|
|
|
.build_runner_cache_path = self.build_runner_cache_path,
|
2020-06-03 09:32:05 +01:00
|
|
|
.zig_exe_path = self.zig_exe_path.?,
|
|
|
|
}) catch |err| {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Failed to load packages of build file {s} (error: {})", .{ build_file.uri, err });
|
2020-06-03 09:32:05 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 20:09:00 +01:00
|
|
|
pub fn applyChanges(
|
|
|
|
self: *DocumentStore,
|
|
|
|
handle: *Handle,
|
|
|
|
content_changes: std.json.Array,
|
2020-11-06 10:32:21 +00:00
|
|
|
offset_encoding: offsets.Encoding,
|
2020-05-19 20:09:00 +01:00
|
|
|
zig_lib_path: ?[]const u8,
|
|
|
|
) !void {
|
2020-05-17 15:39:04 +01:00
|
|
|
const document = &handle.document;
|
2020-05-14 00:10:41 +01:00
|
|
|
|
|
|
|
for (content_changes.items) |change| {
|
2020-07-05 22:56:41 +01:00
|
|
|
if (change.Object.get("range")) |range| {
|
2020-11-06 10:32:21 +00:00
|
|
|
std.debug.assert(document.text.ptr == document.mem.ptr);
|
|
|
|
|
2021-03-30 09:33:21 +01:00
|
|
|
// TODO: add tests and validate the JSON
|
|
|
|
const start_obj = range.Object.get("start").?.Object;
|
2020-05-14 00:10:41 +01:00
|
|
|
const start_pos = types.Position{
|
2021-03-30 09:33:21 +01:00
|
|
|
.line = start_obj.get("line").?.Integer,
|
|
|
|
.character = start_obj.get("character").?.Integer,
|
2020-05-14 00:10:41 +01:00
|
|
|
};
|
2021-03-30 09:33:21 +01:00
|
|
|
const end_obj = range.Object.get("end").?.Object;
|
2020-05-14 00:10:41 +01:00
|
|
|
const end_pos = types.Position{
|
2021-03-30 09:33:21 +01:00
|
|
|
.line = end_obj.get("line").?.Integer,
|
|
|
|
.character = end_obj.get("character").?.Integer,
|
2020-05-14 00:10:41 +01:00
|
|
|
};
|
|
|
|
|
2020-11-06 10:32:21 +00:00
|
|
|
const change_text = change.Object.get("text").?.String;
|
|
|
|
const start_index = (try offsets.documentPosition(document.*, start_pos, offset_encoding)).absolute_index;
|
|
|
|
const end_index = (try offsets.documentPosition(document.*, end_pos, offset_encoding)).absolute_index;
|
2020-05-14 00:10:41 +01:00
|
|
|
|
|
|
|
const old_len = document.text.len;
|
2020-11-06 10:32:21 +00:00
|
|
|
const new_len = old_len - (end_index - start_index) + change_text.len;
|
2020-05-14 00:10:41 +01:00
|
|
|
if (new_len > document.mem.len) {
|
|
|
|
// We need to reallocate memory.
|
|
|
|
// We reallocate twice the current filesize or the new length, if it's more than that
|
|
|
|
// so that we can reduce the amount of realloc calls.
|
|
|
|
// We can tune this to find a better size if needed.
|
|
|
|
const realloc_len = std.math.max(2 * old_len, new_len);
|
|
|
|
document.mem = try self.allocator.realloc(document.mem, realloc_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The first part of the string, [0 .. start_index] need not be changed.
|
|
|
|
// We then copy the last part of the string, [end_index ..] to its
|
|
|
|
// new position, [start_index + change_len .. ]
|
2020-11-06 10:32:21 +00:00
|
|
|
if (new_len < old_len) {
|
|
|
|
std.mem.copy(u8, document.mem[start_index + change_text.len ..][0 .. old_len - end_index], document.mem[end_index..old_len]);
|
|
|
|
} else {
|
|
|
|
std.mem.copyBackwards(u8, document.mem[start_index + change_text.len ..][0 .. old_len - end_index], document.mem[end_index..old_len]);
|
|
|
|
}
|
2020-05-14 00:10:41 +01:00
|
|
|
// Finally, we copy the changes over.
|
2020-05-17 07:47:48 +01:00
|
|
|
std.mem.copy(u8, document.mem[start_index..][0..change_text.len], change_text);
|
2020-05-14 00:10:41 +01:00
|
|
|
|
|
|
|
// Reset the text substring.
|
2020-05-17 07:47:48 +01:00
|
|
|
document.text = document.mem[0..new_len];
|
2020-05-14 00:10:41 +01:00
|
|
|
} else {
|
2020-07-05 22:56:41 +01:00
|
|
|
const change_text = change.Object.get("text").?.String;
|
2020-05-14 00:10:41 +01:00
|
|
|
const old_len = document.text.len;
|
|
|
|
|
|
|
|
if (change_text.len > document.mem.len) {
|
|
|
|
// Like above.
|
|
|
|
const realloc_len = std.math.max(2 * old_len, change_text.len);
|
|
|
|
document.mem = try self.allocator.realloc(document.mem, realloc_len);
|
|
|
|
}
|
|
|
|
|
2020-05-17 07:47:48 +01:00
|
|
|
std.mem.copy(u8, document.mem[0..change_text.len], change_text);
|
|
|
|
document.text = document.mem[0..change_text.len];
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-24 17:00:21 +01:00
|
|
|
try self.refreshDocument(handle, zig_lib_path);
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|
|
|
|
|
2020-05-22 16:51:57 +01:00
|
|
|
pub fn uriFromImportStr(
|
2020-06-10 17:54:01 +01:00
|
|
|
self: *DocumentStore,
|
2020-05-19 20:09:00 +01:00
|
|
|
allocator: *std.mem.Allocator,
|
|
|
|
handle: Handle,
|
|
|
|
import_str: []const u8,
|
|
|
|
) !?[]const u8 {
|
2020-05-22 16:56:57 +01:00
|
|
|
if (std.mem.eql(u8, import_str, "std")) {
|
2020-06-10 17:54:01 +01:00
|
|
|
if (self.std_uri) |uri| return try std.mem.dupe(allocator, u8, uri) else {
|
2020-11-15 19:32:27 +00:00
|
|
|
log.debug("Cannot resolve std library import, path is null.", .{});
|
2020-05-14 12:51:07 +01:00
|
|
|
return null;
|
|
|
|
}
|
2020-05-22 16:56:57 +01:00
|
|
|
} else if (std.mem.eql(u8, import_str, "builtin")) {
|
|
|
|
return null; // TODO find the correct zig-cache folder
|
|
|
|
} else if (!std.mem.endsWith(u8, import_str, ".zig")) {
|
2020-05-25 17:33:08 +01:00
|
|
|
if (handle.associated_build_file) |build_file| {
|
|
|
|
for (build_file.packages.items) |pkg| {
|
|
|
|
if (std.mem.eql(u8, import_str, pkg.name)) {
|
|
|
|
return try std.mem.dupe(allocator, u8, pkg.uri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
2020-05-22 16:56:57 +01:00
|
|
|
} else {
|
2021-03-29 13:02:52 +01:00
|
|
|
const base = handle.uri();
|
|
|
|
var base_len = base.len;
|
2021-03-30 13:41:59 +01:00
|
|
|
while (base[base_len - 1] != '/' and base_len > 0) {
|
|
|
|
base_len -= 1;
|
|
|
|
}
|
2021-03-29 13:02:52 +01:00
|
|
|
base_len -= 1;
|
|
|
|
if (base_len <= 0) {
|
|
|
|
return error.UriBadScheme;
|
|
|
|
}
|
|
|
|
return try URI.pathRelative(allocator, base[0..base_len], import_str);
|
2020-05-22 16:56:57 +01:00
|
|
|
}
|
2020-05-14 12:51:07 +01:00
|
|
|
}
|
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
pub fn resolveImport(self: *DocumentStore, handle: *Handle, import_str: []const u8) !?*Handle {
|
2021-03-30 13:41:59 +01:00
|
|
|
std.debug.print("RESOLVING IMPORT STR: {s}\n", .{import_str});
|
2020-06-10 17:54:01 +01:00
|
|
|
const allocator = self.allocator;
|
|
|
|
const final_uri = (try self.uriFromImportStr(
|
|
|
|
self.allocator,
|
|
|
|
handle.*,
|
|
|
|
import_str,
|
|
|
|
)) orelse return null;
|
|
|
|
var consumed_final_uri = false;
|
|
|
|
defer if (!consumed_final_uri) allocator.free(final_uri);
|
|
|
|
|
2021-03-30 13:41:59 +01:00
|
|
|
for (handle.imports_used.items) |uri| {
|
2020-06-10 17:54:01 +01:00
|
|
|
if (std.mem.eql(u8, uri, final_uri)) {
|
2021-03-30 13:41:59 +01:00
|
|
|
return self.getHandle(final_uri).?;
|
2020-05-14 02:54:05 +01:00
|
|
|
}
|
2020-06-10 17:54:01 +01:00
|
|
|
}
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
// New import.
|
|
|
|
// Check if the import is already opened by others.
|
|
|
|
if (self.getHandle(final_uri)) |new_handle| {
|
|
|
|
// If it is, append it to our imports, increment the count, set our new handle
|
|
|
|
// and return the parsed tree root node.
|
2021-03-30 13:41:59 +01:00
|
|
|
try handle.imports_used.append(self.allocator, final_uri);
|
2020-06-10 17:54:01 +01:00
|
|
|
consumed_final_uri = true;
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
new_handle.count += 1;
|
|
|
|
return new_handle;
|
|
|
|
}
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
// New document, read the file then call into openDocument.
|
|
|
|
const file_path = try URI.parse(allocator, final_uri);
|
|
|
|
defer allocator.free(file_path);
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
var file = std.fs.cwd().openFile(file_path, .{}) catch {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Cannot open import file {s}", .{file_path});
|
2020-06-10 17:54:01 +01:00
|
|
|
return null;
|
|
|
|
};
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
defer file.close();
|
|
|
|
const size = std.math.cast(usize, try file.getEndPos()) catch std.math.maxInt(usize);
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
{
|
|
|
|
const file_contents = try allocator.alloc(u8, size);
|
|
|
|
errdefer allocator.free(file_contents);
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-08-14 11:41:34 +01:00
|
|
|
file.reader().readNoEof(file_contents) catch {
|
2021-01-04 17:51:26 +00:00
|
|
|
log.debug("Could not read from file {s}", .{file_path});
|
2020-06-10 17:54:01 +01:00
|
|
|
return null;
|
|
|
|
};
|
2020-05-14 09:51:49 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
// Add to import table of current handle.
|
2021-03-30 13:41:59 +01:00
|
|
|
try handle.imports_used.append(self.allocator, final_uri);
|
2020-06-10 17:54:01 +01:00
|
|
|
consumed_final_uri = true;
|
2020-05-14 09:40:17 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
// Swap handles.
|
|
|
|
// This takes ownership of the passed uri and text.
|
|
|
|
const duped_final_uri = try std.mem.dupe(allocator, u8, final_uri);
|
|
|
|
errdefer allocator.free(duped_final_uri);
|
|
|
|
return try self.newDocument(duped_final_uri, file_contents);
|
2020-05-14 02:54:05 +01:00
|
|
|
}
|
2020-06-10 17:54:01 +01:00
|
|
|
}
|
2020-05-14 02:54:05 +01:00
|
|
|
|
2020-06-10 17:54:01 +01:00
|
|
|
fn stdUriFromLibPath(allocator: *std.mem.Allocator, zig_lib_path: ?[]const u8) !?[]const u8 {
|
2020-05-19 20:09:00 +01:00
|
|
|
if (zig_lib_path) |zpath| {
|
|
|
|
const std_path = std.fs.path.resolve(allocator, &[_][]const u8{
|
|
|
|
zpath, "./std/std.zig",
|
2020-08-14 11:27:10 +01:00
|
|
|
}) catch |err| {
|
2020-11-15 19:32:27 +00:00
|
|
|
log.debug("Failed to resolve zig std library path, error: {}", .{err});
|
2020-05-19 20:09:00 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-05-14 00:10:41 +01:00
|
|
|
pub fn deinit(self: *DocumentStore) void {
|
2020-05-14 13:26:10 +01:00
|
|
|
var entry_iterator = self.handles.iterator();
|
|
|
|
while (entry_iterator.next()) |entry| {
|
2020-06-16 22:26:45 +01:00
|
|
|
entry.value.document_scope.deinit(self.allocator);
|
2021-02-26 20:26:52 +00:00
|
|
|
entry.value.tree.deinit(self.allocator);
|
2020-05-14 13:26:10 +01:00
|
|
|
self.allocator.free(entry.value.document.mem);
|
2021-03-30 13:41:59 +01:00
|
|
|
for (entry.value.import_uris) |uri| {
|
2020-05-14 13:26:10 +01:00
|
|
|
self.allocator.free(uri);
|
|
|
|
}
|
2021-03-30 13:41:59 +01:00
|
|
|
self.allocator.free(entry.value.import_uris);
|
|
|
|
entry.value.imports_used.deinit(self.allocator);
|
2020-05-14 13:26:10 +01:00
|
|
|
self.allocator.free(entry.key);
|
2020-05-18 08:28:36 +01:00
|
|
|
self.allocator.destroy(entry.value);
|
2020-05-14 13:26:10 +01:00
|
|
|
}
|
2020-05-14 00:10:41 +01:00
|
|
|
|
|
|
|
self.handles.deinit();
|
2020-05-25 17:49:04 +01:00
|
|
|
for (self.build_files.items) |build_file| {
|
|
|
|
for (build_file.packages.items) |pkg| {
|
|
|
|
self.allocator.free(pkg.name);
|
|
|
|
self.allocator.free(pkg.uri);
|
|
|
|
}
|
2020-11-04 11:00:55 +00:00
|
|
|
build_file.packages.deinit(self.allocator);
|
2020-05-25 17:49:04 +01:00
|
|
|
self.allocator.free(build_file.uri);
|
|
|
|
self.allocator.destroy(build_file);
|
|
|
|
}
|
2020-06-10 17:54:01 +01:00
|
|
|
if (self.std_uri) |std_uri| {
|
|
|
|
self.allocator.free(std_uri);
|
|
|
|
}
|
2020-07-02 12:44:12 +01:00
|
|
|
self.allocator.free(self.build_runner_path);
|
2021-01-12 11:10:51 +00:00
|
|
|
self.allocator.free(self.build_runner_cache_path);
|
2020-05-25 17:49:04 +01:00
|
|
|
self.build_files.deinit(self.allocator);
|
2020-07-08 02:05:44 +01:00
|
|
|
}
|
|
|
|
|
2021-03-29 12:02:58 +01:00
|
|
|
fn tagStoreCompletionItems(
|
|
|
|
self: DocumentStore,
|
|
|
|
arena: *std.heap.ArenaAllocator,
|
|
|
|
base: *DocumentStore.Handle,
|
|
|
|
comptime name: []const u8,
|
|
|
|
) ![]types.CompletionItem {
|
2020-07-08 02:05:44 +01:00
|
|
|
// TODO Better solution for deciding what tags to include
|
2021-03-29 12:02:58 +01:00
|
|
|
var max_len: usize = @field(base.document_scope, name).count();
|
2021-03-30 13:41:59 +01:00
|
|
|
for (base.imports_used.items) |uri| {
|
2021-03-29 12:02:58 +01:00
|
|
|
max_len += @field(self.handles.get(uri).?.document_scope, name).count();
|
2020-07-08 02:05:44 +01:00
|
|
|
}
|
|
|
|
|
2021-03-29 12:02:58 +01:00
|
|
|
var result_set = analysis.CompletionSet{};
|
|
|
|
try result_set.ensureCapacity(&arena.allocator, max_len);
|
|
|
|
result_set.entries.appendSliceAssumeCapacity(@field(base.document_scope, name).entries.items);
|
|
|
|
try result_set.reIndex(&arena.allocator);
|
|
|
|
|
2021-03-30 13:41:59 +01:00
|
|
|
for (base.imports_used.items) |uri| {
|
2021-03-29 12:02:58 +01:00
|
|
|
const curr_set = &@field(self.handles.get(uri).?.document_scope, name);
|
|
|
|
for (curr_set.entries.items) |entry| {
|
|
|
|
result_set.putAssumeCapacity(entry.key, {});
|
2020-07-08 02:05:44 +01:00
|
|
|
}
|
|
|
|
}
|
2021-03-29 12:02:58 +01:00
|
|
|
// This is safe to do because CompletionSet.Entry == struct { value: types.CompletionItem }
|
|
|
|
return std.mem.bytesAsSlice(types.CompletionItem, std.mem.sliceAsBytes(result_set.entries.items));
|
2020-07-08 02:05:44 +01:00
|
|
|
}
|
|
|
|
|
2021-03-29 12:02:58 +01:00
|
|
|
pub fn errorCompletionItems(
|
|
|
|
self: DocumentStore,
|
|
|
|
arena: *std.heap.ArenaAllocator,
|
|
|
|
base: *DocumentStore.Handle,
|
|
|
|
) ![]types.CompletionItem {
|
2020-07-08 02:05:44 +01:00
|
|
|
return try self.tagStoreCompletionItems(arena, base, "error_completions");
|
|
|
|
}
|
|
|
|
|
2021-03-29 12:02:58 +01:00
|
|
|
pub fn enumCompletionItems(
|
|
|
|
self: DocumentStore,
|
|
|
|
arena: *std.heap.ArenaAllocator,
|
|
|
|
base: *DocumentStore.Handle,
|
|
|
|
) ![]types.CompletionItem {
|
2020-07-08 02:05:44 +01:00
|
|
|
return try self.tagStoreCompletionItems(arena, base, "enum_completions");
|
2020-05-14 00:10:41 +01:00
|
|
|
}
|