From e0e2f978a019c4973f9c91e7a598e92e913393f2 Mon Sep 17 00:00:00 2001 From: Willaaaaaaa Date: Mon, 17 Aug 2026 01:29:34 +0800 Subject: [PATCH] feat(vim.fs): slug() supports URI #41241 Problem: `vim.fs.slug()` does not handle URIs like `term://foo//123:bash`, so callers (e.g. terminal persistence) must strip the scheme before calling `slug()`. Solution: Detect `scheme://` from the raw input before `normalize()` and replace it with a `=uri--` prefix. --- runtime/doc/lua.txt | 2 ++ runtime/lua/vim/fs.lua | 14 +++++++++++--- test/functional/lua/fs_spec.lua | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 7fc6fbe3bf..a3356f407b 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2958,6 +2958,7 @@ vim.fs.slug({path}, {opts}) *vim.fs.slug()* • A hash of the normalized input is appended to prevent collisions. • Unsafe chars are replaced with "-". • `$HOME` is replaced with "~". + • URIs (`scheme://...`) are prefixed with "=uri-{scheme}-". • UNC paths (Windows) are prefixed with "=unc-". • If `opts.maxlen` is exceeded, the result will be truncated to `{head}~~~{tail}-{hash8}`. @@ -2969,6 +2970,7 @@ vim.fs.slug({path}, {opts}) *vim.fs.slug()* vim.print(vim.fs.slug('C:/src/project/main.c')) --> "C--src-project-main.c-{hash}" vim.print(vim.fs.slug(vim.fn.expand('~/file.txt'))) --> "~-file.txt-{hash}" vim.print(vim.fs.slug('---')) --> "=special-{hash}" + vim.print(vim.fs.slug('term://foo/bar//123:bash')) --> "=uri-term-foo-bar-123-bash-{hash}" vim.print(vim.fs.slug(('/a/very/long/path'):rep(10) .. '/file.txt', { maxlen = 60 })) --> "a-very-long-~~~-path-a-very-long-path-file.txt-{hash}" < diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 6cd5f893aa..986d328c06 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -154,6 +154,7 @@ end --- - A hash of the normalized input is appended to prevent collisions. --- - Unsafe chars are replaced with "-". --- - `$HOME` is replaced with "~". +--- - URIs (`scheme://...`) are prefixed with "=uri-{scheme}-". --- - UNC paths (Windows) are prefixed with "=unc-". --- - If `opts.maxlen` is exceeded, the result will be truncated to `{head}~~~{tail}-{hash8}`. --- - If the sanitized name is empty, the reserved label `=special` will be used. @@ -165,6 +166,7 @@ end --- vim.print(vim.fs.slug('C:/src/project/main.c')) --> "C--src-project-main.c-{hash}" --- vim.print(vim.fs.slug(vim.fn.expand('~/file.txt'))) --> "~-file.txt-{hash}" --- vim.print(vim.fs.slug('---')) --> "=special-{hash}" +--- vim.print(vim.fs.slug('term://foo/bar//123:bash')) --> "=uri-term-foo-bar-123-bash-{hash}" --- vim.print(vim.fs.slug(('/a/very/long/path'):rep(10) .. '/file.txt', { maxlen = 60 })) --- --> "a-very-long-~~~-path-a-very-long-path-file.txt-{hash}" --- ``` @@ -182,13 +184,19 @@ function M.slug(path, opts) end, true, '`opts.maxlen` must be >= 8') local maxlen = opts.maxlen or 180 + -- Capture URI scheme before normalize() collapses "://" to ":/" (which looks like "C:/") + local uri_scheme = path:match('^([a-zA-Z][a-zA-Z0-9.+-]*)://') ---@type string? + -- Normalize before computing the hash so equivalent paths produce the same result path = vim.fs.normalize(path, { plain = true }) local s = path - -- Replace $HOME with `~` - -- `fnamemodify` resolves relative paths against CWD, so only call it on absolute paths - if vim.startswith(s, '/') or (iswin and s:match('^%w:/')) then + if uri_scheme then + -- Replace scheme prefix with "=uri-{scheme}-" + s = '=uri-' .. uri_scheme .. '-' .. s:sub(#uri_scheme + 3) -- `+ 3` skips`:/` + elseif vim.startswith(s, '/') or (iswin and s:match('^%w:/')) then + -- Replace $HOME with `~` + -- `fnamemodify` resolves relative paths against CWD, so only call it on absolute paths s = vim.fn.fnamemodify(s, ':~') end diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index 9664afef04..26c86c920a 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -694,6 +694,10 @@ describe('vim.fs', function() eq('aux-321f6814', vim.fs.slug('aux')) eq('con.foo.bar-f386c405', vim.fs.slug('con.foo.bar')) eq('con-.-txt-48f98581', vim.fs.slug('con . txt')) + -- URIs are prefixed with `=uri--`. + eq('=uri-term-foo-bar-123-bash-7fd6bb99', vim.fs.slug('term://foo/bar//123:bash')) + eq('=uri-http-example.com-2e152ec0', vim.fs.slug('http://example.com')) + eq('=uri-scp-host-path-5188b9a2', vim.fs.slug('scp://host/path')) -- $HOME is replaced with `~` local p = vim.uv.os_homedir() .. '/my-project' local hash8_2 = vim.fn.sha256(vim.fs.normalize(p)):sub(1, 8)