diff --git a/DOCS.md b/DOCS.md index b2ff994f0..cefad5490 100644 --- a/DOCS.md +++ b/DOCS.md @@ -572,6 +572,14 @@ Open selected agenda item in the same buffer #### **org_agenda_goto** *mapped to*: `{''}`
Open selected agenda item in split window +#### **org_agenda_open_link** +*mapped to*: `oo`
+Open hyperlink under cursor.
+Hyperlink types supported: +* URL (http://, https://) +* File (starts with `file:`. Example: `file:/home/user/.config/nvim/init.lua`) Optionally, a line number can be specified +using the '+' character. Example: `file:/home/user/.config/nvim/init.lua +10` +* Fallback: If file path, opens the file.
#### **org_agenda_goto_date** *mapped to*: `J`
Open calendar that allows selecting date to jump to diff --git a/lua/orgmode/agenda/init.lua b/lua/orgmode/agenda/init.lua index a3a5677d2..69ec26024 100644 --- a/lua/orgmode/agenda/init.lua +++ b/lua/orgmode/agenda/init.lua @@ -9,6 +9,7 @@ local AgendaSearchView = require('orgmode.agenda.views.search') local AgendaTodosView = require('orgmode.agenda.views.todos') local AgendaTagsView = require('orgmode.agenda.views.tags') local AgendaView = require('orgmode.agenda.views.agenda') +local Hyperlinks = require('orgmode.org.hyperlinks') ---@class Agenda ---@field content table[] @@ -245,6 +246,71 @@ function Agenda:change_todo_state() }) end +function Agenda:open_link() + local link = Hyperlinks.get_link_under_cursor() + if not link then + return + end + local parts = vim.split(link, '][', true) + local url = parts[1] + local link_ctx = { base = url, skip_add_prefix = true } + -- file links + if url:find('^file:') then + if url:find(' +', 1, true) then + parts = vim.split(url, ' +', true) + url = parts[1] + local line_number = parts[2] + vim.cmd(string.format('edit +%s %s', line_number, url:sub(6))) + vim.cmd([[normal! zv]]) + return + end + + if url:find('^file:(.-)::') then + link_ctx.line = url + else + vim.cmd(string.format('edit %s', url:sub(6))) + vim.cmd([[normal! zv]]) + return + end + end + -- web links + if url:find('^https?://') then + if not vim.g.loaded_netrwPlugin then + return utils.echo_warning('Netrw plugin must be loaded in order to open urls.') + end + return vim.fn['netrw#BrowseX'](url, vim.fn['netrw#CheckIfRemote']()) + end + -- fallback: filepath + local stat = vim.loop.fs_stat(url) + if stat and stat.type == 'file' then + return vim.cmd(string.format('edit %s', url)) + end + -- headline link + local headlines = Hyperlinks.find_matching_links(link_ctx) + if #headlines == 0 then + utils.echo_warning('foobar') + return + end + local headline = headlines[1] + if #headlines > 1 then + local longest_headline = utils.reduce(headlines, function(acc, h) + return math.max(acc, h.line:len()) + end, 0) + local options = {} + for i, h in ipairs(headlines) do + table.insert(options, string.format('%d) %-' .. longest_headline .. 's (%s)', i, h.line, h.file)) + end + vim.cmd([[echo "Multiple targets found. Select target:"]]) + local choice = vim.fn.inputlist(options) + if choice < 1 or choice > #headlines then + return + end + headline = headlines[choice] + end + vim.cmd(string.format('edit %s', headline.file)) + vim.fn.cursor(headline.range.start_line, 0) +end + function Agenda:clock_in() return self:_remote_edit({ action = 'clock.org_clock_in', diff --git a/lua/orgmode/config/defaults.lua b/lua/orgmode/config/defaults.lua index 87a1e08c9..543dfc7cd 100644 --- a/lua/orgmode/config/defaults.lua +++ b/lua/orgmode/config/defaults.lua @@ -67,6 +67,7 @@ return { org_agenda_quit = 'q', org_agenda_switch_to = '', org_agenda_goto = '', + org_agenda_open_link = 'o', org_agenda_goto_date = 'J', org_agenda_redo = 'r', org_agenda_todo = 't', diff --git a/lua/orgmode/config/mappings/init.lua b/lua/orgmode/config/mappings/init.lua index 97dd5762a..852c85671 100644 --- a/lua/orgmode/config/mappings/init.lua +++ b/lua/orgmode/config/mappings/init.lua @@ -19,6 +19,7 @@ return { { opts = { desc = 'org open agenda item (same buffer)' } } ), org_agenda_goto = m.action('agenda.goto_item', { opts = { desc = 'org open agenda item (split buffer)' } }), + org_agenda_open_link = m.action('agenda.open_link', { opts = { desc = 'org open hyperlink' } }), org_agenda_goto_date = m.action('agenda.goto_date', { opts = { desc = 'org goto date' } }), org_agenda_redo = m.action('agenda.redo', { opts = { desc = 'org redo' } }), org_agenda_todo = m.action('agenda.change_todo_state', { opts = { desc = 'org cycle todo state' } }), diff --git a/lua/orgmode/org/hyperlinks.lua b/lua/orgmode/org/hyperlinks.lua index d4482d271..e2c7e34be 100644 --- a/lua/orgmode/org/hyperlinks.lua +++ b/lua/orgmode/org/hyperlinks.lua @@ -2,6 +2,24 @@ local Files = require('orgmode.parser.files') local utils = require('orgmode.utils') local Hyperlinks = {} +---@return string|nil +function Hyperlinks.get_link_under_cursor() + local found_link = nil + local links = {} + local line = vim.fn.getline('.') + local col = vim.fn.col('.') + for link in line:gmatch('%[%[(.-)%]%]') do + local start_from = #links > 0 and links[#links].to or nil + local from, to = line:find('%[%[(.-)%]%]', start_from) + if col >= from and col <= to then + found_link = link + break + end + table.insert(links, { link = link, from = from, to = to }) + end + return found_link +end + local function get_file_from_context(ctx) return ( ctx.hyperlinks and ctx.hyperlinks.filepath and Files.get(ctx.hyperlinks.filepath, true) diff --git a/lua/orgmode/org/mappings.lua b/lua/orgmode/org/mappings.lua index 5408752ae..7a4f64537 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -650,7 +650,7 @@ function OrgMappings:open_at_point() return self.agenda:open_day(date) end - local link = self:_get_link_under_cursor() + local link = Hyperlinks.get_link_under_cursor() if not link then return end @@ -910,22 +910,4 @@ function OrgMappings:_adjust_date(amount, span, fallback) return vim.api.nvim_feedkeys(utils.esc(fallback), 'n', true) end ----@return string|nil -function OrgMappings:_get_link_under_cursor() - local found_link = nil - local links = {} - local line = vim.fn.getline('.') - local col = vim.fn.col('.') - for link in line:gmatch('%[%[(.-)%]%]') do - local start_from = #links > 0 and links[#links].to or nil - local from, to = line:find('%[%[(.-)%]%]', start_from) - if col >= from and col <= to then - found_link = link - break - end - table.insert(links, { link = link, from = from, to = to }) - end - return found_link -end - return OrgMappings