Skip to content

Commit ae38aa8

Browse files
feat(agenda): Allow opening links from agenda headlines
1 parent a49d5d4 commit ae38aa8

File tree

6 files changed

+57
-1
lines changed

6 files changed

+57
-1
lines changed

lua/orgmode/agenda/init.lua

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ local Menu = require('orgmode.ui.menu')
88
local Promise = require('orgmode.utils.promise')
99
local AgendaTypes = require('orgmode.agenda.types')
1010
local Input = require('orgmode.ui.input')
11+
local OrgHyperlink = require('orgmode.org.links.hyperlink')
1112

1213
---@class OrgAgenda
1314
---@field highlights table[]
1415
---@field views OrgAgendaViewType[]
1516
---@field filters OrgAgendaFilter
1617
---@field files OrgFiles
1718
---@field highlighter OrgHighlighter
19+
---@field links OrgLinks
1820
local Agenda = {}
1921

20-
---@param opts? { highlighter: OrgHighlighter, files: OrgFiles }
22+
---@param opts? { highlighter: OrgHighlighter, files: OrgFiles, links: OrgLinks }
2123
---@return OrgAgenda
2224
function Agenda:new(opts)
2325
opts = opts or {}
@@ -28,6 +30,7 @@ function Agenda:new(opts)
2830
highlights = {},
2931
files = opts.files,
3032
highlighter = opts.highlighter,
33+
links = opts.links
3134
}
3235
setmetatable(data, self)
3336
self.__index = self
@@ -619,6 +622,16 @@ function Agenda:get_headline_at_cursor()
619622
end
620623
end
621624

625+
function Agenda:open_at_point()
626+
local link = OrgHyperlink.from_current_line_position()
627+
628+
if link then
629+
return self.links:follow(link.url:to_string())
630+
end
631+
632+
utils.echo_error('No link found under cursor')
633+
end
634+
622635
function Agenda:quit()
623636
vim.api.nvim_win_close(0, true)
624637
end

lua/orgmode/config/_meta.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
---@field org_agenda_filter? OrgMappingValue Default: '/'
8484
---@field org_agenda_refile? OrgMappingValue Default: '<prefix>r'
8585
---@field org_agenda_add_note? OrgMappingValue Default: '<prefix>na'
86+
---@field org_agenda_open_at_point? OrgMappingValue Default: '<prefix>o'
8687
---@field org_agenda_show_help? OrgMappingValue Default: 'g?'
8788
---
8889
---@class OrgMappingsCapture

lua/orgmode/config/defaults.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ local DefaultConfig = {
108108
org_agenda_goto_date = 'J',
109109
org_agenda_redo = 'r',
110110
org_agenda_todo = 't',
111+
org_agenda_open_at_point = '<prefix>o',
111112
org_agenda_clock_goto = '<prefix>xj',
112113
org_agenda_set_effort = '<prefix>xe',
113114
org_agenda_clock_in = 'I',

lua/orgmode/config/mappings/init.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ return {
117117
help_desc = 'Open prompt that allows filtering by category, tags and title(vim regex)',
118118
},
119119
}),
120+
org_agenda_open_at_point = m.action(
121+
'agenda.open_at_point',
122+
{ opts = { desc = 'org open', help_desc = 'Open hyperlink under cursor' } }
123+
),
120124
org_agenda_refile = m.action('agenda.refile', {
121125
opts = {
122126
desc = 'org refile',

lua/orgmode/init.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ function Org:init()
6161
self.agenda = require('orgmode.agenda'):new({
6262
files = self.files,
6363
highlighter = self.highlighter,
64+
links = self.links,
6465
})
6566
self.capture = require('orgmode.capture'):new({
6667
files = self.files,

lua/orgmode/org/links/hyperlink.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,41 @@ function OrgHyperlink:new(str, range)
2020
return this
2121
end
2222

23+
---Get hyperlink under current cursor position by parsing the line
24+
---@return OrgHyperlink | nil
25+
function OrgHyperlink.from_current_line_position()
26+
local pos = vim.fn.getpos('.')
27+
local line = vim.api.nvim_get_current_line()
28+
local start_pos = 0
29+
local end_pos = 0
30+
for i = pos[3], 1, -1 do
31+
if line:sub(i, i) == '[' and line:sub(i - 1, i - 1) == '[' then
32+
start_pos = i - 1
33+
break
34+
end
35+
end
36+
for i = pos[3], #line do
37+
if line:sub(i, i) == ']' and line:sub(i + 1, i + 1) == ']' then
38+
end_pos = i + 1
39+
break
40+
end
41+
end
42+
43+
if start_pos == 0 or end_pos == 0 then
44+
return nil
45+
end
46+
local str = line:sub(start_pos + 2, end_pos - 2)
47+
return OrgHyperlink:new(
48+
str,
49+
Range:new({
50+
start_line = pos[2],
51+
start_col = start_pos,
52+
end_line = pos[2],
53+
end_col = end_pos,
54+
})
55+
)
56+
end
57+
2358
---@param node TSNode
2459
---@param source number | string
2560
---@return OrgHyperlink
@@ -33,6 +68,7 @@ function OrgHyperlink.from_node(node, source)
3368
return this
3469
end
3570

71+
---Get hyperlink under current cursor position by parsing the treesitter node
3672
---@return OrgHyperlink | nil
3773
function OrgHyperlink.at_cursor()
3874
local link_node = ts_utils.closest_node(ts_utils.get_node(), { 'link', 'link_desc' })

0 commit comments

Comments
 (0)