From 761967cc1cd8ff7d2e7e4aa9ed59f4df4b98f768 Mon Sep 17 00:00:00 2001 From: "simon.mandlik" Date: Sat, 29 Nov 2025 10:50:55 +0100 Subject: [PATCH] feat(#3213): add `view.width.lines_excluded` option --- doc/nvim-tree-lua.txt | 10 +++++++ lua/nvim-tree.lua | 67 +++++++++++++++++++++++++++--------------- lua/nvim-tree/view.lua | 33 ++++++++++----------- 3 files changed, 69 insertions(+), 41 deletions(-) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 42884d678e8..6aadaf3dc27 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -822,6 +822,15 @@ longest line. Type: `string | number | fun(): number|string` Default: `-1` + *nvim-tree.view.width.lines_excluded* + Exclude these lines when computing width. + Supported values: `"root". + Type: `table` + Default: + `{` + `"root"` + `}` + *nvim-tree.view.width.padding* Extra padding to the right. Type: `number | fun(): number|string` @@ -3323,6 +3332,7 @@ highlight group is not, hard linking as follows: > |nvim-tree.view.side| |nvim-tree.view.signcolumn| |nvim-tree.view.width| +|nvim-tree.view.width.lines_excluded| |nvim-tree.view.width.max| |nvim-tree.view.width.min| |nvim-tree.view.width.padding| diff --git a/lua/nvim-tree.lua b/lua/nvim-tree.lua index 18858af14f3..8ff6705fdd4 100644 --- a/lua/nvim-tree.lua +++ b/lua/nvim-tree.lua @@ -553,6 +553,7 @@ local ACCEPTED_TYPES = { "table", min = { "string", "function", "number" }, max = { "string", "function", "number" }, + lines_excluded = { "table" }, padding = { "function", "number" }, }, }, @@ -612,6 +613,14 @@ local ACCEPTED_STRINGS = { }, } +local ACCEPTED_ENUMS = { + view = { + width = { + lines_excluded = { "root", }, + }, + }, +} + ---@param conf table|nil local function validate_options(conf) local msg @@ -620,15 +629,19 @@ local function validate_options(conf) ---@param def any ---@param strs table ---@param types table + ---@param enums table ---@param prefix string - local function validate(user, def, strs, types, prefix) + local function validate(user, def, strs, types, enums, prefix) -- if user's option is not a table there is nothing to do if type(user) ~= "table" then return end - -- only compare tables with contents that are not integer indexed - if type(def) ~= "table" or not next(def) or type(next(def)) == "number" then + -- we have hit a leaf enum to validate against - it's an integer indexed table + local enum_value = type(enums) == "table" and next(enums) and type(next(enums)) == "number" + + -- only compare tables with contents that are not integer indexed nor enums + if not enum_value and (type(def) ~= "table" or not next(def) or type(next(def)) == "number") then -- unless the field can be a table (and is not a table in default config) if vim.tbl_contains(types, "table") then -- use a dummy default to allow all checks @@ -642,27 +655,33 @@ local function validate_options(conf) if not FIELD_SKIP_VALIDATE[k] then local invalid - if def[k] == nil and types[k] == nil then - -- option does not exist - invalid = string.format("Unknown option: %s%s", prefix, k) - elseif type(v) ~= type(def[k]) then - local expected - - if types[k] and #types[k] > 0 then - if not vim.tbl_contains(types[k], type(v)) then - expected = table.concat(types[k], "|") - end - else - expected = type(def[k]) + if enum_value then + if not vim.tbl_contains(enums, v) then + invalid = string.format("Invalid value for field %s%s: Expected one of enum '%s', got '%s'", prefix, k, table.concat(enums, "'|'"), tostring(v)) end + else + if def[k] == nil and types[k] == nil then + -- option does not exist + invalid = string.format("Unknown option: %s%s", prefix, k) + elseif type(v) ~= type(def[k]) then + local expected + + if types[k] and #types[k] > 0 then + if not vim.tbl_contains(types[k], type(v)) then + expected = table.concat(types[k], "|") + end + else + expected = type(def[k]) + end - if expected then - -- option is of the wrong type - invalid = string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) + if expected then + -- option is of the wrong type + invalid = string.format("Invalid option: %s%s. Expected %s, got %s", prefix, k, expected, type(v)) + end + elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then + -- option has type `string` but value is not accepted + invalid = string.format("Invalid value for field %s%s: '%s'", prefix, k, v) end - elseif type(v) == "string" and strs[k] and not vim.tbl_contains(strs[k], v) then - -- option has type `string` but value is not accepted - invalid = string.format("Invalid value for field %s%s: '%s'", prefix, k, v) end if invalid then @@ -672,14 +691,14 @@ local function validate_options(conf) msg = invalid end user[k] = nil - else - validate(v, def[k], strs[k] or {}, types[k] or {}, prefix .. k .. ".") + elseif not enum_value then + validate(v, def[k], strs[k] or {}, types[k] or {}, enums[k] or {}, prefix .. k .. ".") end end end end - validate(conf, DEFAULT_OPTS, ACCEPTED_STRINGS, ACCEPTED_TYPES, "") + validate(conf, DEFAULT_OPTS, ACCEPTED_STRINGS, ACCEPTED_TYPES, ACCEPTED_ENUMS, "") if msg then notify.warn(msg .. "\n\nsee :help nvim-tree-opts for available configuration options") diff --git a/lua/nvim-tree/view.lua b/lua/nvim-tree/view.lua index 90f2b033f3e..5e53b954aa1 100644 --- a/lua/nvim-tree/view.lua +++ b/lua/nvim-tree/view.lua @@ -12,6 +12,9 @@ local M = {} local DEFAULT_MIN_WIDTH = 30 local DEFAULT_MAX_WIDTH = -1 +local DEFAULT_LINES_EXCLUDED = { + "root", +} local DEFAULT_PADDING = 1 M.View = { @@ -303,7 +306,7 @@ function M.open(options) end local function grow() - local starts_at = M.is_root_folder_visible(require("nvim-tree.core").get_cwd()) and 1 or 0 + local starts_at = (M.is_root_folder_visible(require("nvim-tree.core").get_cwd()) and M.View.root_excluded) and 1 or 0 local lines = vim.api.nvim_buf_get_lines(M.get_bufnr(), starts_at, -1, false) -- number of columns of right-padding to indicate end of path local padding = get_size(M.View.padding) @@ -314,31 +317,25 @@ local function grow() padding = padding + wininfo[1].textoff end - local resizing_width = M.View.initial_width - padding - local max_width - - -- maybe bound max - if M.View.max_width == -1 then - max_width = -1 - else - max_width = get_width(M.View.max_width) - padding + local final_width = M.View.initial_width + local max_width = math.huge + if M.View.max_width ~= -1 then + max_width = get_width(M.View.max_width) end local ns_id = vim.api.nvim_get_namespaces()["NvimTreeExtmarks"] for line_nr, l in pairs(lines) do - local count = vim.fn.strchars(l) + local line_width = vim.fn.strchars(l) -- also add space for right-aligned icons local extmarks = vim.api.nvim_buf_get_extmarks(M.get_bufnr(), ns_id, { line_nr, 0 }, { line_nr, -1 }, { details = true }) - count = count + utils.extmarks_length(extmarks) - if resizing_width < count then - resizing_width = count - end - if M.View.adaptive_size and max_width >= 0 and resizing_width >= max_width then - resizing_width = max_width + line_width = line_width + utils.extmarks_length(extmarks) + padding + final_width = math.max(final_width, line_width) + if final_width >= max_width then + final_width = max_width break end end - M.resize(resizing_width + padding) + M.resize(final_width) end function M.grow_from_content() @@ -600,6 +597,8 @@ function M.configure_width(width) M.View.adaptive_size = true M.View.width = width.min or DEFAULT_MIN_WIDTH M.View.max_width = width.max or DEFAULT_MAX_WIDTH + local lines_excluded = width.lines_excluded or DEFAULT_LINES_EXCLUDED + M.View.root_excluded = vim.tbl_contains(lines_excluded, "root") M.View.padding = width.padding or DEFAULT_PADDING elseif width == nil then if M.config.width ~= nil then