diff --git a/src/Server.zig b/src/Server.zig index 9bccbe8..713b2b5 100644 --- a/src/Server.zig +++ b/src/Server.zig @@ -1574,7 +1574,7 @@ fn initializeHandler(server: *Server, writer: anytype, id: types.RequestId, req: .colorProvider = false, .documentFormattingProvider = true, .documentRangeFormattingProvider = false, - .foldingRangeProvider = false, + .foldingRangeProvider = true, .selectionRangeProvider = false, .workspaceSymbolProvider = false, .rangeProvider = false, @@ -2339,6 +2339,89 @@ fn codeActionHandler(server: *Server, writer: anytype, id: types.RequestId, req: }); } +fn foldingRangeHandler(server: *Server, writer: anytype, id: types.RequestId, req: requests.FoldingRange) !void +{ + const Tag = std.zig.Token.Tag; + const allocator = server.arena.allocator(); + const handle = server.document_store.getHandle(req.params.textDocument.uri) orelse { + log.warn("Trying to get folding ranges of non existent document {s}", .{req.params.textDocument.uri}); + return try respondGeneric(writer, id, null_result_response); + }; + + // Used to store the result + var ranges = std.ArrayList(types.FoldingRange).init(allocator); + + // We add opened curly braces to a stack as we go and pop one off when we find a closing brace. + // As an optimization we start with a capacity of 10 which should work well in most cases since + // people will almost never have more than 10 levels deep of nested braces. + var stack = try std.ArrayList(usize).initCapacity(allocator, 10); + + // Iterate over the token tags and look for pairs of braces + for (handle.tree.tokens.items(.tag)) |tag, i| { + const token_index = @intCast(Ast.TokenIndex, i); + + // If we found a `{` we add it to our stack + if (tag == Tag.l_brace) { + const line = handle.tree.tokenLocation(0, token_index).line; + try stack.append(line); + } + + // If we found a close `}` we have a matching pair + if (tag == Tag.r_brace and stack.items.len > 0) { + const start_line = stack.pop(); + const end_line = handle.tree.tokenLocation(0, token_index).line; + + // Add brace pairs but discard those from the same line, no need to waste memory on them + if (start_line != end_line) + { + try ranges.append(.{ + .startLine = start_line, + .endLine = end_line, + }); + } + } + } + + // Iterate over the source code and look for code regions with #region #endregion + { + // We will reuse the stack + stack.clearRetainingCapacity(); + + var i: usize = 0; + var lines_count: usize = 0; + while (i < handle.tree.source.len) : (i += 1) { + const slice = handle.tree.source[i..]; + + if (slice[0] == '\n') { + lines_count += 1; + } + + if (std.mem.startsWith(u8, slice, "//#region")) { + try stack.append(lines_count); + } + + if (std.mem.startsWith(u8, slice, "//#endregion") and stack.items.len > 0) { + const start_line = stack.pop(); + const end_line = lines_count; + + // Add brace pairs but discard those from the same line, no need to waste memory on them + if (start_line != end_line) + { + try ranges.append(.{ + .startLine = start_line, + .endLine = end_line, + }); + } + } + } + } + + try send(writer, allocator, types.Response { + .id = id, + .result = .{ .FoldingRange = ranges.items }, + }); +} + // Needed for the hack seen below. fn extractErr(val: anytype) anyerror { val catch |e| return e; @@ -2475,6 +2558,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void .{ "textDocument/documentHighlight", requests.DocumentHighlight, documentHighlightHandler }, .{ "textDocument/codeAction", requests.CodeAction, codeActionHandler }, .{ "workspace/didChangeConfiguration", Config.DidChangeConfigurationParams, didChangeConfigurationHandler }, + .{ "textDocument/foldingRange", requests.FoldingRange, foldingRangeHandler } }; if (zig_builtin.zig_backend == .stage1) { @@ -2539,7 +2623,6 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void .{ "textDocument/rangeFormatting", true }, .{ "textDocument/onTypeFormatting", true }, .{ "textDocument/prepareRename", true }, - .{ "textDocument/foldingRange", true }, .{ "textDocument/selectionRange", true }, .{ "textDocument/semanticTokens/range", true }, .{ "workspace/didChangeWorkspaceFolders", false }, diff --git a/src/requests.zig b/src/requests.zig index 35cd68d..d926d4d 100644 --- a/src/requests.zig +++ b/src/requests.zig @@ -288,3 +288,9 @@ pub const CodeAction = struct { }, }, }; + +pub const FoldingRange = struct { + params: struct { + textDocument: TextDocumentIdentifier, + }, +}; \ No newline at end of file diff --git a/src/types.zig b/src/types.zig index 9e80c39..2be811f 100644 --- a/src/types.zig +++ b/src/types.zig @@ -43,6 +43,7 @@ pub const ResponseParams = union(enum) { DocumentHighlight: []DocumentHighlight, CodeAction: []CodeAction, ApplyEdit: ApplyWorkspaceEditParams, + FoldingRange: []FoldingRange, }; pub const Response = struct { @@ -520,3 +521,8 @@ pub const DocumentHighlight = struct { range: Range, kind: ?DocumentHighlightKind, }; + +pub const FoldingRange = struct { + startLine: usize, + endLine: usize, +};