@@ -2,7 +2,6 @@ local Files = require('orgmode.parser.files')
22local config = require (' orgmode.config' )
33local Hyperlinks = require (' orgmode.org.hyperlinks' )
44local Url = require (' orgmode.objects.url' )
5- local Link = require (' orgmode.objects.link' )
65
76local data = {
87 directives = { ' #+title' , ' #+author' , ' #+email' , ' #+name' , ' #+filetags' , ' #+archive' , ' #+options' , ' #+category' },
@@ -33,8 +32,8 @@ local properties = {
3332}
3433
3534local 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 ' ' )
101116end
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
149178end
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