Skip to content

Commit ec2b0ae

Browse files
authored
fix(container): truncate text by cell width/characters (#1623)
1 parent cf9a5eb commit ec2b0ae

File tree

3 files changed

+66
-59
lines changed

3 files changed

+66
-59
lines changed

lua/neo-tree/sources/common/container.lua

+31-34
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ local log = require("neo-tree.log")
55

66
local M = {}
77

8+
local strwidth = vim.api.nvim_strwidth
89
local calc_rendered_width = function(rendered_item)
910
local width = 0
1011

1112
for _, item in ipairs(rendered_item) do
1213
if item.text then
13-
width = width + vim.fn.strchars(item.text)
14+
width = width + strwidth(item.text)
1415
end
1516
end
1617

@@ -53,7 +54,7 @@ local render_content = function(config, node, state, context)
5354
local add_padding = function(rendered_item, should_pad)
5455
for _, data in ipairs(rendered_item) do
5556
if data.text then
56-
local padding = (should_pad and #data.text and data.text:sub(1, 1) ~= " ") and " " or ""
57+
local padding = (should_pad and #data.text > 0 and data.text:sub(1, 1) ~= " ") and " " or ""
5758
data.text = padding .. data.text
5859
should_pad = data.text:sub(#data.text) ~= " "
5960
end
@@ -97,35 +98,36 @@ local render_content = function(config, node, state, context)
9798
return context
9899
end
99100

101+
local truncate = utils.truncate_by_cell
102+
100103
---Takes a list of rendered components and truncates them to fit the container width
101104
---@param layer table The list of rendered components.
102105
---@param skip_count number The number of characters to skip from the begining/left.
103-
---@param max_length number The maximum number of characters to return.
104-
local truncate_layer_keep_left = function(layer, skip_count, max_length)
106+
---@param max_width number The maximum number of characters to return.
107+
local truncate_layer_keep_left = function(layer, skip_count, max_width)
105108
local result = {}
106109
local taken = 0
107110
local skipped = 0
108111
for _, item in ipairs(layer) do
109112
local remaining_to_skip = skip_count - skipped
113+
local text_width = strwidth(item.text)
110114
if remaining_to_skip > 0 then
111-
if #item.text <= remaining_to_skip then
112-
skipped = skipped + vim.fn.strchars(item.text)
115+
if text_width <= remaining_to_skip then
116+
skipped = skipped + text_width
113117
item.text = ""
114118
else
115-
item.text = item.text:sub(remaining_to_skip)
116-
if #item.text + taken > max_length then
117-
item.text = item.text:sub(1, max_length - taken)
119+
item.text, text_width = truncate(item.text, text_width - remaining_to_skip, "right")
120+
if text_width > max_width - taken then
121+
item.text, text_width = truncate(item.text, max_width - taken)
118122
end
119123
table.insert(result, item)
120-
taken = taken + #item.text
124+
taken = taken + text_width
121125
skipped = skipped + remaining_to_skip
122126
end
123-
elseif taken <= max_length then
124-
if #item.text + taken > max_length then
125-
item.text = item.text:sub(1, max_length - taken)
126-
end
127+
elseif taken <= max_width then
128+
item.text, text_width = truncate(item.text, max_width - taken)
127129
table.insert(result, item)
128-
taken = taken + vim.fn.strchars(item.text)
130+
taken = taken + text_width
129131
end
130132
end
131133
return result
@@ -134,39 +136,34 @@ end
134136
---Takes a list of rendered components and truncates them to fit the container width
135137
---@param layer table The list of rendered components.
136138
---@param skip_count number The number of characters to skip from the end/right.
137-
---@param max_length number The maximum number of characters to return.
138-
local truncate_layer_keep_right = function(layer, skip_count, max_length)
139+
---@param max_width number The maximum number of characters to return.
140+
local truncate_layer_keep_right = function(layer, skip_count, max_width)
139141
local result = {}
140142
local taken = 0
141143
local skipped = 0
142-
local i = #layer
143-
while i > 0 do
144+
for i = #layer, 1, -1 do
144145
local item = layer[i]
145-
i = i - 1
146-
local text_length = vim.fn.strchars(item.text)
146+
local text_width = strwidth(item.text)
147147
local remaining_to_skip = skip_count - skipped
148148
if remaining_to_skip > 0 then
149-
if text_length <= remaining_to_skip then
150-
skipped = skipped + text_length
149+
if text_width <= remaining_to_skip then
150+
skipped = skipped + text_width
151151
item.text = ""
152152
else
153-
item.text = vim.fn.strcharpart(item.text, 0, text_length - remaining_to_skip)
154-
text_length = vim.fn.strchars(item.text)
155-
if text_length + taken > max_length then
156-
item.text = vim.fn.strcharpart(item.text, text_length - (max_length - taken))
157-
text_length = vim.fn.strchars(item.text)
153+
item.text, text_width = truncate(item.text, text_width - remaining_to_skip)
154+
if text_width > max_width - taken then
155+
item.text, text_width = truncate(item.text, max_width - taken, "right")
158156
end
159157
table.insert(result, item)
160-
taken = taken + text_length
158+
taken = taken + text_width
161159
skipped = skipped + remaining_to_skip
162160
end
163-
elseif taken <= max_length then
164-
if text_length + taken > max_length then
165-
item.text = vim.fn.strcharpart(item.text, text_length - (max_length - taken))
166-
text_length = vim.fn.strchars(item.text)
161+
elseif taken <= max_width then
162+
if text_width > max_width - taken then
163+
item.text, text_width = truncate(item.text, max_width - taken, "right")
167164
end
168165
table.insert(result, item)
169-
taken = taken + text_length
166+
taken = taken + text_width
170167
end
171168
end
172169
return result

lua/neo-tree/ui/selector.lua

+2-25
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,6 @@ local sep_tbl = function(sep)
3939
return sep
4040
end
4141

42-
-- Function below provided by @akinsho
43-
-- https://github.com/nvim-neo-tree/neo-tree.nvim/pull/427#discussion_r924947766
44-
45-
-- truncate a string based on number of display columns/cells it occupies
46-
-- so that multibyte characters are not broken up mid-character
47-
---@param str string
48-
---@param col_limit number
49-
---@return string
50-
local function truncate_by_cell(str, col_limit)
51-
local api = vim.api
52-
local fn = vim.fn
53-
if str and str:len() == api.nvim_strwidth(str) then
54-
return fn.strcharpart(str, 0, col_limit)
55-
end
56-
local short = fn.strcharpart(str, 0, col_limit)
57-
if api.nvim_strwidth(short) > col_limit then
58-
while api.nvim_strwidth(short) > col_limit do
59-
short = fn.strcharpart(short, 0, fn.strchars(short) - 1)
60-
end
61-
end
62-
return short
63-
end
64-
6542
---get_separators
6643
-- Returns information about separator on each tab.
6744
---@param source_index integer: index of source
@@ -174,9 +151,9 @@ local text_layout = function(text, content_layout, output_width, trunc_char)
174151
local left_pad, right_pad = 0, 0
175152
if pad_length < 0 then
176153
if output_width < 4 then
177-
return truncate_by_cell(text, output_width)
154+
return utils.truncate_by_cell(text, output_width)
178155
else
179-
return truncate_by_cell(text, output_width - 1) .. trunc_char
156+
return utils.truncate_by_cell(text, output_width - 1) .. trunc_char
180157
end
181158
elseif content_layout == "start" then
182159
left_pad, right_pad = 0, pad_length

lua/neo-tree/utils/init.lua

+33
Original file line numberDiff line numberDiff line change
@@ -1363,4 +1363,37 @@ M.index_by_path = function(tbl, key)
13631363
return value
13641364
end
13651365

1366+
local strwidth = vim.api.nvim_strwidth
1367+
local slice = vim.fn.slice
1368+
-- Function below provided by @akinsho, modified by @pynappo
1369+
-- https://github.com/nvim-neo-tree/neo-tree.nvim/pull/427#discussion_r924947766
1370+
-- TODO: maybe use vim.stf_utf* functions instead of strchars, once neovim updates enough
1371+
1372+
-- Truncate a string based on number of display columns/cells it occupies
1373+
-- so that multibyte characters are not broken up mid-character
1374+
---@param str string
1375+
---@param col_limit number
1376+
---@param align 'left'|'right'|nil
1377+
---@return string shortened
1378+
---@return number width
1379+
M.truncate_by_cell = function(str, col_limit, align)
1380+
local width = strwidth(str)
1381+
if width <= col_limit then
1382+
return str, width
1383+
end
1384+
local short = str
1385+
if align == "right" then
1386+
short = slice(short, 1)
1387+
while strwidth(short) > col_limit do
1388+
short = slice(short, 1)
1389+
end
1390+
else
1391+
short = slice(short, 0, -1)
1392+
while strwidth(short) > col_limit do
1393+
short = slice(short, 0, -1)
1394+
end
1395+
end
1396+
return short, strwidth(short)
1397+
end
1398+
13661399
return M

0 commit comments

Comments
 (0)