Repository: nvim-lualine/lualine.nvim
Pull request #833 (2022-09-08 17:01:47)

Proposed feature: always include one letter on shortened hidden paths
In the current implementation of the path shortening a hidden path segment like e.g. `.config` appears as `.` in the final output, which might be of little help on guessing the real path.

This PR changes the shortening in a way, that hidden path segments are abbreviated to two characters. In the above example this would make `.config` appear as `.c` in the final output.

Generalization idea: Instead of applying this to just hidden files, it might be also an idea to make the maximum length of a segment configurable, viz. `max_segment_length = 3` and apply it to all files. With this configuration  `.config/nvim/lua/config/lualine.lua` would be abbreviated to `.co/nvi/lua/con/lualine.lua`.


Patch:
diff --git a/lua/lualine/components/diagnostics/sources.lua b/lua/lualine/components/diagnostics/sources.lua
index 96611ea..3432b9e 100644
--- a/lua/lualine/components/diagnostics/sources.lua
+++ b/lua/lualine/components/diagnostics/sources.lua
@@ -40,8 +40,8 @@ M.sources = {
 
     return workspace_diag(diag_severity.ERROR),
       workspace_diag(diag_severity.WARN),
-      workspace_diag(diag_severity.HINT),
-      workspace_diag(diag_severity.INFO)
+      workspace_diag(diag_severity.INFO),
+      workspace_diag(diag_severity.HINT)
   end,
   nvim_diagnostic = function()
     local diagnostics = vim.diagnostic.get(0)
diff --git a/lua/lualine/components/filename.lua b/lua/lualine/components/filename.lua
index b7f74f4..1d9232f 100644
--- a/lua/lualine/components/filename.lua
+++ b/lua/lualine/components/filename.lua
@@ -19,14 +19,6 @@ local default_options = {
   shorting_target = 40,
 }
 
----counts how many times pattern occur in base ( used for counting path-sep )
----@param base string
----@param pattern string
----@return number
-local function count(base, pattern)
-  return select(2, string.gsub(base, pattern, ''))
-end
-
 local function is_new_file()
   local filename = vim.fn.expand('%')
   return filename ~= '' and vim.bo.buftype == '' and vim.fn.filereadable(filename) == 0
@@ -35,10 +27,27 @@ end
 ---shortens path by turning apple/orange -> a/orange
 ---@param path string
 ---@param sep string path separator
+---@param max_len integer maximum length of the full filename string
 ---@return string
-local function shorten_path(path, sep)
-  -- ('([^/])[^/]+%/', '%1/', 1)
-  return path:gsub(string.format('([^%s])[^%s]+%%%s', sep, sep, sep), '%1' .. sep, 1)
+local function shorten_path(path, sep, max_len)
+  local len = #path
+  if len <= max_len then
+    return path
+  end
+
+  local segments = vim.split(path, sep)
+  for idx = 1, #segments - 1 do
+    if len <= max_len then
+      break
+    end
+
+    local segment = segments[idx]
+    local shortened = segment:sub(1, vim.startswith(segment, '.') and 2 or 1)
+    segments[idx] = shortened
+    len = len - (#segment - #shortened)
+  end
+
+  return table.concat(segments, sep)
 end
 
 M.init = function(self, options)
@@ -73,11 +82,7 @@ M.update_status = function(self)
     local estimated_space_available = windwidth - self.options.shorting_target
 
     local path_separator = package.config:sub(1, 1)
-    for _ = 0, count(data, path_separator) do
-      if windwidth <= 84 or #data > estimated_space_available then
-        data = shorten_path(data, path_separator)
-      end
-    end
+    data = shorten_path(data, path_separator, estimated_space_available)
   end
 
   if self.options.file_status then


Test patch:
diff --git a/tests/spec/component_spec.lua b/tests/spec/component_spec.lua
index 4b7d1e9..f4f6634 100644
--- a/tests/spec/component_spec.lua
+++ b/tests/spec/component_spec.lua
@@ -455,19 +455,6 @@ describe('FileSize component', function()
 end)
 
 describe('Filename component', function()
-  local function shorten_path(path, target)
-    target = target and target or 40
-    local sep = package.config:sub(1, 1)
-    local winwidth = vim.fn.winwidth(0)
-    local segments = select(2, string.gsub(path, sep, ''))
-    for _ = 0, segments do
-      if winwidth <= 84 or #path > winwidth - target then
-        path = path:gsub(string.format('([^%s])[^%s]+%%%s', sep, sep, sep), '%1' .. sep, 1)
-      end
-    end
-    return path
-  end
-
   it('works', function()
     local opts = build_component_opts {
       component_separators = { left = '', right = '' },
@@ -506,7 +493,7 @@ describe('Filename component', function()
       path = 1,
     }
     vim.cmd(':e test-file.txt')
-    assert_component('filename', opts, shorten_path(vim.fn.expand('%:~:.')))
+    assert_component('filename', opts, vim.fn.expand('%:~:.'))
     vim.cmd(':bdelete!')
   end)
 
@@ -516,10 +503,74 @@ describe('Filename component', function()
       padding = 0,
       file_status = false,
       path = 2,
+      shorting_target = 0,
+    }
+    vim.cmd(':e test-file.txt')
+    assert_component('filename', opts, vim.fn.expand('%:p'))
+    vim.cmd(':bdelete!')
+  end)
+
+  it('shortens path', function()
+    stub(vim.fn, 'expand')
+    vim.fn.expand.on_call_with('%:p').returns('/home/foobar/test/test.lua')
+    stub(vim.fn, 'winwidth')
+    vim.fn.winwidth.on_call_with(0).returns(100)
+
+    local opts = build_component_opts {
+      component_separators = { left = '', right = '' },
+      padding = 0,
+      file_status = false,
+      path = 2,
+      shorting_target = 90,
     }
     vim.cmd(':e test-file.txt')
-    assert_component('filename', opts, shorten_path(vim.fn.expand('%:p')))
+    assert_component('filename', opts, '/h/f/t/test.lua')
+
+    vim.cmd(':bdelete!')
+    vim.fn.winwidth:revert()
+    vim.fn.expand:revert()
+  end)
+
+  it('shortens path with tilde', function()
+    stub(vim.fn, 'expand')
+    vim.fn.expand.on_call_with('%:p:~').returns('~/test/test.lua')
+    stub(vim.fn, 'winwidth')
+    vim.fn.winwidth.on_call_with(0).returns(100)
+
+    local opts = build_component_opts {
+      component_separators = { left = '', right = '' },
+      padding = 0,
+      file_status = false,
+      path = 3,
+      shorting_target = 90,
+    }
+    vim.cmd(':e test-file.txt')
+    assert_component('filename', opts, '~/t/test.lua')
+
+    vim.cmd(':bdelete!')
+    vim.fn.winwidth:revert()
+    vim.fn.expand:revert()
+  end)
+
+  it('shortens path with hidden directory', function()
+    stub(vim.fn, 'expand')
+    vim.fn.expand.on_call_with('%:p').returns('/home/foobar/.test/test.lua')
+    stub(vim.fn, 'winwidth')
+    vim.fn.winwidth.on_call_with(0).returns(100)
+
+    local opts = build_component_opts {
+      component_separators = { left = '', right = '' },
+      padding = 0,
+      file_status = false,
+      path = 2,
+      shorting_target = 90,
+    }
+    vim.cmd(':e test-file.txt')
+    assert_component('filename', opts, '/h/f/.t/test.lua')
+
     vim.cmd(':bdelete!')
+    vim.fn.winwidth:revert()
+    vim.fn.expand:revert()
   end)
 end)