Skip to content

Commit 5e5bc06

Browse files
authored
Remove duplicate completion suggestions (#639)
1 parent 7507dee commit 5e5bc06

File tree

2 files changed

+133
-122
lines changed

2 files changed

+133
-122
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Fixed
11+
12+
- Removed duplicate suggestions in completion of references.
13+
1014
## [v3.8.0](https://github.com/epwalsh/obsidian.nvim/releases/tag/v3.8.0) - 2024-06-21
1115

1216
### Added

lua/cmp_obsidian.lua

Lines changed: 129 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ local util = require "obsidian.util"
55
local iter = require("obsidian.itertools").iter
66
local LinkStyle = require("obsidian.config").LinkStyle
77

8+
---@class cmp_obsidian.CompletionItem
9+
---@field label string
10+
---@field new_text string
11+
---@field sort_text string
12+
---@field documentation table|?
13+
814
---@class cmp_obsidian.Source : obsidian.ABC
915
local source = abc.new_class()
1016

@@ -57,6 +63,9 @@ source.complete = function(_, request, callback)
5763
-- Completion items.
5864
local items = {}
5965

66+
---@type table<string, cmp_obsidian.CompletionItem>
67+
local new_text_to_option = {}
68+
6069
for note in iter(results) do
6170
---@cast note obsidian.Note
6271

@@ -99,32 +108,114 @@ source.complete = function(_, request, callback)
99108
end
100109
end
101110

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-
106111
---@param label string|?
107112
---@param alt_label string|?
108113
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 = {}
109116
if matching_anchors ~= nil then
110117
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 })
112119
end
113120
elseif matching_blocks ~= nil then
114121
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 })
116123
end
117124
else
118125
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 })
120127
end
121128

122129
-- Add all blocks and anchors, let cmp sort it out.
123130
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 })
125132
end
126133
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 }
128219
end
129220
end
130221
end
@@ -159,123 +250,39 @@ source.complete = function(_, request, callback)
159250
update_completion_options(note:display_name(), note.alt_alias)
160251
end
161252
end
253+
end
162254

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
197266

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,
204278
},
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,
275282
},
276-
})
277-
end
278-
end
283+
},
284+
},
285+
})
279286
end
280287

281288
callback {

0 commit comments

Comments
 (0)