Skip to content

Commit

Permalink
Refactor/omnicompletion (#620)
Browse files Browse the repository at this point in the history
Co-authored-by: Sebastian Flügge <[email protected]>
  • Loading branch information
seflue and seflue authored Oct 30, 2023
1 parent a6fc6b2 commit a14e1e5
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 87 deletions.
2 changes: 1 addition & 1 deletion ftplugin/org.vim
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function! OrgmodeFoldText()
endfunction

function! OrgmodeOmni(findstart, base)
return luaeval('require("orgmode.org.autocompletion.omni")(_A[1], _A[2])', [a:findstart, a:base])
return luaeval('require("orgmode.org.autocompletion.omni").omnifunc(_A[1], _A[2])', [a:findstart, a:base])
endfunction

function! OrgmodeFormatExpr()
Expand Down
6 changes: 3 additions & 3 deletions lua/orgmode/org/autocompletion/cmp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ if not has_cmp then
return
end

local OrgmodeOmniCompletion = require('orgmode.org.autocompletion.omni')
local Omni = require('orgmode.org.autocompletion.omni')

local Source = {}

Expand All @@ -25,9 +25,9 @@ function Source:get_trigger_characters(_)
end

function Source:complete(params, callback)
local offset = OrgmodeOmniCompletion(1, '') + 1
local offset = Omni.find_start() + 1
local input = string.sub(params.context.cursor_before_line, offset)
local results = OrgmodeOmniCompletion(0, input)
local results = Omni.get_completions(input)
local items = {}
for _, item in ipairs(results) do
table.insert(items, {
Expand Down
6 changes: 3 additions & 3 deletions lua/orgmode/org/autocompletion/compe.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ if not has_compe then
return
end

local OrgmodeOmniCompletion = require('orgmode.org.autocompletion.omni')
local Omni = require('orgmode.org.autocompletion.omni')

local CompeSource = {}

Expand All @@ -22,7 +22,7 @@ function CompeSource.get_metadata()
end

function CompeSource.determine(_, context)
local offset = OrgmodeOmniCompletion(1, '') + 1
local offset = Omni.find_start() + 1
if offset > 0 then
return {
keyword_pattern_offset = offset,
Expand All @@ -32,7 +32,7 @@ function CompeSource.determine(_, context)
end

function CompeSource.complete(_, context)
local items = OrgmodeOmniCompletion(0, context.input)
local items = Omni.get_completions(context.input)
context.callback({
items = items,
incomplete = true,
Expand Down
91 changes: 62 additions & 29 deletions lua/orgmode/org/autocompletion/omni.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ local Files = require('orgmode.parser.files')
local config = require('orgmode.config')
local Hyperlinks = require('orgmode.org.hyperlinks')
local Url = require('orgmode.objects.url')
local Link = require('orgmode.objects.link')

local data = {
directives = { '#+title', '#+author', '#+email', '#+name', '#+filetags', '#+archive', '#+options', '#+category' },
Expand Down Expand Up @@ -33,8 +32,8 @@ local properties = {
}

local links = {
line_rgx = vim.regex([[\(\(^\|\s\+\)\[\[\)\@<=\(\*\|\#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\|_\|\d\)\+\)\?]]),
rgx = vim.regex([[\(\*\|\#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\|_\|\d\)\+\)\?$]]),
line_rgx = vim.regex([[\(\(^\|\s\+\)\[\[\)\@<=\(\*\|#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\)\+\)\?]]),
rgx = vim.regex([[\(\*\|#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\)\+\)\?$]]),
fetcher = function(url)
local hyperlinks, mapper = Hyperlinks.find_matching_links(url)
return mapper(hyperlinks)
Expand Down Expand Up @@ -91,54 +90,84 @@ local headline_contexts = {
todo_keywords,
}

local Omni = {}

---@return string: the line before the current cursor position
function Omni.get_line_content_before_cursor()
return vim.api.nvim_get_current_line():sub(1, vim.api.nvim_call_function('col', { '.' }) - 1)
end

function Omni.is_headline()
return Omni.get_line_content_before_cursor():match('^%*+%s+')
end

---@return Table
function Omni.get_all_contexts()
return Omni.is_headline() and headline_contexts or contexts
end

---Determines an URL for link handling. Handles a couple of corner-cases
---@param base string The string to complete
---@return string
local function get_url_str(line, base)
function Omni.get_url_str(line, base)
local line_base = line:match('%[%[(.-)$') or line
line_base = line_base:gsub(base .. '$', '')
return (line_base or '') .. (base or '')
end

--- This function is registered to omnicompletion in ftplugin/org.vim.
---
--- If the user want to use it in his completion plugin (like cmp) he has to do
--- that in the configuration of that plugin.
---@return table
local function omni(findstart, base)
local line = vim.api.nvim_get_current_line():sub(1, vim.api.nvim_call_function('col', { '.' }) - 1)
local is_headline = line:match('^%*+%s+')
local ctx = is_headline and headline_contexts or contexts
if findstart == 1 then
for _, context in ipairs(ctx) do
local word = context.rgx:match_str(line)
if word and (not context.extra_cond or context.extra_cond(line, base)) then
return word
end
--- Is true and only true, if all given regex in the context match appropriatly
--- line_rgx and extra_cond are optional, but if the context defines them, they must match.
--- The basic rgx must always match the base, because it is used to determine the start position for
--- the completion.
---@param context table: the context candidate
---@param line string: characters left to the cursor
---@param base string: characters after the trigger (filter)
function Omni.all_ctx_conditions_apply(context, line, base)
return (not context.line_rgx or context.line_rgx:match_str(line))
and context.rgx:match_str(base)
and (not context.extra_cond or context.extra_cond(line, base))
end

---@param base? string
---@return number
function Omni.find_start(base)
local line = Omni.get_line_content_before_cursor()
for _, context in ipairs(Omni.get_all_contexts()) do
local word = context.rgx:match_str(line)
if word and (not context.extra_cond or context.extra_cond(line, base)) then
return word
end
return -1
end
return -1
end

local url = Url.new(get_url_str(line, base))
local results = {}
---@param base string
---@return table
function Omni.get_completions(base)
-- Workaround for the corner case of matching custom_ids to file paths without file: prefix
-- Bug is probably in the regex, but hard to fix, because the regex is so hard to read
base = base:match('^:#') and base:gsub('^:', '') or base

for _, context in ipairs(ctx) do
if
(not context.line_rgx or context.line_rgx:match_str(line))
and context.rgx:match_str(base)
and (not context.extra_cond or context.extra_cond(line, base))
then
local line = Omni.get_line_content_before_cursor()
local url = Url.new(Omni.get_url_str(line, base))
local results = {}
for _, context in ipairs(Omni.get_all_contexts()) do
if Omni.all_ctx_conditions_apply(context, line, base) then
local items = {}

-- fetch or just take context specific completion candidates
if context.fetcher then
items = context.fetcher(url)
else
items = { unpack(context.list) }
end

-- incrementally limit candidates to what the user has already been typed
items = vim.tbl_filter(function(i)
return i:find('^' .. vim.pesc(base))
end, items)

-- craft the actual completion entries and append them to the overall results
for _, item in ipairs(items) do
table.insert(results, { word = item, menu = '[Org]' })
end
Expand All @@ -148,4 +177,8 @@ local function omni(findstart, base)
return results
end

return omni
function Omni.omnifunc(findstart, base)
return findstart == 1 and Omni.find_start(base) or Omni.get_completions(base)
end

return Omni
Loading

0 comments on commit a14e1e5

Please sign in to comment.