Skip to content

neumachen/yank-path.nvim

Repository files navigation

yank-path.nvim

CI

A small, single-command Neovim plugin for yanking the current buffer's path into a register through a picker-driven menu. Path transformations are pluggable strategies; the picker auto-detects fzf-lua, snacks.nvim, and vim.ui.select and uses whichever is available.

Features

  • One user command (:YankPath) that works in normal and visual mode
  • Eight built-in path strategies selected via single-letter shortcut:
    • f filename
    • a absolute path
    • h home-relative path (uses ~ prefix)
    • n parent + filename (last two path segments)
    • p project-root relative (auto-detects .git, configurable markers)
    • r relative N levels up (interactive prompt for N)
    • t relative to a chosen base directory (interactive prompt)
    • v VCS permalink URL (GitHub/GitLab/Bitbucket blob link)
  • Visual-mode range is automatically appended (path:start-end or path:line; VCS URLs use #L fragment style)
  • Backend-agnostic picker: fzf-luasnacks.nvimvim.ui.select
  • Configurable destination register (defaults to the system clipboard +)
  • Runtime strategy registration API for custom transformations
  • Lazy, per-directory cached project-root lookup with user-overridable resolver
  • No default keymaps — bind whatever you prefer

Requirements

  • Neovim ≥ 0.11 (the project CI matrix tests on 0.11, 0.12, and nightly)
  • Optional: fzf-lua and/or snacks.nvim for a richer picker UX

Installation

lazy.nvim

{
  "neumachen/yank-path.nvim",
  cmd = "YankPath",
  opts = {},
}

With overrides:

{
  "neumachen/yank-path.nvim",
  cmd = "YankPath",
  opts = {
    register = "+",
    picker = "auto",
    project = {
      markers = { ".git", "Cargo.toml", "go.mod" },
    },
  },
}

packer.nvim

use({
  "neumachen/yank-path.nvim",
  config = function()
    require("yank-path").setup()
  end,
})

vim-plug

Plug 'neumachen/yank-path.nvim'

lua << EOF
require("yank-path").setup()
EOF

Usage

Open any file buffer and run:

:YankPath

A picker appears with the registered strategies. Pick one and the result is written to the configured register (default +).

In visual mode, the current selection's line range is appended to the result automatically:

foo/bar.lua:42        " single-line visual selection
foo/bar.lua:10-25     " multi-line visual selection

Built-in strategies

Key Name Example output
f Filename bar.lua
a Absolute /home/user/proj/src/bar.lua
h Home ~/proj/src/bar.lua
n Parent src/bar.lua
p Project src/bar.lua (relative to git root)
r Relative src/bar.lua (prompts for N levels)
t Relative to src/bar.lua (prompts for base directory)
v VCS URL https://github.com/user/proj/blob/abc123/src/bar.lua

For r, the plugin prompts Levels up: and accepts a non-negative integer. The result keeps the last N + 1 path segments — so N = 0 returns just the filename and N = 2 returns the file plus its two parent directories.

For t, the plugin prompts Base dir: (default: current working directory) and renders the buffer path relative to the entered base. ~ is expanded in the entered base. Falls back to the absolute path when the buffer is not under the chosen base.

For h, the plugin renders paths under $HOME with a ~ prefix. Paths outside $HOME fall back to the absolute path (never emits ~/../...).

For n, the plugin returns the parent directory plus the filename (the last two path segments).

VCS URL strategy

The v (VCS URL) strategy builds a remote permalink for the current buffer using git. It requires git on your PATH.

Host-aware URL formats:

Host pattern URL format
github.com https://github.com/{owner}/{repo}/blob/{ref}/{path}
gitlab.com / GitLab https://{host}/{owner}/{repo}/-/blob/{ref}/{path}
bitbucket.org https://bitbucket.org/{owner}/{repo}/src/{ref}/{path}
Other / generic https://{host}/{owner}/{repo}/blob/{ref}/{path}

The strategy parses SSH (git@host:owner/repo.git), ssh://, and HTTPS remote URLs; trailing .git is stripped. Nested GitLab groups are preserved in the owner segment.

Ref resolution:

  • Prefers the commit SHA at HEAD.
  • If no SHA is resolvable (e.g. a new repository with no commits yet / unborn HEAD) and vcs.branch_fallback = true, the current branch name is used (or vcs.default_branch if the branch cannot be determined).
  • If no SHA is resolvable and vcs.branch_fallback = false (the default), the strategy errors with a message suggesting you enable branch_fallback.

Visual-mode line fragments:

In visual mode, the VCS strategy appends a #L URL fragment instead of the :line suffix used by other strategies:

https://github.com/user/proj/blob/abc123/src/bar.lua#L42      " single line
https://github.com/user/proj/blob/abc123/src/bar.lua#L10-L25  " range

Skipping the picker

Bind keymaps to specific strategies via the programmatic API:

vim.keymap.set({ "n", "x" }, "<leader>ya", function()
  require("yank-path").yank_with("absolute")
end, { desc = "Yank absolute path" })

vim.keymap.set({ "n", "x" }, "<leader>yp", function()
  require("yank-path").yank_with("project")
end, { desc = "Yank project-relative path" })

vim.keymap.set({ "n", "x" }, "<leader>yh", function()
  require("yank-path").yank_with("home")
end, { desc = "Yank home-relative path" })

vim.keymap.set({ "n", "x" }, "<leader>yv", function()
  require("yank-path").yank_with("vcs")
end, { desc = "Yank VCS permalink URL" })

yank_with accepts either the key ("a") or the display name ("absolute", case-insensitive). It also accepts a per-call register override:

require("yank-path").yank_with("absolute", { register = "*" })

Configuration

require("yank-path").setup({
  -- Destination register. Anything vim.fn.setreg accepts.
  register = "+",

  -- Picker backend selection.
  --   "auto"                                  -- default priority
  --   "fzf-lua" | "snacks" | "vim.ui.select"  -- force a single backend
  --   { "fzf-lua", "vim.ui.select" }          -- ordered fallback list
  --
  -- Default priority for "auto": fzf-lua -> snacks -> vim.ui.select.
  picker = "auto",

  project = {
    -- Filenames or directory names that mark a project root.
    markers = { ".git" },

    -- Optional custom resolver. If set, this function fully owns root
    -- lookup; markers and the built-in cache are bypassed.
    --
    --   find_root = function(bufnr)
    --     return vim.fs.root(bufnr, { ".git", "Cargo.toml" })
    --   end,
    find_root = nil,

    -- Cache root lookups per buffer directory. Invalidated on BufFilePost.
    cache = true,
  },

  vcs = {
    -- Git remote name to use when building VCS URLs.
    remote = "origin",

    -- Default branch name used when branch_fallback is enabled and the
    -- current branch cannot be determined.
    default_branch = "main",

    -- When true, use a branch name instead of a SHA when no commit is
    -- resolvable (e.g. unborn HEAD). When false (default), the strategy
    -- errors with a message suggesting you enable this option.
    branch_fallback = false,
  },
})

All values are optional; calling setup() with no arguments uses the defaults shown above.

Registering custom strategies

require("yank-path").register_strategy({
  key = "u",                                    -- single alphanumeric character
  name = "Upper",
  desc = "Upper-case the absolute path",
  transform = function(absolute, ctx)
    return absolute:upper(), nil                -- (result, err)
  end,
})

Strategies are pure functions of the absolute path and a context table. Return (result, nil) to write result to the register, (nil, err) to surface an error notification, or (nil, nil) to indicate the strategy is asynchronous (it must then call ctx.continue(result, err) itself; see lua/yank-path/strategies/relative.lua for an example).

Strategy spec fields:

Field Required Description
key yes Single alphanumeric character (picker shortcut)
name yes Display name
desc yes Description shown in pickers that render previews
transform yes function(absolute, ctx) -> (result, err)
range_style no "line" (default) or "url". Controls how visual-mode ranges are appended: "line" appends :42 / :10-25; "url" appends #L42 / #L10-L25 (useful for URL strategies).

The ctx table contains:

Field Description
bufnr Source buffer number
absolute Absolute path of the buffer
range Visual range table or nil when not in visual mode
config Live plugin config
continue Async completion callback (result, err)

Note: The VCS permalink strategy (key v) is now a built-in. The registration API was designed to support strategies like this without changing the core — see the VCS URL strategy section for details.

Architecture

The plugin composes a strict linear pipeline:

path.get(bufnr)
  -> transform(absolute, ctx)
  -> append_range_if_visual(result, range)
  -> register.write(result, register)

Each step is a single-responsibility module under lua/yank-path/:

File Responsibility
init.lua Public API and built-in registration
config.lua Config schema, defaults, validation
pipeline.lua Linear pipeline composition
path.lua Buffer → absolute path
range.lua Visual mode detection and range append
register.lua Register write
util.lua Notifications, callable check, root cache
strategies/init.lua Strategy registry
strategies/{f,a,h,n,p,r,t,v}.lua Built-in strategies
picker/init.lua Backend resolver
picker/{ui_select,fzf_lua,snacks}.lua Backend adapters

The picker adapter interface is is_available() + select(items, on_choice). Adding a new backend is a single file under lua/yank-path/picker/.

Development

make test          # run the plenary spec suite
make lint          # luacheck
make format        # stylua --in-place
make format-check  # stylua --check
make check         # lint + format-check + test

Tests live under tests/, one spec file per module plus tests/integration_spec.lua for end-to-end flows. Tests must pass on the full CI matrix (Neovim 0.11, 0.12, nightly).

Release process: RELEASING.md.

Security

Reporting policy: SECURITY.md. Disclose privately via GitHub Security Advisories.

License

MIT. See LICENSE.

About

Yank buffer paths through a picker-driven menu. Pluggable strategies, fzf-lua / snacks.nvim / vim.ui.select backends, no default keymaps.

Topics

Resources

Security policy

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages