Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -94,6 +95,12 @@ require("cfp").setup({
| `<leader>ch` | `:CopyHashURL` | Copy file path as GitHub URL with commit hash |
| `<leader>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/<hash>/path/to/file#L10-L20`

## Example Output

- **CopyPath**: `lua/cfp/init.lua`
Expand Down
109 changes: 92 additions & 17 deletions lua/cfp/commands.lua
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
21 changes: 21 additions & 0 deletions lua/cfp/keymaps.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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<CR>", {
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<CR>", {
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<CR>", {
desc = "Copy current file relative path with line range as GitHub URL with commit hash to clipboard",
silent = true
})
end
end

Expand Down
10 changes: 6 additions & 4 deletions plugin/cfp.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})