Skip to content

Commit a14e1e5

Browse files
authored
Refactor/omnicompletion (#620)
Co-authored-by: Sebastian Flügge <[email protected]>
1 parent a6fc6b2 commit a14e1e5

File tree

5 files changed

+162
-87
lines changed

5 files changed

+162
-87
lines changed

ftplugin/org.vim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ function! OrgmodeFoldText()
1212
endfunction
1313

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

1818
function! OrgmodeFormatExpr()

lua/orgmode/org/autocompletion/cmp.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ if not has_cmp then
33
return
44
end
55

6-
local OrgmodeOmniCompletion = require('orgmode.org.autocompletion.omni')
6+
local Omni = require('orgmode.org.autocompletion.omni')
77

88
local Source = {}
99

@@ -25,9 +25,9 @@ function Source:get_trigger_characters(_)
2525
end
2626

2727
function Source:complete(params, callback)
28-
local offset = OrgmodeOmniCompletion(1, '') + 1
28+
local offset = Omni.find_start() + 1
2929
local input = string.sub(params.context.cursor_before_line, offset)
30-
local results = OrgmodeOmniCompletion(0, input)
30+
local results = Omni.get_completions(input)
3131
local items = {}
3232
for _, item in ipairs(results) do
3333
table.insert(items, {

lua/orgmode/org/autocompletion/compe.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ if not has_compe then
33
return
44
end
55

6-
local OrgmodeOmniCompletion = require('orgmode.org.autocompletion.omni')
6+
local Omni = require('orgmode.org.autocompletion.omni')
77

88
local CompeSource = {}
99

@@ -22,7 +22,7 @@ function CompeSource.get_metadata()
2222
end
2323

2424
function CompeSource.determine(_, context)
25-
local offset = OrgmodeOmniCompletion(1, '') + 1
25+
local offset = Omni.find_start() + 1
2626
if offset > 0 then
2727
return {
2828
keyword_pattern_offset = offset,
@@ -32,7 +32,7 @@ function CompeSource.determine(_, context)
3232
end
3333

3434
function CompeSource.complete(_, context)
35-
local items = OrgmodeOmniCompletion(0, context.input)
35+
local items = Omni.get_completions(context.input)
3636
context.callback({
3737
items = items,
3838
incomplete = true,

lua/orgmode/org/autocompletion/omni.lua

Lines changed: 62 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ local Files = require('orgmode.parser.files')
22
local config = require('orgmode.config')
33
local Hyperlinks = require('orgmode.org.hyperlinks')
44
local Url = require('orgmode.objects.url')
5-
local Link = require('orgmode.objects.link')
65

76
local data = {
87
directives = { '#+title', '#+author', '#+email', '#+name', '#+filetags', '#+archive', '#+options', '#+category' },
@@ -33,8 +32,8 @@ local properties = {
3332
}
3433

3534
local links = {
36-
line_rgx = vim.regex([[\(\(^\|\s\+\)\[\[\)\@<=\(\*\|\#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\|_\|\d\)\+\)\?]]),
37-
rgx = vim.regex([[\(\*\|\#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\|_\|\d\)\+\)\?$]]),
35+
line_rgx = vim.regex([[\(\(^\|\s\+\)\[\[\)\@<=\(\*\|#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\)\+\)\?]]),
36+
rgx = vim.regex([[\(\*\|#\|file:\)\?\(\(\w\|\/\|\.\|\\\|-\)\+\)\?$]]),
3837
fetcher = function(url)
3938
local hyperlinks, mapper = Hyperlinks.find_matching_links(url)
4039
return mapper(hyperlinks)
@@ -91,54 +90,84 @@ local headline_contexts = {
9190
todo_keywords,
9291
}
9392

93+
local Omni = {}
94+
95+
---@return string: the line before the current cursor position
96+
function Omni.get_line_content_before_cursor()
97+
return vim.api.nvim_get_current_line():sub(1, vim.api.nvim_call_function('col', { '.' }) - 1)
98+
end
99+
100+
function Omni.is_headline()
101+
return Omni.get_line_content_before_cursor():match('^%*+%s+')
102+
end
103+
104+
---@return Table
105+
function Omni.get_all_contexts()
106+
return Omni.is_headline() and headline_contexts or contexts
107+
end
108+
94109
---Determines an URL for link handling. Handles a couple of corner-cases
95110
---@param base string The string to complete
96111
---@return string
97-
local function get_url_str(line, base)
112+
function Omni.get_url_str(line, base)
98113
local line_base = line:match('%[%[(.-)$') or line
99114
line_base = line_base:gsub(base .. '$', '')
100115
return (line_base or '') .. (base or '')
101116
end
102117

103-
--- This function is registered to omnicompletion in ftplugin/org.vim.
104-
---
105-
--- If the user want to use it in his completion plugin (like cmp) he has to do
106-
--- that in the configuration of that plugin.
107-
---@return table
108-
local function omni(findstart, base)
109-
local line = vim.api.nvim_get_current_line():sub(1, vim.api.nvim_call_function('col', { '.' }) - 1)
110-
local is_headline = line:match('^%*+%s+')
111-
local ctx = is_headline and headline_contexts or contexts
112-
if findstart == 1 then
113-
for _, context in ipairs(ctx) do
114-
local word = context.rgx:match_str(line)
115-
if word and (not context.extra_cond or context.extra_cond(line, base)) then
116-
return word
117-
end
118+
--- Is true and only true, if all given regex in the context match appropriatly
119+
--- line_rgx and extra_cond are optional, but if the context defines them, they must match.
120+
--- The basic rgx must always match the base, because it is used to determine the start position for
121+
--- the completion.
122+
---@param context table: the context candidate
123+
---@param line string: characters left to the cursor
124+
---@param base string: characters after the trigger (filter)
125+
function Omni.all_ctx_conditions_apply(context, line, base)
126+
return (not context.line_rgx or context.line_rgx:match_str(line))
127+
and context.rgx:match_str(base)
128+
and (not context.extra_cond or context.extra_cond(line, base))
129+
end
130+
131+
---@param base? string
132+
---@return number
133+
function Omni.find_start(base)
134+
local line = Omni.get_line_content_before_cursor()
135+
for _, context in ipairs(Omni.get_all_contexts()) do
136+
local word = context.rgx:match_str(line)
137+
if word and (not context.extra_cond or context.extra_cond(line, base)) then
138+
return word
118139
end
119-
return -1
120140
end
141+
return -1
142+
end
121143

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

125-
for _, context in ipairs(ctx) do
126-
if
127-
(not context.line_rgx or context.line_rgx:match_str(line))
128-
and context.rgx:match_str(base)
129-
and (not context.extra_cond or context.extra_cond(line, base))
130-
then
151+
local line = Omni.get_line_content_before_cursor()
152+
local url = Url.new(Omni.get_url_str(line, base))
153+
local results = {}
154+
for _, context in ipairs(Omni.get_all_contexts()) do
155+
if Omni.all_ctx_conditions_apply(context, line, base) then
131156
local items = {}
157+
158+
-- fetch or just take context specific completion candidates
132159
if context.fetcher then
133160
items = context.fetcher(url)
134161
else
135162
items = { unpack(context.list) }
136163
end
137164

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

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

151-
return omni
180+
function Omni.omnifunc(findstart, base)
181+
return findstart == 1 and Omni.find_start(base) or Omni.get_completions(base)
182+
end
183+
184+
return Omni

0 commit comments

Comments
 (0)