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('', 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