Skip to content

Commit a793c7e

Browse files
committed
Enable link opening from agenda
1 parent 72a7a62 commit a793c7e

File tree

6 files changed

+95
-19
lines changed

6 files changed

+95
-19
lines changed

DOCS.md

+8
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,14 @@ Open selected agenda item in the same buffer
572572
#### **org_agenda_goto**
573573
*mapped to*: `{'<TAB>'}`<br />
574574
Open selected agenda item in split window
575+
#### **org_agenda_open_link**
576+
*mapped to*: `<Leader>oo`<br />
577+
Open hyperlink under cursor.<br />
578+
Hyperlink types supported:
579+
* URL (http://, https://)
580+
* File (starts with `file:`. Example: `file:/home/user/.config/nvim/init.lua`) Optionally, a line number can be specified
581+
using the '+' character. Example: `file:/home/user/.config/nvim/init.lua +10`
582+
* Fallback: If file path, opens the file.<br />
575583
#### **org_agenda_goto_date**
576584
*mapped to*: `J`<br />
577585
Open calendar that allows selecting date to jump to

lua/orgmode/agenda/init.lua

+66
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ local AgendaSearchView = require('orgmode.agenda.views.search')
99
local AgendaTodosView = require('orgmode.agenda.views.todos')
1010
local AgendaTagsView = require('orgmode.agenda.views.tags')
1111
local AgendaView = require('orgmode.agenda.views.agenda')
12+
local Hyperlinks = require('orgmode.org.hyperlinks')
1213

1314
---@class Agenda
1415
---@field content table[]
@@ -245,6 +246,71 @@ function Agenda:change_todo_state()
245246
})
246247
end
247248

249+
function Agenda:open_link()
250+
local link = Hyperlinks.get_link_under_cursor()
251+
if not link then
252+
return
253+
end
254+
local parts = vim.split(link, '][', true)
255+
local url = parts[1]
256+
local link_ctx = { base = url, skip_add_prefix = true }
257+
-- file links
258+
if url:find('^file:') then
259+
if url:find(' +', 1, true) then
260+
parts = vim.split(url, ' +', true)
261+
url = parts[1]
262+
local line_number = parts[2]
263+
vim.cmd(string.format('edit +%s %s', line_number, url:sub(6)))
264+
vim.cmd([[normal! zv]])
265+
return
266+
end
267+
268+
if url:find('^file:(.-)::') then
269+
link_ctx.line = url
270+
else
271+
vim.cmd(string.format('edit %s', url:sub(6)))
272+
vim.cmd([[normal! zv]])
273+
return
274+
end
275+
end
276+
-- web links
277+
if url:find('^https?://') then
278+
if not vim.g.loaded_netrwPlugin then
279+
return utils.echo_warning('Netrw plugin must be loaded in order to open urls.')
280+
end
281+
return vim.fn['netrw#BrowseX'](url, vim.fn['netrw#CheckIfRemote']())
282+
end
283+
-- fallback: filepath
284+
local stat = vim.loop.fs_stat(url)
285+
if stat and stat.type == 'file' then
286+
return vim.cmd(string.format('edit %s', url))
287+
end
288+
-- headline link
289+
local headlines = Hyperlinks.find_matching_links(link_ctx)
290+
if #headlines == 0 then
291+
utils.echo_warning('foobar')
292+
return
293+
end
294+
local headline = headlines[1]
295+
if #headlines > 1 then
296+
local longest_headline = utils.reduce(headlines, function(acc, h)
297+
return math.max(acc, h.line:len())
298+
end, 0)
299+
local options = {}
300+
for i, h in ipairs(headlines) do
301+
table.insert(options, string.format('%d) %-' .. longest_headline .. 's (%s)', i, h.line, h.file))
302+
end
303+
vim.cmd([[echo "Multiple targets found. Select target:"]])
304+
local choice = vim.fn.inputlist(options)
305+
if choice < 1 or choice > #headlines then
306+
return
307+
end
308+
headline = headlines[choice]
309+
end
310+
vim.cmd(string.format('edit %s', headline.file))
311+
vim.fn.cursor(headline.range.start_line, 0)
312+
end
313+
248314
function Agenda:clock_in()
249315
return self:_remote_edit({
250316
action = 'clock.org_clock_in',

lua/orgmode/config/defaults.lua

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ return {
6767
org_agenda_quit = 'q',
6868
org_agenda_switch_to = '<CR>',
6969
org_agenda_goto = '<TAB>',
70+
org_agenda_open_link = '<prefix>o',
7071
org_agenda_goto_date = 'J',
7172
org_agenda_redo = 'r',
7273
org_agenda_todo = 't',

lua/orgmode/config/mappings/init.lua

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ return {
1919
{ opts = { desc = 'org open agenda item (same buffer)' } }
2020
),
2121
org_agenda_goto = m.action('agenda.goto_item', { opts = { desc = 'org open agenda item (split buffer)' } }),
22+
org_agenda_open_link = m.action('agenda.open_link', { opts = { desc = 'org open hyperlink' } }),
2223
org_agenda_goto_date = m.action('agenda.goto_date', { opts = { desc = 'org goto date' } }),
2324
org_agenda_redo = m.action('agenda.redo', { opts = { desc = 'org redo' } }),
2425
org_agenda_todo = m.action('agenda.change_todo_state', { opts = { desc = 'org cycle todo state' } }),

lua/orgmode/org/hyperlinks.lua

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@ local Files = require('orgmode.parser.files')
22
local utils = require('orgmode.utils')
33
local Hyperlinks = {}
44

5+
---@return string|nil
6+
function Hyperlinks.get_link_under_cursor()
7+
local found_link = nil
8+
local links = {}
9+
local line = vim.fn.getline('.')
10+
local col = vim.fn.col('.')
11+
for link in line:gmatch('%[%[(.-)%]%]') do
12+
local start_from = #links > 0 and links[#links].to or nil
13+
local from, to = line:find('%[%[(.-)%]%]', start_from)
14+
if col >= from and col <= to then
15+
found_link = link
16+
break
17+
end
18+
table.insert(links, { link = link, from = from, to = to })
19+
end
20+
return found_link
21+
end
22+
523
local function get_file_from_context(ctx)
624
return (
725
ctx.hyperlinks and ctx.hyperlinks.filepath and Files.get(ctx.hyperlinks.filepath, true)

lua/orgmode/org/mappings.lua

+1-19
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ function OrgMappings:open_at_point()
650650
return self.agenda:open_day(date)
651651
end
652652

653-
local link = self:_get_link_under_cursor()
653+
local link = Hyperlinks.get_link_under_cursor()
654654
if not link then
655655
return
656656
end
@@ -910,22 +910,4 @@ function OrgMappings:_adjust_date(amount, span, fallback)
910910
return vim.api.nvim_feedkeys(utils.esc(fallback), 'n', true)
911911
end
912912

913-
---@return string|nil
914-
function OrgMappings:_get_link_under_cursor()
915-
local found_link = nil
916-
local links = {}
917-
local line = vim.fn.getline('.')
918-
local col = vim.fn.col('.')
919-
for link in line:gmatch('%[%[(.-)%]%]') do
920-
local start_from = #links > 0 and links[#links].to or nil
921-
local from, to = line:find('%[%[(.-)%]%]', start_from)
922-
if col >= from and col <= to then
923-
found_link = link
924-
break
925-
end
926-
table.insert(links, { link = link, from = from, to = to })
927-
end
928-
return found_link
929-
end
930-
931913
return OrgMappings

0 commit comments

Comments
 (0)