diff --git a/DOCS.md b/DOCS.md
index 65be6a5fd..e4b598527 100644
--- a/DOCS.md
+++ b/DOCS.md
@@ -1248,15 +1248,6 @@ By default, `omnifunc` is provided in `org` files that autocompletes these types
* Orgfile special keywords (`#+TITLE`, `#+BEGIN_SRC`, `#+ARCHIVE`, etc.)
* Hyperlinks (`* - headlines`, `# - headlines with CUSTOM_ID property`, `headlines matching title`)
-If you use [nvim-compe](https://github.com/hrsh7th/nvim-compe) add this to compe setup:
-```lua
- require'compe'.setup({
- source = {
- orgmode = true
- }
- })
-```
-
For [nvim-cmp](https://github.com/hrsh7th/nvim-cmp), add `orgmode` to list of sources:
```lua
require'cmp'.setup({
diff --git a/README.md b/README.md
index bbebc300f..366287597 100644
--- a/README.md
+++ b/README.md
@@ -132,20 +132,6 @@ EOF
```
#### Completion
-
- nvim-compe
-
-
-```lua
-require('compe').setup({
- source = {
- orgmode = true
- }
-})
-```
-
-
-
nvim-cmp
diff --git a/ftplugin/org.lua b/ftplugin/org.lua
index c93a67bfc..1a6955bef 100644
--- a/ftplugin/org.lua
+++ b/ftplugin/org.lua
@@ -31,7 +31,7 @@ vim.opt_local.omnifunc = 'v:lua.orgmode.omnifunc'
vim.opt_local.commentstring = '# %s'
_G.orgmode.omnifunc = function(findstart, base)
- return require('orgmode.org.autocompletion.omni').omnifunc(findstart, base)
+ return require('orgmode').completion:omnifunc(findstart, base)
end
local abbreviations = {
diff --git a/lua/orgmode/init.lua b/lua/orgmode/init.lua
index fbd43a19d..0cff73a4a 100644
--- a/lua/orgmode/init.lua
+++ b/lua/orgmode/init.lua
@@ -11,6 +11,7 @@ local auto_instance_keys = {
clock = true,
org_mappings = true,
notifications = true,
+ completion = true,
}
---@class Org
@@ -20,6 +21,7 @@ local auto_instance_keys = {
---@field agenda OrgAgenda
---@field capture OrgCapture
---@field clock OrgClock
+---@field completion OrgCompletion
---@field org_mappings OrgMappings
---@field notifications OrgNotifications
local Org = {}
@@ -62,7 +64,7 @@ function Org:init()
self.clock = require('orgmode.clock'):new({
files = self.files,
})
- require('orgmode.org.autocompletion').register()
+ self.completion = require('orgmode.org.autocompletion'):new({ files = self.files })
self.statusline_debounced = require('orgmode.utils').debounce('statusline', function()
return self.clock:get_statusline()
end, 300)
diff --git a/lua/orgmode/objects/url.lua b/lua/orgmode/objects/url.lua
index 62af202f7..32b5bdee7 100644
--- a/lua/orgmode/objects/url.lua
+++ b/lua/orgmode/objects/url.lua
@@ -3,15 +3,10 @@ local fs = require('orgmode.utils.fs')
---@class OrgUrl
---@field str string
local Url = {}
-
-function Url:init(str)
- self.str = str
-end
+Url.__index = Url
function Url.new(str)
- local self = setmetatable({}, { __index = Url })
- self:init(str)
- return self
+ return setmetatable({ str = str }, Url)
end
---@return boolean
@@ -44,11 +39,6 @@ function Url:is_file_custom_id()
return self:is_file() and self:get_custom_id() and true or false
end
----@return boolean
-function Url:is_file_anchor()
- return self:get_dedicated_target() and true
-end
-
---@return boolean
function Url:is_org_link()
return (self:get_dedicated_target() or self:get_custom_id() or self:get_headline()) and true
@@ -77,7 +67,7 @@ function Url:is_internal_custom_id()
end
function Url:is_dedicated_anchor_or_internal_title()
- return self:get_dedicated_target() ~= nil
+ return self:get_dedicated_target()
end
---@return string | false
@@ -145,6 +135,11 @@ function Url:get_linenumber()
or self.str:match('^/[^:]+ %+(%d+)$')
end
+---@return string | false
+function Url:get_protocol()
+ return self.str:match('^([%w]+):')
+end
+
---@return string | false
function Url:get_filepath()
return
@@ -166,6 +161,27 @@ function Url:get_filepath()
or self.str:match('^(%./)$')
or self.str:match('^(/)$')
end
+
+function Url:get_file()
+ return
+ -- for backwards compatibility
+ self.str:match('^(file:[^:]+) %+%d+')
+ or self.str:match('^(%.%./[^:]+) %+%d+')
+ or self.str:match('^(%./[^:]+) %+%d+')
+ or self.str:match('^(/[^:]+) %+%d+')
+ -- official orgmode convention
+ or self.str:match('^(file:[^:]+)::')
+ or self.str:match('^(%.%./[^:]+)::')
+ or self.str:match('^(%./[^:]+)::')
+ or self.str:match('^(/[^:]+)::')
+ or self.str:match('^(file:[^:]+)$')
+ or self.str:match('^(%.%./[^:]+)$')
+ or self.str:match('^(%./[^:]+)$')
+ or self.str:match('^(/[^:]+)$')
+ or self.str:match('^(%.%./)$')
+ or self.str:match('^(%./)$')
+ or self.str:match('^(/)$')
+end
--
---@return string
function Url:get_headline_completion()
diff --git a/lua/orgmode/org/autocompletion/_meta.lua b/lua/orgmode/org/autocompletion/_meta.lua
new file mode 100644
index 000000000..d73d752cf
--- /dev/null
+++ b/lua/orgmode/org/autocompletion/_meta.lua
@@ -0,0 +1,9 @@
+---@meta
+
+---@alias OrgCompletionContext { line: string, base?: string }
+---@alias OrgCompletionItem { word: string, menu: string }
+
+---@class OrgCompletionSource
+---@field get_name fun(self: OrgCompletionSource): string
+---@field get_start fun(self: OrgCompletionSource, context: OrgCompletionContext): number | nil
+---@field get_results fun(self: OrgCompletionSource, context: OrgCompletionContext): string[]
diff --git a/lua/orgmode/org/autocompletion/cmp.lua b/lua/orgmode/org/autocompletion/cmp.lua
index f62450a70..fcac2a90b 100644
--- a/lua/orgmode/org/autocompletion/cmp.lua
+++ b/lua/orgmode/org/autocompletion/cmp.lua
@@ -3,7 +3,7 @@ if not has_cmp then
return
end
-local Omni = require('orgmode.org.autocompletion.omni')
+local org = require('orgmode')
local Source = {}
@@ -25,9 +25,12 @@ function Source:get_trigger_characters(_)
end
function Source:complete(params, callback)
- local offset = Omni.find_start() + 1
- local input = string.sub(params.context.cursor_before_line, offset)
- local results = Omni.get_completions(input)
+ local offset = org.completion:get_start({ line = params.context.cursor_before_line }) + 1
+ local base = string.sub(params.context.cursor_before_line, offset)
+ local results = org.completion:complete({
+ line = params.context.cursor_before_line,
+ base = base,
+ })
local items = {}
for _, item in ipairs(results) do
table.insert(items, {
diff --git a/lua/orgmode/org/autocompletion/compe.lua b/lua/orgmode/org/autocompletion/compe.lua
deleted file mode 100644
index f844246d9..000000000
--- a/lua/orgmode/org/autocompletion/compe.lua
+++ /dev/null
@@ -1,42 +0,0 @@
-local has_compe, compe = pcall(require, 'compe')
-if not has_compe then
- return
-end
-
-local Omni = require('orgmode.org.autocompletion.omni')
-
-local CompeSource = {}
-
-function CompeSource.new()
- return setmetatable({}, { __index = CompeSource })
-end
-
-function CompeSource.get_metadata()
- return {
- priority = 999,
- sort = false,
- dup = 0,
- filetypes = { 'org' },
- menu = '[Org]',
- }
-end
-
-function CompeSource.determine(_, context)
- local offset = Omni.find_start() + 1
- if offset > 0 then
- return {
- keyword_pattern_offset = offset,
- trigger_character_offset = vim.tbl_contains({ '#', '+', ':', '*' }, context.before_char) and context.col or 0,
- }
- end
-end
-
-function CompeSource.complete(_, context)
- local items = Omni.get_completions(context.input)
- context.callback({
- items = items,
- incomplete = true,
- })
-end
-
-compe.register_source('orgmode', CompeSource)
diff --git a/lua/orgmode/org/autocompletion/init.lua b/lua/orgmode/org/autocompletion/init.lua
index 2b37c140a..9a376c514 100644
--- a/lua/orgmode/org/autocompletion/init.lua
+++ b/lua/orgmode/org/autocompletion/init.lua
@@ -1,8 +1,107 @@
-local function register()
- require('orgmode.org.autocompletion.compe')
+---@class OrgCompletion
+---@field files OrgFiles
+---@field private sources OrgCompletionSource[]
+---@field private sources_by_name table
+---@field menu string
+local OrgCompletion = {
+ menu = '[Org]',
+}
+OrgCompletion.__index = OrgCompletion
+
+---@param opts { files: OrgFiles }
+function OrgCompletion:new(opts)
+ local this = setmetatable({
+ files = opts.files,
+ sources = {},
+ sources_by_name = {},
+ }, OrgCompletion)
+ this:setup_builtin_sources()
+ this:register_frameworks()
+ return this
+end
+
+function OrgCompletion:setup_builtin_sources()
+ self:add_source(require('orgmode.org.autocompletion.sources.todo_keywords'):new())
+ self:add_source(require('orgmode.org.autocompletion.sources.tags'):new({ completion = self }))
+ self:add_source(require('orgmode.org.autocompletion.sources.plan'):new({ completion = self }))
+ self:add_source(require('orgmode.org.autocompletion.sources.directives'):new())
+ self:add_source(require('orgmode.org.autocompletion.sources.properties'):new({ completion = self }))
+ self:add_source(require('orgmode.org.autocompletion.sources.hyperlinks'):new({ completion = self }))
+end
+
+---@param source OrgCompletionSource
+function OrgCompletion:add_source(source)
+ if self.sources_by_name[source:get_name()] then
+ error('Completion source ' .. source:get_name() .. ' already exists')
+ end
+ self.sources_by_name[source:get_name()] = source
+ table.insert(self.sources, source)
+end
+
+---@param context OrgCompletionContext
+---@return OrgCompletionItem
+function OrgCompletion:complete(context)
+ local results = {}
+ 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))
+ end
+ end
+
+ return results
+end
+
+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
+ table.insert(valid_results, {
+ word = item,
+ menu = self.menu,
+ })
+ end
+ end
+
+ return valid_results
+end
+
+---@param context OrgCompletionContext
+function OrgCompletion:get_start(context)
+ for _, source in ipairs(self.sources) do
+ local start = source:get_start(context)
+ if start then
+ return start
+ end
+ end
+
+ return -1
+end
+
+function OrgCompletion:omnifunc(findstart, base)
+ if findstart == 1 then
+ self._context = { line = self:get_line() }
+ return self:get_start(self._context)
+ end
+
+ self._context = self._context or { line = self:get_line() }
+ self._context.base = base
+ return self:complete(self._context)
+end
+
+function OrgCompletion:get_line()
+ local cursor = vim.api.nvim_win_get_cursor(0)
+ return vim.api.nvim_get_current_line():sub(1, cursor[2])
+end
+
+---@param line string
+function OrgCompletion:is_headline_line(line)
+ return line:find([[^%*+%s+]]) ~= nil
+end
+
+function OrgCompletion:register_frameworks()
require('orgmode.org.autocompletion.cmp')
end
-return {
- register = register,
-}
+return OrgCompletion
diff --git a/lua/orgmode/org/autocompletion/omni.lua b/lua/orgmode/org/autocompletion/omni.lua
deleted file mode 100644
index cd6aeb6cf..000000000
--- a/lua/orgmode/org/autocompletion/omni.lua
+++ /dev/null
@@ -1,184 +0,0 @@
-local org = require('orgmode')
-local config = require('orgmode.config')
-local Hyperlinks = require('orgmode.org.hyperlinks')
-local Url = require('orgmode.objects.url')
-
-local data = {
- directives = { '#+title', '#+author', '#+email', '#+name', '#+filetags', '#+archive', '#+options', '#+category' },
- begin_blocks = { '#+begin_src', '#+end_src', '#+begin_example', '#+end_example' },
- properties = { ':PROPERTIES:', ':END:', ':LOGBOOK:', ':STYLE:', ':REPEAT_TO_STATE:', ':CUSTOM_ID:', ':CATEGORY:' },
- metadata = { 'DEADLINE:', 'SCHEDULED:', 'CLOSED:' },
-}
-
-local directives = {
- line_rgx = vim.regex([[^\#\?+\?\w*$]]),
- rgx = vim.regex([[^\#+\?\w*$]]),
- list = data.directives,
-}
-
-local begin_blocks = {
- line_rgx = vim.regex([[^\s*\#\?+\?\w*$]]),
- rgx = vim.regex([[\(^\s*\)\@<=\#+\?\w*$]]),
- list = data.begin_blocks,
-}
-
-local properties = {
- line_rgx = vim.regex([[\(^\s\+\|^\s*:\?$\)]]),
- rgx = vim.regex([[\(^\|^\s\+\)\@<=:\w*$]]),
- extra_cond = function(line, _)
- return not string.find(line, 'file:.*$')
- end,
- list = data.properties,
-}
-
-local links = {
- 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)
- end,
-}
-
-local metadata = {
- rgx = vim.regex([[\(\s*\)\@<=\w\+$]]),
- list = data.metadata,
-}
-
-local tags = {
- rgx = vim.regex([[:\([0-9A-Za-z_%@\#]*\)$]]),
- fetcher = function()
- return vim.tbl_map(function(tag)
- return ':' .. tag .. ':'
- end, org.files:get_tags() or {})
- end,
-}
-
-local filetags = {
- line_rgx = vim.regex([[\c^\#+filetags:\s\+]]),
- rgx = vim.regex([[:\([0-9A-Za-z_%@\#]*\)$]]),
- extra_cond = function(line, _)
- return not string.find(line, 'file:.*$')
- end,
- fetcher = function()
- return vim.tbl_map(function(tag)
- return ':' .. tag .. ':'
- end, org.files:get_tags() or {})
- end,
-}
-
-local todo_keywords = {
- line_rgx = vim.regex([[^\*\+\s\+\w*$]]),
- rgx = vim.regex([[\(^\(\*\+\s\+\)\?\)\@<=\w*$]]),
- fetcher = function()
- return config:get_todo_keywords().ALL
- end,
-}
-
-local contexts = {
- directives,
- begin_blocks,
- filetags,
- properties,
- links,
- metadata,
-}
-
-local headline_contexts = {
- tags,
- links,
- 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 OrgTable
-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
-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
-
---- 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
- end
- return -1
-end
-
----@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
-
- 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
- end
- end
-
- return results
-end
-
-function Omni.omnifunc(findstart, base)
- return findstart == 1 and Omni.find_start(base) or Omni.get_completions(base)
-end
-
-return Omni
diff --git a/lua/orgmode/org/autocompletion/sources/directives.lua b/lua/orgmode/org/autocompletion/sources/directives.lua
new file mode 100644
index 000000000..8945f39f2
--- /dev/null
+++ b/lua/orgmode/org/autocompletion/sources/directives.lua
@@ -0,0 +1,40 @@
+---@class OrgCompletionDirectives:OrgCompletionSource
+---@field private pattern vim.regex
+local OrgCompletionDirectives = {}
+OrgCompletionDirectives.__index = OrgCompletionDirectives
+
+function OrgCompletionDirectives:new()
+ return setmetatable({
+ pattern = vim.regex([[^\s*\zs\#+\?\w*$]]),
+ }, OrgCompletionDirectives)
+end
+
+---@param context OrgCompletionContext
+---@return number | nil
+function OrgCompletionDirectives:get_start(context)
+ return self.pattern:match_str(context.line)
+end
+
+function OrgCompletionDirectives:get_name()
+ return 'directives'
+end
+
+---@return string[]
+function OrgCompletionDirectives:get_results(_)
+ return {
+ '#+title',
+ '#+author',
+ '#+email',
+ '#+name',
+ '#+filetags',
+ '#+archive',
+ '#+options',
+ '#+category',
+ '#+begin_src',
+ '#+begin_example',
+ '#+end_src',
+ '#+end_example',
+ }
+end
+
+return OrgCompletionDirectives
diff --git a/lua/orgmode/org/autocompletion/sources/hyperlinks.lua b/lua/orgmode/org/autocompletion/sources/hyperlinks.lua
new file mode 100644
index 000000000..b2f51c110
--- /dev/null
+++ b/lua/orgmode/org/autocompletion/sources/hyperlinks.lua
@@ -0,0 +1,35 @@
+local Hyperlinks = require('orgmode.org.hyperlinks')
+local Url = require('orgmode.objects.url')
+local Link = require('orgmode.objects.link')
+---@class OrgCompletionHyperlinks:OrgCompletionSource
+---@field completion OrgCompletion
+---@field private pattern vim.regex
+local OrgCompletionHyperlinks = {}
+OrgCompletionHyperlinks.__index = OrgCompletionHyperlinks
+
+---@param opts { completion: OrgCompletion }
+function OrgCompletionHyperlinks:new(opts)
+ return setmetatable({
+ completion = opts.completion,
+ pattern = vim.regex([[\s*\[\[\zs.*$]]),
+ }, OrgCompletionHyperlinks)
+end
+
+function OrgCompletionHyperlinks:get_name()
+ return 'hyperlinks'
+end
+
+---@param context OrgCompletionContext
+---@return number | nil
+function OrgCompletionHyperlinks:get_start(context)
+ return self.pattern:match_str(context.line)
+end
+
+---@return string[]
+function OrgCompletionHyperlinks:get_results(context)
+ local link = Link.new(context.base)
+ local result, mapper = Hyperlinks.find_matching_links(link.url)
+ return mapper(result)
+end
+
+return OrgCompletionHyperlinks
diff --git a/lua/orgmode/org/autocompletion/sources/plan.lua b/lua/orgmode/org/autocompletion/sources/plan.lua
new file mode 100644
index 000000000..85fd09db9
--- /dev/null
+++ b/lua/orgmode/org/autocompletion/sources/plan.lua
@@ -0,0 +1,42 @@
+local config = require('orgmode.config')
+
+---@class OrgCompletionPlan:OrgCompletionSource
+---@field completion OrgCompletion
+---@field private pattern vim.regex
+local OrgCompletionPlan = {}
+OrgCompletionPlan.__index = OrgCompletionPlan
+
+---@param opts { completion: OrgCompletion }
+function OrgCompletionPlan:new(opts)
+ local this = setmetatable({
+ pattern = vim.regex([[\(^\s*\|\s\+\)\zs\w*$]]),
+ completion = opts.completion,
+ }, OrgCompletionPlan)
+ return this
+end
+
+function OrgCompletionPlan:get_name()
+ return 'plan'
+end
+
+---@param context OrgCompletionContext
+---@return number | nil
+function OrgCompletionPlan:get_start(context)
+ local prev_line = vim.fn.getline(vim.fn.line('.') - 1)
+ if not self.completion:is_headline_line(prev_line) then
+ return nil
+ end
+
+ return self.pattern:match_str(context.line)
+end
+
+---@return string[]
+function OrgCompletionPlan:get_results(_)
+ return {
+ 'DEADLINE:',
+ 'SCHEDULED:',
+ 'CLOSED:',
+ }
+end
+
+return OrgCompletionPlan
diff --git a/lua/orgmode/org/autocompletion/sources/properties.lua b/lua/orgmode/org/autocompletion/sources/properties.lua
new file mode 100644
index 000000000..ade57121c
--- /dev/null
+++ b/lua/orgmode/org/autocompletion/sources/properties.lua
@@ -0,0 +1,42 @@
+---@class OrgCompletionProperties:OrgCompletionSource
+---@field completion OrgCompletion
+---@field private pattern vim.regex
+local OrgCompletionProperties = {}
+OrgCompletionProperties.__index = OrgCompletionProperties
+
+---@param opts { completion: OrgCompletion }
+function OrgCompletionProperties:new(opts)
+ return setmetatable({
+ completion = opts.completion,
+ pattern = vim.regex([[^\s*\zs:\w*$]]),
+ }, OrgCompletionProperties)
+end
+
+function OrgCompletionProperties:get_name()
+ return 'properties'
+end
+
+---@param context OrgCompletionContext
+---@return number | nil
+function OrgCompletionProperties:get_start(context)
+ if self.completion:is_headline_line(context.line) then
+ return nil
+ end
+
+ return self.pattern:match_str(context.line)
+end
+
+---@return string[]
+function OrgCompletionProperties:get_results(_)
+ return {
+ ':PROPERTIES:',
+ ':END:',
+ ':LOGBOOK:',
+ ':STYLE:',
+ ':REPEAT_TO_STATE:',
+ ':CUSTOM_ID:',
+ ':CATEGORY:',
+ }
+end
+
+return OrgCompletionProperties
diff --git a/lua/orgmode/org/autocompletion/sources/tags.lua b/lua/orgmode/org/autocompletion/sources/tags.lua
new file mode 100644
index 000000000..ed94c7e2f
--- /dev/null
+++ b/lua/orgmode/org/autocompletion/sources/tags.lua
@@ -0,0 +1,37 @@
+---@class OrgCompletionTags:OrgCompletionSource
+---@field completion OrgCompletion
+---@field private pattern vim.regex
+---@field private filetags_pattern vim.regex
+local OrgCompletionTags = {}
+OrgCompletionTags.__index = OrgCompletionTags
+
+---@param opts { completion: OrgCompletion }
+function OrgCompletionTags:new(opts)
+ return setmetatable({
+ completion = opts.completion,
+ filetags_pattern = vim.regex([[\c^\s*\#+filetags:\s\+]]),
+ pattern = vim.regex([[:\([0-9A-Za-z_%@\#]*\)$]]),
+ }, OrgCompletionTags)
+end
+
+function OrgCompletionTags:get_name()
+ return 'tags'
+end
+
+---@param context OrgCompletionContext
+---@return number | nil
+function OrgCompletionTags:get_start(context)
+ if not self.completion:is_headline_line(context.line) and not self.filetags_pattern:match_str(context.line) then
+ return nil
+ end
+ return self.pattern:match_str(context.line)
+end
+
+---@return string[]
+function OrgCompletionTags:get_results(_)
+ return vim.tbl_map(function(tag)
+ return table.concat({ ':', tag, ':' }, '')
+ end, self.completion.files:get_tags())
+end
+
+return OrgCompletionTags
diff --git a/lua/orgmode/org/autocompletion/sources/todo_keywords.lua b/lua/orgmode/org/autocompletion/sources/todo_keywords.lua
new file mode 100644
index 000000000..49e33f890
--- /dev/null
+++ b/lua/orgmode/org/autocompletion/sources/todo_keywords.lua
@@ -0,0 +1,30 @@
+local config = require('orgmode.config')
+
+---@class OrgCompletionTodoKeywords:OrgCompletionSource
+---@field private pattern vim.regex
+local OrgCompletionTodoKeywords = {}
+OrgCompletionTodoKeywords.__index = OrgCompletionTodoKeywords
+
+function OrgCompletionTodoKeywords:new()
+ local this = setmetatable({
+ pattern = vim.regex([[^\*\+\s\+\zs\w*$]]),
+ }, OrgCompletionTodoKeywords)
+ return this
+end
+
+function OrgCompletionTodoKeywords:get_name()
+ return 'todo_keywords'
+end
+
+---@param context OrgCompletionContext
+---@return number | nil
+function OrgCompletionTodoKeywords:get_start(context)
+ return self.pattern:match_str(context.line)
+end
+
+---@return string[]
+function OrgCompletionTodoKeywords:get_results(_)
+ return config:get_todo_keywords().ALL
+end
+
+return OrgCompletionTodoKeywords
diff --git a/lua/orgmode/org/hyperlinks.lua b/lua/orgmode/org/hyperlinks.lua
index 209e3ef10..df5e71737 100644
--- a/lua/orgmode/org/hyperlinks.lua
+++ b/lua/orgmode/org/hyperlinks.lua
@@ -23,9 +23,9 @@ function Hyperlinks.find_by_filepath(url)
return {}
end
--TODO integrate with orgmode.utils.fs or orgmode.objects.url
- local file_base_no_start_path = file_base:gsub('^%./', '') .. ''
+ local file_base_no_start_path = vim.pesc(file_base:gsub('^%./', '') .. '')
local is_relative_path = file_base:match('^%./')
- local current_file_directory = fs.get_current_file_dir()
+ local current_file_directory = vim.pesc(fs.get_current_file_dir())
local valid_filenames = {}
for _, f in ipairs(filenames) do
if is_relative_path then
@@ -40,10 +40,11 @@ function Hyperlinks.find_by_filepath(url)
end
end
- -- Outer checks already filter cases where `ctx.skip_add_prefix` is truthy,
- -- so no need to check it here
+ local protocol = url:get_protocol()
+ local prefix = protocol and protocol == 'file' and 'file:' or ''
+
return vim.tbl_map(function(path)
- return 'file:' .. path
+ return prefix .. path
end, valid_filenames)
end
@@ -59,26 +60,30 @@ function Hyperlinks.find_by_custom_id_property(url)
return file:find_headlines_with_property_matching('CUSTOM_ID', custom_id)
end
----@param headlines OrgHeadline[]
----@return string[]
-function Hyperlinks.as_custom_id_anchors(headlines)
- return vim.tbl_map(function(headline)
- ---@cast headline OrgHeadline
- local custom_id = headline:get_property('custom_id')
- if custom_id then
- return '#' .. custom_id
- end
- end, headlines)
+---@param url OrgUrl
+---@return fun(headlines: OrgHeadline[]): string[]
+function Hyperlinks.as_custom_id_anchors(url)
+ local prefix = url:is_file_custom_id() and url:get_file() .. '::' or ''
+ return function(headlines)
+ return vim.tbl_map(function(headline)
+ ---@cast headline OrgHeadline
+ local custom_id = headline:get_property('custom_id')
+ return ('%s#%s'):format(prefix, custom_id)
+ end, headlines)
+ end
end
----@param headlines OrgHeadline[]
+---@param url OrgUrl
---@param omit_prefix? boolean
----@return string[]
-function Hyperlinks.as_headline_anchors(headlines, omit_prefix)
- return vim.tbl_map(function(headline)
- local title = headline:get_title()
- return omit_prefix and title or '*' .. title
- end, headlines)
+---@return fun(headlines: OrgHeadline[]): string[]
+function Hyperlinks.as_headline_anchors(url, omit_prefix)
+ local prefix = url:is_file_headline() and url:get_file() .. '::' or ''
+ return function(headlines)
+ return vim.tbl_map(function(headline)
+ local title = (omit_prefix and '' or '*') .. headline:get_title()
+ return ('%s%s'):format(prefix, title)
+ end, headlines)
+ end
end
---@param url OrgUrl
@@ -101,11 +106,10 @@ end
---@return OrgHeadline[]
function Hyperlinks.find_by_dedicated_target(url)
local anchor = url and url:get_dedicated_target()
- if anchor then
- return org.files:get_current_file():find_headlines_matching_search_term(as_dedicated_anchor_pattern(anchor), true)
- else
+ if not anchor then
return {}
end
+ return org.files:get_current_file():find_headlines_matching_search_term(as_dedicated_anchor_pattern(anchor), true)
end
---@param url OrgUrl
@@ -133,7 +137,7 @@ end
function Hyperlinks.as_dedicated_anchors_or_internal_titles(url)
return function(headlines)
local dedicated_anchors = Hyperlinks.as_dedicated_targets(url)(headlines)
- local fuzzy_titles = Hyperlinks.as_headline_anchors(headlines, true)
+ local fuzzy_titles = Hyperlinks.as_headline_anchors(url, true)(headlines)
return utils.concat(dedicated_anchors, fuzzy_titles, true)
end
end
@@ -151,10 +155,10 @@ function Hyperlinks.find_matching_links(url)
result = Hyperlinks.find_by_filepath(url)
elseif url:is_custom_id() then
result = Hyperlinks.find_by_custom_id_property(url)
- mapper = Hyperlinks.as_custom_id_anchors
+ mapper = Hyperlinks.as_custom_id_anchors(url)
elseif url:is_headline() then
result = Hyperlinks.find_by_title(url)
- mapper = Hyperlinks.as_headline_anchors
+ mapper = Hyperlinks.as_headline_anchors(url)
elseif url:is_dedicated_anchor_or_internal_title() then
result = utils.concat(
Hyperlinks.find_by_dedicated_target(url),
@@ -199,18 +203,10 @@ function Hyperlinks.autocomplete_links(arg_lead)
local url = Url.new(arg_lead)
local result, mapper = Hyperlinks.find_matching_links(url)
- if url:is_file_plain() then
+ if url:is_file_plain() or url:is_custom_id() or url:is_headline() then
return mapper(result)
end
- if url:is_custom_id() or url:is_headline() then
- local file = get_file_from_url(url)
- local results = mapper(result)
- return vim.tbl_map(function(value)
- return ('file:%s::%s'):format(file.filename, value)
- end, results)
- end
-
return vim.tbl_keys(Hyperlinks.stored_links)
end
diff --git a/lua/orgmode/utils/fs.lua b/lua/orgmode/utils/fs.lua
index c6d7d3726..cf2329e8d 100644
--- a/lua/orgmode/utils/fs.lua
+++ b/lua/orgmode/utils/fs.lua
@@ -36,7 +36,7 @@ end
function M.get_current_file_dir()
local current_file = utils.current_file_path()
local current_dir = vim.fn.fnamemodify(current_file, ':p:h')
- return current_dir
+ return current_dir or ''
end
return M
diff --git a/queries/org/highlights.scm b/queries/org/highlights.scm
index 57b3395c1..cb3eefcb6 100644
--- a/queries/org/highlights.scm
+++ b/queries/org/highlights.scm
@@ -28,6 +28,7 @@
(latex_env) @org.latex_env
(drawer) @org.drawer
(tag_list) @org.tag
+(directive name: (expr) @_directive_name value: (value) @org.tag (#match? @_directive_name "\\c^filetags$"))
(plan) @org.plan
(comment) @org.comment @spell
(directive) @org.directive
diff --git a/tests/plenary/org/autocompletion_spec.lua b/tests/plenary/org/autocompletion_spec.lua
index dd1ccd246..c04bc1556 100644
--- a/tests/plenary/org/autocompletion_spec.lua
+++ b/tests/plenary/org/autocompletion_spec.lua
@@ -1,160 +1,164 @@
-local Omni = require('orgmode.org.autocompletion.omni')
local helpers = require('tests.plenary.helpers')
+local org = require('orgmode')
-describe('Autocompletion should properly find start offset for omni autocompletion', function()
+describe('Autocompletion', function()
local function setup_file(content)
-- Add space to the end of content because insert mode in
-- tests doesn't pick up a proper cursor location
helpers.create_agenda_file({ content .. ' ' })
vim.cmd('norm!A')
end
- it('for an empty line', function()
- setup_file('')
- local result = Omni.find_start()
- assert.are.same(0, result)
- end)
- it('for an empty headline', function()
- setup_file('* ')
- vim.cmd('norm!A')
- local result = Omni.find_start()
- assert.are.same(2, result)
- end)
- it('within TODO in headline', function()
- setup_file('* TO')
- vim.cmd('norm!A')
- local result = Omni.find_start()
- assert.are.same(2, result)
- setup_file('* TODO')
- vim.cmd('norm!A')
- result = Omni.find_start()
- assert.are.same(2, result)
- end)
- it('in the middle of a headline', function()
- setup_file('* TODO some text ')
- vim.cmd('norm!A')
- local result = Omni.find_start()
- assert.are.same(17, result)
- end)
- it('within tag in headline', function()
- setup_file('* TODO tags goes at the end :')
- local result = Omni.find_start()
- assert.are.same(28, result)
-
- setup_file('* TODO tags goes at the end :SOMET')
- result = Omni.find_start()
- assert.are.same(28, result)
- end)
- it('after tag in headline', function()
- setup_file('* TODO tags goes at the end :SOMETAG:')
- local result = Omni.find_start()
- assert.are.same(36, result)
- end)
- it('within special directives (#+)', function()
- setup_file('#')
- local result = Omni.find_start()
- assert.are.same(0, result)
-
- setup_file('#+')
- result = Omni.find_start()
- assert.are.same(0, result)
-
- setup_file('#+ar')
- result = Omni.find_start()
- assert.are.same(0, result)
- end)
+ describe('omni find start', function()
+ it('for an empty line', function()
+ setup_file('')
+ local result = org.completion:omnifunc(1)
+ assert.are.same(-1, result)
+ end)
+ it('for an empty headline', function()
+ setup_file('* ')
+ vim.cmd('norm!A')
+ local result = org.completion:omnifunc(1)
+ assert.are.same(2, result)
+ end)
+ it('within TODO in headline', function()
+ setup_file('* TO')
+ vim.cmd('norm!A')
+ local result = org.completion:omnifunc(1)
+ assert.are.same(2, result)
+
+ setup_file('* TODO')
+ vim.cmd('norm!A')
+ result = org.completion:omnifunc(1)
+ assert.are.same(2, result)
+ end)
+ it('in the middle of a headline', function()
+ setup_file('* TODO some text ')
+ vim.cmd('norm!A')
+ local result = org.completion:omnifunc(1)
+ assert.are.same(-1, result)
+ end)
+ it('within tag in headline', function()
+ setup_file('* TODO tags goes at the end :')
+ local result = org.completion:omnifunc(1)
+ assert.are.same(28, result)
+
+ setup_file('* TODO tags goes at the end :SOMET')
+ result = org.completion:omnifunc(1)
+ assert.are.same(28, result)
+ end)
+ it('after tag in headline', function()
+ setup_file('* TODO tags goes at the end :SOMETAG:')
+ local result = org.completion:omnifunc(1)
+ assert.are.same(36, result)
+ end)
+ it('within special directives (#+)', function()
+ setup_file('#')
+ local result = org.completion:omnifunc(1)
+ assert.are.same(0, result)
+
+ setup_file('#+')
+ result = org.completion:omnifunc(1)
+ assert.are.same(0, result)
+
+ setup_file('#+ar')
+ result = org.completion:omnifunc(1)
+ assert.are.same(0, result)
+ end)
- describe('Autocompletion', function()
it('within properties', function()
setup_file(':')
- local result = Omni.find_start()
+ local result = org.completion:omnifunc(1)
assert.are.same(0, result)
setup_file(' :')
- result = Omni.find_start()
+ result = org.completion:omnifunc(1)
assert.are.same(2, result)
setup_file(' :PROP')
- result = Omni.find_start()
+ result = org.completion:omnifunc(1)
assert.are.same(2, result)
setup_file(' :PROPERTI')
- result = Omni.find_start()
+ result = org.completion:omnifunc(1)
assert.are.same(2, result)
end)
it('within hyperlinks', function()
setup_file(' [[')
- local result = Omni.find_start()
+ local result = org.completion:omnifunc(1)
assert.are.same(4, result)
setup_file(' [[*some')
- result = Omni.find_start()
+ result = org.completion:omnifunc(1)
assert.are.same(4, result)
setup_file(' [[#val')
- result = Omni.find_start()
+ result = org.completion:omnifunc(1)
assert.are.same(4, result)
setup_file(' [[test')
- result = Omni.find_start()
+ result = org.completion:omnifunc(1)
assert.are.same(4, result)
setup_file(' [[file:')
- result = Omni.find_start()
+ result = org.completion:omnifunc(1)
assert.are.same(4, result)
end)
it('within file hyperlink anchors (file: prefix)', function()
setup_file(' [[file:./some/path/file.org::*')
- local result = Omni.find_start()
- assert.are.same(31, result)
+ local result = org.completion:omnifunc(1)
+ assert.are.same(4, result)
setup_file(' [[file:./some/path/file.org::#')
- result = Omni.find_start()
- assert.are.same(31, result)
+ result = org.completion:omnifunc(1)
+ assert.are.same(4, result)
setup_file(' [[file:./some/path/file.org::')
- result = Omni.find_start()
- assert.are.same(31, result)
+ result = org.completion:omnifunc(1)
+ assert.are.same(4, result)
end)
it('within file hyperlink anchors (./ prefix, headline)', function()
setup_file(' [[./1-34_some/path/file.org::*')
- local result = Omni.find_start()
- assert.are.same(31, result)
+ local result = org.completion:omnifunc(1)
+ assert.are.same(4, result)
end)
--TODO These tests expose a bug. Actually the expected start should be 31 as in the tests before
it('within file hyperlink anchors (./ prefix, custom_id)', function()
setup_file(' [[./1-34_some/path/file.org::#')
- local result = Omni.find_start()
- assert.are.same(30, result)
+ local result = org.completion:omnifunc(1)
+ assert.are.same(4, result)
end)
it('within file hyperlink anchors (./ prefix, dedicated anchor)', function()
setup_file(' [[./1-34_some/path/file.org::')
- local result = Omni.find_start()
- assert.are.same(30, result)
+ local result = org.completion:omnifunc(1)
+ assert.are.same(4, result)
end)
end)
- describe('Autocompletion', function()
- before_each(function()
- setup_file('')
- end)
-
+ describe('omni complete', function()
it('should return an empty table when base is empty', function()
setup_file('')
- local result = Omni.get_completions('')
+ local result = org.completion:omnifunc(0, '')
assert.are.same({}, result)
end)
- it('should return DEADLINE: when base is D', function()
+ it('should return DEADLINE: when base is D on second headline line', function()
-- Metadata
- local result = Omni.get_completions('D')
+ helpers.create_agenda_file({
+ '* TODO test',
+ ' A',
+ })
+ vim.fn.cursor({ 2, 1 })
+ vim.cmd('norm!A')
+ local result = org.completion:omnifunc(0, 'D')
assert.are.same({
{ menu = '[Org]', word = 'DEADLINE:' },
}, result)
end)
it('should return defined keywords when base is :', function()
- local result = Omni.get_completions(':')
+ setup_file(':')
+ local result = org.completion:omnifunc(0, ':')
local props = {
{ menu = '[Org]', word = ':PROPERTIES:' },
{ menu = '[Org]', word = ':END:' },
@@ -168,21 +172,23 @@ describe('Autocompletion should properly find start offset for omni autocompleti
end)
it('should filter keywords down', function()
- local result = Omni.get_completions(':C')
+ setup_file(':')
+ local result = org.completion:omnifunc(0, ':C')
assert.are.same({
{ menu = '[Org]', word = ':CUSTOM_ID:' },
{ menu = '[Org]', word = ':CATEGORY:' },
}, result)
- result = Omni.get_completions(':CA')
+ result = org.completion:omnifunc(0, ':CA')
assert.are.same({
{ menu = '[Org]', word = ':CATEGORY:' },
}, result)
end)
it('should find and filter down export options when base is #', function()
+ setup_file('#')
-- Directives
- local result = Omni.get_completions('#')
+ local result = org.completion:omnifunc(0, '#')
local directives = {
{ menu = '[Org]', word = '#+title' },
{ menu = '[Org]', word = '#+author' },
@@ -193,149 +199,146 @@ describe('Autocompletion should properly find start offset for omni autocompleti
{ menu = '[Org]', word = '#+options' },
{ menu = '[Org]', word = '#+category' },
{ menu = '[Org]', word = '#+begin_src' },
- { menu = '[Org]', word = '#+end_src' },
{ menu = '[Org]', word = '#+begin_example' },
+ { menu = '[Org]', word = '#+end_src' },
{ menu = '[Org]', word = '#+end_example' },
}
assert.are.same(directives, result)
- result = Omni.get_completions('#+')
+ result = org.completion:omnifunc(0, '#+')
assert.are.same(directives, result)
- result = Omni.get_completions('#+b')
+ result = org.completion:omnifunc(0, '#+b')
assert.are.same({
{ menu = '[Org]', word = '#+begin_src' },
{ menu = '[Org]', word = '#+begin_example' },
}, result)
end)
- end)
- before_each(function()
- setup_file('* ')
- end)
+ it('should find and filter down TODO keywords at the beginning of a headline', function()
+ setup_file('* ')
+ local result = org.completion:omnifunc(0, '')
+ assert.are.same({
+ { menu = '[Org]', word = 'TODO' },
+ { menu = '[Org]', word = 'DONE' },
+ }, result)
- it('should find and filter down TODO keywords at the beginning of a headline', function()
- local result = Omni.get_completions('')
- assert.are.same({
- { menu = '[Org]', word = 'TODO' },
- { menu = '[Org]', word = 'DONE' },
- }, result)
-
- setup_file('* T')
- result = Omni.get_completions('T')
- assert.are.same({
- { menu = '[Org]', word = 'TODO' },
- }, result)
- end)
+ setup_file('* T')
+ result = org.completion:omnifunc(0, 'T')
+ assert.are.same({
+ { menu = '[Org]', word = 'TODO' },
+ }, result)
+ end)
- it('should find defined tags', function()
- local file = helpers.create_agenda_file({
- '#+filetags: :OFFICE:PRIVATE:',
- })
- setup_file('* TODO tags go at the end :')
- local result = Omni.get_completions(':')
- assert.are.same({
- { menu = '[Org]', word = ':OFFICE:' },
- { menu = '[Org]', word = ':PRIVATE:' },
- { menu = '[Org]', word = ':SOMETAG:' },
- }, result)
-
- result = Omni.get_completions(':OFF')
- assert.are.same({
- { menu = '[Org]', word = ':OFFICE:' },
- }, result)
-
- vim.fn.setline(1, '* TODO tags go at the end :OFFICE:')
- result = Omni.get_completions(':')
- assert.are.same({
- { menu = '[Org]', word = ':OFFICE:' },
- { menu = '[Org]', word = ':PRIVATE:' },
- { menu = '[Org]', word = ':SOMETAG:' },
- }, result)
-
- setup_file('#+filetags: ')
- result = Omni.get_completions('')
- assert.are.same({}, result)
- --
- setup_file('#+filetags: :')
- result = Omni.get_completions(':')
- assert.are.same({
- { menu = '[Org]', word = ':OFFICE:' },
- { menu = '[Org]', word = ':PRIVATE:' },
- { menu = '[Org]', word = ':SOMETAG:' },
- }, result)
- end)
-end)
+ it('should find defined tags', function()
+ local file = helpers.create_agenda_file({
+ '#+filetags: :OFFICE:PRIVATE:',
+ })
+ setup_file('* TODO tags go at the end :')
+ local result = org.completion:omnifunc(0, ':')
+ assert.are.same({
+ { menu = '[Org]', word = ':OFFICE:' },
+ { menu = '[Org]', word = ':PRIVATE:' },
+ { menu = '[Org]', word = ':SOMETAG:' },
+ }, result)
-describe('Autocompletion in hyperlinks', function()
- it('should complete headlines', function()
- local orgfile = helpers.create_agenda_file({
- '* Item for work 1',
- '* Item for work 2',
- })
- local filename = vim.fn.fnamemodify(orgfile.filename, ':t')
- local file_path_relative = string.format('./%s', filename)
-
- local line = string.format(' [[%s::* ', file_path_relative)
- helpers.create_file({ line })
-
- vim.fn.cursor({ 1, #line })
- local result = Omni.get_completions('')
- assert.are.same({
- { menu = '[Org]', word = '*Item for work 1' },
- { menu = '[Org]', word = '*Item for work 2' },
- }, result)
- end)
+ result = org.completion:omnifunc(0, ':OFF')
+ assert.are.same({
+ { menu = '[Org]', word = ':OFFICE:' },
+ }, result)
- it('should complete custom_ids', function()
- local orgfile = helpers.create_agenda_file({
- '* Item for work 1',
- ':PROPERTIES:',
- ':CUSTOM_ID: ID_1',
- ':END:',
- '* Item for work 2',
- ':PROPERTIES:',
- ':CUSTOM_ID: ID_2',
- ':END:',
- })
- local filename = vim.fn.fnamemodify(orgfile.filename, ':t')
- local file_path_relative = string.format('./%s', filename)
-
- local line = string.format(' [[%s::# ', file_path_relative)
- helpers.create_file({ line })
-
- vim.fn.cursor({ 1, #line })
- local result = Omni.get_completions('')
- assert.are.same({
- { menu = '[Org]', word = '#ID_1' },
- { menu = '[Org]', word = '#ID_2' },
- }, result)
- end)
+ vim.fn.setline(1, '* TODO tags go at the end :OFFICE:')
+ result = org.completion:omnifunc(0, ':')
+ assert.are.same({
+ { menu = '[Org]', word = ':OFFICE:' },
+ { menu = '[Org]', word = ':PRIVATE:' },
+ { menu = '[Org]', word = ':SOMETAG:' },
+ }, result)
+
+ setup_file('#+filetags: ')
+ result = org.completion:omnifunc(0, '')
+ assert.are.same({}, result)
+ --
+ setup_file('#+filetags: :')
+ result = org.completion:omnifunc(0, ':')
+ assert.are.same({
+ { menu = '[Org]', word = ':OFFICE:' },
+ { menu = '[Org]', word = ':PRIVATE:' },
+ { menu = '[Org]', word = ':SOMETAG:' },
+ }, result)
+ end)
- it('should complete fuzzy titles', function()
- helpers.create_agenda_file({
- '* Title with an <>',
- 'line1',
- 'line2',
- 'line3',
- '* This headline should not be found',
- 'line1',
- '... <> ...',
- 'line3',
- '* Title without anchor',
- 'line1',
- 'line2',
- 'line3',
- '',
- ' [[Tit ',
- })
- vim.fn.cursor({ 14, 8 })
-
- local result = Omni.get_completions('Tit')
-
- assert.are.same({
- { menu = '[Org]', word = 'Title with an <>' },
- { menu = '[Org]', word = 'Title without anchor' },
- }, result)
+ describe('in hyperlinks', function()
+ it('should complete headlines', function()
+ local orgfile = helpers.create_agenda_file({
+ '* Item for work 1',
+ '* Item for work 2',
+ })
+ local filename = vim.fn.fnamemodify(orgfile.filename, ':t')
+ local file_path_relative = string.format('./%s', filename)
+
+ local line = string.format(' [[%s::* ', file_path_relative)
+ helpers.create_file({ line })
+
+ vim.fn.cursor({ 1, #line })
+ local result = org.completion:omnifunc(0, ('%s::*'):format(file_path_relative))
+ assert.are.same({
+ { menu = '[Org]', word = ('%s::*Item for work 1'):format(file_path_relative) },
+ { menu = '[Org]', word = ('%s::*Item for work 2'):format(file_path_relative) },
+ }, result)
+ end)
+
+ it('should complete custom_ids', function()
+ local orgfile = helpers.create_agenda_file({
+ '* Item for work 1',
+ ':PROPERTIES:',
+ ':CUSTOM_ID: ID_1',
+ ':END:',
+ '* Item for work 2',
+ ':PROPERTIES:',
+ ':CUSTOM_ID: ID_2',
+ ':END:',
+ })
+ local filename = vim.fn.fnamemodify(orgfile.filename, ':t')
+ local file_path_relative = string.format('./%s', filename)
+
+ local line = string.format(' [[%s::# ', file_path_relative)
+ helpers.create_file({ line })
+
+ vim.fn.cursor({ 1, #line })
+ local result = org.completion:omnifunc(0, ('%s::#'):format(file_path_relative))
+ assert.are.same({
+ { menu = '[Org]', word = ('%s::#ID_1'):format(file_path_relative) },
+ { menu = '[Org]', word = ('%s::#ID_2'):format(file_path_relative) },
+ }, result)
+ end)
+
+ it('should complete fuzzy titles', function()
+ helpers.create_agenda_file({
+ '* Title with an <>',
+ 'line1',
+ 'line2',
+ 'line3',
+ '* This headline should not be found',
+ 'line1',
+ '... <> ...',
+ 'line3',
+ '* Title without anchor',
+ 'line1',
+ 'line2',
+ 'line3',
+ '',
+ ' [[Tit ',
+ })
+ vim.fn.cursor({ 14, 8 })
+
+ local result = org.completion:omnifunc(0, 'Tit')
+
+ assert.are.same({
+ { menu = '[Org]', word = 'Title with an <>' },
+ { menu = '[Org]', word = 'Title without anchor' },
+ }, result)
+ end)
+ end)
end)
end)