Skip to content

fix(container): truncate text by cell width/characters #1623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 32 additions & 35 deletions lua/neo-tree/sources/common/container.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ local log = require("neo-tree.log")

local M = {}

local strwidth = vim.api.nvim_strwidth
local calc_rendered_width = function(rendered_item)
local width = 0

for _, item in ipairs(rendered_item) do
if item.text then
width = width + vim.fn.strchars(item.text)
width = width + strwidth(item.text)
end
end

Expand Down Expand Up @@ -53,7 +54,7 @@ local render_content = function(config, node, state, context)
local add_padding = function(rendered_item, should_pad)
for _, data in ipairs(rendered_item) do
if data.text then
local padding = (should_pad and #data.text and data.text:sub(1, 1) ~= " ") and " " or ""
local padding = (should_pad and #data.text > 0 and data.text:sub(1, 1) ~= " ") and " " or ""
data.text = padding .. data.text
should_pad = data.text:sub(#data.text) ~= " "
end
Expand Down Expand Up @@ -85,7 +86,7 @@ local render_content = function(config, node, state, context)
vim.list_extend(zindex_rendered[align], rendered_item)
rendered_width = rendered_width + calc_rendered_width(rendered_item)
end
::continue::
::continue::
end

max_width = math.max(max_width, rendered_width)
Expand All @@ -97,35 +98,36 @@ local render_content = function(config, node, state, context)
return context
end

local truncate = utils.truncate_by_cell

---Takes a list of rendered components and truncates them to fit the container width
---@param layer table The list of rendered components.
---@param skip_count number The number of characters to skip from the begining/left.
---@param max_length number The maximum number of characters to return.
local truncate_layer_keep_left = function(layer, skip_count, max_length)
---@param max_width number The maximum number of characters to return.
local truncate_layer_keep_left = function(layer, skip_count, max_width)
local result = {}
local taken = 0
local skipped = 0
for _, item in ipairs(layer) do
local remaining_to_skip = skip_count - skipped
local text_width = strwidth(item.text)
if remaining_to_skip > 0 then
if #item.text <= remaining_to_skip then
skipped = skipped + vim.fn.strchars(item.text)
if text_width <= remaining_to_skip then
skipped = skipped + text_width
item.text = ""
else
item.text = item.text:sub(remaining_to_skip)
if #item.text + taken > max_length then
item.text = item.text:sub(1, max_length - taken)
item.text, text_width = truncate(item.text, text_width - remaining_to_skip, "right")
if text_width > max_width - taken then
item.text, text_width = truncate(item.text, max_width - taken)
end
table.insert(result, item)
taken = taken + #item.text
taken = taken + text_width
skipped = skipped + remaining_to_skip
end
elseif taken <= max_length then
if #item.text + taken > max_length then
item.text = item.text:sub(1, max_length - taken)
end
elseif taken <= max_width then
item.text, text_width = truncate(item.text, max_width - taken)
table.insert(result, item)
taken = taken + vim.fn.strchars(item.text)
taken = taken + text_width
end
end
return result
Expand All @@ -134,39 +136,34 @@ end
---Takes a list of rendered components and truncates them to fit the container width
---@param layer table The list of rendered components.
---@param skip_count number The number of characters to skip from the end/right.
---@param max_length number The maximum number of characters to return.
local truncate_layer_keep_right = function(layer, skip_count, max_length)
---@param max_width number The maximum number of characters to return.
local truncate_layer_keep_right = function(layer, skip_count, max_width)
local result = {}
local taken = 0
local skipped = 0
local i = #layer
while i > 0 do
for i = #layer, 1, -1 do
local item = layer[i]
i = i - 1
local text_length = vim.fn.strchars(item.text)
local text_width = strwidth(item.text)
local remaining_to_skip = skip_count - skipped
if remaining_to_skip > 0 then
if text_length <= remaining_to_skip then
skipped = skipped + text_length
if text_width <= remaining_to_skip then
skipped = skipped + text_width
item.text = ""
else
item.text = vim.fn.strcharpart(item.text, 0, text_length - remaining_to_skip)
text_length = vim.fn.strchars(item.text)
if text_length + taken > max_length then
item.text = vim.fn.strcharpart(item.text, text_length - (max_length - taken))
text_length = vim.fn.strchars(item.text)
item.text, text_width = truncate(item.text, text_width - remaining_to_skip)
if text_width > max_width - taken then
item.text, text_width = truncate(item.text, max_width - taken, "right")
end
table.insert(result, item)
taken = taken + text_length
taken = taken + text_width
skipped = skipped + remaining_to_skip
end
elseif taken <= max_length then
if text_length + taken > max_length then
item.text = vim.fn.strcharpart(item.text, text_length - (max_length - taken))
text_length = vim.fn.strchars(item.text)
elseif taken <= max_width then
if text_width > max_width - taken then
item.text, text_width = truncate(item.text, max_width - taken, "right")
end
table.insert(result, item)
taken = taken + text_length
taken = taken + text_width
end
end
return result
Expand Down
1 change: 0 additions & 1 deletion lua/neo-tree/ui/renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ end
---@param state table State of the source to close
---@param focus_prior_window boolean | nil if true or nil, focus the window that was previously focused
M.close = function(state, focus_prior_window)

log.debug("Closing window, but saving position first.")
M.position.save(state)

Expand Down
27 changes: 2 additions & 25 deletions lua/neo-tree/ui/selector.lua
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,6 @@ local sep_tbl = function(sep)
return sep
end

-- Function below provided by @akinsho
-- https://github.com/nvim-neo-tree/neo-tree.nvim/pull/427#discussion_r924947766

-- truncate a string based on number of display columns/cells it occupies
-- so that multibyte characters are not broken up mid-character
---@param str string
---@param col_limit number
---@return string
local function truncate_by_cell(str, col_limit)
local api = vim.api
local fn = vim.fn
if str and str:len() == api.nvim_strwidth(str) then
return fn.strcharpart(str, 0, col_limit)
end
local short = fn.strcharpart(str, 0, col_limit)
if api.nvim_strwidth(short) > col_limit then
while api.nvim_strwidth(short) > col_limit do
short = fn.strcharpart(short, 0, fn.strchars(short) - 1)
end
end
return short
end

---get_separators
-- Returns information about separator on each tab.
---@param source_index integer: index of source
Expand Down Expand Up @@ -172,9 +149,9 @@ local text_layout = function(text, content_layout, output_width, trunc_char)
local left_pad, right_pad = 0, 0
if pad_length < 0 then
if output_width < 4 then
return truncate_by_cell(text, output_width)
return utils.truncate_by_cell(text, output_width)
else
return truncate_by_cell(text, output_width - 1) .. trunc_char
return utils.truncate_by_cell(text, output_width - 1) .. trunc_char
end
elseif content_layout == "start" then
left_pad, right_pad = 0, pad_length
Expand Down
33 changes: 33 additions & 0 deletions lua/neo-tree/utils/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1293,4 +1293,37 @@ M.index_by_path = function(tbl, key)
return value
end

local strwidth = vim.api.nvim_strwidth
local slice = vim.fn.slice
-- Function below provided by @akinsho, modified by @pynappo
-- https://github.com/nvim-neo-tree/neo-tree.nvim/pull/427#discussion_r924947766
-- TODO: maybe use vim.stf_utf* functions instead of strchars, once neovim updates enough

-- Truncate a string based on number of display columns/cells it occupies
-- so that multibyte characters are not broken up mid-character
---@param str string
---@param col_limit number
---@param align 'left'|'right'|nil
---@return string shortened
---@return number width
M.truncate_by_cell = function(str, col_limit, align)
local width = strwidth(str)
if width <= col_limit then
return str, width
end
local short = str
if align == "right" then
short = slice(short, 1)
while strwidth(short) > col_limit do
short = slice(short, 1)
end
else
short = slice(short, 0, -1)
while strwidth(short) > col_limit do
short = slice(short, 0, -1)
end
end
return short, strwidth(short)
end

return M
Loading