Skip to content

feat(autocomplete): Fuzzy match autocompletion #1024

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion lua/orgmode/org/autocompletion/_meta.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---@meta

---@alias OrgCompletionContext { line: string, base?: string }
---@alias OrgCompletionContext { line: string, base?: string, fuzzy?: boolean, matcher?: fun(value?: string, pattern?: string): boolean }
---@alias OrgCompletionItem { word: string, menu: string }

---@class OrgCompletionSource
Expand Down
1 change: 1 addition & 0 deletions lua/orgmode/org/autocompletion/blink.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ function Source:get_completions(ctx, callback)
local results = org.completion:complete({
line = line,
base = base,
fuzzy = true,
})

local cb = function(items)
Expand Down
1 change: 1 addition & 0 deletions lua/orgmode/org/autocompletion/cmp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function Source:complete(params, callback)
local results = org.completion:complete({
line = params.context.cursor_before_line,
base = base,
fuzzy = true,
})
local items = {}
for _, item in ipairs(results) do
Expand Down
23 changes: 20 additions & 3 deletions lua/orgmode/org/autocompletion/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ end
---@return OrgCompletionItem
function OrgCompletion:complete(context)
local results = {}
context.base = context.base or ''
if not context.matcher then
context.matcher = function(value, pattern)
pattern = pattern or ''
if pattern == '' then
return true
end
if context.fuzzy then
return #vim.fn.matchfuzzy({ value }, pattern) > 0
end
return value:find('^' .. vim.pesc(pattern)) ~= nil
end
end
for _, source in ipairs(self.sources) do
if source:get_start(context) then
vim.list_extend(results, self:_get_valid_results(source:get_results(context), context))
Expand All @@ -53,12 +66,13 @@ function OrgCompletion:complete(context)
return results
end

---@param results string[]
---@param context OrgCompletionContext
---@return OrgCompletionItem[]
function OrgCompletion:_get_valid_results(results, context)
local base = context.base or ''

local valid_results = {}
for _, item in ipairs(results) do
if base == '' or item:find('^' .. vim.pesc(base)) then
if context.matcher(item, context.base) then
table.insert(valid_results, {
word = item,
menu = self.menu,
Expand Down Expand Up @@ -89,6 +103,9 @@ function OrgCompletion:omnifunc(findstart, base)

self._context = self._context or { line = self:get_line() }
self._context.base = base
if vim.tbl_contains(vim.opt_local.completeopt:get(), 'fuzzy') then
self._context.fuzzy = true
end
return self:complete(self._context)
end

Expand Down
2 changes: 1 addition & 1 deletion lua/orgmode/org/autocompletion/sources/hyperlinks.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ end
---@param context OrgCompletionContext
---@return string[]
function OrgCompletionHyperlinks:get_results(context)
return self.completion.links:autocomplete(context.base)
return self.completion.links:autocomplete(context)
end

return OrgCompletionHyperlinks
2 changes: 1 addition & 1 deletion lua/orgmode/org/links/_meta.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
---@class OrgLinkType
---@field get_name fun(self: OrgLinkType): string
---@field follow fun(self: OrgLinkType, link: string): boolean
---@field autocomplete fun(self: OrgLinkType, link: string): string[]
---@field autocomplete fun(self: OrgLinkType, context: OrgCompletionContext): string[]
12 changes: 5 additions & 7 deletions lua/orgmode/org/links/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,20 @@ function OrgLinks:follow(link)
return self.headline_search:follow(link)
end

---@param link string
---@param context OrgCompletionContext
---@return string[]
function OrgLinks:autocomplete(link)
local pattern = '^' .. vim.pesc(link:lower())

function OrgLinks:autocomplete(context)
local items = vim.tbl_filter(function(stored_link)
return stored_link:lower():match(pattern)
return context.matcher(stored_link, context.base)
end, vim.tbl_keys(self.stored_links))

for _, source in ipairs(self.types) do
if source.autocomplete then
utils.concat(items, source:autocomplete(link))
utils.concat(items, source:autocomplete(context))
end
end

utils.concat(items, self.headline_search:autocomplete(link))
utils.concat(items, self.headline_search:autocomplete(context))
return items
end

Expand Down
6 changes: 3 additions & 3 deletions lua/orgmode/org/links/types/custom_id.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ function OrgLinkCustomId:follow(link)
return link_utils.open_file_and_search(opts.file_path, opts.custom_id)
end

---@param link string
---@param context OrgCompletionContext
---@return string[]
function OrgLinkCustomId:autocomplete(link)
local opts = self:_parse(link)
function OrgLinkCustomId:autocomplete(context)
local opts = self:_parse(context.base)
if not opts then
return {}
end
Expand Down
10 changes: 6 additions & 4 deletions lua/orgmode/org/links/types/headline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ function OrgLinkHeadline:follow(link)
return link_utils.open_file_and_search(opts.file_path, opts.headline)
end

---@param link string
---@param context OrgCompletionContext
---@return string[]
function OrgLinkHeadline:autocomplete(link)
local opts = self:_parse(link)
function OrgLinkHeadline:autocomplete(context)
local opts = self:_parse(context.base)
if not opts then
return {}
end
Expand All @@ -52,7 +52,9 @@ function OrgLinkHeadline:autocomplete(link)
return {}
end

local headlines = file:find_headlines_by_title(opts.headline)
local headlines = vim.tbl_filter(function(headline)
return context.matcher(headline:get_title(), opts.headline)
end, file:get_headlines())
local prefix = opts.type == 'internal' and '' or opts.link_url:get_path_with_protocol() .. '::'

return vim.tbl_map(function(headline)
Expand Down
31 changes: 11 additions & 20 deletions lua/orgmode/org/links/types/headline_search.lua
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ function OrgLinkHeadlineSearch:follow(link)
return link_utils.open_file_and_search(opts.file_path, search_text)
end

---@param link string
---@param context OrgCompletionContext
---@return string[]
function OrgLinkHeadlineSearch:autocomplete(link)
local opts = self:_parse(link)
function OrgLinkHeadlineSearch:autocomplete(context)
local opts = self:_parse(context.base)
if not opts then
return {}
end
Expand All @@ -71,22 +71,9 @@ function OrgLinkHeadlineSearch:autocomplete(link)
local filenames = self.files:filenames()
local valid_filenames = {}
for _, f in ipairs(filenames) do
if f:find('^' .. opts.file_path) then
f = f:gsub('^' .. opts.file_path, opts.link_url.path)
table.insert(valid_filenames, f)
end

local real_path = opts.link_url:get_real_path()

if not real_path then
local substitute_path = fs.substitute_path(opts.file_path)
if substitute_path then
local full_path = vim.fn.fnamemodify(substitute_path, ':p')
if f:find('^' .. full_path) then
f = f:gsub('^' .. full_path, opts.link_url.path)
table.insert(valid_filenames, f)
end
end
local converted_path = fs.convert_path(opts.link_url.path, f)
if context.matcher(converted_path, opts.link_url.path) then
table.insert(valid_filenames, converted_path)
end
end

Expand All @@ -108,11 +95,15 @@ function OrgLinkHeadlineSearch:autocomplete(link)
return headline:get_title()
end, file:find_headlines_matching_search_term(pattern, true))

local matching_headlines = vim.tbl_filter(function(headline)
return context.matcher(headline:get_title(), opts.headline_text)
end, file:get_headlines())

utils.concat(
headlines,
vim.tbl_map(function(headline)
return headline:get_title()
end, file:find_headlines_by_title(opts.headline_text)),
end, matching_headlines),
true
)
local prefix = opts.type == 'internal' and '' or opts.link_url:get_path_with_protocol() .. '::'
Expand Down
31 changes: 31 additions & 0 deletions lua/orgmode/utils/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,37 @@ function M.substitute_path(path_str)
return false
end

--Convert absolute path to the same format as source path
--If source path has relative parts, like ~, ./ or ../,
--apply same to the long path and return
---@param source_path string
---@param long_path string
function M.convert_path(source_path, long_path)
if source_path:match('^/') then
return long_path
end

if source_path:match('^~/') then
local home_path = os.getenv('HOME')
if home_path then
return long_path:gsub('^' .. vim.pesc(home_path), '~')
end
return long_path
end

if source_path:match('^%./') then
local base = vim.fn.fnamemodify(utils.current_file_path(), ':p:h')
return long_path:gsub('^' .. vim.pesc(base), '.')
end

if source_path:match('^%.%./') then
local base = vim.fn.fnamemodify(utils.current_file_path(), ':p:h:h')
return long_path:gsub('^' .. vim.pesc(base), '..')
end

return long_path
end

---@param filepath string
function M.get_real_path(filepath)
if not filepath then
Expand Down
Loading