From c8dc1635f8a921269f714117f414bbc7ba24f9fd Mon Sep 17 00:00:00 2001 From: moxie Date: Sat, 14 Mar 2026 10:28:08 +0200 Subject: refactor: consolidate modules and improve structure --- lua/muwiki/config.lua | 9 +- lua/muwiki/external.lua | 59 ++++++++ lua/muwiki/files.lua | 98 ------------- lua/muwiki/fs.lua | 85 ------------ lua/muwiki/handlers.lua | 44 ------ lua/muwiki/health.lua | 4 +- lua/muwiki/init.lua | 13 +- lua/muwiki/io.lua | 161 ++++++++++++++++++++++ lua/muwiki/links.lua | 280 ++++++++++++++++++++++++++++++++++++++ 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 ---------- lua/muwiki/paths.lua | 35 +---- lua/muwiki/template.lua | 35 +++++ lua/muwiki/templates.lua | 35 ----- 17 files changed, 554 insertions(+), 612 deletions(-) create mode 100644 lua/muwiki/external.lua delete mode 100644 lua/muwiki/files.lua delete mode 100644 lua/muwiki/fs.lua delete mode 100644 lua/muwiki/handlers.lua create mode 100644 lua/muwiki/io.lua create mode 100644 lua/muwiki/links.lua delete mode 100644 lua/muwiki/links/creation.lua delete mode 100644 lua/muwiki/links/detection.lua delete mode 100644 lua/muwiki/links/navigation.lua delete mode 100644 lua/muwiki/links/open.lua delete mode 100644 lua/muwiki/links/url_handlers.lua create mode 100644 lua/muwiki/template.lua delete mode 100644 lua/muwiki/templates.lua (limited to 'lua') diff --git a/lua/muwiki/config.lua b/lua/muwiki/config.lua index 2a89427..1e7e3de 100644 --- a/lua/muwiki/config.lua +++ b/lua/muwiki/config.lua @@ -1,5 +1,3 @@ -local fs = require('muwiki.fs') - local M = {} M.options = { @@ -23,6 +21,11 @@ date: ${date} create_missing_dirs = false, } +local function dir_exists(path) + local stat = vim.uv.fs_stat(path) + return stat and stat.type == 'directory' +end + function M.setup(opts) opts = opts or {} @@ -46,7 +49,7 @@ function M.setup(opts) end for _, dir in ipairs(M.options.dirs or {}) do - if not fs.dir_exists(dir.path) then + if not dir_exists(dir.path) then vim.notify('Wiki directory not found: ' .. dir.path, vim.log.levels.WARN) end end diff --git a/lua/muwiki/external.lua b/lua/muwiki/external.lua new file mode 100644 index 0000000..cf26aca --- /dev/null +++ b/lua/muwiki/external.lua @@ -0,0 +1,59 @@ +local config = require('muwiki.config') +local paths = require('muwiki.paths') + +local M = {} + +function M.open(url) + if type(url) ~= 'string' then + vim.notify('Invalid URL type', vim.log.levels.ERROR) + return false + end + + local valid, err = paths.validate_url_scheme(url) + if not valid then + vim.notify(err, vim.log.levels.ERROR) + return false + end + + vim.system({ 'xdg-open', url }, { detach = true }) + return true +end + +function M.execute(handler, url) + if type(handler.cmd) == 'function' then + handler.cmd(url) + else + vim.system({ handler.cmd, url }, { detach = true }) + end +end + +function M.matches(handler, url) + local pattern = handler.pattern + + if pattern == nil then + return true + end + + if type(pattern) == 'string' then + return url:match(pattern) ~= nil + end + + for _, p in ipairs(pattern) do + if url:match(p) then + return true + end + end + return false +end + +function M.get_matching(url) + local matching = {} + for _, handler in ipairs(config.options.external_handlers) do + if M.matches(handler, url) then + table.insert(matching, handler) + end + end + return matching +end + +return M diff --git a/lua/muwiki/files.lua b/lua/muwiki/files.lua deleted file mode 100644 index 6ec2bc2..0000000 --- a/lua/muwiki/files.lua +++ /dev/null @@ -1,98 +0,0 @@ -local config = require('muwiki.config') -local paths = require('muwiki.paths') -local fs = require('muwiki.fs') - -local M = {} - -function M.open_in_buffer(filepath) - local bufnr = vim.fn.bufnr(filepath, true) - vim.api.nvim_win_set_buf(0, bufnr) -end - -function M.open_external(url) - if type(url) ~= 'string' then - vim.notify('Invalid URL type', vim.log.levels.ERROR) - return false - end - - local valid, err = paths.validate_url_scheme(url) - if not valid then - vim.notify(err, vim.log.levels.ERROR) - return false - end - - vim.system({ 'xdg-open', url }, { detach = true }) - return true -end - -function M.open_index(name) - local wiki_path = config.wiki_path(name) - if not wiki_path then - return - end - - local index_path = vim.fs.joinpath(wiki_path, config.options.index_file) - M.open_in_buffer(index_path) -end - -function M.resolve(filepath, wiki_root) - if not wiki_root then - error('wiki_root parameter is required for secure path resolution') - end - - local path = paths.strip_file_protocol(filepath) - - local resolved = paths.resolve(path, wiki_root) - - return paths.validate_within_wiki(resolved, wiki_root, filepath) -end - -function M.is_text_file(ext) - local ext_lower = ext:lower() - for _, text_ext in ipairs(config.options.text_extensions) do - if ext_lower == text_ext then - return true - end - end - return false -end - -function M.normalize_filename(text) - local normalized = text:lower() - normalized = normalized:gsub('%s+', '_') - normalized = normalized:gsub('[^%w_%-%.]', '') - return normalized -end - -function M.open_wiki_file(filepath) - local templates = require('muwiki.templates') - - local exists = fs.file_exists(filepath) - - if not exists and config.options.create_missing_dirs then - local wiki_root = config.wiki_root(0) - if wiki_root then - local mode = config.options.create_missing_dirs - - if mode == 'prompt' then - fs.ensure_parent_dirs(filepath, wiki_root, mode, function(success) - if success then - M.open_in_buffer(filepath) - templates.init_file(vim.api.nvim_get_current_buf(), filepath) - end - end) - return - else - local success = fs.ensure_parent_dirs(filepath, wiki_root, mode) - if not success then - return - end - end - end - end - - M.open_in_buffer(filepath) - templates.init_file(vim.api.nvim_get_current_buf(), filepath) -end - -return M diff --git a/lua/muwiki/fs.lua b/lua/muwiki/fs.lua deleted file mode 100644 index 6d53c0a..0000000 --- a/lua/muwiki/fs.lua +++ /dev/null @@ -1,85 +0,0 @@ -local paths = require('muwiki.paths') - -local M = {} - -function M.file_exists(path) - local stat = vim.uv.fs_stat(path) - return stat and stat.type == 'file' -end - -function M.dir_exists(path) - local stat = vim.uv.fs_stat(path) - return stat and stat.type == 'directory' -end - -function M.create_dir_safely(dirpath, wiki_root) - if not paths.is_within_wiki(dirpath, wiki_root) then - return false, 'Directory is outside wiki root' - end - - if M.dir_exists(dirpath) then - return true, nil - end - - if vim.fn.mkdir(dirpath, 'p') ~= 1 then - return false, 'Failed to create directory' - end - - if not paths.is_within_wiki(dirpath, wiki_root) then - vim.fn.delete(dirpath, 'd') - return false, 'Symlink attack detected' - end - - return true, nil -end - -function M.ensure_parent_dirs(filepath, wiki_root, mode, callback) - local dirpath = vim.fs.dirname(filepath) - - if M.dir_exists(dirpath) then - if callback then - callback(true) - end - return true - end - - local function do_create() - local success, err = M.create_dir_safely(dirpath, wiki_root) - if not success then - vim.notify(string.format('Cannot create directory: %s', err), vim.log.levels.ERROR) - if callback then - callback(false) - end - return false - end - - if mode == 'notify' then - vim.notify(string.format('Created directory: %s', dirpath), vim.log.levels.INFO) - end - - if callback then - callback(true) - end - return true - end - - if mode == 'prompt' then - vim.ui.select({ 'Yes', 'No' }, { - prompt = string.format('Directory does not exist. Create %s?', dirpath), - }, function(choice) - if choice == 'Yes' then - do_create() - else - vim.notify('Directory creation cancelled', vim.log.levels.INFO) - if callback then - callback(false) - end - end - end) - return nil - end - - return do_create() -end - -return M diff --git a/lua/muwiki/handlers.lua b/lua/muwiki/handlers.lua deleted file mode 100644 index 4b320c3..0000000 --- a/lua/muwiki/handlers.lua +++ /dev/null @@ -1,44 +0,0 @@ -local config = require('muwiki.config') - - -local M = {} - - -function M.execute(handler, url) - if type(handler.cmd) == 'function' then - handler.cmd(url) - else - vim.system({ handler.cmd, url }, { detach = true }) - end -end - -function M.matches(handler, url) - local pattern = handler.pattern - - if pattern == nil then - return true - end - - if type(pattern) == 'string' then - return url:match(pattern) ~= nil - end - - for _, p in ipairs(pattern) do - if url:match(p) then - return true - end - end - return false -end - -function M.get_matching(url) - local matching = {} - for _, handler in ipairs(config.options.external_handlers) do - if M.matches(handler, url) then - table.insert(matching, handler) - end - end - return matching -end - -return M diff --git a/lua/muwiki/health.lua b/lua/muwiki/health.lua index a118cc8..2dfd45b 100644 --- a/lua/muwiki/health.lua +++ b/lua/muwiki/health.lua @@ -1,5 +1,5 @@ -local fs = require('muwiki.fs') +local io_module = require('muwiki.io') local M = {} @@ -26,7 +26,7 @@ M.check = function() vim.health.info('Add to your config: dirs = {{name = "default", path = "~/wiki"}}') else for _, dir in ipairs(cfg.dirs) do - if fs.dir_exists(dir.path) then + if io_module.dir_exists(dir.path) then vim.health.ok(string.format("Wiki '%s': %s", dir.name, dir.path)) else vim.health.warn(string.format("Wiki '%s': %s (not found)", dir.name, dir.path)) diff --git a/lua/muwiki/init.lua b/lua/muwiki/init.lua index 0369692..5538d91 100644 --- a/lua/muwiki/init.lua +++ b/lua/muwiki/init.lua @@ -1,4 +1,3 @@ - local M = {} local config = require('muwiki.config') @@ -8,27 +7,27 @@ M.setup = function(opts) end function M.open_link() - require('muwiki.links.open').open_link() + require('muwiki.links').open_link() end function M.next_link() - require('muwiki.links.navigation').next_link() + require('muwiki.links').next_link() end function M.prev_link() - require('muwiki.links.navigation').prev_link() + require('muwiki.links').prev_link() end function M.open_link_with() - require('muwiki.links.open').open_link_with() + require('muwiki.links').open_link_with() end function M.create_link() - require('muwiki.links.creation').create_link() + require('muwiki.links').create_link() end function M.open_index(name) - require('muwiki.files').open_index(name) + require('muwiki.io').open_index(name) end function M.wiki_root(bufnr) diff --git a/lua/muwiki/io.lua b/lua/muwiki/io.lua new file mode 100644 index 0000000..e86fb2e --- /dev/null +++ b/lua/muwiki/io.lua @@ -0,0 +1,161 @@ +local config = require('muwiki.config') +local paths = require('muwiki.paths') + +local M = {} + +function M.exists(path) + local stat = vim.uv.fs_stat(path) + return stat and stat.type or nil +end + +function M.file_exists(path) + return M.exists(path) == 'file' +end + +function M.dir_exists(path) + return M.exists(path) == 'directory' +end + +function M.create_dir_safely(dirpath, wiki_root) + if not paths.is_within_wiki(dirpath, wiki_root) then + return false, 'Directory is outside wiki root' + end + + if M.dir_exists(dirpath) then + return true, nil + end + + if vim.fn.mkdir(dirpath, 'p') ~= 1 then + return false, 'Failed to create directory' + end + + if not paths.is_within_wiki(dirpath, wiki_root) then + vim.fn.delete(dirpath, 'd') + return false, 'Symlink attack detected' + end + + return true, nil +end + +function M.ensure_parent_dirs(filepath, wiki_root, mode, callback) + local dirpath = vim.fs.dirname(filepath) + + if M.dir_exists(dirpath) then + if callback then + callback(true) + end + return true + end + + local function do_create() + local success, err = M.create_dir_safely(dirpath, wiki_root) + if not success then + vim.notify(string.format('Cannot create directory: %s', err), vim.log.levels.ERROR) + if callback then + callback(false) + end + return false + end + + if mode == 'notify' then + vim.notify(string.format('Created directory: %s', dirpath), vim.log.levels.INFO) + end + + if callback then + callback(true) + end + return true + end + + if mode == 'prompt' then + vim.ui.select({ 'Yes', 'No' }, { + prompt = string.format('Directory does not exist. Create %s?', dirpath), + }, function(choice) + if choice == 'Yes' then + do_create() + else + vim.notify('Directory creation cancelled', vim.log.levels.INFO) + if callback then + callback(false) + end + end + end) + return nil + end + + return do_create() +end + +function M.open_in_buffer(filepath) + local bufnr = vim.fn.bufnr(filepath, true) + vim.api.nvim_win_set_buf(0, bufnr) +end + +function M.open_index(name) + local wiki_path = config.wiki_path(name) + if not wiki_path then + return + end + + local index_path = vim.fs.joinpath(wiki_path, config.options.index_file) + M.open_in_buffer(index_path) +end + +function M.resolve(filepath, wiki_root) + if not wiki_root then + error('wiki_root parameter is required for secure path resolution') + end + + local path = paths.strip_file_protocol(filepath) + local resolved = paths.resolve(path, wiki_root) + return paths.validate_within_wiki(resolved, wiki_root, filepath) +end + +function M.is_text_file(ext) + local ext_lower = ext:lower() + for _, text_ext in ipairs(config.options.text_extensions) do + if ext_lower == text_ext then + return true + end + end + return false +end + +function M.normalize_filename(text) + local normalized = text:lower() + normalized = normalized:gsub('%s+', '_') + normalized = normalized:gsub('[^%w_%-%.]', '') + return normalized +end + +function M.open_wiki_file(filepath) + local template = require('muwiki.template') + local exists = M.file_exists(filepath) + + if not exists and config.options.create_missing_dirs then + local wiki_root = config.wiki_root(0) + if wiki_root then + local mode = config.options.create_missing_dirs + + if mode == 'prompt' then + M.ensure_parent_dirs(filepath, wiki_root, mode, function(success) + if success then + M.open_in_buffer(filepath) + template.init_file(vim.api.nvim_get_current_buf(), filepath) + end + end) + return + else + local success = M.ensure_parent_dirs(filepath, wiki_root, mode) + if not success then + return + end + end + end + end + + M.open_in_buffer(filepath) + template.init_file(vim.api.nvim_get_current_buf(), filepath) +end + +return M 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('', 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 diff --git a/lua/muwiki/links/creation.lua b/lua/muwiki/links/creation.lua deleted file mode 100644 index 81c45a6..0000000 --- a/lua/muwiki/links/creation.lua +++ /dev/null @@ -1,65 +0,0 @@ -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.wiki_root(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 deleted file mode 100644 index 7b1f317..0000000 --- a/lua/muwiki/links/detection.lua +++ /dev/null @@ -1,58 +0,0 @@ -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 - -return M diff --git a/lua/muwiki/links/navigation.lua b/lua/muwiki/links/navigation.lua deleted file mode 100644 index 42b6654..0000000 --- a/lua/muwiki/links/navigation.lua +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index c8a1233..0000000 --- a/lua/muwiki/links/open.lua +++ /dev/null @@ -1,94 +0,0 @@ -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.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 = 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 deleted file mode 100644 index f84715a..0000000 --- a/lua/muwiki/links/url_handlers.lua +++ /dev/null @@ -1,71 +0,0 @@ -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 diff --git a/lua/muwiki/paths.lua b/lua/muwiki/paths.lua index aa07776..f679888 100644 --- a/lua/muwiki/paths.lua +++ b/lua/muwiki/paths.lua @@ -1,4 +1,3 @@ - local M = {} function M.get_path_type(path) @@ -11,40 +10,16 @@ function M.get_path_type(path) end end -function M.resolve_relative(path, base) - local result - - if vim.startswith(path, './') then - result = vim.fs.joinpath(base, path:sub(3)) - elseif vim.startswith(path, '../') then - local current_base = base - local remaining = path - - while vim.startswith(remaining, '../') do - current_base = vim.fs.dirname(current_base) - remaining = remaining:sub(4) - end - - result = vim.fs.joinpath(current_base, remaining) - else - result = vim.fs.joinpath(base, path) - end - - return vim.fs.normalize(result) -end - function M.resolve(filepath, current_file) local path_type = M.get_path_type(filepath) - if path_type == 'absolute' then - return vim.fs.normalize(filepath) - elseif path_type == 'home' then - return vim.fs.normalize(filepath) - else + if path_type == 'relative' then local base = current_file and vim.fs.dirname(current_file) - or vim.fs.dirname(vim.api.nvim_buf_get_name(0)) - return M.resolve_relative(filepath, base) + or vim.fs.dirname(vim.api.nvim_buf_get_name(0)) + filepath = vim.fs.joinpath(base, filepath) end + + return vim.fs.normalize(filepath) end function M.strip_file_protocol(url) diff --git a/lua/muwiki/template.lua b/lua/muwiki/template.lua new file mode 100644 index 0000000..d8eca16 --- /dev/null +++ b/lua/muwiki/template.lua @@ -0,0 +1,35 @@ +local config = require('muwiki.config') +local io_module = require('muwiki.io') + +local M = {} + +local function process_template(template, title) + local date_fmt = config.options.date_fmt or '%Y-%m-%d' + local date = os.date(date_fmt) + local result = template:gsub('${title}', title):gsub('${date}', date) + + if not result:match('\n$') then + result = result .. '\n' + end + + return result +end + +function M.init_file(bufnr, filepath) + if io_module.file_exists(filepath) then + return + end + + local filename = vim.fs.basename(filepath) + + if config.options.use_template then + local title = filename:gsub('%.md$', ''):gsub('_', ' ') + local content = process_template(config.options.template, title) + local lines = vim.split(content, '\n', { plain = true }) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + end + + vim.notify(string.format('%s (unsaved)', filename), vim.log.levels.INFO) +end + +return M diff --git a/lua/muwiki/templates.lua b/lua/muwiki/templates.lua deleted file mode 100644 index c8dcd13..0000000 --- a/lua/muwiki/templates.lua +++ /dev/null @@ -1,35 +0,0 @@ -local config = require('muwiki.config') -local fs = require('muwiki.fs') - -local M = {} - -local function process_template(template, title) - local date_fmt = config.options.date_fmt or '%Y-%m-%d' - local date = os.date(date_fmt) - local result = template:gsub('${title}', title):gsub('${date}', date) - - if not result:match('\n$') then - result = result .. '\n' - end - - return result -end - -function M.init_file(bufnr, filepath) - if fs.file_exists(filepath) then - return - end - - local filename = vim.fs.basename(filepath) - - if config.options.use_template then - local title = filename:gsub('%.md$', ''):gsub('_', ' ') - local content = process_template(config.options.template, title) - local lines = vim.split(content, '\n', { plain = true }) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - end - - vim.notify(string.format('%s (unsaved)', filename), vim.log.levels.INFO) -end - -return M -- cgit v1.2.3