From bc2944651f4dabc68d7f34c796400d80ba132016 Mon Sep 17 00:00:00 2001 From: moxie Date: Fri, 13 Mar 2026 09:58:53 +0200 Subject: chore: init --- lua/muwiki/links/creation.lua | 65 +++++++++++++++++++++++++++ lua/muwiki/links/detection.lua | 58 ++++++++++++++++++++++++ lua/muwiki/links/navigation.lua | 20 +++++++++ lua/muwiki/links/open.lua | 94 +++++++++++++++++++++++++++++++++++++++ lua/muwiki/links/url_handlers.lua | 71 +++++++++++++++++++++++++++++ 5 files changed, 308 insertions(+) create mode 100644 lua/muwiki/links/creation.lua create mode 100644 lua/muwiki/links/detection.lua create mode 100644 lua/muwiki/links/navigation.lua create mode 100644 lua/muwiki/links/open.lua create mode 100644 lua/muwiki/links/url_handlers.lua (limited to 'lua/muwiki/links') diff --git a/lua/muwiki/links/creation.lua b/lua/muwiki/links/creation.lua new file mode 100644 index 0000000..0b31b10 --- /dev/null +++ b/lua/muwiki/links/creation.lua @@ -0,0 +1,65 @@ +local M = {} + +function M.create_link() + local files = require('muwiki.files') + local config = require('muwiki.config') + + 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 = files.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.get_wiki_root_for_file(vim.api.nvim_buf_get_name(0)) + if not wiki_root then + vim.notify('Not in a wiki buffer', vim.log.levels.ERROR) + return + end + + local target_path = files.resolve(link_target, wiki_root) + files.open_wiki_file(target_path) +end + +return M diff --git a/lua/muwiki/links/detection.lua b/lua/muwiki/links/detection.lua new file mode 100644 index 0000000..356e0ba --- /dev/null +++ b/lua/muwiki/links/detection.lua @@ -0,0 +1,58 @@ +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 + 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 + +return M diff --git a/lua/muwiki/links/navigation.lua b/lua/muwiki/links/navigation.lua new file mode 100644 index 0000000..42b6654 --- /dev/null +++ b/lua/muwiki/links/navigation.lua @@ -0,0 +1,20 @@ +local M = {} + +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 diff --git a/lua/muwiki/links/open.lua b/lua/muwiki/links/open.lua new file mode 100644 index 0000000..5afec53 --- /dev/null +++ b/lua/muwiki/links/open.lua @@ -0,0 +1,94 @@ +local config = require('muwiki.config') +local links = require('muwiki.links.detection') +local files = require('muwiki.files') +local handlers = require('muwiki.handlers') +local url_handlers = require('muwiki.links.url_handlers') + +local M = {} + +local function get_wiki_root_or_notify() + local wiki_root = config.get_wiki_root_for_buffer(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 = links.get_link() + if not link then + vim.notify('No link found under cursor', vim.log.levels.WARN) + return + end + + if link.type == 'web' then + url_handlers.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 + url_handlers.handle_file_url(link.target) + else + url_handlers.handle_file_link(link.target, wiki_root) + end + return + end + + url_handlers.handle_wiki_link(link.target, wiki_root) +end + +function M.open_link_with() + local link = links.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 = files.resolve(url, wiki_root) + end + + local matching_handlers = handlers.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 + handlers.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 + handlers.execute(matching_handlers[idx], url) + end + end) +end + +return M diff --git a/lua/muwiki/links/url_handlers.lua b/lua/muwiki/links/url_handlers.lua new file mode 100644 index 0000000..f84715a --- /dev/null +++ b/lua/muwiki/links/url_handlers.lua @@ -0,0 +1,71 @@ +local config = require('muwiki.config') +local paths = require('muwiki.paths') +local files = require('muwiki.files') +local fs = require('muwiki.fs') + +local M = {} + +function M.handle_web_link(url) + if not config.options.use_external_handlers then + return + end + files.open_external(url) +end + +local function resolve_file_url(url) + local path = paths.strip_file_protocol(url) + local resolved_path + + local path_type = paths.get_path_type(path) + + if path_type == 'absolute' then + resolved_path = path + elseif path_type == 'home' then + resolved_path = vim.fs.normalize(path) + else + local current_dir = vim.fs.dirname(vim.api.nvim_buf_get_name(0)) + resolved_path = paths.resolve_relative(path, current_dir) + end + + return 'file://' .. vim.fs.normalize(resolved_path) +end + +function M.handle_file_url(url) + if not config.options.use_external_handlers then + return + end + + local absolute_url = resolve_file_url(url) + files.open_external(absolute_url) +end + +function M.handle_file_link(target, wiki_root) + local ok, file_path = pcall(files.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 fs.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 files.is_text_file(ext) then + if not config.options.use_external_handlers then + return + end + files.open_external(file_path) + return + end + + files.open_in_buffer(file_path) +end + +function M.handle_wiki_link(target, wiki_root) + local file_path = files.resolve(target, wiki_root) + files.open_wiki_file(file_path) +end + +return M -- cgit v1.2.3