diff --git a/README.md b/README.md index 2c06638..8354db5 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ A Neovim plugin for easily copying file paths and GitHub URLs to clipboard. - 📍 Copy current file path with line number - 🔗 Copy current file path as GitHub URL (with branch or commit hash) - 📍 Copy current file path as GitHub URL with line number (with branch or commit hash) +- 🧭 Visual mode support: line range is included for line-based actions - ⚡ Simple and lightweight - 🎨 Customizable keymaps @@ -94,6 +95,12 @@ require("cfp").setup({ | `ch` | `:CopyHashURL` | Copy file path as GitHub URL with commit hash | | `cH` | `:CopyHashURLLine` | Copy file path as GitHub URL with commit hash and line number | +These keymaps work in both normal and visual modes. In visual mode, the line-based actions (`*Line`) include the selected line range: + +- `CopyPathLine`: `path/to/file:10-20` +- `CopyBranchURLLine`: `https://github.com/owner/repo/blob/main/path/to/file#L10-L20` +- `CopyHashURLLine`: `https://github.com/owner/repo/blob//path/to/file#L10-L20` + ## Example Output - **CopyPath**: `lua/cfp/init.lua` diff --git a/lua/cfp/commands.lua b/lua/cfp/commands.lua index 78e4bfd..32a0326 100644 --- a/lua/cfp/commands.lua +++ b/lua/cfp/commands.lua @@ -1,13 +1,32 @@ local M = {} --- Get the current buffer's file path relative to the current working directory +-- Get the current buffer's file path relative to the Git root (if available), +-- otherwise relative to the current working directory. local function get_relative_path() local buf_path = vim.api.nvim_buf_get_name(0) if buf_path == "" then return nil end - local relative_path = vim.fn.fnamemodify(buf_path, ":.") + -- Normalize to absolute and realpath for robust prefix checks + local abs_buf = vim.fn.fnamemodify(buf_path, ":p") + local real_buf = (vim.loop or vim.uv).fs_realpath(abs_buf) or abs_buf + + -- Try Git root first + local toplevel = vim.fn.system("git rev-parse --show-toplevel"):gsub("\n", "") + if vim.v.shell_error == 0 and toplevel ~= "" then + local abs_root = vim.fn.fnamemodify(toplevel, ":p") + local real_root = (vim.loop or vim.uv).fs_realpath(abs_root) or abs_root + if real_buf:sub(1, #real_root) == real_root then + local rel = real_buf:sub(#real_root + 2) -- skip trailing '/' + -- Normalize separators to forward slashes + rel = rel:gsub("\\", "/") + return rel + end + end - return relative_path + -- Fallback: relative to current working directory + local rel_cwd = vim.fn.fnamemodify(buf_path, ":.") + rel_cwd = rel_cwd:gsub("\\", "/") + return rel_cwd end -- Copy text to clipboard @@ -16,8 +35,20 @@ local function copy_to_clipboard(text) vim.notify("Copied to clipboard: " .. text, vim.log.levels.INFO) end +-- Get selected line range when in visual mode +local function get_selected_line_range() + local mode = vim.fn.mode() + if mode == 'v' or mode == 'V' or mode == '\022' then -- characterwise, linewise, blockwise + local s = vim.api.nvim_buf_get_mark(0, '<')[1] + local e = vim.api.nvim_buf_get_mark(0, '>')[1] + if s > e then s, e = e, s end + return s, e + end + return nil, nil +end + -- Create GitHub URL for file -local function create_github_url(path, ref_type, include_line) +local function create_github_url(path, ref_type, line_range) -- Get Git remote URL local git_remote = vim.fn.system("git config --get remote.origin.url"):gsub("\n", "") if vim.v.shell_error ~= 0 then @@ -53,11 +84,19 @@ local function create_github_url(path, ref_type, include_line) end end + -- Ensure path uses forward slashes and is not absolute + local rel_path = path:gsub("^/+", ""):gsub("\\", "/") + -- Build URL - local url = github_url .. "/blob/" .. ref .. "/" .. path - if include_line then - local line_num = vim.api.nvim_win_get_cursor(0)[1] - url = url .. "#L" .. line_num + local url = github_url .. "/blob/" .. ref .. "/" .. rel_path + if line_range then + local s = line_range.start + local e = line_range.finish + if s and e and e > s then + url = url .. "#L" .. s .. "-L" .. e + elseif s then + url = url .. "#L" .. s + end end return url @@ -75,15 +114,29 @@ function M.copy_path() end -- Copy current file relative path with line number to clipboard -function M.copy_path_line() +function M.copy_path_line(opts) local path = get_relative_path() if not path then vim.notify("No file path available", vim.log.levels.WARN) return end - local line_num = vim.api.nvim_win_get_cursor(0)[1] - local path_with_line = path .. ":" .. line_num + local s, e = get_selected_line_range() + if opts and opts.range and opts.range > 0 then + s = opts.line1 + e = opts.line2 + end + local path_with_line + if s then + if e and e > s then + path_with_line = path .. ":" .. s .. "-" .. e + else + path_with_line = path .. ":" .. s + end + else + local line_num = vim.api.nvim_win_get_cursor(0)[1] + path_with_line = path .. ":" .. line_num + end copy_to_clipboard(path_with_line) end @@ -96,21 +149,32 @@ function M.copy_branch_url() return end - local url = create_github_url(path, "branch", false) + local url = create_github_url(path, "branch", nil) if url then copy_to_clipboard(url) end end -- Copy current file relative path with line number as GitHub URL with branch name to clipboard -function M.copy_branch_url_line() +function M.copy_branch_url_line(opts) local path = get_relative_path() if not path then vim.notify("No file path available", vim.log.levels.WARN) return end - local url = create_github_url(path, "branch", true) + local s, e = get_selected_line_range() + if opts and opts.range and opts.range > 0 then + s = opts.line1 + e = opts.line2 + end + local range + if s then range = { start = s, finish = e } else + local line_num = vim.api.nvim_win_get_cursor(0)[1] + range = { start = line_num } + end + + local url = create_github_url(path, "branch", range) if url then copy_to_clipboard(url) end @@ -124,21 +188,32 @@ function M.copy_hash_url() return end - local url = create_github_url(path, "hash", false) + local url = create_github_url(path, "hash", nil) if url then copy_to_clipboard(url) end end -- Copy current file relative path with line number as GitHub URL with commit hash to clipboard -function M.copy_hash_url_line() +function M.copy_hash_url_line(opts) local path = get_relative_path() if not path then vim.notify("No file path available", vim.log.levels.WARN) return end - local url = create_github_url(path, "hash", true) + local s, e = get_selected_line_range() + if opts and opts.range and opts.range > 0 then + s = opts.line1 + e = opts.line2 + end + local range + if s then range = { start = s, finish = e } else + local line_num = vim.api.nvim_win_get_cursor(0)[1] + range = { start = line_num } + end + + local url = create_github_url(path, "hash", range) if url then copy_to_clipboard(url) end diff --git a/lua/cfp/keymaps.lua b/lua/cfp/keymaps.lua index e89f77b..f3f0183 100644 --- a/lua/cfp/keymaps.lua +++ b/lua/cfp/keymaps.lua @@ -5,36 +5,57 @@ function M.set_keymaps(keymaps, commands) vim.keymap.set("n", keymaps.copy_path, commands.copy_path, { desc = "Copy current file relative path to clipboard" }) + vim.keymap.set("x", keymaps.copy_path, commands.copy_path, { + desc = "Copy current file relative path to clipboard" + }) end if keymaps.copy_path_line then vim.keymap.set("n", keymaps.copy_path_line, commands.copy_path_line, { desc = "Copy current file relative path with line number to clipboard" }) + vim.keymap.set("x", keymaps.copy_path_line, ":'<,'>CopyPathLine", { + desc = "Copy current file relative path with line number or range to clipboard", + silent = true + }) end if keymaps.copy_branch_url then vim.keymap.set("n", keymaps.copy_branch_url, commands.copy_branch_url, { desc = "Copy current file relative path as GitHub URL with branch name to clipboard" }) + vim.keymap.set("x", keymaps.copy_branch_url, commands.copy_branch_url, { + desc = "Copy current file relative path as GitHub URL with branch name to clipboard" + }) end if keymaps.copy_branch_url_line then vim.keymap.set("n", keymaps.copy_branch_url_line, commands.copy_branch_url_line, { desc = "Copy current file relative path with line number as GitHub URL with branch name to clipboard", }) + vim.keymap.set("x", keymaps.copy_branch_url_line, ":'<,'>CopyBranchURLLine", { + desc = "Copy current file relative path with line range as GitHub URL with branch name to clipboard", + silent = true + }) end if keymaps.copy_hash_url then vim.keymap.set("n", keymaps.copy_hash_url, commands.copy_hash_url, { desc = "Copy current file relative path as GitHub URL with commit hash to clipboard" }) + vim.keymap.set("x", keymaps.copy_hash_url, commands.copy_hash_url, { + desc = "Copy current file relative path as GitHub URL with commit hash to clipboard" + }) end if keymaps.copy_hash_url_line then vim.keymap.set("n", keymaps.copy_hash_url_line, commands.copy_hash_url_line, { desc = "Copy current file relative path with line number as GitHub URL with commit hash to clipboard" }) + vim.keymap.set("x", keymaps.copy_hash_url_line, ":'<,'>CopyHashURLLine", { + desc = "Copy current file relative path with line range as GitHub URL with commit hash to clipboard", + silent = true + }) end end diff --git a/plugin/cfp.lua b/plugin/cfp.lua index 2a6871b..84d5590 100644 --- a/plugin/cfp.lua +++ b/plugin/cfp.lua @@ -5,23 +5,25 @@ vim.api.nvim_create_user_command("CopyPath", function() commands.copy_path() end desc = "Copy current file relative path to clipboard", }) -vim.api.nvim_create_user_command("CopyPathLine", function() commands.copy_path_line() end, { +vim.api.nvim_create_user_command("CopyPathLine", function(opts) commands.copy_path_line(opts) end, { desc = "Copy current file relative path with line number to clipboard", + range = true, }) vim.api.nvim_create_user_command("CopyBranchURL", function() commands.copy_branch_url() end, { desc = "Copy current file relative path as GitHub URL with branch name to clipboard", }) -vim.api.nvim_create_user_command("CopyBranchURLLine", function() commands.copy_branch_url_line() end, { +vim.api.nvim_create_user_command("CopyBranchURLLine", function(opts) commands.copy_branch_url_line(opts) end, { desc = "Copy current file relative path with line number as GitHub URL with branch name to clipboard", + range = true, }) vim.api.nvim_create_user_command("CopyHashURL", function() commands.copy_hash_url() end, { desc = "Copy current file relative path as GitHub URL with commit hash to clipboard", }) -vim.api.nvim_create_user_command("CopyHashURLLine", function() commands.copy_hash_url_line() end, { +vim.api.nvim_create_user_command("CopyHashURLLine", function(opts) commands.copy_hash_url_line(opts) end, { desc = "Copy current file relative path with line number as GitHub URL with commit hash to clipboard", + range = true, }) -