@@ -22,6 +22,38 @@ const Inclusivity = enum {
2222 exclusive_ignore_space ,
2323};
2424
25+ /// Check if a node is an @import() call or an alias/field access based on an import.
26+ /// This recursively drills down field accesses to find the base.
27+ fn isImportOrAlias (tree : * const Ast , node : Ast.Node.Index ) bool {
28+ const token = tree .nodeMainToken (node );
29+ switch (tree .nodeTag (node )) {
30+ .builtin_call_two , .builtin_call_two_comma = > {
31+ // Check if this is @import("...")
32+ const builtin_name = offsets .tokenToSlice (tree , token );
33+ if (! std .mem .eql (u8 , builtin_name , "@import" )) return false ;
34+
35+ const first_param , const second_param = tree .nodeData (node ).opt_node_and_opt_node ;
36+ const param_node = first_param .unwrap () orelse return false ;
37+ if (second_param != .none ) return false ;
38+ return tree .nodeTag (param_node ) == .string_literal ;
39+ },
40+ .field_access = > {
41+ // Field access like @import("foo").bar or std.ascii
42+ // Accept it as an import/alias pattern
43+ return true ;
44+ },
45+ .identifier = > {
46+ // This could be an alias like `const ascii = std.ascii`
47+ // We can't easily determine this without full symbol resolution,
48+ // so we'll be conservative and return false here.
49+ // The code_actions.zig uses full symbol lookup which is expensive.
50+ // For folding ranges, we'll only fold direct @import statements.
51+ return false ;
52+ },
53+ else = > return false ,
54+ }
55+ }
56+
2557const Builder = struct {
2658 allocator : std.mem.Allocator ,
2759 locations : std .ArrayList (FoldingRange ),
@@ -150,7 +182,40 @@ pub fn generateFoldingRanges(allocator: std.mem.Allocator, tree: *const Ast, enc
150182
151183 // TODO add folding range normal comments
152184
153- // TODO add folding range for top level `@Import()`
185+ // Folding range for top level imports
186+ {
187+ var start_import : ? Ast.Node.Index = null ;
188+ var end_import : ? Ast.Node.Index = null ;
189+
190+ const root_decls = tree .rootDecls ();
191+ for (root_decls ) | node | {
192+ const is_import = blk : {
193+ if (tree .nodeTag (node ) != .simple_var_decl ) break :blk false ;
194+ const var_decl = tree .simpleVarDecl (node );
195+ const init_node = var_decl .ast .init_node .unwrap () orelse break :blk false ;
196+
197+ // Check if this is an @import() call or a field access/identifier based on an import
198+ break :blk isImportOrAlias (tree , init_node );
199+ };
200+
201+ if (is_import ) {
202+ if (start_import == null ) {
203+ start_import = node ;
204+ }
205+ end_import = node ;
206+ } else if (start_import != null and end_import != null ) {
207+ // We found a non-import after a sequence of imports, create folding range
208+ try builder .add (null , tree .firstToken (start_import .? ), ast .lastToken (tree , end_import .? ), .inclusive , .inclusive );
209+ start_import = null ;
210+ end_import = null ;
211+ }
212+ }
213+
214+ // Handle the case where imports continue to the end of the file
215+ if (start_import != null and end_import != null and start_import .? != end_import .? ) {
216+ try builder .add (null , tree .firstToken (start_import .? ), ast .lastToken (tree , end_import .? ), .inclusive , .inclusive );
217+ }
218+ }
154219
155220 for (0.. tree .nodes .len ) | i | {
156221 const node : Ast.Node.Index = @enumFromInt (i );
0 commit comments