Home

Awesome

<!-- markdownlint-disable MD001 MD013 MD034 MD033 -->

gitlinker.nvim

<p> <a href="https://github.com/neovim/neovim/releases/v0.7.0"><img alt="Neovim" src="https://img.shields.io/badge/require-0.7%2B-blue" /></a> <a href="https://github.com/linrongbin16/commons.nvim"><img alt="commons.nvim" src="https://img.shields.io/badge/power_by-commons.nvim-pink" /></a> <a href="https://luarocks.org/modules/linrongbin16/gitlinker.nvim"><img alt="luarocks" src="https://img.shields.io/luarocks/v/linrongbin16/gitlinker.nvim" /></a> <a href="https://github.com/linrongbin16/gitlinker.nvim/actions/workflows/ci.yml"><img alt="ci.yml" src="https://img.shields.io/github/actions/workflow/status/linrongbin16/gitlinker.nvim/ci.yml?label=ci" /></a> <a href="https://app.codecov.io/github/linrongbin16/gitlinker.nvim"><img alt="codecov" src="https://img.shields.io/codecov/c/github/linrongbin16/gitlinker.nvim/main?label=codecov" /></a> </p>

Maintained fork of ruifm's gitlinker, refactored with bug fixes, ssh host alias, blame support and other improvements.

A lua plugin for Neovim to generate sharable file permalinks (with line ranges) for git host websites. Inspired by tpope/vim-fugitive's :GBrowse.

Here's an example of git permalink: https://github.com/neovim/neovim/blob/2e156a3b7d7e25e56b03683cc6228c531f4c91ef/src/nvim/main.c#L137-L156.

https://github.com/linrongbin16/gitlinker.nvim/assets/6496887/d3e425a5-cf08-487f-badc-d393ca9dda2f

For now supported platforms are:

PRs are welcomed for other git host websites!

Table of Contents

Break Changes & Updates

  1. Break Changes:
    • Provide GitLink command instead of default key mappings.
  2. New Features:
    • Windows (+wsl2) support.
    • Respect ssh host alias.
    • Add ?plain=1 for markdown files.
    • Support blame url.
    • Full git protocols support.
  3. Improvements:
    • Use stderr from git command as error message.
    • Async child process IO via coroutine and uv.spawn.
    • Drop off plenary dependency.

Requirements

Installation

<details> <summary><b>With <a href="https://github.com/folke/lazy.nvim">lazy.nvim</a></b></summary>
require("lazy").setup({
  {
    "linrongbin16/gitlinker.nvim",
    cmd = "GitLink",
    opts = {},
    keys = {
      { "<leader>gy", "<cmd>GitLink<cr>", mode = { "n", "v" }, desc = "Yank git link" },
      { "<leader>gY", "<cmd>GitLink!<cr>", mode = { "n", "v" }, desc = "Open git link" },
    },
  },
})
</details> <details> <summary><b>With <a href="https://github.com/lewis6991/pckr.nvim">pckr.nvim</a></b></summary>
return require('pckr').add(
  {
    'linrongbin16/gitlinker.nvim',
    config = function()
      require('gitlinker').setup()
    end,
  };
)
</details>

Usage

Command

You can use the user command GitLink to generate git permlink:

There're several router types:

[!NOTE]

A router type is a general collection of router implementations binding on different git hosts, thus it can work for any git hosts, for example for bitbucket.org:

To specify the remote when there're multiple git remotes, add remote=xxx parameter, for example:

By default GitLink will use the first detected remote (usually it's origin).

To specify the relative file path when current buffer's file path is not a normal file name, add file=xxx parameter, for example:

By default GitLink will use the current buffer's file name.

To specify the git commit ID when current repository's commit ID is not on your needs, add rev=xxx parameter, for example:

By default GitLink will use the current repository's commit ID.

API

[!NOTE]

Highly recommend reading Customize Urls before this section, which helps understanding the router design of this plugin.

<details> <summary><i>Click here to see lua api</i></summary> <br/>

You can also use the link API to generate git permlink:

--- @alias gitlinker.Linker {remote_url:string,protocol:string?,username:string?,password:string?,host:string,port:string?,org:string?,user:string?,repo:string,rev:string,file:string,lstart:integer,lend:integer,file_changed:boolean,default_branch:string?,current_branch:string?}
--- @alias gitlinker.Router fun(lk:gitlinker.Linker):string?
--- @alias gitlinker.Action fun(url:string):any
--- @param opts {router_type:string?,router:gitlinker.Router?,action:gitlinker.Action?,lstart:integer?,lend:integer?,message:boolean?,highlight_duration:integer?,remote:string?,file:string?,rev:string?}?
require("gitlinker").link(opts)

Parameters:

gitlinker.Router

gitlinker.Router is a lua function that implements a router for a git host. It use below function signature:

function(lk:gitlinker.Linker):string?

Parameters:

Returns:

gitlinker.Action

gitlinker.Action is a lua function that do some operations with a generated git link. It use below function signature:

function(url:string):any

Parameters:

For now we have below builtin actions:

If you only need to get the generated url, instead of do some actions, you can pass a callback function to accept the url:

require("gitlinker").link({
  action = function(url)
    print("generated url:" .. vim.inspect(url))
  end,
})

The link API is running in async mode and cannot directly returns the generated link, because it uses lua coroutine to avoid blocking IO.

</details>

Recommended Key Mappings

<details> <summary><i>Click here to see key mappings with vim command</i></summary> <br/>
-- with vim command:

-- browse
vim.keymap.set(
  {"n", 'v'},
  "<leader>gl",
  "<cmd>GitLink<cr>",
  { silent = true, noremap = true, desc = "Yank git permlink" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gL",
  "<cmd>GitLink!<cr>",
  { silent = true, noremap = true, desc = "Open git permlink" }
)
-- blame
vim.keymap.set(
  {"n", 'v'},
  "<leader>gb",
  "<cmd>GitLink blame<cr>",
  { silent = true, noremap = true, desc = "Yank git blame link" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gB",
  "<cmd>GitLink! blame<cr>",
  { silent = true, noremap = true, desc = "Open git blame link" }
)
-- default branch
vim.keymap.set(
  {"n", 'v'},
  "<leader>gd",
  "<cmd>GitLink default_branch<cr>",
  { silent = true, noremap = true, desc = "Copy default branch link" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gD",
  "<cmd>GitLink! default_branch<cr>",
  { silent = true, noremap = true, desc = "Open default branch link" }
)
-- default branch
vim.keymap.set(
  {"n", 'v'},
  "<leader>gc",
  "<cmd>GitLink current_branch<cr>",
  { silent = true, noremap = true, desc = "Copy current branch link" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gD",
  "<cmd>GitLink! current_branch<cr>",
  { silent = true, noremap = true, desc = "Open current branch link" }
)
</details> <details> <summary><i>Click here to see key mappings with lua api</i></summary> <br/>
-- with lua api:

-- browse
vim.keymap.set(
  {"n", 'v'},
  "<leader>gl",
  require("gitlinker").link,
  { silent = true, noremap = true, desc = "GitLink" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gL",
  function()
    require("gitlinker").link({ action = require("gitlinker.actions").system })
  end,
  { silent = true, noremap = true, desc = "GitLink!" }
)
-- blame
vim.keymap.set(
  {"n", 'v'},
  "<leader>gb",
  function()
    require("gitlinker").link({ router_type = "blame" })
  end,
  { silent = true, noremap = true, desc = "GitLink blame" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gB",
  function()
    require("gitlinker").link({
      router_type = "blame",
      action = require("gitlinker.actions").system,
    })
  end,
  { silent = true, noremap = true, desc = "GitLink! blame" }
)
-- default branch
vim.keymap.set(
  {"n", 'v'},
  "<leader>gd",
  function()
    require("gitlinker").link({ router_type = "default_branch" })
  end,
  { silent = true, noremap = true, desc = "GitLink default_branch" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gD",
  function()
    require("gitlinker").link({
      router_type = "default_branch",
      action = require("gitlinker.actions").system,
    })
  end,
  { silent = true, noremap = true, desc = "GitLink! default_branch" }
)
-- default branch
vim.keymap.set(
  {"n", 'v'},
  "<leader>gc",
  function()
    require("gitlinker").link({ router_type = "current_branch" })
  end,
  { silent = true, noremap = true, desc = "GitLink current_branch" }
)
vim.keymap.set(
  {"n", 'v'},
  "<leader>gC",
  function()
    require("gitlinker").link({
      router_type = "current_branch",
      action = require("gitlinker.actions").system,
    })
  end,
  { silent = true, noremap = true, desc = "GitLink! current_branch" }
)
</details>

Configuration

require('gitlinker').setup(opts)

The opts is an optional lua table that override the default options.

For complete default options, please see Defaults in configs.lua.

Customize Urls

[!NOTE]

Please refer to Git Protocols and giturlparser for better understanding git url.

String Template

[!NOTE]

Please refer to Defaults.router in configs.lua for more examples about string template.

To create customized urls for other git hosts, please bind the target git host name with a new router. A router simply constructs the url string from below components (upper case with prefix _A.):

There're 2 more sugar components derived from _A.PATH:

[!IMPORTANT]

The _A.ORG component can be empty when the _A.PATH contains only 1 slash (/), for example: the _A.ORG in ssh://git@host.xyz/repo.git is empty.

There're 2 more sugar components for git branches:

For example you can customize the line numbers in form ?&line=1&lines-count=2 like this:

require("gitlinker").setup({
  router = {
    browse = {
      ["^github%.your%.host"] = "https://github.your.host/"
        .. "{_A.ORG}/"
        .. "{_A.REPO}/blob/"
        .. "{_A.REV}/"
        .. "{_A.FILE}"
        .. "?&lines={_A.LSTART}"
        .. "{_A.LEND > _A.LSTART and ('&lines-count=' .. _A.LEND - _A.LSTART + 1) or ''}",
    },
  },
})

The template string use curly braces {} to contain lua scripts, and evaluate via luaeval(), while the error message can be confusing if there's any syntax issue.

Lua Function

[!NOTE]

Please refer to routers.lua for more examples about function-based routers.

You can also bind a lua function to the git host, the function accepts only 1 lua table as its parameter, which contains the same fields as string template, but in lower case, without the prefix _A.:

The 2 derived components are:

The 2 branch components are:

Recall to previous use case, e.g. customize the line numbers in form ?&line=1&lines-count=2, you can implement the router with below function:

--- @param s string
--- @param t string
local function string_endswith(s, t)
  return string.len(s) >= string.len(t) and string.sub(s, #s - #t + 1) == t
end

--- @param lk gitlinker.Linker
local function your_router(lk)
  local builder = "https://"
  -- host
  builder = builder .. lk.host .. "/"
  -- org
  builder = builder .. lk.org .. "/"
  -- repo
  builder = builder
    .. (string_endswith(lk.repo, ".git") and lk.repo:sub(1, #lk.repo - 4) or lk.repo)
    .. "/"
  -- rev
  builder = lk.rev .. "/"
  -- file
  builder = builder
    .. lk.file
    .. (string_endswith(lk.file, ".md") and "?plain=1" or "")
  -- line range
  builder = builder .. string.format("&lines=%d", lk.lstart)
  if lk.lend > lk.lstart then
    builder = builder
      .. string.format("&lines-count=%d", lk.lend - lk.lstart + 1)
  end
  return builder
end

require("gitlinker").setup({
  router = {
    browse = {
      ["^github%.your%.host"] = your_router,
    },
  },
})

There are some pre-defined lua apis in gitlinker.routers that you can use:

For example if you need to bind a github enterprise domain, you can use:

require('gitlinker').setup({
  router = {
    browse = {
      ["^github%.your%.host"] = require('gitlinker.routers').github_browse,
    },
    blame = {
      ["^github%.your%.host"] = require('gitlinker.routers').github_blame,
    },
  }
})

Create Your Own Router

You can even create your own router (e.g. use the same engine with browse/blame), for example create the file_only router type (generate link without line numbers):

require("gitlinker").setup({
  router = {
    file_only = {
      ["^github%.com"] = "https://github.com/"
        .. "{_A.ORG}/"
        .. "{_A.REPO}/blob/"
        .. "{_A.REV}/"
        .. "{_A.FILE}"
    },
  },
})

Then use it just like browse:

GitLink file_only
GitLink! file_only

Highlight Group

Highlight GroupDefault GroupDescription
NvimGitLinkerHighlightTextObjectSearchhighlight line ranges when copy/open

Development

To develop the project and make PR, please setup with:

To run unit tests, please install below dependencies:

Then test with vusted ./spec.

Contribute

Please open issue/PR for anything about gitlinker.nvim.

Like gitlinker.nvim? Consider

Github Sponsor Wechat Pay Alipay