aboutsummaryrefslogtreecommitdiff
path: root/lua/muwiki/links.lua
diff options
context:
space:
mode:
authormoxie <moxie@3kgcat.fi>2026-03-14 10:28:08 +0200
committermoxie <moxie@3kgcat.fi>2026-03-14 10:29:54 +0200
commitc8dc1635f8a921269f714117f414bbc7ba24f9fd (patch)
tree336a0bb466a5e023c0a0e6472c9876ff7458de68 /lua/muwiki/links.lua
parentffc0482ab89924cb35155fa82033e3d0ddc6c93d (diff)
refactor: consolidate modules and improve structure
Diffstat (limited to 'lua/muwiki/links.lua')
-rw-r--r--lua/muwiki/links.lua280
1 files changed, 280 insertions, 0 deletions
diff --git a/lua/muwiki/links.lua b/lua/muwiki/links.lua
new file mode 100644
index 0000000..990ecb1
--- /dev/null
+++ b/lua/muwiki/links.lua
@@ -0,0 +1,280 @@
+local config = require('muwiki.config')
+local paths = require('muwiki.paths')
+local io_module = require('muwiki.io')
+local external = require('muwiki.external')
+
+local M = {}
+
+local function get_link_type(target)
+ if target:match('^https?://') then
+ return 'web'
+ end
+ if target:match('^file://') then
+ return 'file'
+ end
+ return 'wiki'
+end
+
+function M.get_link()
+ local cursor = vim.api.nvim_win_get_cursor(0)
+
+ local ok, node = pcall(vim.treesitter.get_node, {
+ bufnr = 0,
+ pos = { cursor[1] - 1, cursor[2] },
+ lang = 'markdown',
+ ignore_injections = false,
+ })
+
+ if not ok or not node then
+ return nil
+ end
+
+ local link_node = node ---@type TSNode|nil
+ while link_node and link_node:type() ~= 'inline_link' do
+ link_node = link_node:parent()
+ end
+
+ if not link_node then
+ return nil
+ end
+
+ local text_node, dest_node
+ for child in link_node:iter_children() do
+ local t = child:type()
+ if t == 'link_text' then
+ text_node = child
+ elseif t == 'link_destination' then
+ dest_node = child
+ end
+ end
+
+ if not text_node or not dest_node then
+ return nil
+ end
+
+ local destination = vim.treesitter.get_node_text(dest_node, 0)
+ return {
+ text = vim.treesitter.get_node_text(text_node, 0),
+ target = destination,
+ type = get_link_type(destination),
+ }
+end
+
+local function resolve_file_url(url)
+ local path = paths.strip_file_protocol(url)
+ local path_type = paths.get_path_type(path)
+
+ if path_type == 'relative' then
+ local current_dir = vim.fs.dirname(vim.api.nvim_buf_get_name(0))
+ path = vim.fs.joinpath(current_dir, path)
+ end
+
+ return 'file://' .. vim.fs.normalize(path)
+end
+
+function M.handle_web_link(url)
+ if not config.options.use_external_handlers then
+ return
+ end
+ external.open(url)
+end
+
+function M.handle_file_url(url)
+ if not config.options.use_external_handlers then
+ return
+ end
+ local absolute_url = resolve_file_url(url)
+ external.open(absolute_url)
+end
+
+function M.handle_file_link(target, wiki_root)
+ local ok, file_path = pcall(io_module.resolve, target, wiki_root)
+ if not ok then
+ vim.notify(string.format('Cannot resolve path: %s', target), vim.log.levels.ERROR)
+ return
+ end
+
+ if not io_module.file_exists(file_path) then
+ vim.notify(string.format('File not found: %s', file_path), vim.log.levels.ERROR)
+ return
+ end
+
+ local ext = file_path:match('%.([^%.]+)$') or ''
+ if not io_module.is_text_file(ext) then
+ if not config.options.use_external_handlers then
+ return
+ end
+ external.open(file_path)
+ return
+ end
+
+ io_module.open_in_buffer(file_path)
+end
+
+function M.handle_wiki_link(target, wiki_root)
+ local file_path = io_module.resolve(target, wiki_root)
+ io_module.open_wiki_file(file_path)
+end
+
+local function get_wiki_root_or_notify()
+ local wiki_root = config.wiki_root(0)
+ if not wiki_root then
+ vim.notify('Not in a wiki buffer', vim.log.levels.ERROR)
+ return nil
+ end
+ return wiki_root
+end
+
+function M.open_link()
+ local link = M.get_link()
+ if not link then
+ vim.notify('No link found under cursor', vim.log.levels.WARN)
+ return
+ end
+
+ if link.type == 'web' then
+ M.handle_web_link(link.target)
+ return
+ end
+
+ local wiki_root = get_wiki_root_or_notify()
+ if not wiki_root then
+ return
+ end
+
+ if link.type == 'file' then
+ if vim.startswith(link.target, 'file://') then
+ M.handle_file_url(link.target)
+ else
+ M.handle_file_link(link.target, wiki_root)
+ end
+ return
+ end
+
+ M.handle_wiki_link(link.target, wiki_root)
+end
+
+function M.open_link_with()
+ local link = M.get_link()
+ if not link then
+ vim.notify('No link found under cursor', vim.log.levels.WARN)
+ return
+ end
+
+ if link.type == 'wiki' then
+ vim.notify('Menu not available for wiki links', vim.log.levels.WARN)
+ return
+ end
+
+ local url = link.target
+ if link.type == 'file' then
+ local wiki_root = get_wiki_root_or_notify()
+ if not wiki_root then
+ return
+ end
+ url = io_module.resolve(url, wiki_root)
+ end
+
+ local matching_handlers = external.get_matching(url)
+
+ if #matching_handlers == 0 then
+ vim.notify('No handlers available for this URL', vim.log.levels.WARN)
+ return
+ end
+
+ if #matching_handlers == 1 then
+ external.execute(matching_handlers[1], url)
+ return
+ end
+
+ local handler_names = {}
+ for _, handler in ipairs(matching_handlers) do
+ table.insert(handler_names, handler.name)
+ end
+
+ vim.ui.select(handler_names, {
+ prompt = 'Open with:',
+ }, function(choice, idx)
+ if choice and idx then
+ external.execute(matching_handlers[idx], url)
+ end
+ end)
+end
+
+function M.create_link()
+ local mode = vim.fn.mode()
+ if mode ~= 'v' and mode ~= 'V' then
+ vim.notify('Must be in visual mode to create a link', vim.log.levels.WARN)
+ return
+ end
+
+ local start_pos = vim.fn.getpos('v')
+ local end_pos = vim.fn.getpos('.')
+ local region = vim.fn.getregion(start_pos, end_pos, { type = mode })
+
+ if not region or #region == 0 then
+ vim.notify('No text selected', vim.log.levels.WARN)
+ return
+ end
+
+ if #region > 1 then
+ vim.notify('Multi-line selection not supported', vim.log.levels.WARN)
+ return
+ end
+
+ local selected_text = region[1]
+ local normalized = io_module.normalize_filename(selected_text)
+ local link_target = normalized .. '.md'
+ local link_text = string.format('[%s](%s)', selected_text, link_target)
+
+ local start_row = start_pos[2]
+ local start_col = start_pos[3]
+ local end_row = end_pos[2]
+ local end_col = end_pos[3]
+
+ if start_row > end_row or (start_row == end_row and start_col > end_col) then
+ start_row, end_row = end_row, start_row
+ start_col, end_col = end_col, start_col
+ end
+
+ start_row = start_row - 1
+ start_col = start_col - 1
+ end_row = end_row - 1
+
+ if mode == 'V' then
+ start_col = 0
+ local line = vim.api.nvim_buf_get_lines(0, end_row, end_row + 1, false)[1]
+ end_col = #line
+ end
+
+ vim.api.nvim_buf_set_text(0, start_row, start_col, end_row, end_col, { link_text })
+ vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Esc>', true, false, true), 'n', false)
+
+ local wiki_root = config.wiki_root(0)
+ if not wiki_root then
+ vim.notify('Not in a wiki buffer', vim.log.levels.ERROR)
+ return
+ end
+
+ local target_path = io_module.resolve(link_target, wiki_root)
+ io_module.open_wiki_file(target_path)
+end
+
+local function jump_link(direction)
+ local flags = direction == 'next' and 'w' or 'bw'
+ local msg = direction == 'next' and 'No more links' or 'No previous links'
+
+ if vim.fn.search('\\[.\\{-}\\]', flags) == 0 then
+ vim.notify(msg, vim.log.levels.INFO)
+ end
+end
+
+function M.next_link()
+ jump_link('next')
+end
+
+function M.prev_link()
+ jump_link('prev')
+end
+
+return M