Skip to content

Commit

Permalink
Remove duplicate completion suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
epwalsh committed Jun 26, 2024
1 parent 7507dee commit 61be0c2
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 122 deletions.
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

0 comments on commit 61be0c2

Please sign in to comment.