Skip to content

[WIP][RFC] Hyperlink Refactor - Registry Version #793

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

Closed
Closed
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/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ function Config:setup_ts_predicates()
if not text or vim.trim(text) == '' then
return
end
metadata['injection.language'] = utils.detect_filetype(text)
metadata['injection.language'] = utils.detect_filetype(text, true)
end, { force = true })

vim.treesitter.query.add_predicate('org-is-headline-level?', function(match, _, _, predicate)
Expand Down
4 changes: 4 additions & 0 deletions lua/orgmode/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ local auto_instance_keys = {
---@field capture OrgCapture
---@field clock OrgClock
---@field completion OrgCompletion
---@field links OrgLinks
---@field org_mappings OrgMappings
---@field notifications OrgNotifications
local Org = {}
Expand Down Expand Up @@ -66,6 +67,9 @@ function Org:init()
files = self.files,
})
self.completion = require('orgmode.org.autocompletion'):new({ files = self.files })
self.links = require('orgmode.org.links'):new({
files = self.files,
})
self.statusline_debounced = require('orgmode.utils').debounce('statusline', function()
return self.clock:get_statusline()
end, 300)
Expand Down
53 changes: 53 additions & 0 deletions lua/orgmode/org/links/handlers/_internal/custom_id.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
local utils = require('orgmode.utils')
local Org = require('orgmode')
local Internal = require('orgmode.org.hyperlinks.builtin.internal')

---@class OrgLinkHandlerCustomId:OrgLinkHandlerInternal
local CustomId = Internal:new()

function CustomId:new(custom_id)
---@class OrgLinkHandlerCustomId
local this = Internal:new()
this.custom_id = custom_id
setmetatable(this, self)
self.__index = self
return this
end

---@param input string
function CustomId.parse(input)
return CustomId:new(input)
end

function CustomId:__tostring()
return string.format('#%s', self.custom_id)
end

function CustomId:follow()
local headlines = Org.files:get_current_file():find_headlines_with_property_matching('custom_id', self.custom_id)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pass down files from the top through constructor instead of requiring orgmode directly.


if #headlines == 0 then
return utils.echo_warning(('Could not find custom ID "%s".'):format(self.custom_id))
end

utils.goto_oneof(headlines)
end

function CustomId:insert_description()
return self.custom_id
end

function CustomId:complete(lead, context)
local file = self.get_file_from_context(context)
local headlines = file:find_headlines_with_property_matching('CUSTOM_ID', lead)

local completions = {}
for _, headline in pairs(headlines) do
local id = headline:get_property('CUSTOM_ID')
table.insert(completions, tostring(self:new(id)))
end

return completions
end

return CustomId
53 changes: 53 additions & 0 deletions lua/orgmode/org/links/handlers/_internal/headline.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
local utils = require('orgmode.utils')
local Org = require('orgmode')
local Internal = require('orgmode.org.hyperlinks.builtin.internal')
local Id = require('orgmode.org.hyperlinks.builtin.id')

---@class OrgLinkHandlerHeadline:OrgLinkHandlerInternal
local Headline = Internal:new()

function Headline:new(headline)
---@class OrgLinkHandlerHeadline
local this = Internal:new()
this.headline = headline
setmetatable(this, self)
self.__index = self
return this
end

---@param input string
function Headline.parse(input)
return Headline:new(input)
end

function Headline:__tostring()
return string.format('*%s', self.headline)
end

function Headline:follow()
local headlines = Org.files:get_current_file():find_headlines_by_title(self.headline)

if #headlines == 0 then
return utils.echo_warning(('Could not find headline "%s".'):format(self.headline))
end

utils.goto_oneof(headlines)
end

function Headline:insert_description()
return self.headline
end

function Headline:complete(lead, context)
local file = self.get_file_from_context(context)
local headlines = file:find_headlines_by_title(lead)

local completions = {}
for _, headline in pairs(headlines) do
table.insert(completions, tostring(Headline:new(headline:get_title())))
end

return completions
end

return Headline
28 changes: 28 additions & 0 deletions lua/orgmode/org/links/handlers/_internal/line_number.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
local Internal = require('orgmode.org.hyperlinks.builtin.internal')

---@class OrgLinkHandlerLineNumber:OrgLinkHandlerInternal
local LineNumber = Internal:new()

function LineNumber:new(line_number)
---@class OrgLinkHandlerLineNumber
local this = Internal:new()
this.line_number = line_number
setmetatable(this, self)
self.__index = self
return this
end

---@param input string
function LineNumber.parse(input)
return LineNumber:new(tonumber(input))
end

function LineNumber:__tostring()
return string.format('%d', self.line_number)
end

function LineNumber:follow()
vim.cmd(('normal! %dGzv'):format(self.line_number))
end

return LineNumber
69 changes: 69 additions & 0 deletions lua/orgmode/org/links/handlers/_internal/plain.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
local utils = require('orgmode.utils')
local Org = require('orgmode')
local Internal = require('orgmode.org.hyperlinks.builtin.internal')

---@class OrgLinkHandlerPlain:OrgLinkHandlerInternal
local Plain = Internal:new()

function Plain:new(text)
---@class OrgLinkHandlerPlain
local this = Internal:new()
this.text = text
setmetatable(this, self)
self.__index = self
return this
end

---@param input string
function Plain.parse(input)
return Plain:new(input)
end

function Plain:__tostring()
return self.text
end

function Plain:follow()
local anchors = vim.fn.matchbufline(0, ('<<<?%s[^>]*>>>?'):format(self.text), 0, '$')

if #anchors >= 1 then
vim.fn.cursor(anchors[1].lnum, anchors[1].byteidx)
return
end

-- TODO from here, behaviour should depend on org-link-search-must-match-exact-headline
-- TODO #+NAME tag support should be added, but it didn't exists yet

local plain_text_matches = vim.fn.matchbufline(0, self.text, 0, '$')

if #plain_text_matches >= 1 then
vim.fn.cursor(plain_text_matches[1].lnum, plain_text_matches[1].byteidx)
return
end

return utils.echo_warning(('No matches found for "%s".'):format(self.text))
end

function Plain:insert_description()
return self.text
end

-- TODO #+NAME tag support should be added, but it didn't exists yet
function Plain:complete(lead, context)
local file = self.get_file_from_context(context)
local completions = {}

local anchors = file.content:gmatch(('<<<?%s[^>]*>>>?'):format(lead))
for anchor in anchors do
table.insert(completions, tostring(Plain:new(anchor)))
end

local headlines = file:find_headlines_by_title(lead)
for _, headline in pairs(headlines) do
table.insert(completions, tostring(Plain:new(headline:get_title())))
end

return completions
end

return Plain
144 changes: 144 additions & 0 deletions lua/orgmode/org/links/handlers/file.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
local fs = require('orgmode.utils.fs')
local Link = require('orgmode.org.links.link_handler')
local Id = require('orgmode.org.links.handlers.id')
local Internal = require('orgmode.org.links.handlers.internal')

---@class OrgLinkHandlerFile:OrgLinkHandler
---@field new fun(self: OrgLinkHandlerFile, path: string, target: OrgLinkHandlerInternal | nil, prefix: boolean | nil, files: OrgFile[]): OrgLinkHandlerFile
---@field parse fun(link: string, prefix: boolean | nil): OrgLinkHandlerFile | nil
---@field path string
---@field skip_prefix boolean
---@field target OrgLinkHandlerInternal | nil
local File = Link:new('file')

function File:new(path, target, skip_prefix, files)
---@class OrgLinkHandlerFile
local this = Link:new()
this.skip_prefix = skip_prefix or false
this.path = path
this.target = target
this.files = files
setmetatable(this, self)
self.__index = self
return this
end

-- TODO make protocol prefix optional. Based on what?
function File:__tostring()
local v = ''
if self.skip_prefix then
v = ('%s'):format(self.path)
else
v = ('%s:%s'):format(self.protocol, self.path)
end

if self.target then
v = string.format('%s::%s', v, self.target)
end

return v
end

function File:follow()
vim.cmd('edit ' .. fs.get_real_path(self.path))

if self.target then
self.target:follow()
end
end

function File:_autocompletions_filenames(lead)
local filenames = self.files:filenames()

local matches = {}
for _, f in ipairs(filenames) do
local realpath = fs.substitute_path(lead) or lead
if f:find('^' .. realpath) then
local path = f:gsub('^' .. realpath, lead)
table.insert(matches, { real = f, path = path })
end
end

print(vim.inspect(matches))
return matches
end

function File:insert_description()
local Org = require('orgmode')
if self.target then
return self.target:insert_description()
end

local path = fs.get_real_path(self.path)
if not path then
return nil
end
local file = Org.files:get(path)
if not file then
return nil
end

return file:get_title()
end

local FileFactory = {}

function FileFactory:init(files)
self.files = files
end

function FileFactory:new(path, target, skip_prefix)
return File:new(path, target, skip_prefix, self.files)
end

function FileFactory:parse(input, skip_prefix)
if input == nil or #input == 0 then
return nil
end
local deliniator_start, deliniator_stop = input:find('::')

---@type OrgLinkHandlerInternal | nil
local target = nil
local path = input

if deliniator_start then
---@class OrgLinkHandlerInternal | nil
target = Internal.parse(input:sub(deliniator_stop + 1), true)
path = input:sub(0, deliniator_start - 1)
end

self:new(path, target, skip_prefix)
end

function FileFactory:complete(lead, context)
context = context or {}
local deliniator_start, deliniator_stop = lead:find('::')

if not deliniator_start then
return self:_complete(lead, context)
else
local path = lead:sub(0, deliniator_start - 1)
local target_lead = lead:sub(deliniator_stop + 1)
return self:_complete_targets(path, target_lead, context)
end
end

function FileFactory:_complete(lead, context)
return vim.tbl_map(function(f)
return tostring(self:new(f, nil, context.skip_prefix))
end, self:_autocompletions_filenames(lead))
end

function FileFactory:_complete_targets(path, target_lead, context)
return vim.tbl_map(
function(t)
return tostring(self:new(path, t, context.skip_prefix))
end,
Internal:complete(target_lead, {
filename = fs.get_real_path(path),
only_internal = true,
})
)
end

return FileFactory
Loading
Loading