Skip to content

Commit cf650e5

Browse files
WIP Hyperlink Parsing
1 parent de02a0c commit cf650e5

27 files changed

+1069
-1190
lines changed

lua/orgmode/api/file.lua

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
---@diagnostic disable: invisible
22
local OrgHeadline = require('orgmode.api.headline')
3-
local Hyperlinks = require('orgmode.org.hyperlinks')
43
local org = require('orgmode')
54

65
---@class OrgApiFile
@@ -94,6 +93,22 @@ function OrgFile:get_closest_headline(cursor)
9493
return nil
9594
end
9695

96+
---@param file OrgFile
97+
---@param path? string
98+
local function get_link_to_file(file, path)
99+
local title = file:get_title()
100+
101+
if config.org_id_link_to_org_use_id then
102+
local id = file:id_get_or_create()
103+
if id then
104+
return ('id:%s::*%s'):format(id, title)
105+
end
106+
end
107+
108+
path = path or file.filename
109+
return ('file:%s::*%s'):format(path, title)
110+
end
111+
97112
--- Get a link destination as string
98113
---
99114
--- Depending if org_id_link_to_org_use_id is set the format is
@@ -112,12 +127,12 @@ function OrgFile:get_link()
112127
-- do remote edit
113128
return org.files
114129
:update_file(filename, function(file)
115-
return Hyperlinks.get_link_to_file(file)
130+
return get_link_to_file(file)
116131
end)
117132
:wait()
118133
end
119134

120-
return Hyperlinks.get_link_to_file(self._file)
135+
return get_link_to_file(self._file)
121136
end
122137

123138
return OrgFile

lua/orgmode/api/headline.lua

+19-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
local utils = require('orgmode.utils')
12
local OrgPosition = require('orgmode.api.position')
23
local config = require('orgmode.config')
34
local PriorityState = require('orgmode.objects.priority_state')
45
local Date = require('orgmode.objects.date')
56
local Calendar = require('orgmode.objects.calendar')
67
local Promise = require('orgmode.utils.promise')
7-
local Hyperlinks = require('orgmode.org.hyperlinks')
88
local org = require('orgmode')
99

1010
---@class OrgApiHeadline
@@ -263,6 +263,22 @@ function OrgHeadline:_do_action(action)
263263
end)
264264
end
265265

266+
---@param headline OrgHeadline
267+
---@param path? string
268+
local function get_link_to_headline(headline, path)
269+
local title = headline:get_title()
270+
271+
if config.org_id_link_to_org_use_id then
272+
local id = headline:id_get_or_create()
273+
if id then
274+
return ('id:%s::*%s'):format(id, title)
275+
end
276+
end
277+
278+
path = path or utils.current_file_path()
279+
return ('file:%s::*%s'):format(path, title)
280+
end
281+
266282
--- Get a link destination as string
267283
---
268284
--- Depending if org_id_link_to_org_use_id is set the format is
@@ -281,12 +297,12 @@ function OrgHeadline:get_link()
281297
-- do remote edit
282298
return org.files
283299
:update_file(filename, function(_)
284-
return Hyperlinks.get_link_to_headline(self._section)
300+
return get_link_to_headline(self._section)
285301
end)
286302
:wait()
287303
end
288304

289-
return Hyperlinks.get_link_to_headline(self._section)
305+
return get_link_to_headline(self._section)
290306
end
291307

292308
return OrgHeadline

lua/orgmode/api/init.lua

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
---@diagnostic disable: invisible
22
local OrgFile = require('orgmode.api.file')
33
local OrgHeadline = require('orgmode.api.headline')
4-
local Hyperlinks = require('orgmode.org.hyperlinks')
4+
local Link = require('orgmode.org.hyperlinks.link')
5+
local HyperLink = require('orgmode.org.hyperlinks')
56
local orgmode = require('orgmode')
67

78
---@class OrgApiRefileOpts
@@ -110,7 +111,19 @@ end
110111
--- @param link_location string
111112
--- @return boolean
112113
function OrgApi.insert_link(link_location)
113-
Hyperlinks.insert_link(link_location)
114+
local link = Link.parse(link_location)
115+
if not link then
116+
return false
117+
end
118+
119+
local desc = nil
120+
if link.target and link.target.headline then
121+
desc = link.target.headline
122+
end
123+
124+
HyperLink.insert_link(HyperLink:new(link, desc))
125+
126+
return true
114127
end
115128

116129
return OrgApi

lua/orgmode/config/defaults.lua

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
local default_hyperlinks = require('orgmode.org.hyperlinks.builtin')
2+
13
---@class OrgDefaultConfig
24
---@field org_id_method 'uuid' | 'ts' | 'org'
35
---@field org_agenda_span 'day' | 'week' | 'month' | 'year' | number
@@ -208,6 +210,7 @@ local DefaultConfig = {
208210
handler = nil,
209211
},
210212
},
213+
hyperlinks = default_hyperlinks,
211214
}
212215

213216
return DefaultConfig

lua/orgmode/config/init.lua

+74-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local defaults = require('orgmode.config.defaults')
66
local mappings = require('orgmode.config.mappings')
77
local TodoKeywords = require('orgmode.objects.todo_keywords')
88
local PriorityState = require('orgmode.objects.priority_state')
9+
local Alias = require('orgmode.org.hyperlinks.builtin.alias')
910

1011
---@class OrgConfig:OrgDefaultConfig
1112
---@field opts table
@@ -53,13 +54,85 @@ function Config:extend(opts)
5354
opts.org_priority_lowest = self.opts.org_priority_lowest
5455
opts.org_priority_default = self.opts.org_priority_default
5556
end
57+
opts.hyperlinks = self:_process_links(opts.hyperlinks)
5658
self.opts = vim.tbl_deep_extend('force', self.opts, opts)
5759
if self.org_startup_indented then
5860
self.org_adapt_indentation = not self.org_indent_mode_turns_off_org_adapt_indentation
5961
end
6062
return self
6163
end
6264

65+
function Config:_process_links(links)
66+
if not (links or type(links) == table) then
67+
return nil
68+
end
69+
70+
local processed = {}
71+
72+
for protocol, hyperlink in pairs(links) do
73+
if type(hyperlink) == 'string' then
74+
if not protocol then
75+
utils.echo_warning(('A link alias must have a protocol key. Skipped %s'):format(hyperlink))
76+
else
77+
hyperlink = self:_process_link_alias(protocol, hyperlink)
78+
processed[hyperlink.protocol] = hyperlink
79+
end
80+
goto continue
81+
end
82+
83+
if type(hyperlink) == 'table' then
84+
hyperlink = self:_process_link_table(protocol, hyperlink)
85+
if hyperlink then
86+
processed[hyperlink.protocol] = hyperlink
87+
end
88+
end
89+
90+
::continue::
91+
end
92+
93+
return processed
94+
end
95+
96+
function Config:_process_link_table(protocol, link)
97+
if not link.parse or not (type(link.parse) == 'function') then
98+
utils.echo_warning("A link must have a 'parse' function.")
99+
return
100+
end
101+
102+
if not link.follow or not (type(link.follow) == 'function') then
103+
utils.echo_warning("A link must have a 'follow' method.")
104+
return
105+
end
106+
107+
if not link.protocol then
108+
if not protocol then
109+
utils.echo_warning('A link must have a protocol.')
110+
return
111+
end
112+
link.protocol = protocol
113+
end
114+
115+
return link
116+
end
117+
118+
function Config:_process_link_alias(protocol, alias)
119+
local components = {}
120+
local expression = vim.regex([[%s\|%h\|%(.-)]])
121+
repeat
122+
local start_special, end_special = expression:match_str(alias)
123+
if not start_special then
124+
table.insert(components, alias)
125+
break
126+
end
127+
128+
table.insert(components, alias:sub(0, start_special))
129+
table.insert(components, alias:sub(start_special + 1, end_special))
130+
alias = alias:sub(end_special + 1)
131+
until #alias <= 0
132+
133+
return Alias(protocol, components)
134+
end
135+
63136
function Config:_are_priorities_valid(opts)
64137
local high = opts.org_priority_highest
65138
local low = opts.org_priority_lowest
@@ -97,7 +170,7 @@ function Config:_are_priorities_valid(opts)
97170
)
98171
return false
99172
end
100-
-- one-char strings
173+
-- one-char strings
101174
elseif
102175
(type(high) == 'string' and #high == 1)
103176
and (type(low) == 'string' and #low == 1)

lua/orgmode/files/file.lua

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ local Headline = require('orgmode.files.headline')
55
local ts = vim.treesitter
66
local config = require('orgmode.config')
77
local Block = require('orgmode.files.elements.block')
8-
local Link = require('orgmode.org.hyperlinks.link')
8+
local HyperLink = require('orgmode.org.hyperlinks')
99
local Range = require('orgmode.files.elements.range')
1010
local Memoize = require('orgmode.utils.memoize')
1111

@@ -715,7 +715,7 @@ function OrgFile:get_archive_file_location()
715715
end
716716

717717
memoize('get_links')
718-
---@return OrgLink[]
718+
---@return OrgHyperLink[]
719719
function OrgFile:get_links()
720720
self:parse(true)
721721
local ts_query = ts_utils.get_query([[
@@ -729,7 +729,7 @@ function OrgFile:get_links()
729729
for _, match in ts_query:iter_captures(self.root, self:_get_source()) do
730730
local line = match:start()
731731
if not processed_lines[line] then
732-
vim.list_extend(links, Link.all_from_line(self.lines[line + 1], line + 1))
732+
vim.list_extend(links, HyperLink.all_from_line(self.lines[line + 1], line + 1))
733733
processed_lines[line] = true
734734
end
735735
end

lua/orgmode/files/init.lua

+10
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ function OrgFiles:find_files_with_property(property_name, term)
273273
return files
274274
end
275275

276+
---@param property_name string
277+
---@param term string
278+
---@return OrgHeadline[]
279+
function OrgFiles:find_files_with_property_matching(property_name, term)
280+
return vim.tbl_filter(function(item)
281+
local property = item:get_property(property_name)
282+
return property and property:lower():match('^' .. vim.pesc(term:lower()))
283+
end, self:all())
284+
end
285+
276286
---@param term string
277287
---@param no_escape boolean
278288
---@param search_extra_files boolean

lua/orgmode/org/autocompletion/sources/hyperlinks.lua

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
local Hyperlinks = require('orgmode.org.hyperlinks')
2-
local Link = require('orgmode.org.hyperlinks.link')
1+
local HyperLink = require('orgmode.org.hyperlinks')
2+
33
---@class OrgCompletionHyperlinks:OrgCompletionSource
44
---@field completion OrgCompletion
55
---@field private pattern vim.regex
6-
local OrgCompletionHyperlinks = {}
6+
local OrgCompletionHyperlinks = {
7+
stored_links = {},
8+
}
79
OrgCompletionHyperlinks.__index = OrgCompletionHyperlinks
810

911
---@param opts { completion: OrgCompletion }
@@ -26,9 +28,10 @@ end
2628

2729
---@return string[]
2830
function OrgCompletionHyperlinks:get_results(context)
29-
local link = Link:new(context.base)
30-
local result, mapper = Hyperlinks.find_matching_links(link.url)
31-
return mapper(result)
31+
local hyperlinks = HyperLink:autocompletions(context.base)
32+
return vim.tbl_map(function(hyperlink)
33+
return hyperlink.link:__tostring()
34+
end, hyperlinks)
3235
end
3336

3437
return OrgCompletionHyperlinks
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
local utils = require('orgmode.utils')
2+
local Link = require('orgmode.org.hyperlinks.link')
3+
4+
return function(protocol, components)
5+
for _, component in pairs(components) do
6+
if not (type(component) == 'string') then
7+
return nil
8+
end
9+
end
10+
11+
---@class OrgLinkAlias:OrgLink
12+
local Alias = Link:new(protocol)
13+
Alias.components = components
14+
15+
---@param input string
16+
function Alias.parse(input)
17+
local processed_components = {}
18+
for _, component in pairs(Alias.components) do
19+
---@cast component string
20+
if component == '%s' then
21+
table.insert(processed_components, input)
22+
goto continue
23+
end
24+
if component == '%h' then
25+
table.insert(processed_components, vim.uri_encode(input))
26+
goto continue
27+
end
28+
if component:find('^%%%b()$') then
29+
local func = component:sub(3, -2)
30+
table.insert(processed_components, vim.fn.luaeval(('%s(_A)'):format(func), input))
31+
goto continue
32+
end
33+
table.insert(processed_components, component)
34+
::continue::
35+
end
36+
37+
return Link.parse(table.concat(processed_components))
38+
end
39+
40+
return Alias
41+
end

0 commit comments

Comments
 (0)