@@ -2339,74 +2339,255 @@ fn codeActionHandler(server: *Server, writer: anytype, id: types.RequestId, req:
2339
2339
});
2340
2340
}
2341
2341
2342
- fn foldingRangeHandler (server : * Server , writer : anytype , id : types.RequestId , req : requests.FoldingRange ) ! void
2343
- {
2344
- const Tag = std . zig . Token . Tag ;
2342
+ fn foldingRangeHandler (server : * Server , writer : anytype , id : types.RequestId , req : requests.FoldingRange ) ! void {
2343
+ const Token = std . zig . Token ;
2344
+ const Node = Ast . Node ;
2345
2345
const allocator = server .arena .allocator ();
2346
2346
const handle = server .document_store .getHandle (req .params .textDocument .uri ) orelse {
2347
2347
log .warn ("Trying to get folding ranges of non existent document {s}" , .{req .params .textDocument .uri });
2348
2348
return try respondGeneric (writer , id , null_result_response );
2349
2349
};
2350
2350
2351
+ const helper = struct {
2352
+ const Inclusivity = enum { inclusive , exclusive };
2353
+ /// Returns true if added.
2354
+ fn maybeAddTokRange (
2355
+ p_ranges : * std .ArrayList (types.FoldingRange ),
2356
+ tree : Ast ,
2357
+ start : Ast.TokenIndex ,
2358
+ end : Ast.TokenIndex ,
2359
+ end_reach : Inclusivity ,
2360
+ ) std .mem .Allocator .Error ! bool {
2361
+ const can_add = ! tree .tokensOnSameLine (start , end );
2362
+ if (can_add ) {
2363
+ try addTokRange (p_ranges , tree , start , end , end_reach );
2364
+ }
2365
+ return can_add ;
2366
+ }
2367
+ fn addTokRange (
2368
+ p_ranges : * std .ArrayList (types.FoldingRange ),
2369
+ tree : Ast ,
2370
+ start : Ast.TokenIndex ,
2371
+ end : Ast.TokenIndex ,
2372
+ end_reach : Inclusivity ,
2373
+ ) std .mem .Allocator .Error ! void {
2374
+ std .debug .assert (! std .debug .runtime_safety or ! tree .tokensOnSameLine (start , end ));
2375
+
2376
+ const start_loc = tree .tokenLocation (0 , start );
2377
+ const end_loc_rel = tree .tokenLocation (@intCast (Ast .ByteOffset , start_loc .line_start ), end );
2378
+ std .debug .assert (end_loc_rel .line != 0 );
2379
+
2380
+ try p_ranges .append (.{
2381
+ .startLine = start_loc .line ,
2382
+ .endLine = (start_loc .line + end_loc_rel .line ) -
2383
+ @boolToInt (end_reach == .exclusive ),
2384
+ });
2385
+ }
2386
+ };
2387
+
2351
2388
// Used to store the result
2352
2389
var ranges = std .ArrayList (types .FoldingRange ).init (allocator );
2353
2390
2354
- // We add opened curly braces to a stack as we go and pop one off when we find a closing brace.
2355
- // As an optimization we start with a capacity of 10 which should work well in most cases since
2356
- // people will almost never have more than 10 levels deep of nested braces.
2357
- var stack = try std .ArrayList (usize ).initCapacity (allocator , 10 );
2391
+ const token_tags : []const Token.Tag = handle .tree .tokens .items (.tag );
2392
+ const node_tags : []const Node.Tag = handle .tree .nodes .items (.tag );
2358
2393
2359
- // Iterate over the token tags and look for pairs of braces
2360
- for (handle .tree .tokens .items (.tag )) | tag , i | {
2361
- const token_index = @intCast (Ast .TokenIndex , i );
2362
-
2363
- // If we found a `{` we add it to our stack
2364
- if (tag == Tag .l_brace ) {
2365
- const line = handle .tree .tokenLocation (0 , token_index ).line ;
2366
- try stack .append (line );
2394
+ if (token_tags .len == 0 ) return ;
2395
+ if (token_tags [0 ] == .container_doc_comment ) {
2396
+ var tok : Ast.TokenIndex = 1 ;
2397
+ while (tok < token_tags .len ) : (tok += 1 ) {
2398
+ if (token_tags [tok ] != .container_doc_comment ) {
2399
+ break ;
2400
+ }
2401
+ }
2402
+ if (tok > 1 ) { // each container doc comment has its own line, so each one counts for a line
2403
+ try ranges .append (.{
2404
+ .startLine = 1 ,
2405
+ .endLine = tok ,
2406
+ });
2367
2407
}
2408
+ }
2368
2409
2369
- // If we found a close `}` we have a matching pair
2370
- if (tag == Tag .r_brace and stack .items .len > 0 ) {
2371
- const start_line = stack .pop ();
2372
- const end_line = handle .tree .tokenLocation (0 , token_index ).line ;
2373
-
2374
- // Add brace pairs but discard those from the same line, no need to waste memory on them
2375
- if (start_line != end_line )
2376
- {
2377
- try ranges .append (.{
2378
- .startLine = start_line ,
2379
- .endLine = end_line ,
2380
- });
2381
- }
2410
+ for (node_tags ) | node_tag , i | {
2411
+ const node = @intCast (Node .Index , i );
2412
+
2413
+ switch (node_tag ) {
2414
+ // only fold the expression pertaining to the if statement, and the else statement, each respectively.
2415
+ // TODO: Should folding multiline condition expressions also be supported? Ditto for the other control flow structures.
2416
+ .@"if" , .if_simple = > {
2417
+ const if_full = ast .ifFull (handle .tree , node );
2418
+
2419
+ const start_tok_1 = handle .tree .lastToken (if_full .ast .cond_expr );
2420
+ const end_tok_1 = handle .tree .lastToken (if_full .ast .then_expr );
2421
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , start_tok_1 , end_tok_1 , .inclusive );
2422
+
2423
+ if (if_full .ast .else_expr == 0 ) continue ;
2424
+
2425
+ const start_tok_2 = if_full .else_token ;
2426
+ const end_tok_2 = handle .tree .lastToken (if_full .ast .else_expr );
2427
+
2428
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , start_tok_2 , end_tok_2 , .inclusive );
2429
+ },
2430
+
2431
+ // same as if/else
2432
+ .@"for" ,
2433
+ .for_simple ,
2434
+ .@"while" ,
2435
+ .while_cont ,
2436
+ .while_simple ,
2437
+ = > {
2438
+ const loop_full = ast .whileAst (handle .tree , node ).? ;
2439
+
2440
+ const start_tok_1 = handle .tree .lastToken (loop_full .ast .cond_expr );
2441
+ const end_tok_1 = handle .tree .lastToken (loop_full .ast .then_expr );
2442
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , start_tok_1 , end_tok_1 , .inclusive );
2443
+
2444
+ if (loop_full .ast .else_expr == 0 ) continue ;
2445
+
2446
+ const start_tok_2 = loop_full .else_token ;
2447
+ const end_tok_2 = handle .tree .lastToken (loop_full .ast .else_expr );
2448
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , start_tok_2 , end_tok_2 , .inclusive );
2449
+ },
2450
+
2451
+ .global_var_decl ,
2452
+ .simple_var_decl ,
2453
+ .aligned_var_decl ,
2454
+ .container_field_init ,
2455
+ .container_field_align ,
2456
+ .container_field ,
2457
+ .fn_proto ,
2458
+ .fn_proto_multi ,
2459
+ .fn_proto_one ,
2460
+ .fn_proto_simple ,
2461
+ .fn_decl ,
2462
+ = > decl_node_blk : {
2463
+ doc_comment_range : {
2464
+ const first_tok : Ast.TokenIndex = handle .tree .firstToken (node );
2465
+ if (first_tok == 0 ) break :doc_comment_range ;
2466
+
2467
+ const end_doc_tok = first_tok - 1 ;
2468
+ if (token_tags [end_doc_tok ] != .doc_comment ) break :doc_comment_range ;
2469
+
2470
+ var start_doc_tok = end_doc_tok ;
2471
+ while (start_doc_tok != 0 ) {
2472
+ if (token_tags [start_doc_tok - 1 ] != .doc_comment ) break ;
2473
+ start_doc_tok -= 1 ;
2474
+ }
2475
+
2476
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , start_doc_tok , end_doc_tok , .inclusive );
2477
+ }
2478
+
2479
+ // Function prototype folding regions
2480
+ var fn_proto_buffer : [1 ]Node.Index = undefined ;
2481
+ const fn_proto = ast .fnProto (handle .tree , node , fn_proto_buffer [0.. ]) orelse
2482
+ break :decl_node_blk ;
2483
+
2484
+ const list_start_tok : Ast.TokenIndex = fn_proto .lparen ;
2485
+ const list_end_tok : Ast.TokenIndex = handle .tree .lastToken (fn_proto .ast .proto_node );
2486
+
2487
+ if (handle .tree .tokensOnSameLine (list_start_tok , list_end_tok )) break :decl_node_blk ;
2488
+ try ranges .ensureUnusedCapacity (1 + fn_proto .ast .params .len ); // best guess, doesn't include anytype params
2489
+ helper .addTokRange (& ranges , handle .tree , list_start_tok , list_end_tok , .exclusive ) catch | err | switch (err ) {
2490
+ error .OutOfMemory = > unreachable ,
2491
+ };
2492
+
2493
+ var it = fn_proto .iterate (& handle .tree );
2494
+ while (ast .nextFnParam (& it )) | param | {
2495
+ const doc_start_tok = param .first_doc_comment orelse continue ;
2496
+ var doc_end_tok = doc_start_tok ;
2497
+
2498
+ while (token_tags [doc_end_tok + 1 ] == .doc_comment )
2499
+ doc_end_tok += 1 ;
2500
+
2501
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , doc_start_tok , doc_end_tok , .inclusive );
2502
+ }
2503
+ },
2504
+
2505
+ .@"catch" ,
2506
+ .@"orelse" ,
2507
+ .multiline_string_literal ,
2508
+ // TODO: Similar to condition expressions in control flow structures, should folding multiline grouped expressions be enabled?
2509
+ // .grouped_expression,
2510
+ = > {
2511
+ const start_tok = handle .tree .firstToken (node );
2512
+ const end_tok = handle .tree .lastToken (node );
2513
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , start_tok , end_tok , .inclusive );
2514
+ },
2515
+
2516
+ // most other trivial cases can go through here.
2517
+ else = > {
2518
+ switch (node_tag ) {
2519
+ .array_init ,
2520
+ .array_init_one ,
2521
+ .array_init_dot_two ,
2522
+ .array_init_one_comma ,
2523
+ .array_init_dot_two_comma ,
2524
+ .array_init_dot ,
2525
+ .array_init_dot_comma ,
2526
+ .array_init_comma ,
2527
+
2528
+ .struct_init ,
2529
+ .struct_init_one ,
2530
+ .struct_init_one_comma ,
2531
+ .struct_init_dot_two ,
2532
+ .struct_init_dot_two_comma ,
2533
+ .struct_init_dot ,
2534
+ .struct_init_dot_comma ,
2535
+ .struct_init_comma ,
2536
+
2537
+ .@"switch" ,
2538
+ .switch_comma ,
2539
+ = > {},
2540
+
2541
+ else = > disallow_fold : {
2542
+ if (ast .isBlock (handle .tree , node ))
2543
+ break :disallow_fold ;
2544
+
2545
+ if (ast .isCall (handle .tree , node ))
2546
+ break :disallow_fold ;
2547
+
2548
+ if (ast .isBuiltinCall (handle .tree , node ))
2549
+ break :disallow_fold ;
2550
+
2551
+ if (ast .isContainer (handle .tree , node ) and node_tag != .root )
2552
+ break :disallow_fold ;
2553
+
2554
+ continue ; // no conditions met, continue iterating without adding this potential folding range
2555
+ },
2556
+ }
2557
+
2558
+ const start_tok = handle .tree .firstToken (node );
2559
+ const end_tok = handle .tree .lastToken (node );
2560
+ _ = try helper .maybeAddTokRange (& ranges , handle .tree , start_tok , end_tok , .exclusive );
2561
+ },
2382
2562
}
2383
2563
}
2384
2564
2385
2565
// Iterate over the source code and look for code regions with #region #endregion
2386
2566
{
2387
- // We will reuse the stack
2388
- stack .clearRetainingCapacity ();
2567
+ // We add opened folding regions to a stack as we go and pop one off when we find a closing brace.
2568
+ // As an optimization we start with a reasonable capacity, which should work well in most cases since
2569
+ // people will almost never have nesting that deep.
2570
+ var stack = try std .ArrayList (usize ).initCapacity (allocator , 10 );
2389
2571
2390
2572
var i : usize = 0 ;
2391
2573
var lines_count : usize = 0 ;
2392
2574
while (i < handle .tree .source .len ) : (i += 1 ) {
2393
2575
const slice = handle .tree .source [i .. ];
2394
-
2576
+
2395
2577
if (slice [0 ] == '\n ' ) {
2396
2578
lines_count += 1 ;
2397
2579
}
2398
-
2580
+
2399
2581
if (std .mem .startsWith (u8 , slice , "//#region" )) {
2400
2582
try stack .append (lines_count );
2401
2583
}
2402
2584
2403
2585
if (std .mem .startsWith (u8 , slice , "//#endregion" ) and stack .items .len > 0 ) {
2404
2586
const start_line = stack .pop ();
2405
2587
const end_line = lines_count ;
2406
-
2588
+
2407
2589
// Add brace pairs but discard those from the same line, no need to waste memory on them
2408
- if (start_line != end_line )
2409
- {
2590
+ if (start_line != end_line ) {
2410
2591
try ranges .append (.{
2411
2592
.startLine = start_line ,
2412
2593
.endLine = end_line ,
@@ -2416,7 +2597,7 @@ fn foldingRangeHandler(server: *Server, writer: anytype, id: types.RequestId, re
2416
2597
}
2417
2598
}
2418
2599
2419
- try send (writer , allocator , types.Response {
2600
+ try send (writer , allocator , types.Response {
2420
2601
.id = id ,
2421
2602
.result = .{ .FoldingRange = ranges .items },
2422
2603
});
@@ -2558,7 +2739,7 @@ pub fn processJsonRpc(server: *Server, writer: anytype, json: []const u8) !void
2558
2739
.{ "textDocument/documentHighlight" , requests .DocumentHighlight , documentHighlightHandler },
2559
2740
.{ "textDocument/codeAction" , requests .CodeAction , codeActionHandler },
2560
2741
.{ "workspace/didChangeConfiguration" , Config .DidChangeConfigurationParams , didChangeConfigurationHandler },
2561
- .{ "textDocument/foldingRange" , requests .FoldingRange , foldingRangeHandler }
2742
+ .{ "textDocument/foldingRange" , requests .FoldingRange , foldingRangeHandler },
2562
2743
};
2563
2744
2564
2745
if (zig_builtin .zig_backend == .stage1 ) {
0 commit comments