Skip to content

Commit

Permalink
use std.Uri as much as possible instead of our own code
Browse files Browse the repository at this point in the history
Windows has been designed by a group of senile raccoons
  • Loading branch information
Techatrix committed Jun 18, 2024
1 parent cfea2d5 commit 4e7417d
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 161 deletions.
28 changes: 14 additions & 14 deletions src/DocumentStore.zig
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,7 @@ fn loadBuildAssociatedConfiguration(allocator: std.mem.Allocator, build_file: Bu

const build_file_path = try URI.parse(allocator, build_file.uri);
defer allocator.free(build_file_path);
const config_file_path = try std.fs.path.resolve(allocator, &.{ build_file_path, "../zls.build.json" });
const config_file_path = try std.fs.path.resolve(allocator, &.{ build_file_path, "..", "zls.build.json" });
defer allocator.free(config_file_path);

var config_file = try std.fs.cwd().openFile(config_file_path, .{});
Expand Down Expand Up @@ -1173,7 +1173,7 @@ fn createBuildFile(self: *DocumentStore, uri: Uri) error{OutOfMemory}!BuildFile

if (cfg.value.relative_builtin_path) |relative_builtin_path| blk: {
const build_file_path = URI.parse(self.allocator, build_file.uri) catch break :blk;
const absolute_builtin_path = std.fs.path.resolve(self.allocator, &.{ build_file_path, "../", relative_builtin_path }) catch break :blk;
const absolute_builtin_path = std.fs.path.resolve(self.allocator, &.{ build_file_path, "..", relative_builtin_path }) catch break :blk;
defer self.allocator.free(absolute_builtin_path);
build_file.builtin_uri = try URI.fromPath(self.allocator, absolute_builtin_path);
}
Expand Down Expand Up @@ -1583,12 +1583,9 @@ pub fn uriFromImportStr(self: *DocumentStore, allocator: std.mem.Allocator, hand
if (std.mem.eql(u8, import_str, "std")) {
const zig_lib_path = self.config.zig_lib_path orelse return null;

const std_path = std.fs.path.resolve(allocator, &[_][]const u8{ zig_lib_path, "./std/std.zig" }) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => return null,
};

const std_path = try std.fs.path.join(allocator, &.{ zig_lib_path, "std", "std.zig" });
defer allocator.free(std_path);

return try URI.fromPath(allocator, std_path);
} else if (std.mem.eql(u8, import_str, "builtin")) {
if (try handle.getAssociatedBuildFileUri(self)) |build_file_uri| {
Expand Down Expand Up @@ -1625,15 +1622,18 @@ pub fn uriFromImportStr(self: *DocumentStore, allocator: std.mem.Allocator, hand
}
return null;
} else {
var separator_index = handle.uri.len;
while (separator_index > 0) : (separator_index -= 1) {
if (std.fs.path.isSep(handle.uri[separator_index - 1])) break;
}
const base = handle.uri[0 .. separator_index - 1];
const base_path = URI.parse(allocator, handle.uri) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => return null,
};
defer allocator.free(base_path);

return URI.pathRelative(allocator, base, import_str) catch |err| switch (err) {
const joined_path = std.fs.path.resolve(allocator, &.{ base_path, "..", import_str }) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.UriBadScheme => return null,
else => return null,
};
defer allocator.free(joined_path);

return try URI.fromPath(allocator, joined_path);
}
}
9 changes: 3 additions & 6 deletions src/analysis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1765,12 +1765,9 @@ fn resolveTypeOfNodeUncached(analyser: *Analyser, node_handle: NodeWithHandle) e
}

if (std.mem.eql(u8, call_name, "@typeInfo")) {
const zig_lib_path = try URI.fromPath(analyser.arena.allocator(), analyser.store.config.zig_lib_path orelse return null);

const builtin_uri = URI.pathRelative(analyser.arena.allocator(), zig_lib_path, "/std/builtin.zig") catch |err| switch (err) {
error.OutOfMemory => |e| return e,
else => return null,
};
const zig_lib_path = analyser.store.config.zig_lib_path orelse return null;
const builtin_path = try std.fs.path.join(analyser.arena.allocator(), &.{ zig_lib_path, "std", "builtin.zig" });
const builtin_uri = try URI.fromPath(analyser.arena.allocator(), builtin_path);

const new_handle = analyser.store.getOrLoadHandle(builtin_uri) orelse return null;
const new_handle_document_scope = try new_handle.getDocumentScope();
Expand Down
177 changes: 37 additions & 140 deletions src/uri.zig
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
const std = @import("std");
const builtin = @import("builtin");

// http://tools.ietf.org/html/rfc3986#section-2.2
const reserved_chars = &[_]u8{
'!', '#', '$', '%', '&', '\'',
'(', ')', '*', '+', ',', ':',
';', '=', '?', '@', '[', ']',
};

const reserved_escapes = blk: {
var escapes: [reserved_chars.len][3]u8 = [_][3]u8{[_]u8{undefined} ** 3} ** reserved_chars.len;

for (reserved_chars, 0..) |c, i| {
escapes[i][0] = '%';
_ = std.fmt.bufPrint(escapes[i][1..], "{X}", .{c}) catch unreachable;
}
break :blk escapes;
};

/// Returns a URI from a path, caller owns the memory allocated with `allocator`
pub fn fromPath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
if (path.len == 0) return "";
/// Returns a file URI from a path.
/// Caller owns the returned memory
pub fn fromPath(allocator: std.mem.Allocator, path: []const u8) error{OutOfMemory}![]u8 {
if (path.len == 0) return try allocator.dupe(u8, "/");
const prefix = if (builtin.os.tag == .windows) "file:///" else "file://";

var buf = try std.ArrayListUnmanaged(u8).initCapacity(allocator, prefix.len + path.len);
errdefer buf.deinit(allocator);

buf.appendSliceAssumeCapacity(prefix);

for (path) |char| {
if (char == std.fs.path.sep) {
try buf.append(allocator, '/');
} else if (std.mem.indexOfScalar(u8, reserved_chars, char)) |reserved| {
try buf.appendSlice(allocator, &reserved_escapes[reserved]);
const writer = buf.writer(allocator);

var start: usize = 0;
for (path, 0..) |char, index| {
switch (char) {
// zig fmt: off
'A'...'Z',
'a'...'z',
'0'...'9',
'-', '.', '_', '~', '!',
'$', '&', '\'','(', ')',
'+', ',', ';', '=', '@',
// zig fmt: on
=> continue,
':', '*' => if (builtin.os.tag != .windows) continue,
else => {},
}

try writer.writeAll(path[start..index]);
if (std.fs.path.isSep(char)) {
try writer.writeByte('/');
} else {
try buf.append(allocator, char);
try writer.print("%{X:0>2}", .{char});
}
start = index + 1;
}
try writer.writeAll(path[start..]);

// On windows, we need to lowercase the drive name.
if (builtin.os.tag == .windows) {
Expand All @@ -53,7 +55,7 @@ pub fn fromPath(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {

test fromPath {
if (builtin.os.tag == .windows) {
const fromPathWin = try fromPath(std.testing.allocator, "c:\\main.zig");
const fromPathWin = try fromPath(std.testing.allocator, "C:\\main.zig");
defer std.testing.allocator.free(fromPathWin);
try std.testing.expectEqualStrings("file:///c%3A/main.zig", fromPathWin);
}
Expand All @@ -65,118 +67,13 @@ test fromPath {
}
}

/// Move along `rel` from `base` with a single allocation.
/// `base` is a URI of a folder, `rel` is a raw relative path.
pub fn pathRelative(allocator: std.mem.Allocator, base: []const u8, rel: []const u8) error{ OutOfMemory, UriBadScheme }![]const u8 {
const max_size = base.len + rel.len * 3 + 1;

var result = try std.ArrayListUnmanaged(u8).initCapacity(allocator, max_size);
errdefer result.deinit(allocator);

result.appendSliceAssumeCapacity(base);

var it = std.mem.tokenizeScalar(u8, rel, '/');
while (it.next()) |component| {
if (std.mem.eql(u8, component, ".")) {
continue;
} else if (std.mem.eql(u8, component, "..")) {
while ((result.getLastOrNull() orelse return error.UriBadScheme) == '/') {
_ = result.pop();
}
while (true) {
const char = result.popOrNull() orelse return error.UriBadScheme;
if (char == '/') break;
}
} else {
result.appendAssumeCapacity('/');
for (component) |char| {
if (std.mem.indexOfScalar(u8, reserved_chars, char)) |reserved| {
const escape = &reserved_escapes[reserved];
result.appendSliceAssumeCapacity(escape);
} else {
result.appendAssumeCapacity(char);
}
}
}
}

return result.toOwnedSlice(allocator);
}

test pathRelative {
const join1 = try pathRelative(std.testing.allocator, "file:///project/zig", "/src/main+.zig");
defer std.testing.allocator.free(join1);
try std.testing.expectEqualStrings("file:///project/zig/src/main%2B.zig", join1);

const join2 = try pathRelative(std.testing.allocator, "file:///project/zig/wow", "../]src]/]main.zig");
defer std.testing.allocator.free(join2);
try std.testing.expectEqualStrings("file:///project/zig/%5Dsrc%5D/%5Dmain.zig", join2);

const join3 = try pathRelative(std.testing.allocator, "file:///project/zig/wow//", "../src/main.zig");
defer std.testing.allocator.free(join3);
try std.testing.expectEqualStrings("file:///project/zig/src/main.zig", join3);
}

// Original code: https://github.com/andersfr/zig-lsp/blob/master/uri.zig
fn parseHex(c: u8) !u8 {
return switch (c) {
'0'...'9' => c - '0',
'a'...'f' => c - 'a' + 10,
'A'...'F' => c - 'A' + 10,
else => return error.UriBadHexChar,
};
}

/// Caller should free memory
pub fn parse(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
if (str.len < 7 or !std.mem.eql(u8, "file://", str[0..7])) return error.UriBadScheme;

var uri = try allocator.alloc(u8, str.len - (if (std.fs.path.sep == '\\') 8 else 7));
errdefer allocator.free(uri);

const path = if (std.fs.path.sep == '\\') str[8..] else str[7..];

var i: usize = 0;
var j: usize = 0;
while (j < path.len) : (i += 1) {
if (path[j] == '%') {
if (j + 2 >= path.len) return error.UriBadEscape;
const upper = try parseHex(path[j + 1]);
const lower = try parseHex(path[j + 2]);
uri[i] = (upper << 4) + lower;
j += 3;
} else {
uri[i] = if (path[j] == '/') std.fs.path.sep else path[j];
j += 1;
}
}

// Remove trailing separator
if (i > 0 and uri[i - 1] == std.fs.path.sep) {
i -= 1;
}

return allocator.realloc(uri, i);
}

test parse {
if (builtin.os.tag == .windows) {
const parseWin = try parse(std.testing.allocator, "file:///c%3A/main.zig");
defer std.testing.allocator.free(parseWin);
try std.testing.expectEqualStrings("c:\\main.zig", parseWin);

const parseWin2 = try parse(std.testing.allocator, "file:///c%3A/main%2B.zig");
defer std.testing.allocator.free(parseWin2);
try std.testing.expectEqualStrings("c:\\main+.zig", parseWin2);
}

if (builtin.os.tag != .windows) {
const parseUnix = try parse(std.testing.allocator, "file:///home/main.zig");
defer std.testing.allocator.free(parseUnix);
try std.testing.expectEqualStrings("/home/main.zig", parseUnix);

const parseUnix2 = try parse(std.testing.allocator, "file:///home/main%2B.zig");
defer std.testing.allocator.free(parseUnix2);
try std.testing.expectEqualStrings("/home/main+.zig", parseUnix2);
/// 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 {
var uri = try std.Uri.parse(str);
if (!std.mem.eql(u8, uri.scheme, "file")) return error.InvalidFormat;
if (builtin.os.tag == .windows and uri.path.percent_encoded[0] == '/') {
uri.path.percent_encoded = uri.path.percent_encoded[1..];
}
return try std.fmt.allocPrint(allocator, "{raw}", .{uri.path});
}
3 changes: 2 additions & 1 deletion tests/language_features/cimport.zig
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ fn testTranslate(c_source: []const u8) !translate_c.Result {
var ctx = try Context.init();
defer ctx.deinit();

const result = (try translate_c.translate(allocator, zls.DocumentStore.Config.fromMainConfig(ctx.server.config), &.{}, c_source)).?;
var result = (try translate_c.translate(allocator, zls.DocumentStore.Config.fromMainConfig(ctx.server.config), &.{}, c_source)).?;
errdefer result.deinit(allocator);

switch (result) {
.success => |uri| {
Expand Down

0 comments on commit 4e7417d

Please sign in to comment.