Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove duplicate completion suggestions #639

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Fixed

- Removed duplicate suggestions in completion of references.

## [v3.8.0](https://github.com/epwalsh/obsidian.nvim/releases/tag/v3.8.0) - 2024-06-21

### Added
Expand Down
251 changes: 129 additions & 122 deletions lua/cmp_obsidian.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ local util = require "obsidian.util"
local iter = require("obsidian.itertools").iter
local LinkStyle = require("obsidian.config").LinkStyle

---@class cmp_obsidian.CompletionItem
---@field label string
---@field new_text string
---@field sort_text string
---@field documentation table|?

---@class cmp_obsidian.Source : obsidian.ABC
local source = abc.new_class()

Expand Down Expand Up @@ -57,6 +63,9 @@ source.complete = function(_, request, callback)
-- Completion items.
local items = {}

---@type table<string, cmp_obsidian.CompletionItem>
local new_text_to_option = {}

for note in iter(results) do
---@cast note obsidian.Note

Expand Down Expand Up @@ -99,32 +108,114 @@ source.complete = function(_, request, callback)
end
end

-- Transform aliases into completion options.
---@type { label: string|?, alt_label: string|?, anchor: obsidian.note.HeaderAnchor|?, block: obsidian.note.Block|? }[]
local completion_options = {}

---@param label string|?
---@param alt_label string|?
local function update_completion_options(label, alt_label)
---@type { label: string|?, alt_label: string|?, anchor: obsidian.note.HeaderAnchor|?, block: obsidian.note.Block|? }[]
local new_options = {}
if matching_anchors ~= nil then
for anchor in iter(matching_anchors) do
table.insert(completion_options, { label = label, alt_label = alt_label, anchor = anchor })
table.insert(new_options, { label = label, alt_label = alt_label, anchor = anchor })
end
elseif matching_blocks ~= nil then
for block in iter(matching_blocks) do
table.insert(completion_options, { label = label, alt_label = alt_label, block = block })
table.insert(new_options, { label = label, alt_label = alt_label, block = block })
end
else
if label then
table.insert(completion_options, { label = label, alt_label = alt_label })
table.insert(new_options, { label = label, alt_label = alt_label })
end

-- Add all blocks and anchors, let cmp sort it out.
for _, anchor_data in pairs(note.anchor_links or {}) do
table.insert(completion_options, { label = label, alt_label = alt_label, anchor = anchor_data })
table.insert(new_options, { label = label, alt_label = alt_label, anchor = anchor_data })
end
for _, block_data in pairs(note.blocks or {}) do
table.insert(completion_options, { label = label, alt_label = alt_label, block = block_data })
table.insert(new_options, { label = label, alt_label = alt_label, block = block_data })
end
end

-- De-duplicate options relative to their `new_text`.
for _, option in ipairs(new_options) do
---@type obsidian.config.LinkStyle
local link_style
if ref_type == completion.RefType.Wiki then
link_style = LinkStyle.wiki
elseif ref_type == completion.RefType.Markdown then
link_style = LinkStyle.markdown
else
error "not implemented"
end

---@type string, string, string, table|?
local final_label, sort_text, new_text, documentation
if option.label then
new_text = client:format_link(
note,
{ label = option.label, link_style = link_style, anchor = option.anchor, block = option.block }
)

final_label = assert(option.alt_label or option.label)
if option.anchor then
final_label = final_label .. option.anchor.anchor
elseif option.block then
final_label = final_label .. "#" .. option.block.id
end
sort_text = final_label

documentation = {
kind = "markdown",
value = note:display_info {
label = new_text,
anchor = option.anchor,
block = option.block,
},
}
elseif option.anchor then
-- In buffer anchor link.
-- TODO: allow users to customize this?
if ref_type == completion.RefType.Wiki then
new_text = "[[#" .. option.anchor.header .. "]]"
elseif ref_type == completion.RefType.Markdown then
new_text = "[#" .. option.anchor.header .. "](" .. option.anchor.anchor .. ")"
else
error "not implemented"
end

final_label = option.anchor.anchor
sort_text = final_label

documentation = {
kind = "markdown",
value = string.format("`%s`", new_text),
}
elseif option.block then
-- In buffer block link.
-- TODO: allow users to customize this?
if ref_type == completion.RefType.Wiki then
new_text = "[[#" .. option.block.id .. "]]"
elseif ref_type == completion.RefType.Markdown then
new_text = "[#" .. option.block.id .. "](#" .. option.block.id .. ")"
else
error "not implemented"
end

final_label = "#" .. option.block.id
sort_text = final_label

documentation = {
kind = "markdown",
value = string.format("`%s`", new_text),
}
else
error "should not happen"
end

if new_text_to_option[new_text] then
new_text_to_option[new_text].sort_text = new_text_to_option[new_text].sort_text .. " " .. sort_text
else
new_text_to_option[new_text] =
{ label = final_label, new_text = new_text, sort_text = sort_text, documentation = documentation }
end
end
end
Expand Down Expand Up @@ -159,123 +250,39 @@ source.complete = function(_, request, callback)
update_completion_options(note:display_name(), note.alt_alias)
end
end
end

-- Keep track of completion entries we've added so we don't have duplicates.
---@type table<string, boolean>
local new_text_seen = {}
---@type table<string, boolean>
local sort_text_seen = {}

for option in iter(completion_options) do
---@type obsidian.config.LinkStyle
local link_style
if ref_type == completion.RefType.Wiki then
link_style = LinkStyle.wiki
elseif ref_type == completion.RefType.Markdown then
link_style = LinkStyle.markdown
else
error "not implemented"
end

---@type string, string
local sort_text, new_text
---@type table|?
local documentation = nil

if option.label then
new_text = client:format_link(
note,
{ label = option.label, link_style = link_style, anchor = option.anchor, block = option.block }
)

sort_text = option.alt_label or option.label
if option.anchor then
sort_text = sort_text .. option.anchor.anchor
elseif option.block then
sort_text = sort_text .. "#" .. option.block.id
end
for _, option in pairs(new_text_to_option) do
-- TODO: need a better label, maybe just the note's display name?
---@type string
local label
if ref_type == completion.RefType.Wiki then
label = string.format("[[%s]]", option.label)
elseif ref_type == completion.RefType.Markdown then
label = string.format("[%s](…)", option.label)
else
error "not implemented"
end

documentation = {
kind = "markdown",
value = note:display_info {
label = new_text,
anchor = option.anchor,
block = option.block,
table.insert(items, {
documentation = option.documentation,
sortText = option.sort_text,
label = label,
kind = 18, -- "Reference"
textEdit = {
newText = option.new_text,
range = {
start = {
line = request.context.cursor.row - 1,
character = insert_start,
},
}
elseif option.anchor then
-- In buffer anchor link.
-- TODO: allow users to customize this?
if ref_type == completion.RefType.Wiki then
new_text = "[[#" .. option.anchor.header .. "]]"
elseif ref_type == completion.RefType.Markdown then
new_text = "[#" .. option.anchor.header .. "](" .. option.anchor.anchor .. ")"
else
error "not implemented"
end

sort_text = option.anchor.anchor

documentation = {
kind = "markdown",
value = string.format("`%s`", new_text),
}
elseif option.block then
-- In buffer block link.
-- TODO: allow users to customize this?
if ref_type == completion.RefType.Wiki then
new_text = "[[#" .. option.block.id .. "]]"
elseif ref_type == completion.RefType.Markdown then
new_text = "[#" .. option.block.id .. "](#" .. option.block.id .. ")"
else
error "not implemented"
end

sort_text = "#" .. option.block.id

documentation = {
kind = "markdown",
value = string.format("`%s`", new_text),
}
else
error "should not happen"
end

if not new_text_seen[new_text] or not sort_text_seen[sort_text] then
new_text_seen[new_text] = true
sort_text_seen[sort_text] = true

---@type string
local label
if ref_type == completion.RefType.Wiki then
label = string.format("[[%s]]", sort_text)
elseif ref_type == completion.RefType.Markdown then
label = string.format("[%s](…)", sort_text)
else
error "not implemented"
end

table.insert(items, {
documentation = documentation,
sortText = sort_text,
label = label,
kind = 18, -- "Reference"
textEdit = {
newText = new_text,
range = {
start = {
line = request.context.cursor.row - 1,
character = insert_start,
},
["end"] = {
line = request.context.cursor.row - 1,
character = insert_end,
},
},
["end"] = {
line = request.context.cursor.row - 1,
character = insert_end,
},
})
end
end
},
},
})
end

callback {
Expand Down