Skip to content

Commit 4b80c8b

Browse files
authored
fix(url): fix url encoding issue (#226)
1 parent 0ff97f4 commit 4b80c8b

File tree

12 files changed

+175
-30
lines changed

12 files changed

+175
-30
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ on:
77
branches:
88
- master
99
concurrency:
10-
group: ${{ github.ref }}-ci
11-
cancel-in-progress: true
10+
group: ${{ github.ref }}-${{ github.workflow }}
11+
cancel-in-progress: ${{ !contains(github.ref, 'master') }}
1212
jobs:
1313
commits:
1414
name: Commits
@@ -24,22 +24,22 @@ jobs:
2424
runs-on: ubuntu-latest
2525
steps:
2626
- uses: actions/checkout@v4
27+
- uses: JohnnyMorganz/stylua-action@v3
28+
with:
29+
token: ${{ secrets.GITHUB_TOKEN }}
30+
version: latest
31+
args: --config-path .stylua.toml ./lua ./spec
2732
- uses: stevearc/nvim-typecheck-action@v1
2833
with:
2934
path: lua
30-
level: Information
35+
level: Warning
3136
configpath: ".luarc.json"
3237
neodev-version: stable
3338
- uses: cargo-bins/cargo-binstall@main
3439
- name: Selene
3540
run: |
3641
cargo binstall --no-confirm selene
3742
selene --config selene.toml ./lua
38-
- uses: JohnnyMorganz/stylua-action@v3
39-
with:
40-
token: ${{ secrets.GITHUB_TOKEN }}
41-
version: latest
42-
args: --config-path .stylua.toml ./lua ./spec
4343
- name: Install commons.nvim
4444
if: ${{ github.ref != 'refs/heads/master' }}
4545
shell: bash

.luarc.json

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,7 @@
77
"jit",
88
"utf8"
99
],
10-
"diagnostics.disable": [
11-
"undefined-field",
12-
"inject-field",
13-
"deprecated",
14-
"luadoc-miss-module-name",
15-
"undefined-doc-name",
16-
"lowercase-global"
17-
],
10+
"diagnostics.disable": [],
1811
"runtime.version": "LuaJIT",
1912
"workspace.checkThirdParty": "Disable"
2013
}

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,12 @@ There're several **router types**:
129129
> - `default_branch` generate the `/main` or `/master` url: https://bitbucket.org/gitlinkernvim/gitlinker.nvim/src/master/.gitignore#lines-9:14.
130130
> - `current_branch` generate the current branch url: https://bitbucket.org/gitlinkernvim/gitlinker.nvim/src/master/.gitignore#lines-9:14.
131131
132-
There're several arguments:
132+
To specify the remote when there're multiple git remotes, add `remote=xxx` parameter, for example:
133133

134-
- `remote`: by default `GitLink` will use the first detected remote (usually it's `origin`), but if you need to specify other remotes, please use `remote=xxx`. For example:
135-
- `GitLink remote=upstream`: copy `blob` url to clipboard for `upstream`.
136-
- `GitLink! blame remote=upstream`: open `blame` url in browser for `upstream`.
134+
- `GitLink remote=upstream`: copy `blob` url to clipboard for the `upstream` remote.
135+
- `GitLink! blame remote=upstream`: open `blame` url in browser for the `upstream` remote.
136+
137+
> By default `GitLink` will use the first detected remote (usually it's `origin`).
137138
138139
### API
139140

lua/gitlinker.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,8 @@ local _link = function(opts)
248248
vim.inspect(confs)
249249
)
250250
if message then
251-
local msg = lk.file_changed and string.format("%s (lines can be wrong due to file change)", url)
252-
or url
251+
local msg = lk.file_changed and url .. " (lines can be wrong due to file change)" or url --[[@as string]]
252+
msg = msg:gsub("%%", "%%%%")
253253
logger:info(msg --[[@as string]])
254254
end
255255

lua/gitlinker/commons/_system.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---@diagnostic disable
12
local uv = vim.uv or vim.loop
23

34
--- @class vim.SystemOpts

lua/gitlinker/commons/_system_types.lua

Lines changed: 0 additions & 6 deletions
This file was deleted.

lua/gitlinker/commons/_uri.lua

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
---@diagnostic disable
2+
-- TODO: This is implemented only for files currently.
3+
-- https://tools.ietf.org/html/rfc3986
4+
-- https://tools.ietf.org/html/rfc2732
5+
-- https://tools.ietf.org/html/rfc2396
6+
7+
local M = {}
8+
local sbyte = string.byte
9+
local schar = string.char
10+
local tohex = require('bit').tohex
11+
local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
12+
local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
13+
local PATTERNS = {
14+
-- RFC 2396
15+
-- https://tools.ietf.org/html/rfc2396#section-2.2
16+
rfc2396 = "^A-Za-z0-9%-_.!~*'()",
17+
-- RFC 2732
18+
-- https://tools.ietf.org/html/rfc2732
19+
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
20+
-- RFC 3986
21+
-- https://tools.ietf.org/html/rfc3986#section-2.2
22+
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
23+
}
24+
25+
---Converts hex to char
26+
---@param hex string
27+
---@return string
28+
local function hex_to_char(hex)
29+
return schar(tonumber(hex, 16))
30+
end
31+
32+
---@param char string
33+
---@return string
34+
local function percent_encode_char(char)
35+
return '%' .. tohex(sbyte(char), 2)
36+
end
37+
38+
---@param uri string
39+
---@return boolean
40+
local function is_windows_file_uri(uri)
41+
return uri:match('^file:/+[a-zA-Z]:') ~= nil
42+
end
43+
44+
---URI-encodes a string using percent escapes.
45+
---@param str string string to encode
46+
---@param rfc "rfc2396" | "rfc2732" | "rfc3986" | nil
47+
---@return string encoded string
48+
function M.uri_encode(str, rfc)
49+
local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
50+
return (str:gsub('([' .. pattern .. '])', percent_encode_char)) -- clamped to 1 retval with ()
51+
end
52+
53+
---URI-decodes a string containing percent escapes.
54+
---@param str string string to decode
55+
---@return string decoded string
56+
function M.uri_decode(str)
57+
return (str:gsub('%%([a-fA-F0-9][a-fA-F0-9])', hex_to_char)) -- clamped to 1 retval with ()
58+
end
59+
60+
---Gets a URI from a file path.
61+
---@param path string Path to file
62+
---@return string URI
63+
function M.uri_from_fname(path)
64+
local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') ---@type string?
65+
local is_windows = volume_path ~= nil
66+
if is_windows then
67+
path = volume_path .. M.uri_encode(fname:gsub('\\', '/'))
68+
else
69+
path = M.uri_encode(path)
70+
end
71+
local uri_parts = { 'file://' }
72+
if is_windows then
73+
table.insert(uri_parts, '/')
74+
end
75+
table.insert(uri_parts, path)
76+
return table.concat(uri_parts)
77+
end
78+
79+
---Gets a URI from a bufnr.
80+
---@param bufnr integer
81+
---@return string URI
82+
function M.uri_from_bufnr(bufnr)
83+
local fname = vim.api.nvim_buf_get_name(bufnr)
84+
local volume_path = fname:match('^([a-zA-Z]:).*')
85+
local is_windows = volume_path ~= nil
86+
local scheme ---@type string?
87+
if is_windows then
88+
fname = fname:gsub('\\', '/')
89+
scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
90+
else
91+
scheme = fname:match(URI_SCHEME_PATTERN)
92+
end
93+
if scheme then
94+
return fname
95+
else
96+
return M.uri_from_fname(fname)
97+
end
98+
end
99+
100+
---Gets a filename from a URI.
101+
---@param uri string
102+
---@return string filename or unchanged URI for non-file URIs
103+
function M.uri_to_fname(uri)
104+
local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
105+
if scheme ~= 'file' then
106+
return uri
107+
end
108+
local fragment_index = uri:find('#')
109+
if fragment_index ~= nil then
110+
uri = uri:sub(1, fragment_index - 1)
111+
end
112+
uri = M.uri_decode(uri)
113+
--TODO improve this.
114+
if is_windows_file_uri(uri) then
115+
uri = uri:gsub('^file:/+', ''):gsub('/', '\\')
116+
else
117+
uri = uri:gsub('^file:/+', '/') ---@type string
118+
end
119+
return uri
120+
end
121+
122+
---Gets the buffer for a uri.
123+
---Creates a new unloaded buffer if no buffer for the uri already exists.
124+
---@param uri string
125+
---@return integer bufnr
126+
function M.uri_to_bufnr(uri)
127+
return vim.fn.bufadd(M.uri_to_fname(uri))
128+
end
129+
130+
return M

lua/gitlinker/commons/uri.lua

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
local M = {}
2+
3+
---@param value string?
4+
---@param rfc "rfc2396"|"rfc2732"|"rfc3986"|nil
5+
---@return string?
6+
M.encode = function(value, rfc)
7+
if type(value) ~= "string" then
8+
return nil
9+
end
10+
return require("gitlinker.commons._uri").uri_encode(value, rfc)
11+
end
12+
13+
---@param value string?
14+
---@return string?
15+
M.decode = function(value)
16+
if type(value) ~= "string" then
17+
return nil
18+
end
19+
return require("gitlinker.commons._uri").uri_decode(value)
20+
end
21+
22+
return M

lua/gitlinker/commons/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
11.0.2
1+
12.1.0

lua/gitlinker/configs.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ M._merge_routers = function(opts)
270270
return result
271271
end
272272

273+
--- @alias gitlinker.Options table<string, any>
273274
--- @param opts gitlinker.Options?
274275
--- @return gitlinker.Options
275276
M.setup = function(opts)

0 commit comments

Comments
 (0)