Skip to content

Commit 7c6903b

Browse files
authored
fix: add pre-render phase, remove auto_expand_width infinite loop (#629)
fixes #624
1 parent 4c3d9d5 commit 7c6903b

File tree

2 files changed

+111
-64
lines changed

2 files changed

+111
-64
lines changed

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

+41-33
Original file line numberDiff line numberDiff line change
@@ -187,19 +187,21 @@ local merge_content = function(context)
187187
-- * Repeat until all layers have been merged.
188188
-- * Join the left and right tables together and return.
189189
--
190-
local huge_number = vim.o.columns
191-
local remaining_width = context.auto_expand_width and huge_number or context.container_width
190+
local remaining_width = context.container_width
192191
local left, right = {}, {}
193192
local left_width, right_width = 0, 0
193+
local wanted_width = 0
194194

195195
if context.left_padding and context.left_padding > 0 then
196196
table.insert(left, { text = string.rep(" ", context.left_padding) })
197197
remaining_width = remaining_width - context.left_padding
198198
left_width = left_width + context.left_padding
199+
wanted_width = wanted_width + context.left_padding
199200
end
200201

201202
if context.right_padding and context.right_padding > 0 then
202203
remaining_width = remaining_width - context.right_padding
204+
wanted_width = wanted_width + context.right_padding
203205
end
204206

205207
local keys = utils.get_keys(context.grouped_by_zindex, true)
@@ -212,51 +214,54 @@ local merge_content = function(context)
212214
local layer = context.grouped_by_zindex[key]
213215
i = i - 1
214216

215-
if remaining_width > 0 and utils.truthy(layer.right) then
216-
context.has_right_content = true
217+
if utils.truthy(layer.right) then
217218
local width = calc_rendered_width(layer.right)
218-
if width > remaining_width then
219-
local truncated = truncate_layer_keep_right(layer.right, right_width, remaining_width)
220-
vim.list_extend(right, truncated)
221-
remaining_width = 0
222-
else
223-
remaining_width = remaining_width - width
224-
vim.list_extend(right, layer.right)
225-
right_width = right_width + width
219+
wanted_width = wanted_width + width
220+
if remaining_width > 0 then
221+
context.has_right_content = true
222+
if width > remaining_width then
223+
local truncated = truncate_layer_keep_right(layer.right, right_width, remaining_width)
224+
vim.list_extend(right, truncated)
225+
remaining_width = 0
226+
else
227+
remaining_width = remaining_width - width
228+
vim.list_extend(right, layer.right)
229+
right_width = right_width + width
230+
end
226231
end
227232
end
228233

229-
if remaining_width > 0 and utils.truthy(layer.left) then
234+
if utils.truthy(layer.left) then
230235
local width = calc_rendered_width(layer.left)
231-
if width > remaining_width then
232-
local truncated = truncate_layer_keep_left(layer.left, left_width, remaining_width)
233-
if context.enable_character_fade then
234-
try_fade_content(truncated, 3)
235-
end
236-
vim.list_extend(left, truncated)
237-
remaining_width = 0
238-
else
239-
remaining_width = remaining_width - width
240-
if context.enable_character_fade then
241-
local fade_chars = 3 - remaining_width
242-
if fade_chars > 0 then
243-
try_fade_content(layer.left, fade_chars)
236+
wanted_width = wanted_width + width
237+
if remaining_width > 0 then
238+
if width > remaining_width then
239+
local truncated = truncate_layer_keep_left(layer.left, left_width, remaining_width)
240+
if context.enable_character_fade then
241+
try_fade_content(truncated, 3)
242+
end
243+
vim.list_extend(left, truncated)
244+
remaining_width = 0
245+
else
246+
remaining_width = remaining_width - width
247+
if context.enable_character_fade and not context.auto_expand_width then
248+
local fade_chars = 3 - remaining_width
249+
if fade_chars > 0 then
250+
try_fade_content(layer.left, fade_chars)
251+
end
244252
end
253+
vim.list_extend(left, layer.left)
254+
left_width = left_width + width
245255
end
246-
vim.list_extend(left, layer.left)
247-
left_width = left_width + width
248256
end
249257
end
250258

251-
if remaining_width == 0 then
259+
if remaining_width == 0 and not context.auto_expand_width then
252260
i = 0
253261
break
254262
end
255263
end
256264

257-
if context.auto_expand_width then
258-
remaining_width = context.container_width + remaining_width - huge_number
259-
end
260265
if remaining_width > 0 and #right > 0 then
261266
table.insert(left, { text = string.rep(" ", remaining_width) })
262267
end
@@ -265,10 +270,13 @@ local merge_content = function(context)
265270
vim.list_extend(result, left)
266271
vim.list_extend(result, right)
267272
context.merged_content = result
273+
log.trace("wanted width: ", wanted_width, " actual width: ", context.container_width)
274+
context.wanted_width = math.max(wanted_width, context.wanted_width)
268275
end
269276

270277
M.render = function(config, node, state, available_width)
271278
local context = {
279+
wanted_width = 0,
272280
max_width = 0,
273281
grouped_by_zindex = {},
274282
available_width = available_width,
@@ -285,7 +293,7 @@ M.render = function(config, node, state, available_width)
285293
if context.has_right_content then
286294
state.has_right_content = true
287295
end
288-
return context.merged_content
296+
return context.merged_content, context.wanted_width
289297
end
290298

291299
return M

lua/neo-tree/ui/renderer.lua

+70-31
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,6 @@ create_nodes = function(source_items, state, level)
227227
is_last_child = is_last_child,
228228
}
229229
local indent = (state.renderers[item.type] or {}).indent_size or 4
230-
local estimated_node_length = (#item.name or 0) + level * indent + 8
231-
if level == 0 then
232-
estimated_node_length = estimated_node_length + 16
233-
end
234-
state.longest_node = math.max(state.longest_node, estimated_node_length)
235230

236231
local node_children = nil
237232
if item.children ~= nil then
@@ -289,7 +284,13 @@ end
289284
M.render_component = function(component, item, state, remaining_width)
290285
local component_func = state.components[component[1]]
291286
if component_func then
292-
local success, component_data = pcall(component_func, component, item, state, remaining_width)
287+
local success, component_data, wanted_width = pcall(
288+
component_func,
289+
component,
290+
item,
291+
state,
292+
remaining_width
293+
)
293294
if success then
294295
if component_data == nil then
295296
return { {} }
@@ -302,7 +303,7 @@ M.render_component = function(component, item, state, remaining_width)
302303
for _, data in ipairs(component_data) do
303304
data.text = one_line(data.text)
304305
end
305-
return component_data
306+
return component_data, wanted_width
306307
else
307308
local name = component[1] or "[missing_name]"
308309
local msg = string.format("Error rendering component %s: %s", name, component_data)
@@ -321,6 +322,23 @@ local prepare_node = function(item, state)
321322
if item.skip_node then
322323
return nil
323324
end
325+
-- pre_render is used to calculate the longest node width
326+
-- without actually rendering the node.
327+
-- We'll try to reuse that work if possible.
328+
local pre_render = state._in_pre_render
329+
if item.line and not pre_render then
330+
local line = item.line
331+
-- Only use it once, we don't want to accidentally use stale data
332+
item.line = nil
333+
if
334+
line
335+
and item.wanted_width
336+
and state.longest_node
337+
and item.wanted_width <= state.longest_node
338+
then
339+
return line
340+
end
341+
end
324342
local line = NuiLine()
325343

326344
local renderer = state.renderers[item.type]
@@ -329,24 +347,46 @@ local prepare_node = function(item, state)
329347
line:append(item.name)
330348
else
331349
local remaining_cols = state.win_width
350+
if remaining_cols == nil then
351+
if state.winid then
352+
remaining_cols = vim.api.nvim_win_get_width(state.winid)
353+
else
354+
local default_width = utils.resolve_config_option(state, "window.width", 40)
355+
remaining_cols = default_width
356+
end
357+
end
358+
local wanted_width = 0
332359
if state.current_position == "current" then
333-
remaining_cols = math.min(remaining_cols, state.longest_node)
360+
local longest = state.longest_node or 0
361+
remaining_cols = math.min(remaining_cols, longest + 4)
334362
end
335363
for _, component in ipairs(renderer) do
336-
local component_data = M.render_component(component, item, state, remaining_cols)
364+
local component_data, component_wanted_width = M.render_component(
365+
component,
366+
item,
367+
state,
368+
remaining_cols
369+
)
370+
local actual_width = 0
337371
if component_data then
338372
for _, data in ipairs(component_data) do
339373
if data.text then
374+
actual_width = actual_width + vim.api.nvim_strwidth(data.text)
340375
line:append(data.text, data.highlight)
341376
remaining_cols = remaining_cols - vim.fn.strchars(data.text)
342377
end
343378
end
344379
end
380+
component_wanted_width = component_wanted_width or actual_width
381+
wanted_width = wanted_width + component_wanted_width
382+
end
383+
line.wanted_width = wanted_width
384+
if pre_render then
385+
item.line = line
386+
state.longest_node = math.max(state.longest_node, line.wanted_width)
387+
else
388+
item.line = nil
345389
end
346-
state.longest_width_exact = math.max(
347-
state.longest_width_exact,
348-
vim.api.nvim_strwidth(line:content())
349-
)
350390
end
351391

352392
return line
@@ -389,7 +429,7 @@ M.focus_node = function(state, id, do_not_focus_window, relative_movement, botto
389429

390430
if M.window_exists(state) then
391431
if not linenr then
392-
M.expand_to_node(state.tree, node)
432+
M.expand_to_node(state, node)
393433
node, linenr = tree:get_node(id)
394434
if not linenr then
395435
log.debug("focus_node cannot get linenr for node with id ", id)
@@ -511,7 +551,8 @@ M.collapse_all_nodes = function(tree)
511551
end
512552
end
513553

514-
M.expand_to_node = function(tree, node)
554+
M.expand_to_node = function(state, node)
555+
local tree = state.tree
515556
if type(node) == "string" then
516557
node = tree:get_node(node)
517558
end
@@ -521,7 +562,7 @@ M.expand_to_node = function(tree, node)
521562
parent:expand()
522563
parentId = parent:get_parent_id()
523564
end
524-
tree:render()
565+
render_tree(state)
525566
end
526567

527568
---Functions to save and restore the focused node.
@@ -929,23 +970,21 @@ end
929970
---Renders the given tree and expands window width if needed
930971
--@param state table The state containing tree to render. Almost same as state.tree:render()
931972
render_tree = function(state)
932-
state.tree:render()
933-
if state.window.auto_expand_width and state.window.position ~= "float" then
934-
state.window.last_user_width = vim.api.nvim_win_get_width(0)
935-
if state.longest_width_exact > state.window.last_user_width then
936-
log.trace(
937-
string.format("auto_expand_width: on. Expanding width to %s.", state.longest_width_exact)
938-
)
939-
vim.api.nvim_win_set_width(0, state.longest_width_exact)
940-
if state.longest_width_exact > vim.api.nvim_win_get_width(0) then
941-
log.error("Not enough width to expand. Aborting.")
942-
state.longest_width_exact = vim.api.nvim_win_get_width(0)
943-
return
944-
end
945-
state.win_width = state.longest_width_exact
946-
render_tree(state)
973+
local should_auto_expand = state.window.auto_expand_width and state.current_position ~= "float"
974+
local should_pre_render = should_auto_expand or state.current_position == "current"
975+
if should_pre_render then
976+
log.trace("pre-rendering tree")
977+
state._in_pre_render = true
978+
state.tree:render()
979+
state._in_pre_render = false
980+
state.window.last_user_width = vim.api.nvim_win_get_width(state.winid)
981+
if should_auto_expand and state.longest_node > state.window.last_user_width then
982+
log.trace(string.format("auto_expand_width: on. Expanding width to %s.", state.longest_node))
983+
vim.api.nvim_win_set_width(state.winid, state.longest_node)
984+
state.win_width = state.longest_node
947985
end
948986
end
987+
state.tree:render()
949988
end
950989

951990
---Draws the given nodes on the screen.

0 commit comments

Comments
 (0)