Merge remote-tracking branch 'origin/master' into intern-pool

This commit is contained in:
Techarix 2022-12-11 19:02:24 +01:00
commit 748cd7d6a9
25 changed files with 559 additions and 120 deletions

View File

@ -22,7 +22,7 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
- uses: goto-bus-stop/setup-zig@v1 - uses: goto-bus-stop/setup-zig@v2
with: with:
version: master version: master
@ -32,13 +32,17 @@ jobs:
- name: Build - name: Build
run: zig build run: zig build
- name: Build with Tracy
if: ${{ matrix.os != 'macos-latest' }}
run: zig build -Denable_tracy -Denable_tracy_allocation
- name: Run Tests - name: Run Tests
run: zig build test run: zig build test
- name: Build artifacts - name: Build artifacts
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' }}
run: | run: |
declare -a targets=("x86_64-windows" "x86_64-linux" "x86_64-macos" "x86-windows" "x86-linux" "aarch64-macos") declare -a targets=("x86_64-windows" "x86_64-linux" "x86_64-macos" "x86-windows" "x86-linux" "aarch64-linux" "aarch64-macos")
mkdir -p "artifacts/" mkdir -p "artifacts/"
for target in "${targets[@]}"; do for target in "${targets[@]}"; do
@ -53,21 +57,59 @@ jobs:
fi fi
sed -e '1,5d' < README.md > artifacts/${target}/README.md sed -e '1,5d' < README.md > artifacts/${target}/README.md
cp LICENSE artifacts/${target}/ cp LICENSE artifacts/${target}/
cd artifacts/${target}/
tar cfa ${target}.tar.zst *.md bin/*
mv ${target}.tar.zst ../
cd ../..
done done
- name: Upload artifacts - name: Upload x86_64-windows artifact
if: ${{ matrix.os == 'ubuntu-latest' }} if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v3
with: with:
name: builds name: zls-x86_64-windows
path: artifacts/*.tar.zst path: artifacts/x86_64-windows/
- name: Upload x86_64-linux artifact
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v3
with:
name: zls-x86_64-linux
path: artifacts/x86_64-linux/
- name: Upload x86_64-macos artifact
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v3
with:
name: zls-x86_64-macos
path: artifacts/x86_64-macos/
- name: Upload x86-windows artifact
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v3
with:
name: zls-x86-windows
path: artifacts/x86-windows/
- name: Upload x86-linux artifact
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v3
with:
name: zls-x86-linux
path: artifacts/x86-linux/
- name: Upload aarch64-linux artifact
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v3
with:
name: zls-aarch64-linux
path: artifacts/aarch64-linux/
- name: Upload aarch64-macos artifact
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/upload-artifact@v3
with:
name: zls-aarch64-macos
path: artifacts/aarch64-macos/
- name: Beam to Felix - name: Beam to Felix
if: ${{ matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/master' }} if: ${{ matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/master' && github.repository_owner == 'zigtools' }}
uses: easingthemes/ssh-deploy@v2.1.1 uses: easingthemes/ssh-deploy@v2.1.1
env: env:
SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_PRIVKEY }} SSH_PRIVATE_KEY: ${{ secrets.SERVER_SSH_PRIVKEY }}

View File

@ -6,14 +6,13 @@ const zls_version = std.builtin.Version{ .major = 0, .minor = 11, .patch = 0 };
pub fn build(b: *std.build.Builder) !void { pub fn build(b: *std.build.Builder) !void {
const current_zig = builtin.zig_version; const current_zig = builtin.zig_version;
const min_zig = std.SemanticVersion.parse("0.10.0-dev.4458+b120c819d") catch return; // builtins changed to @min / @max const min_zig = std.SemanticVersion.parse("0.11.0-dev.399+44ee1c885") catch return; // whereabouts allocgate 2.0
if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig })); if (current_zig.order(min_zig).compare(.lt)) @panic(b.fmt("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
const target = b.standardTargetOptions(.{}); const target = b.standardTargetOptions(.{});
const mode = b.standardReleaseOptions(); const mode = b.standardReleaseOptions();
const exe = b.addExecutable("zls", "src/main.zig"); const exe = b.addExecutable("zls", "src/main.zig");
exe.use_stage1 = true;
const exe_options = b.addOptions(); const exe_options = b.addOptions();
exe.addOptions("build_options", exe_options); exe.addOptions("build_options", exe_options);
@ -134,7 +133,6 @@ pub fn build(b: *std.build.Builder) !void {
}); });
} }
tests.use_stage1 = true;
tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items }); tests.addPackage(.{ .name = "zls", .source = .{ .path = "src/zls.zig" }, .dependencies = exe.packages.items });
tests.setBuildMode(.Debug); tests.setBuildMode(.Debug);
tests.setTarget(target); tests.setTarget(target);

View File

@ -73,6 +73,7 @@ pub const Value = struct {
pub const FieldDefinition = struct { pub const FieldDefinition = struct {
node_idx: Ast.Node.Index, node_idx: Ast.Node.Index,
/// Store name so tree doesn't need to be used to access field name /// Store name so tree doesn't need to be used to access field name
/// When the field is a tuple field, `name` will be an empty slice
name: []const u8, name: []const u8,
ty: Type, ty: Type,
default_value: ?Value, default_value: ?Value,
@ -814,7 +815,7 @@ pub fn interpret(
if (index != params.len - 1) if (index != params.len - 1)
try writer.writeAll(", "); try writer.writeAll(", ");
} }
try interpreter.recordError(node_idx, "compile_log", final.toOwnedSlice()); try interpreter.recordError(node_idx, "compile_log", try final.toOwnedSlice());
return InterpretResult{ .nothing = {} }; return InterpretResult{ .nothing = {} };
} }

View File

@ -150,9 +150,8 @@ pub fn configChanged(config: *Config, allocator: std.mem.Allocator, builtin_crea
var env = getZigEnv(allocator, exe_path) orelse break :blk; var env = getZigEnv(allocator, exe_path) orelse break :blk;
defer std.json.parseFree(Env, env, .{ .allocator = allocator }); defer std.json.parseFree(Env, env, .{ .allocator = allocator });
// We know this is allocated with `allocator`, we just steal it! // Make sure the path is absolute
config.zig_lib_path = env.lib_dir.?; config.zig_lib_path = try std.fs.realpathAlloc(allocator, env.lib_dir.?);
env.lib_dir = null;
logger.info("Using zig lib path '{s}'", .{config.zig_lib_path.?}); logger.info("Using zig lib path '{s}'", .{config.zig_lib_path.?});
} else { } else {
logger.warn("Zig executable path not specified in zls.json and could not be found in PATH", .{}); logger.warn("Zig executable path not specified in zls.json and could not be found in PATH", .{});

View File

@ -387,7 +387,7 @@ fn loadBuildConfiguration(
const directory_path = try std.fs.path.resolve(arena_allocator, &.{ build_file_path, "../" }); const directory_path = try std.fs.path.resolve(arena_allocator, &.{ build_file_path, "../" });
// TODO extract this option from `BuildAssociatedConfig.BuildOption` // TODO extract this option from `BuildAssociatedConfig.BuildOption`
const zig_cache_root: []const u8 = "zig-cache"; const zig_cache_root: []const u8 = try std.fs.path.join(arena_allocator, &.{ directory_path, "zig-cache" });
// Since we don't compile anything and no packages should put their // Since we don't compile anything and no packages should put their
// files there this path can be ignored // files there this path can be ignored
const zig_global_cache_root: []const u8 = "ZLS_DONT_CARE"; const zig_global_cache_root: []const u8 = "ZLS_DONT_CARE";

View File

@ -52,6 +52,8 @@ const ClientCapabilities = struct {
supports_snippets: bool = false, supports_snippets: bool = false,
supports_semantic_tokens: bool = false, supports_semantic_tokens: bool = false,
supports_inlay_hints: bool = false, supports_inlay_hints: bool = false,
supports_will_save: bool = false,
supports_will_save_wait_until: bool = false,
hover_supports_md: bool = false, hover_supports_md: bool = false,
completion_doc_supports_md: bool = false, completion_doc_supports_md: bool = false,
label_details_support: bool = false, label_details_support: bool = false,
@ -445,6 +447,36 @@ fn getAstCheckDiagnostics(
} }
} }
/// caller owns returned memory.
fn autofix(server: *Server, allocator: std.mem.Allocator, handle: *const DocumentStore.Handle) !std.ArrayListUnmanaged(types.TextEdit) {
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){};
try getAstCheckDiagnostics(server, handle.*, &diagnostics);
var builder = code_actions.Builder{
.arena = &server.arena,
.document_store = &server.document_store,
.handle = handle,
.offset_encoding = server.offset_encoding,
};
var actions = std.ArrayListUnmanaged(types.CodeAction){};
for (diagnostics.items) |diagnostic| {
try builder.generateCodeAction(diagnostic, &actions);
}
var text_edits = std.ArrayListUnmanaged(types.TextEdit){};
for (actions.items) |action| {
if (action.kind != .SourceFixAll) continue;
if (action.edit.changes.size != 1) continue;
const edits = action.edit.changes.get(handle.uri) orelse continue;
try text_edits.appendSlice(allocator, edits.items);
}
return text_edits;
}
fn typeToCompletion( fn typeToCompletion(
server: *Server, server: *Server,
list: *std.ArrayListUnmanaged(types.CompletionItem), list: *std.ArrayListUnmanaged(types.CompletionItem),
@ -644,14 +676,16 @@ fn nodeToCompletion(
.container_field_init, .container_field_init,
=> { => {
const field = ast.containerField(tree, node).?; const field = ast.containerField(tree, node).?;
try list.append(allocator, .{ if (!field.ast.tuple_like) {
.label = handle.tree.tokenSlice(field.ast.name_token), try list.append(allocator, .{
.kind = .Field, .label = handle.tree.tokenSlice(field.ast.main_token),
.documentation = doc, .kind = .Field,
.detail = analysis.getContainerFieldSignature(handle.tree, field), .documentation = doc,
.insertText = tree.tokenSlice(field.ast.name_token), .detail = analysis.getContainerFieldSignature(handle.tree, field),
.insertTextFormat = .PlainText, .insertText = tree.tokenSlice(field.ast.main_token),
}); .insertTextFormat = .PlainText,
});
}
}, },
.array_type, .array_type,
.array_type_sentinel, .array_type_sentinel,
@ -1247,7 +1281,7 @@ fn completeFieldAccess(server: *Server, handle: *const DocumentStore.Handle, sou
} }
} }
return completions.toOwnedSlice(allocator); return try completions.toOwnedSlice(allocator);
} }
fn formatDetailledLabel(item: *types.CompletionItem, alloc: std.mem.Allocator) !void { fn formatDetailledLabel(item: *types.CompletionItem, alloc: std.mem.Allocator) !void {
@ -1573,6 +1607,10 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
} }
} }
} }
if (textDocument.synchronization) |synchronization| {
server.client_capabilities.supports_will_save = synchronization.willSave.value;
server.client_capabilities.supports_will_save_wait_until = synchronization.willSaveWaitUntil.value;
}
} }
// NOTE: everything is initialized, we got the client capabilities // NOTE: everything is initialized, we got the client capabilities
@ -1599,8 +1637,10 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
}, },
.textDocumentSync = .{ .textDocumentSync = .{
.openClose = true, .openClose = true,
.change = .Full, .change = .Incremental,
.save = true, .save = true,
.willSave = true,
.willSaveWaitUntil = true,
}, },
.renameProvider = true, .renameProvider = true,
.completionProvider = .{ .resolveProvider = false, .triggerCharacters = &[_][]const u8{ ".", ":", "@", "]" }, .completionItem = .{ .labelDetailsSupport = true } }, .completionProvider = .{ .resolveProvider = false, .triggerCharacters = &[_][]const u8{ ".", ":", "@", "]" }, .completionItem = .{ .labelDetailsSupport = true } },
@ -1617,7 +1657,7 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req:
.documentFormattingProvider = true, .documentFormattingProvider = true,
.documentRangeFormattingProvider = false, .documentRangeFormattingProvider = false,
.foldingRangeProvider = true, .foldingRangeProvider = true,
.selectionRangeProvider = false, .selectionRangeProvider = true,
.workspaceSymbolProvider = false, .workspaceSymbolProvider = false,
.rangeProvider = false, .rangeProvider = false,
.documentProvider = true, .documentProvider = true,
@ -1722,6 +1762,13 @@ fn exitHandler(server: *Server, writer: anytype, id: types.RequestId) noreturn {
std.os.exit(error_code); std.os.exit(error_code);
} }
fn cancelRequestHandler(server: *Server, writer: anytype, id: types.RequestId) !void {
_ = id;
_ = writer;
_ = server;
// TODO implement $/cancelRequest
}
fn registerCapability(server: *Server, writer: anytype, method: []const u8) !void { fn registerCapability(server: *Server, writer: anytype, method: []const u8) !void {
const id = try std.fmt.allocPrint(server.arena.allocator(), "register-{s}", .{method}); const id = try std.fmt.allocPrint(server.arena.allocator(), "register-{s}", .{method});
log.debug("Dynamically registering method '{s}'", .{method}); log.debug("Dynamically registering method '{s}'", .{method});
@ -1825,31 +1872,10 @@ fn saveDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, re
if (handle.tree.errors.len != 0) return; if (handle.tree.errors.len != 0) return;
if (!server.config.enable_ast_check_diagnostics) return; if (!server.config.enable_ast_check_diagnostics) return;
if (!server.config.enable_autofix) return; if (!server.config.enable_autofix) return;
if (server.client_capabilities.supports_will_save) return;
if (server.client_capabilities.supports_will_save_wait_until) return;
var diagnostics = std.ArrayListUnmanaged(types.Diagnostic){}; const text_edits = try server.autofix(allocator, handle);
try getAstCheckDiagnostics(server, handle.*, &diagnostics);
var builder = code_actions.Builder{
.arena = &server.arena,
.document_store = &server.document_store,
.handle = handle,
.offset_encoding = server.offset_encoding,
};
var actions = std.ArrayListUnmanaged(types.CodeAction){};
for (diagnostics.items) |diagnostic| {
try builder.generateCodeAction(diagnostic, &actions);
}
var text_edits = std.ArrayListUnmanaged(types.TextEdit){};
for (actions.items) |action| {
if (action.kind != .SourceFixAll) continue;
if (action.edit.changes.size != 1) continue;
const edits = action.edit.changes.get(uri) orelse continue;
try text_edits.appendSlice(allocator, edits.items);
}
var workspace_edit = types.WorkspaceEdit{ .changes = .{} }; var workspace_edit = types.WorkspaceEdit{ .changes = .{} };
try workspace_edit.changes.putNoClobber(allocator, uri, text_edits); try workspace_edit.changes.putNoClobber(allocator, uri, text_edits);
@ -1878,6 +1904,35 @@ fn closeDocumentHandler(server: *Server, writer: anytype, id: types.RequestId, r
server.document_store.closeDocument(req.params.textDocument.uri); server.document_store.closeDocument(req.params.textDocument.uri);
} }
fn willSaveHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.WillSave) !void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
if (server.client_capabilities.supports_will_save_wait_until) return;
try willSaveWaitUntilHandler(server, writer, id, req);
}
fn willSaveWaitUntilHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.WillSave) !void {
const tracy_zone = tracy.trace(@src());
defer tracy_zone.end();
if (!server.config.enable_ast_check_diagnostics) return;
if (!server.config.enable_autofix) return;
const allocator = server.arena.allocator();
const uri = req.params.textDocument.uri;
const handle = server.document_store.getHandle(uri) orelse return;
if (handle.tree.errors.len != 0) return;
var text_edits = try server.autofix(allocator, handle);
return try send(writer, allocator, types.Response{
.id = id,
.result = .{ .TextEdits = try text_edits.toOwnedSlice(allocator) },
});
}
fn semanticTokensFullHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.SemanticTokensFull) !void { fn semanticTokensFullHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.SemanticTokensFull) !void {
const tracy_zone = tracy.trace(@src()); const tracy_zone = tracy.trace(@src());
defer tracy_zone.end(); defer tracy_zone.end();
@ -2091,6 +2146,10 @@ fn formattingHandler(server: *Server, writer: anytype, id: types.RequestId, req:
return try respondGeneric(writer, id, null_result_response); return try respondGeneric(writer, id, null_result_response);
}; };
if (handle.tree.errors.len != 0) {
return try respondGeneric(writer, id, null_result_response);
}
const formatted = try handle.tree.render(server.allocator); const formatted = try handle.tree.render(server.allocator);
defer server.allocator.free(formatted); defer server.allocator.free(formatted);
@ -2230,7 +2289,9 @@ fn generalReferencesHandler(server: *Server, writer: anytype, id: types.RequestI
}; };
const locations = if (pos_context == .label) const locations = if (pos_context == .label)
try references.labelReferences(allocator, decl, server.offset_encoding, include_decl) // FIXME https://github.com/zigtools/zls/issues/728
// try references.labelReferences(allocator, decl, server.offset_encoding, include_decl)
std.ArrayListUnmanaged(types.Location){}
else else
try references.symbolReferences( try references.symbolReferences(
&server.arena, &server.arena,
@ -2386,7 +2447,7 @@ fn foldingRangeHandler(server: *Server, writer: anytype, id: types.RequestId, re
end: Ast.TokenIndex, end: Ast.TokenIndex,
end_reach: Inclusivity, end_reach: Inclusivity,
) std.mem.Allocator.Error!bool { ) std.mem.Allocator.Error!bool {
const can_add = !tree.tokensOnSameLine(start, end); const can_add = start < end and !tree.tokensOnSameLine(start, end);
if (can_add) { if (can_add) {
try addTokRange(p_ranges, tree, start, end, end_reach); try addTokRange(p_ranges, tree, start, end, end_reach);
} }
@ -2631,6 +2692,64 @@ fn foldingRangeHandler(server: *Server, writer: anytype, id: types.RequestId, re
}); });
} }
fn selectionRangeHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.SelectionRange) !void {
const allocator = server.arena.allocator();
const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse {
log.warn("Trying to get selection range of non existent document {s}", .{req.params.textDocument.uri});
return try respondGeneric(writer, id, null_result_response);
};
// For each of the input positons, we need to compute the stack of AST
// nodes/ranges which contain the position. At the moment, we do this in a
// super inefficient way, by iterationg _all_ nodes, selecting the ones that
// contain position, and then sorting.
//
// A faster algorithm would be to walk the tree starting from the root,
// descending into the child containing the position at every step.
var result = try allocator.alloc(*types.SelectionRange, req.params.positions.len);
var locs = try std.ArrayListUnmanaged(offsets.Loc).initCapacity(allocator, 32);
for (req.params.positions) |position, position_index| {
const index = offsets.positionToIndex(handle.text, position, server.offset_encoding);
locs.clearRetainingCapacity();
for (handle.tree.nodes.items(.data)) |_, i| {
const node = @intCast(u32, i);
const loc = offsets.nodeToLoc(handle.tree, node);
if (loc.start <= index and index <= loc.end) {
(try locs.addOne(allocator)).* = loc;
}
}
std.sort.sort(offsets.Loc, locs.items, {}, shorterLocsFirst);
{
var i: usize = 0;
while (i + 1 < locs.items.len) {
if (std.meta.eql(locs.items[i], locs.items[i + 1])) {
_ = locs.orderedRemove(i);
} else {
i += 1;
}
}
}
var selection_ranges = try allocator.alloc(types.SelectionRange, locs.items.len);
for (selection_ranges) |*range, i| {
range.range = offsets.locToRange(handle.text, locs.items[i], server.offset_encoding);
range.parent = if (i + 1 < selection_ranges.len) &selection_ranges[i + 1] else null;
}
result[position_index] = &selection_ranges[0];
}
try send(writer, allocator, types.Response{
.id = id,
.result = .{ .SelectionRange = result },
});
}
fn shorterLocsFirst(_: void, lhs: offsets.Loc, rhs: offsets.Loc) bool {
return (lhs.end - lhs.start) < (rhs.end - rhs.start);
}
// Needed for the hack seen below. // Needed for the hack seen below.
fn extractErr(val: anytype) anyerror { fn extractErr(val: anytype) anyerror {
val catch |e| return e; val catch |e| return e;
@ -2750,15 +2869,16 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
const method_map = .{ const method_map = .{
.{ "initialized", void, initializedHandler }, .{ "initialized", void, initializedHandler },
.{"$/cancelRequest"},
.{"textDocument/willSave"},
.{ "initialize", requests.Initialize, initializeHandler }, .{ "initialize", requests.Initialize, initializeHandler },
.{ "shutdown", void, shutdownHandler }, .{ "shutdown", void, shutdownHandler },
.{ "exit", void, exitHandler }, .{ "exit", void, exitHandler },
.{ "$/cancelRequest", void, cancelRequestHandler },
.{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler }, .{ "textDocument/didOpen", requests.OpenDocument, openDocumentHandler },
.{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler }, .{ "textDocument/didChange", requests.ChangeDocument, changeDocumentHandler },
.{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler }, .{ "textDocument/didSave", requests.SaveDocument, saveDocumentHandler },
.{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler }, .{ "textDocument/didClose", requests.CloseDocument, closeDocumentHandler },
.{ "textDocument/willSave", requests.WillSave, willSaveHandler },
.{ "textDocument/willSaveWaitUntil", requests.WillSave, willSaveWaitUntilHandler },
.{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler }, .{ "textDocument/semanticTokens/full", requests.SemanticTokensFull, semanticTokensFullHandler },
.{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler }, .{ "textDocument/inlayHint", requests.InlayHint, inlayHintHandler },
.{ "textDocument/completion", requests.Completion, completionHandler }, .{ "textDocument/completion", requests.Completion, completionHandler },
@ -2776,6 +2896,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
.{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler },
.{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler },
.{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler }, .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler },
.{ "textDocument/selectionRange", requests.SelectionRange, selectionRangeHandler },
}; };
if (zig_builtin.zig_backend == .stage1) { if (zig_builtin.zig_backend == .stage1) {

View File

@ -226,7 +226,7 @@ pub fn getContainerFieldSignature(tree: Ast, field: Ast.full.ContainerField) []c
if (field.ast.value_expr == 0 and field.ast.type_expr == 0 and field.ast.align_expr == 0) { if (field.ast.value_expr == 0 and field.ast.type_expr == 0 and field.ast.align_expr == 0) {
return ""; // TODO display the container's type return ""; // TODO display the container's type
} }
const start = offsets.tokenToIndex(tree, field.ast.name_token); const start = offsets.tokenToIndex(tree, field.ast.main_token);
const end_node = if (field.ast.value_expr != 0) field.ast.value_expr else field.ast.type_expr; const end_node = if (field.ast.value_expr != 0) field.ast.value_expr else field.ast.type_expr;
const end = offsets.tokenToLoc(tree, ast.lastToken(tree, end_node)).end; const end = offsets.tokenToLoc(tree, ast.lastToken(tree, end_node)).end;
return tree.source[start..end]; return tree.source[start..end];
@ -291,9 +291,21 @@ pub fn getDeclNameToken(tree: Ast, node: Ast.Node.Index) ?Ast.TokenIndex {
}, },
// containers // containers
.container_field => tree.containerField(node).ast.name_token, .container_field => blk: {
.container_field_init => tree.containerFieldInit(node).ast.name_token, const field = tree.containerField(node);
.container_field_align => tree.containerFieldAlign(node).ast.name_token, if (field.ast.tuple_like) break :blk null;
break :blk field.ast.main_token;
},
.container_field_init => blk: {
const field = tree.containerFieldInit(node);
if (field.ast.tuple_like) break :blk null;
break :blk field.ast.main_token;
},
.container_field_align => blk: {
const field = tree.containerFieldAlign(node);
if (field.ast.tuple_like) break :blk null;
break :blk field.ast.main_token;
},
.identifier => main_token, .identifier => main_token,
.error_value => main_token + 2, // 'error'.<main_token +2> .error_value => main_token + 2, // 'error'.<main_token +2>
@ -1399,9 +1411,18 @@ pub fn nodeToString(tree: Ast, node: Ast.Node.Index) ?[]const u8 {
const main_token = tree.nodes.items(.main_token)[node]; const main_token = tree.nodes.items(.main_token)[node];
var buf: [1]Ast.Node.Index = undefined; var buf: [1]Ast.Node.Index = undefined;
switch (tree.nodes.items(.tag)[node]) { switch (tree.nodes.items(.tag)[node]) {
.container_field => return tree.tokenSlice(tree.containerField(node).ast.name_token), .container_field => {
.container_field_init => return tree.tokenSlice(tree.containerFieldInit(node).ast.name_token), const field = tree.containerField(node).ast;
.container_field_align => return tree.tokenSlice(tree.containerFieldAlign(node).ast.name_token), return if (field.tuple_like) null else tree.tokenSlice(field.main_token);
},
.container_field_init => {
const field = tree.containerFieldInit(node).ast;
return if (field.tuple_like) null else tree.tokenSlice(field.main_token);
},
.container_field_align => {
const field = tree.containerFieldAlign(node).ast;
return if (field.tuple_like) null else tree.tokenSlice(field.main_token);
},
.error_value => return tree.tokenSlice(data[node].rhs), .error_value => return tree.tokenSlice(data[node].rhs),
.identifier => return tree.tokenSlice(main_token), .identifier => return tree.tokenSlice(main_token),
.fn_proto, .fn_proto,

View File

@ -23,8 +23,6 @@ pub const generic = [_]Snipped{
.{ .label = "align", .kind = .Keyword }, .{ .label = "align", .kind = .Keyword },
.{ .label = "allowzero", .kind = .Keyword }, .{ .label = "allowzero", .kind = .Keyword },
.{ .label = "and", .kind = .Keyword }, .{ .label = "and", .kind = .Keyword },
.{ .label = "anyframe", .kind = .Keyword },
.{ .label = "anytype", .kind = .Keyword },
.{ .label = "asm", .kind = .Keyword }, .{ .label = "asm", .kind = .Keyword },
.{ .label = "async", .kind = .Keyword }, .{ .label = "async", .kind = .Keyword },
.{ .label = "await", .kind = .Keyword }, .{ .label = "await", .kind = .Keyword },
@ -86,4 +84,31 @@ pub const generic = [_]Snipped{
.{ .label = "log warn", .kind = .Snippet, .text = "std.log.warn(\"$1\", .{$0});" }, .{ .label = "log warn", .kind = .Snippet, .text = "std.log.warn(\"$1\", .{$0});" },
.{ .label = "log info", .kind = .Snippet, .text = "std.log.info(\"$1\", .{$0});" }, .{ .label = "log info", .kind = .Snippet, .text = "std.log.info(\"$1\", .{$0});" },
.{ .label = "log debug", .kind = .Snippet, .text = "std.log.debug(\"$1\", .{$0});" }, .{ .label = "log debug", .kind = .Snippet, .text = "std.log.debug(\"$1\", .{$0});" },
// types
.{ .label = "anyopaque", .kind = .Keyword },
.{ .label = "anyerror", .kind = .Keyword },
.{ .label = "anyframe", .kind = .Keyword },
.{ .label = "anytype", .kind = .Keyword },
.{ .label = "noreturn", .kind = .Keyword },
.{ .label = "type", .kind = .Keyword },
.{ .label = "bool", .kind = .Keyword },
.{ .label = "void", .kind = .Keyword },
.{ .label = "isize", .kind = .Keyword },
.{ .label = "usize", .kind = .Keyword },
.{ .label = "i8", .kind = .Keyword },
.{ .label = "i16", .kind = .Keyword },
.{ .label = "i32", .kind = .Keyword },
.{ .label = "i64", .kind = .Keyword },
.{ .label = "i128", .kind = .Keyword },
.{ .label = "u8", .kind = .Keyword },
.{ .label = "u16", .kind = .Keyword },
.{ .label = "u32", .kind = .Keyword },
.{ .label = "u64", .kind = .Keyword },
.{ .label = "u128", .kind = .Keyword },
.{ .label = "f16", .kind = .Keyword },
.{ .label = "f32", .kind = .Keyword },
.{ .label = "f64", .kind = .Keyword },
.{ .label = "f80", .kind = .Keyword },
.{ .label = "f128", .kind = .Keyword },
}; };

View File

@ -366,6 +366,7 @@ pub fn applyTextEdits(
i -= 1; i -= 1;
if (content_changes[i].range == null) { if (content_changes[i].range == null) {
last_full_text_change = i; last_full_text_change = i;
continue;
} }
} }

View File

@ -73,7 +73,7 @@ const Builder = struct {
}); });
} }
fn toOwnedSlice(self: *Builder) []types.InlayHint { fn toOwnedSlice(self: *Builder) error{OutOfMemory}![]types.InlayHint {
return self.hints.toOwnedSlice(self.allocator); return self.hints.toOwnedSlice(self.allocator);
} }
}; };

View File

@ -154,6 +154,11 @@ pub const Initialize = struct {
workspaceFolders: Default(bool, false), workspaceFolders: Default(bool, false),
}, },
textDocument: ?struct { textDocument: ?struct {
synchronization: ?struct {
willSave: Default(bool, false),
willSaveWaitUntil: Default(bool, false),
didSave: Default(bool, false),
},
semanticTokens: Exists, semanticTokens: Exists,
inlayHint: Exists, inlayHint: Exists,
hover: ?struct { hover: ?struct {
@ -230,6 +235,19 @@ const TextDocumentIdentifierPositionRequest = struct {
}, },
}; };
pub const SaveReason = enum(u32) {
Manual = 1,
AfterDelay = 2,
FocusOut = 3,
};
pub const WillSave = struct {
params: struct {
textDocument: TextDocumentIdentifier,
reason: SaveReason,
},
};
pub const SignatureHelp = struct { pub const SignatureHelp = struct {
params: struct { params: struct {
textDocument: TextDocumentIdentifier, textDocument: TextDocumentIdentifier,
@ -294,3 +312,10 @@ pub const FoldingRange = struct {
textDocument: TextDocumentIdentifier, textDocument: TextDocumentIdentifier,
}, },
}; };
pub const SelectionRange = struct {
params: struct {
textDocument: TextDocumentIdentifier,
positions: []types.Position,
},
};

View File

@ -195,7 +195,7 @@ const Builder = struct {
self.previous_position = start; self.previous_position = start;
} }
fn toOwnedSlice(self: *Builder) []u32 { fn toOwnedSlice(self: *Builder) error{OutOfMemory}![]u32 {
return self.arr.toOwnedSlice(self.arena.allocator()); return self.arr.toOwnedSlice(self.arena.allocator());
} }
}; };
@ -999,7 +999,9 @@ fn writeContainerField(builder: *Builder, node: Ast.Node.Index, field_token_type
try writeDocComments(builder, tree, docs); try writeDocComments(builder, tree, docs);
try writeToken(builder, container_field.comptime_token, .keyword); try writeToken(builder, container_field.comptime_token, .keyword);
if (field_token_type) |tok_type| try writeToken(builder, container_field.ast.name_token, tok_type); if (!container_field.ast.tuple_like) {
if (field_token_type) |tok_type| try writeToken(builder, container_field.ast.main_token, tok_type);
}
if (container_field.ast.type_expr != 0) { if (container_field.ast.type_expr != 0) {
try callWriteNodeTokens(allocator, .{ builder, container_field.ast.type_expr }); try callWriteNodeTokens(allocator, .{ builder, container_field.ast.type_expr });

View File

@ -146,44 +146,54 @@ pub fn TracyAllocator(comptime name: ?[:0]const u8) type {
} }
pub fn allocator(self: *Self) std.mem.Allocator { pub fn allocator(self: *Self) std.mem.Allocator {
return std.mem.Allocator.init(self, allocFn, resizeFn, freeFn); return .{
.ptr = self,
.vtable = &.{
.alloc = allocFn,
.resize = resizeFn,
.free = freeFn,
},
};
} }
fn allocFn(self: *Self, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) std.mem.Allocator.Error![]u8 { fn allocFn(ptr: *anyopaque, len: usize, ptr_align: u8, ret_addr: usize) ?[*]u8 {
const result = self.parent_allocator.rawAlloc(len, ptr_align, len_align, ret_addr); const self = @ptrCast(*Self, @alignCast(@alignOf(Self), ptr));
const result = self.parent_allocator.rawAlloc(len, ptr_align, ret_addr);
if (result) |data| { if (result) |data| {
if (data.len != 0) { if (len != 0) {
if (name) |n| { if (name) |n| {
allocNamed(data.ptr, data.len, n); allocNamed(data, len, n);
} else { } else {
alloc(data.ptr, data.len); alloc(data, len);
} }
} }
} else |_| { } else {
messageColor("allocation failed", 0xFF0000); messageColor("allocation failed", 0xFF0000);
} }
return result; return result;
} }
fn resizeFn(self: *Self, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) ?usize { fn resizeFn(ptr: *anyopaque, buf: []u8, buf_align: u8, new_len: usize, ret_addr: usize) bool {
if (self.parent_allocator.rawResize(buf, buf_align, new_len, len_align, ret_addr)) |resized_len| { const self = @ptrCast(*Self, @alignCast(@alignOf(Self), ptr));
if (self.parent_allocator.rawResize(buf, buf_align, new_len, ret_addr)) {
if (name) |n| { if (name) |n| {
freeNamed(buf.ptr, n); freeNamed(buf.ptr, n);
allocNamed(buf.ptr, resized_len, n); allocNamed(buf.ptr, new_len, n);
} else { } else {
free(buf.ptr); free(buf.ptr);
alloc(buf.ptr, resized_len); alloc(buf.ptr, new_len);
} }
return resized_len; return true;
} }
// during normal operation the compiler hits this case thousands of times due to this // during normal operation the compiler hits this case thousands of times due to this
// emitting messages for it is both slow and causes clutter // emitting messages for it is both slow and causes clutter
return null; return false;
} }
fn freeFn(self: *Self, buf: []u8, buf_align: u29, ret_addr: usize) void { fn freeFn(ptr: *anyopaque, buf: []u8, buf_align: u8, ret_addr: usize) void {
const self = @ptrCast(*Self, @alignCast(@alignOf(Self), ptr));
self.parent_allocator.rawFree(buf, buf_align, ret_addr); self.parent_allocator.rawFree(buf, buf_align, ret_addr);
// this condition is to handle free being called on an empty slice that was never even allocated // this condition is to handle free being called on an empty slice that was never even allocated
// example case: `std.process.getSelfExeSharedLibPaths` can return `&[_][:0]u8{}` // example case: `std.process.getSelfExeSharedLibPaths` can return `&[_][:0]u8{}`

View File

@ -152,7 +152,7 @@ pub fn translate(allocator: std.mem.Allocator, config: Config, include_dirs: []c
var native_paths = std.zig.system.NativePaths.detect(allocator, target_info) catch break :blk null; var native_paths = std.zig.system.NativePaths.detect(allocator, target_info) catch break :blk null;
defer native_paths.deinit(); defer native_paths.deinit();
break :blk native_paths.include_dirs.toOwnedSlice(); break :blk try native_paths.include_dirs.toOwnedSlice();
}; };
defer if (base_include_dirs) |dirs| { defer if (base_include_dirs) |dirs| {
for (dirs) |path| { for (dirs) |path| {

View File

@ -44,6 +44,7 @@ pub const ResponseParams = union(enum) {
CodeAction: []CodeAction, CodeAction: []CodeAction,
ApplyEdit: ApplyWorkspaceEditParams, ApplyEdit: ApplyWorkspaceEditParams,
FoldingRange: []FoldingRange, FoldingRange: []FoldingRange,
SelectionRange: []*SelectionRange,
}; };
pub const Response = struct { pub const Response = struct {
@ -441,6 +442,8 @@ const InitializeResult = struct {
textDocumentSync: struct { textDocumentSync: struct {
openClose: bool, openClose: bool,
change: TextDocumentSyncKind, change: TextDocumentSyncKind,
willSave: bool,
willSaveWaitUntil: bool,
save: bool, save: bool,
}, },
renameProvider: bool, renameProvider: bool,
@ -526,3 +529,8 @@ pub const FoldingRange = struct {
startLine: usize, startLine: usize,
endLine: usize, endLine: usize,
}; };
pub const SelectionRange = struct {
range: Range,
parent: ?*SelectionRange,
};

View File

@ -100,7 +100,7 @@ fn parseHex(c: u8) !u8 {
pub fn parse(allocator: std.mem.Allocator, str: []const u8) ![]u8 { 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; if (str.len < 7 or !std.mem.eql(u8, "file://", str[0..7])) return error.UriBadScheme;
const uri = try allocator.alloc(u8, str.len - (if (std.fs.path.sep == '\\') 8 else 7)); var uri = try allocator.alloc(u8, str.len - (if (std.fs.path.sep == '\\') 8 else 7));
errdefer allocator.free(uri); errdefer allocator.free(uri);
const path = if (std.fs.path.sep == '\\') str[8..] else str[7..]; const path = if (std.fs.path.sep == '\\') str[8..] else str[7..];
@ -125,5 +125,5 @@ pub fn parse(allocator: std.mem.Allocator, str: []const u8) ![]u8 {
i -= 1; i -= 1;
} }
return allocator.shrink(uri, i); return allocator.realloc(uri, i);
} }

View File

@ -105,7 +105,7 @@ pub fn collectReplacePlaceholders(allocator: std.mem.Allocator, source: []const
return CollectPlaceholdersResult{ return CollectPlaceholdersResult{
.locations = locations, .locations = locations,
.new_source = new_source.toOwnedSlice(allocator), .new_source = try new_source.toOwnedSlice(allocator),
}; };
} }

View File

@ -32,18 +32,18 @@ test "ComptimeInterpreter - basic test" {
_ = try interpreter.interpret(0, null, .{}); _ = try interpreter.interpret(0, null, .{});
var bool_type = try interpreter.createType(std.math.maxInt(std.zig.Ast.Node.Index), .{ .@"bool" = .{} }); var bool_type = try interpreter.createType(std.math.maxInt(std.zig.Ast.Node.Index), .{ .bool = {} });
var arg_false = ComptimeInterpreter.Value{ var arg_false = ComptimeInterpreter.Value{
.interpreter = &interpreter, .interpreter = &interpreter,
.node_idx = std.math.maxInt(std.zig.Ast.Node.Index), .node_idx = std.math.maxInt(std.zig.Ast.Node.Index),
.@"type" = bool_type, .type = bool_type,
.value_data = try interpreter.createValueData(.{ .@"bool" = false }), .value_data = try interpreter.createValueData(.{ .bool = false }),
}; };
var arg_true = ComptimeInterpreter.Value{ var arg_true = ComptimeInterpreter.Value{
.interpreter = &interpreter, .interpreter = &interpreter,
.node_idx = std.math.maxInt(std.zig.Ast.Node.Index), .node_idx = std.math.maxInt(std.zig.Ast.Node.Index),
.@"type" = bool_type, .type = bool_type,
.value_data = try interpreter.createValueData(.{ .@"bool" = true }), .value_data = try interpreter.createValueData(.{ .bool = true }),
}; };
const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?; const rmt = interpreter.root_type.?.getTypeInfo().@"struct".scope.declarations.get("ReturnMyType").?;
@ -57,8 +57,8 @@ test "ComptimeInterpreter - basic test" {
}, .{}); }, .{});
defer call_with_true.scope.deinit(); defer call_with_true.scope.deinit();
try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(call_with_false.result.value.value_data.@"type".getTypeInfo())}); try std.testing.expectFmt("u69", "{any}", .{interpreter.formatTypeInfo(call_with_false.result.value.value_data.type.getTypeInfo())});
try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(call_with_true.result.value.value_data.@"type".getTypeInfo())}); try std.testing.expectFmt("u8", "{any}", .{interpreter.formatTypeInfo(call_with_true.result.value.value_data.type.getTypeInfo())});
} }
test "ComptimeInterpreter - struct" { test "ComptimeInterpreter - struct" {
@ -92,5 +92,5 @@ test "ComptimeInterpreter - struct" {
const z = try interpreter.call(null, rmt.node_idx, &.{}, .{}); const z = try interpreter.call(null, rmt.node_idx, &.{}, .{});
defer z.scope.deinit(); defer z.scope.deinit();
try std.testing.expectFmt("struct {slay: bool, var abc: comptime_int = 123, }", "{any}", .{interpreter.formatTypeInfo(z.result.value.value_data.@"type".getTypeInfo())}); try std.testing.expectFmt("struct {slay: bool, var abc: comptime_int = 123, }", "{any}", .{interpreter.formatTypeInfo(z.result.value.value_data.type.getTypeInfo())});
} }

View File

@ -269,17 +269,19 @@ test "completion - union" {
} }
test "completion - enum" { test "completion - enum" {
try testCompletion( // TODO: Fix
\\const E = enum { return error.SkipZigTest;
\\ alpha, // try testCompletion(
\\ beta, // \\const E = enum {
\\}; // \\ alpha,
\\const foo = E.<cursor> // \\ beta,
, &.{ // \\};
// TODO kind should be Enum // \\const foo = E.<cursor>
.{ .label = "alpha", .kind = .Field }, // , &.{
.{ .label = "beta", .kind = .Field }, // // TODO kind should be Enum
}); // .{ .label = "alpha", .kind = .Field },
// .{ .label = "beta", .kind = .Field },
// });
} }
test "completion - error union" { test "completion - error union" {

View File

@ -0,0 +1,66 @@
const std = @import("std");
const zls = @import("zls");
const builtin = @import("builtin");
const Context = @import("../context.zig").Context;
const types = zls.types;
const requests = zls.requests;
const allocator: std.mem.Allocator = std.testing.allocator;
test "foldingRange - empty" {
try testFoldingRange("", "[]");
}
test "foldingRange - smoke" {
try testFoldingRange(
\\fn main() u32 {
\\ return 1 + 1;
\\}
,
\\[{"startLine":0,"endLine":1}]
);
}
test "foldingRange - #801" {
try testFoldingRange(
\\fn score(c: u8) !u32 {
\\ return switch(c) {
\\ 'a'...'z' => c - 'a',
\\ 'A'...'Z' => c - 'A',
\\ _ => error
\\ };
\\}
,
\\[]
);
}
fn testFoldingRange(source: []const u8, expect: []const u8) !void {
var ctx = try Context.init();
defer ctx.deinit();
const test_uri: []const u8 = switch (builtin.os.tag) {
.windows => "file:///C:\\test.zig",
else => "file:///test.zig",
};
try ctx.requestDidOpen(test_uri, source);
const request = requests.FoldingRange{ .params = .{ .textDocument = .{ .uri = test_uri } } };
const response = try ctx.requestGetResponse(?[]types.FoldingRange, "textDocument/foldingRange", request);
defer response.deinit();
var actual = std.ArrayList(u8).init(allocator);
defer actual.deinit();
try std.json.stringify(response.result, .{}, actual.writer());
try expectEqualJson(expect, actual.items);
}
fn expectEqualJson(expect: []const u8, actual: []const u8) !void {
// TODO: Actually compare strings as JSON values.
return std.testing.expectEqualStrings(expect, actual);
}

View File

@ -150,7 +150,7 @@ fn testReferences(source: []const u8) !void {
try locs.append(allocator, new_loc); try locs.append(allocator, new_loc);
} }
break :blk locs.toOwnedSlice(allocator); break :blk try locs.toOwnedSlice(allocator);
}; };
defer allocator.free(expected_locs); defer allocator.free(expected_locs);

View File

@ -0,0 +1,76 @@
const std = @import("std");
const zls = @import("zls");
const builtin = @import("builtin");
const helper = @import("../helper.zig");
const Context = @import("../context.zig").Context;
const ErrorBuilder = @import("../ErrorBuilder.zig");
const types = zls.types;
const offsets = zls.offsets;
const requests = zls.requests;
const allocator: std.mem.Allocator = std.testing.allocator;
test "selectionRange - empty" {
try testSelectionRange("<>", &.{});
}
test "seletionRange - smoke" {
try testSelectionRange(
\\fn main() void {
\\ const x = 1 <>+ 1;
\\}
, &.{ "1 + 1", "const x = 1 + 1", "{\n const x = 1 + 1;\n}" });
}
fn testSelectionRange(source: []const u8, want: []const []const u8) !void {
var phr = try helper.collectClearPlaceholders(allocator, source);
defer phr.deinit(allocator);
var ctx = try Context.init();
defer ctx.deinit();
const test_uri: []const u8 = switch (builtin.os.tag) {
.windows => "file:///C:\\test.zig",
else => "file:///test.zig",
};
try ctx.requestDidOpen(test_uri, phr.new_source);
const position = offsets.locToRange(phr.new_source, phr.locations.items(.new)[0], .utf16).start;
const SelectionRange = struct {
range: types.Range,
parent: ?*@This(),
};
const request = requests.SelectionRange{ .params = .{
.textDocument = .{ .uri = test_uri },
.positions = &[_]types.Position{position},
} };
const response = try ctx.requestGetResponse(?[]SelectionRange, "textDocument/selectionRange", request);
defer response.deinit();
const selectionRanges: []SelectionRange = response.result orelse {
std.debug.print("Server returned `null` as the result\n", .{});
return error.InvalidResponse;
};
var got = std.ArrayList([]const u8).init(allocator);
defer got.deinit();
var it: ?*SelectionRange = &selectionRanges[0];
while (it) |r| {
const slice = offsets.rangeToSlice(phr.new_source, r.range, .utf16);
(try got.addOne()).* = slice;
it = r.parent;
}
const last = got.pop();
try std.testing.expectEqualStrings(phr.new_source, last);
try std.testing.expectEqual(want.len, got.items.len);
for (want) |w, i| {
try std.testing.expectEqualStrings(w, got.items[i]);
}
}

View File

@ -18,6 +18,7 @@ test "semantic tokens" {
, ,
&.{ 0, 0, 5, 7, 0, 0, 6, 3, 0, 33, 0, 4, 1, 11, 0, 0, 2, 7, 12, 0, 0, 8, 5, 9, 0 }, &.{ 0, 0, 5, 7, 0, 0, 6, 3, 0, 33, 0, 4, 1, 11, 0, 0, 2, 7, 12, 0, 0, 8, 5, 9, 0 },
); );
// TODO more tests // TODO more tests
} }

View File

@ -10,10 +10,12 @@ comptime {
// TODO Document Synchronization // TODO Document Synchronization
// LSP features // LSP features
_ = @import("lsp_features/semantic_tokens.zig"); _ = @import("lsp_features/completion.zig");
_ = @import("lsp_features/folding_range.zig");
_ = @import("lsp_features/inlay_hints.zig"); _ = @import("lsp_features/inlay_hints.zig");
_ = @import("lsp_features/references.zig"); _ = @import("lsp_features/references.zig");
_ = @import("lsp_features/completion.zig"); _ = @import("lsp_features/selection_range.zig");
_ = @import("lsp_features/semantic_tokens.zig");
// Language features // Language features
_ = @import("language_features/cimport.zig"); _ = @import("language_features/cimport.zig");

View File

@ -1,16 +1,55 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin");
const zls = @import("zls"); const zls = @import("zls");
const URI = zls.URI; const URI = zls.URI;
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
test "uri - pathRelative" { test "uri - parse (Windows)" {
const join1 = try URI.pathRelative(allocator, "file://project/zig", "/src/main+.zig"); if (builtin.os.tag == .windows) {
defer allocator.free(join1); const parseWin = try URI.parse(allocator, "file:///c%3A/main.zig");
try std.testing.expectEqualStrings("file://project/zig/src/main%2B.zig", join1); defer allocator.free(parseWin);
try std.testing.expectEqualStrings("c:\\main.zig", parseWin);
const join2 = try URI.pathRelative(allocator, "file://project/zig/wow", "../]src]/]main.zig"); const parseWin2 = try URI.parse(allocator, "file:///c%3A/main%2B.zig");
defer allocator.free(join2); defer allocator.free(parseWin2);
try std.testing.expectEqualStrings("file://project/zig/%5Dsrc%5D/%5Dmain.zig", join2); try std.testing.expectEqualStrings("c:\\main+.zig", parseWin2);
}
}
test "uri - parse (Unix-style)" {
if (builtin.os.tag != .windows) {
const parseUnix = try URI.parse(allocator, "file:///home/main.zig");
defer allocator.free(parseUnix);
try std.testing.expectEqualStrings("/home/main.zig", parseUnix);
const parseUnix2 = try URI.parse(allocator, "file:///home/main%2B.zig");
defer allocator.free(parseUnix2);
try std.testing.expectEqualStrings("/home/main+.zig", parseUnix2);
}
}
test "uri - fromPath" {
if (builtin.os.tag == .windows) {
const fromPathWin = try URI.fromPath(allocator, "c:\\main.zig");
defer allocator.free(fromPathWin);
try std.testing.expectEqualStrings("file:///c%3A/main.zig", fromPathWin);
}
if (builtin.os.tag != .windows) {
const fromPathUnix = try URI.fromPath(allocator, "/home/main.zig");
defer allocator.free(fromPathUnix);
try std.testing.expectEqualStrings("file:///home/main.zig", fromPathUnix);
}
}
test "uri - pathRelative" {
const join1 = try URI.pathRelative(allocator, "file:///project/zig", "/src/main+.zig");
defer allocator.free(join1);
try std.testing.expectEqualStrings("file:///project/zig/src/main%2B.zig", join1);
const join2 = try URI.pathRelative(allocator, "file:///project/zig/wow", "../]src]/]main.zig");
defer allocator.free(join2);
try std.testing.expectEqualStrings("file:///project/zig/%5Dsrc%5D/%5Dmain.zig", join2);
} }