@@ -5,6 +5,12 @@ local util = require "obsidian.util"
5
5
local iter = require (" obsidian.itertools" ).iter
6
6
local LinkStyle = require (" obsidian.config" ).LinkStyle
7
7
8
+ --- @class cmp_obsidian.CompletionItem
9
+ --- @field label string
10
+ --- @field new_text string
11
+ --- @field sort_text string
12
+ --- @field documentation table |?
13
+
8
14
--- @class cmp_obsidian.Source : obsidian.ABC
9
15
local source = abc .new_class ()
10
16
@@ -57,6 +63,9 @@ source.complete = function(_, request, callback)
57
63
-- Completion items.
58
64
local items = {}
59
65
66
+ --- @type table<string , cmp_obsidian.CompletionItem>
67
+ local new_text_to_option = {}
68
+
60
69
for note in iter (results ) do
61
70
--- @cast note obsidian.Note
62
71
@@ -99,32 +108,114 @@ source.complete = function(_, request, callback)
99
108
end
100
109
end
101
110
102
- -- Transform aliases into completion options.
103
- --- @type { label : string |?, alt_label : string |?, anchor : obsidian.note.HeaderAnchor |?, block : obsidian.note.Block |? } []
104
- local completion_options = {}
105
-
106
111
--- @param label string |?
107
112
--- @param alt_label string |?
108
113
local function update_completion_options (label , alt_label )
114
+ --- @type { label : string |?, alt_label : string |?, anchor : obsidian.note.HeaderAnchor |?, block : obsidian.note.Block |? } []
115
+ local new_options = {}
109
116
if matching_anchors ~= nil then
110
117
for anchor in iter (matching_anchors ) do
111
- table.insert (completion_options , { label = label , alt_label = alt_label , anchor = anchor })
118
+ table.insert (new_options , { label = label , alt_label = alt_label , anchor = anchor })
112
119
end
113
120
elseif matching_blocks ~= nil then
114
121
for block in iter (matching_blocks ) do
115
- table.insert (completion_options , { label = label , alt_label = alt_label , block = block })
122
+ table.insert (new_options , { label = label , alt_label = alt_label , block = block })
116
123
end
117
124
else
118
125
if label then
119
- table.insert (completion_options , { label = label , alt_label = alt_label })
126
+ table.insert (new_options , { label = label , alt_label = alt_label })
120
127
end
121
128
122
129
-- Add all blocks and anchors, let cmp sort it out.
123
130
for _ , anchor_data in pairs (note .anchor_links or {}) do
124
- table.insert (completion_options , { label = label , alt_label = alt_label , anchor = anchor_data })
131
+ table.insert (new_options , { label = label , alt_label = alt_label , anchor = anchor_data })
125
132
end
126
133
for _ , block_data in pairs (note .blocks or {}) do
127
- table.insert (completion_options , { label = label , alt_label = alt_label , block = block_data })
134
+ table.insert (new_options , { label = label , alt_label = alt_label , block = block_data })
135
+ end
136
+ end
137
+
138
+ -- De-duplicate options relative to their `new_text`.
139
+ for _ , option in ipairs (new_options ) do
140
+ --- @type obsidian.config.LinkStyle
141
+ local link_style
142
+ if ref_type == completion .RefType .Wiki then
143
+ link_style = LinkStyle .wiki
144
+ elseif ref_type == completion .RefType .Markdown then
145
+ link_style = LinkStyle .markdown
146
+ else
147
+ error " not implemented"
148
+ end
149
+
150
+ --- @type string , string , string , table |?
151
+ local final_label , sort_text , new_text , documentation
152
+ if option .label then
153
+ new_text = client :format_link (
154
+ note ,
155
+ { label = option .label , link_style = link_style , anchor = option .anchor , block = option .block }
156
+ )
157
+
158
+ final_label = assert (option .alt_label or option .label )
159
+ if option .anchor then
160
+ final_label = final_label .. option .anchor .anchor
161
+ elseif option .block then
162
+ final_label = final_label .. " #" .. option .block .id
163
+ end
164
+ sort_text = final_label
165
+
166
+ documentation = {
167
+ kind = " markdown" ,
168
+ value = note :display_info {
169
+ label = new_text ,
170
+ anchor = option .anchor ,
171
+ block = option .block ,
172
+ },
173
+ }
174
+ elseif option .anchor then
175
+ -- In buffer anchor link.
176
+ -- TODO: allow users to customize this?
177
+ if ref_type == completion .RefType .Wiki then
178
+ new_text = " [[#" .. option .anchor .header .. " ]]"
179
+ elseif ref_type == completion .RefType .Markdown then
180
+ new_text = " [#" .. option .anchor .header .. " ](" .. option .anchor .anchor .. " )"
181
+ else
182
+ error " not implemented"
183
+ end
184
+
185
+ final_label = option .anchor .anchor
186
+ sort_text = final_label
187
+
188
+ documentation = {
189
+ kind = " markdown" ,
190
+ value = string.format (" `%s`" , new_text ),
191
+ }
192
+ elseif option .block then
193
+ -- In buffer block link.
194
+ -- TODO: allow users to customize this?
195
+ if ref_type == completion .RefType .Wiki then
196
+ new_text = " [[#" .. option .block .id .. " ]]"
197
+ elseif ref_type == completion .RefType .Markdown then
198
+ new_text = " [#" .. option .block .id .. " ](#" .. option .block .id .. " )"
199
+ else
200
+ error " not implemented"
201
+ end
202
+
203
+ final_label = " #" .. option .block .id
204
+ sort_text = final_label
205
+
206
+ documentation = {
207
+ kind = " markdown" ,
208
+ value = string.format (" `%s`" , new_text ),
209
+ }
210
+ else
211
+ error " should not happen"
212
+ end
213
+
214
+ if new_text_to_option [new_text ] then
215
+ new_text_to_option [new_text ].sort_text = new_text_to_option [new_text ].sort_text .. " " .. sort_text
216
+ else
217
+ new_text_to_option [new_text ] =
218
+ { label = final_label , new_text = new_text , sort_text = sort_text , documentation = documentation }
128
219
end
129
220
end
130
221
end
@@ -159,123 +250,39 @@ source.complete = function(_, request, callback)
159
250
update_completion_options (note :display_name (), note .alt_alias )
160
251
end
161
252
end
253
+ end
162
254
163
- -- Keep track of completion entries we've added so we don't have duplicates.
164
- --- @type table<string , boolean>
165
- local new_text_seen = {}
166
- --- @type table<string , boolean>
167
- local sort_text_seen = {}
168
-
169
- for option in iter (completion_options ) do
170
- --- @type obsidian.config.LinkStyle
171
- local link_style
172
- if ref_type == completion .RefType .Wiki then
173
- link_style = LinkStyle .wiki
174
- elseif ref_type == completion .RefType .Markdown then
175
- link_style = LinkStyle .markdown
176
- else
177
- error " not implemented"
178
- end
179
-
180
- --- @type string , string
181
- local sort_text , new_text
182
- --- @type table |?
183
- local documentation = nil
184
-
185
- if option .label then
186
- new_text = client :format_link (
187
- note ,
188
- { label = option .label , link_style = link_style , anchor = option .anchor , block = option .block }
189
- )
190
-
191
- sort_text = option .alt_label or option .label
192
- if option .anchor then
193
- sort_text = sort_text .. option .anchor .anchor
194
- elseif option .block then
195
- sort_text = sort_text .. " #" .. option .block .id
196
- end
255
+ for _ , option in pairs (new_text_to_option ) do
256
+ -- TODO: need a better label, maybe just the note's display name?
257
+ --- @type string
258
+ local label
259
+ if ref_type == completion .RefType .Wiki then
260
+ label = string.format (" [[%s]]" , option .label )
261
+ elseif ref_type == completion .RefType .Markdown then
262
+ label = string.format (" [%s](…)" , option .label )
263
+ else
264
+ error " not implemented"
265
+ end
197
266
198
- documentation = {
199
- kind = " markdown" ,
200
- value = note :display_info {
201
- label = new_text ,
202
- anchor = option .anchor ,
203
- block = option .block ,
267
+ table.insert (items , {
268
+ documentation = option .documentation ,
269
+ sortText = option .sort_text ,
270
+ label = label ,
271
+ kind = 18 , -- "Reference"
272
+ textEdit = {
273
+ newText = option .new_text ,
274
+ range = {
275
+ start = {
276
+ line = request .context .cursor .row - 1 ,
277
+ character = insert_start ,
204
278
},
205
- }
206
- elseif option .anchor then
207
- -- In buffer anchor link.
208
- -- TODO: allow users to customize this?
209
- if ref_type == completion .RefType .Wiki then
210
- new_text = " [[#" .. option .anchor .header .. " ]]"
211
- elseif ref_type == completion .RefType .Markdown then
212
- new_text = " [#" .. option .anchor .header .. " ](" .. option .anchor .anchor .. " )"
213
- else
214
- error " not implemented"
215
- end
216
-
217
- sort_text = option .anchor .anchor
218
-
219
- documentation = {
220
- kind = " markdown" ,
221
- value = string.format (" `%s`" , new_text ),
222
- }
223
- elseif option .block then
224
- -- In buffer block link.
225
- -- TODO: allow users to customize this?
226
- if ref_type == completion .RefType .Wiki then
227
- new_text = " [[#" .. option .block .id .. " ]]"
228
- elseif ref_type == completion .RefType .Markdown then
229
- new_text = " [#" .. option .block .id .. " ](#" .. option .block .id .. " )"
230
- else
231
- error " not implemented"
232
- end
233
-
234
- sort_text = " #" .. option .block .id
235
-
236
- documentation = {
237
- kind = " markdown" ,
238
- value = string.format (" `%s`" , new_text ),
239
- }
240
- else
241
- error " should not happen"
242
- end
243
-
244
- if not new_text_seen [new_text ] or not sort_text_seen [sort_text ] then
245
- new_text_seen [new_text ] = true
246
- sort_text_seen [sort_text ] = true
247
-
248
- --- @type string
249
- local label
250
- if ref_type == completion .RefType .Wiki then
251
- label = string.format (" [[%s]]" , sort_text )
252
- elseif ref_type == completion .RefType .Markdown then
253
- label = string.format (" [%s](…)" , sort_text )
254
- else
255
- error " not implemented"
256
- end
257
-
258
- table.insert (items , {
259
- documentation = documentation ,
260
- sortText = sort_text ,
261
- label = label ,
262
- kind = 18 , -- "Reference"
263
- textEdit = {
264
- newText = new_text ,
265
- range = {
266
- start = {
267
- line = request .context .cursor .row - 1 ,
268
- character = insert_start ,
269
- },
270
- [" end" ] = {
271
- line = request .context .cursor .row - 1 ,
272
- character = insert_end ,
273
- },
274
- },
279
+ [" end" ] = {
280
+ line = request .context .cursor .row - 1 ,
281
+ character = insert_end ,
275
282
},
276
- })
277
- end
278
- end
283
+ },
284
+ },
285
+ })
279
286
end
280
287
281
288
callback {
0 commit comments