From bf461f6844f4d20b30e6ead46d86acc0830841db Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:53:23 -0400 Subject: [PATCH] feat(columns): per-character permission column highlights (#375) (#146) Problem: the permissions column rendered as a monolithic unstyled string, making it hard to scan `rwx` bits at a glance. Solution: add per-character highlight groups for permission characters following the `eza`/`lsd` convention. All groups link to standard Neovim highlights so every colorscheme works out of the box. --- doc/oil.txt | 18 ++++++++++++++++ doc/upstream.md | 2 +- lua/oil/adapters/files.lua | 3 ++- lua/oil/adapters/files/permissions.lua | 25 +++++++++++++++++++++ lua/oil/adapters/ssh.lua | 6 +++++- lua/oil/init.lua | 30 ++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 3 deletions(-) diff --git a/doc/oil.txt b/doc/oil.txt index fce558a..2397207 100644 --- a/doc/oil.txt +++ b/doc/oil.txt @@ -868,6 +868,24 @@ OilExecutable *hl-OilExecutabl OilExecutableHidden *hl-OilExecutableHidden* Hidden executable files in an oil buffer +OilPermissionRead *hl-OilPermissionRead* + Read permission character in the permissions column + +OilPermissionWrite *hl-OilPermissionWrite* + Write permission character in the permissions column + +OilPermissionExec *hl-OilPermissionExec* + Execute permission character in the permissions column + +OilPermissionSetuid *hl-OilPermissionSetuid* + Setuid/setgid permission character in the permissions column + +OilPermissionSticky *hl-OilPermissionSticky* + Sticky bit permission character in the permissions column + +OilPermissionNone *hl-OilPermissionNone* + No permission character in the permissions column + OilCreate *hl-OilCreate* Create action in the oil preview window diff --git a/doc/upstream.md b/doc/upstream.md index 537c07b..2d56af1 100644 --- a/doc/upstream.md +++ b/doc/upstream.md @@ -66,7 +66,7 @@ issues against this fork. | [#363](https://github.com/stevearc/oil.nvim/issues/363) | `prompt_save_on_select_new_entry` wrong prompt | fixed | | [#371](https://github.com/stevearc/oil.nvim/issues/371) | Constrain cursor in insert mode | fixed ([#93](https://github.com/barrettruth/canola.nvim/pull/93)) | | [#373](https://github.com/stevearc/oil.nvim/issues/373) | Dir from quickfix with bqf/trouble broken | open | -| [#375](https://github.com/stevearc/oil.nvim/issues/375) | Highlights for file types and permissions | open | +| [#375](https://github.com/stevearc/oil.nvim/issues/375) | Highlights for file types and permissions | fixed — per-character permission column highlights | | [#380](https://github.com/stevearc/oil.nvim/issues/380) | Silently overriding `show_hidden` | not actionable — counter to config intent | | [#382](https://github.com/stevearc/oil.nvim/issues/382) | Relative path in window title | not actionable — solved by `get_win_title` callback ([#482](https://github.com/stevearc/oil.nvim/pull/482)) | | [#392](https://github.com/stevearc/oil.nvim/issues/392) | Option to skip delete prompt | fixed | diff --git a/lua/oil/adapters/files.lua b/lua/oil/adapters/files.lua index 8b2936c..07d54cc 100644 --- a/lua/oil/adapters/files.lua +++ b/lua/oil/adapters/files.lua @@ -100,7 +100,8 @@ if not fs.is_windows then if not stat then return columns.EMPTY end - return permissions.mode_to_str(stat.mode) + local str = permissions.mode_to_str(stat.mode) + return { str, permissions.mode_to_highlights(str) } end, parse = function(line, conf) diff --git a/lua/oil/adapters/files/permissions.lua b/lua/oil/adapters/files/permissions.lua index 32c8488..5c8894d 100644 --- a/lua/oil/adapters/files/permissions.lua +++ b/lua/oil/adapters/files/permissions.lua @@ -25,6 +25,31 @@ M.mode_to_str = function(mode) .. perm_to_str(bit.band(extra, 1) ~= 0 and 't', mode) end +local char_hl = { + r = 'OilPermissionRead', + w = 'OilPermissionWrite', + x = 'OilPermissionExec', + s = 'OilPermissionSetuid', + S = 'OilPermissionSetuid', + t = 'OilPermissionSticky', + T = 'OilPermissionSticky', + ['-'] = 'OilPermissionNone', +} + +---@param str string +---@return table[] +M.mode_to_highlights = function(str) + local highlights = {} + for i = 1, #str do + local c = str:sub(i, i) + local hl = char_hl[c] + if hl then + table.insert(highlights, { hl, i - 1, i }) + end + end + return highlights +end + ---@param mode integer ---@return string M.mode_to_octal_str = function(mode) diff --git a/lua/oil/adapters/ssh.lua b/lua/oil/adapters/ssh.lua index 22384e1..a125c6c 100644 --- a/lua/oil/adapters/ssh.lua +++ b/lua/oil/adapters/ssh.lua @@ -118,7 +118,11 @@ local ssh_columns = {} ssh_columns.permissions = { render = function(entry, conf) local meta = entry[FIELD_META] - return meta and permissions.mode_to_str(meta.mode) + if not meta then + return + end + local str = permissions.mode_to_str(meta.mode) + return { str, permissions.mode_to_highlights(str) } end, parse = function(line, conf) diff --git a/lua/oil/init.lua b/lua/oil/init.lua index 459cf31..9adc5f5 100644 --- a/lua/oil/init.lua +++ b/lua/oil/init.lua @@ -1066,6 +1066,36 @@ M._get_highlights = function() link = 'OilHidden', desc = 'Hidden executable files in an oil buffer', }, + { + name = 'OilPermissionRead', + link = 'DiagnosticWarn', + desc = 'Read permission character in the permissions column', + }, + { + name = 'OilPermissionWrite', + link = 'DiagnosticError', + desc = 'Write permission character in the permissions column', + }, + { + name = 'OilPermissionExec', + link = 'DiagnosticOk', + desc = 'Execute permission character in the permissions column', + }, + { + name = 'OilPermissionSetuid', + link = 'DiagnosticError', + desc = 'Setuid/setgid permission character in the permissions column', + }, + { + name = 'OilPermissionSticky', + link = 'DiagnosticInfo', + desc = 'Sticky bit permission character in the permissions column', + }, + { + name = 'OilPermissionNone', + link = 'NonText', + desc = 'No permission character in the permissions column', + }, { name = 'OilCreate', link = 'DiagnosticInfo',