@@ -24,11 +24,82 @@ const Builder = struct {
2424 arena : std.mem.Allocator ,
2525 orig_handle : * DocumentStore.Handle ,
2626 source_index : usize ,
27- completions : std . ArrayList ( types . completion . Item ) ,
27+ completions : Completions ,
2828 cached_prepare_function_completion_result : ? PrepareFunctionCompletionResult = null ,
2929 use_snippets : bool ,
3030};
3131
32+ pub const Completions = struct {
33+ map : std .StringArrayHashMapUnmanaged (types .completion .Item ),
34+
35+ pub const empty : Completions = .{ .map = .empty };
36+
37+ pub fn items (completions : Completions ) []types.completion.Item {
38+ return completions .map .values ();
39+ }
40+
41+ pub fn ensureUnusedCapacity (
42+ completions : * Completions ,
43+ allocator : std.mem.Allocator ,
44+ additional_count : usize ,
45+ ) ! void {
46+ try completions .map .ensureUnusedCapacity (allocator , additional_count );
47+ }
48+
49+ pub fn append (
50+ completions : * Completions ,
51+ allocator : std.mem.Allocator ,
52+ item : types.completion.Item ,
53+ ) ! void {
54+ try completions .ensureUnusedCapacity (allocator , 1 );
55+ completions .appendAssumeCapacity (item );
56+ }
57+
58+ pub fn appendSlice (
59+ completions : * Completions ,
60+ allocator : std.mem.Allocator ,
61+ slice : []const types.completion.Item ,
62+ ) ! void {
63+ try completions .ensureUnusedCapacity (allocator , slice .len );
64+ for (slice ) | item |
65+ completions .appendAssumeCapacity (item );
66+ }
67+
68+ pub fn appendAssumeCapacity (
69+ completions : * Completions ,
70+ item : types.completion.Item ,
71+ ) void {
72+ const gop = completions .map .getOrPutAssumeCapacity (item .label );
73+ const ptr = gop .value_ptr ;
74+
75+ if (! gop .found_existing ) {
76+ ptr .* = item ;
77+ return ;
78+ }
79+
80+ const keep_detail = eqlSlices (u8 , ptr .detail , item .detail );
81+ const keep_label_details =
82+ if (item .labelDetails ) | item_details |
83+ if (ptr .labelDetails ) | ptr_details |
84+ eqlSlices (u8 , ptr_details .detail , item_details .detail ) and
85+ eqlSlices (u8 , ptr_details .description , item_details .description )
86+ else
87+ false
88+ else
89+ false ;
90+
91+ ptr .deprecated = ptr .deprecated orelse item .deprecated ;
92+ ptr .tags = ptr .tags orelse item .tags ;
93+ if (! keep_detail ) ptr .detail = null ;
94+ if (! keep_label_details ) ptr .labelDetails = null ;
95+ }
96+
97+ fn eqlSlices (comptime T : type , a : ? []const T , b : ? []const T ) bool {
98+ return (a == null and b == null ) or
99+ (a != null and b != null and std .mem .eql (T , a .? , b .? ));
100+ }
101+ };
102+
32103fn typeToCompletion (builder : * Builder , ty : Analyser.Type ) Analyser.Error ! void {
33104 const tracy_zone = tracy .trace (@src ());
34105 defer tracy_zone .end ();
@@ -175,12 +246,12 @@ fn declToCompletion(builder: *Builder, decl_handle: Analyser.DeclWithHandle) Ana
175246 }
176247 }
177248
178- const documentation : types.Documentation = .{
249+ const documentation : ? types.Documentation = if ( doc_comments . items . len > 0 ) .{
179250 .markup_content = .{
180251 .kind = if (builder .server .client_capabilities .completion_doc_supports_md ) .markdown else .plaintext ,
181252 .value = try std .mem .join (builder .arena , "\n\n " , doc_comments .items ),
182253 },
183- };
254+ } else null ;
184255
185256 try builder .completions .ensureUnusedCapacity (builder .arena , 1 );
186257
@@ -906,7 +977,7 @@ fn completeFileSystemStringLiteral(builder: *Builder, pos_context: Analyser.Posi
906977 const string_content_range = offsets .locToRange (source , .{ .start = insert_loc .start , .end = string_content_loc .end }, builder .server .offset_encoding );
907978
908979 // completions on module replace the entire string literal
909- for (builder .completions .items ) | * item | {
980+ for (builder .completions .items () ) | * item | {
910981 if (item .kind == .Module and item .textEdit == null ) {
911982 item .textEdit = createTextEdit (builder , .{ .newText = item .label , .insert = insert_range , .replace = string_content_range });
912983 }
@@ -1011,7 +1082,7 @@ pub fn completionAtIndex(
10111082
10121083 if (line_until_index .len == 0 or std .zig .isValidId (line_until_index )) {
10131084 try populateSnippedCompletions (& builder , .top_level );
1014- return .{ .isIncomplete = false , .items = builder .completions .items };
1085+ return .{ .isIncomplete = false , .items = builder .completions .items () };
10151086 }
10161087
10171088 const pos_context = try Analyser .getPositionContext (arena , & handle .tree , source_index , false );
@@ -1035,7 +1106,7 @@ pub fn completionAtIndex(
10351106 else = > return null ,
10361107 }
10371108
1038- const completions = builder .completions .items ;
1109+ const completions = builder .completions .items () ;
10391110 if (completions .len == 0 ) return null ;
10401111
10411112 const identifier_loc = prepareCompletionLoc (& handle .tree , source_index );
0 commit comments