1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-04 08:50:05 +08:00
SpaceVim/bundle/neo-tree.nvim/lua/neo-tree/sources/common/container.lua
2023-05-30 21:09:18 +08:00

332 lines
11 KiB
Lua

local utils = require("neo-tree.utils")
local renderer = require("neo-tree.ui.renderer")
local highlights = require("neo-tree.ui.highlights")
local log = require("neo-tree.log")
local M = {}
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)
end
end
return width
end
local calc_container_width = function(config, node, state, context)
local container_width = 0
if type(config.width) == "string" then
if config.width == "fit_content" then
container_width = context.max_width
elseif config.width == "100%" then
container_width = context.available_width
elseif config.width:match("^%d+%%$") then
local percent = tonumber(config.width:sub(1, -2)) / 100
container_width = math.floor(percent * context.available_width)
else
error("Invalid container width: " .. config.width)
end
elseif type(config.width) == "number" then
container_width = config.width
elseif type(config.width) == "function" then
container_width = config.width(node, state)
else
error("Invalid container width: " .. config.width)
end
if config.min_width then
container_width = math.max(container_width, config.min_width)
end
if config.max_width then
container_width = math.min(container_width, config.max_width)
end
context.container_width = container_width
return container_width
end
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 ""
data.text = padding .. data.text
should_pad = data.text:sub(#data.text) ~= " "
end
end
return should_pad
end
local max_width = 0
local grouped_by_zindex = utils.group_by(config.content, "zindex")
for zindex, items in pairs(grouped_by_zindex) do
local should_pad = { left = false, right = false }
local zindex_rendered = { left = {}, right = {} }
local rendered_width = 0
for _, item in ipairs(items) do
local rendered_item = renderer.render_component(item, node, state, context.available_width)
if rendered_item then
local align = item.align or "left"
should_pad[align] = add_padding(rendered_item, should_pad[align])
vim.list_extend(zindex_rendered[align], rendered_item)
rendered_width = rendered_width + calc_rendered_width(rendered_item)
end
end
max_width = math.max(max_width, rendered_width)
grouped_by_zindex[zindex] = zindex_rendered
end
context.max_width = max_width
context.grouped_by_zindex = grouped_by_zindex
return context
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 begining/left.
---@param max_length number The maximum number of characters to return.
local truncate_layer_keep_left = function(layer, skip_count, max_length)
local result = {}
local taken = 0
local skipped = 0
for _, item in ipairs(layer) do
local remaining_to_skip = skip_count - skipped
if remaining_to_skip > 0 then
if #item.text <= remaining_to_skip then
skipped = skipped + vim.fn.strchars(item.text)
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)
end
table.insert(result, item)
taken = taken + #item.text
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
table.insert(result, item)
taken = taken + vim.fn.strchars(item.text)
end
end
return result
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)
local result = {}
local taken = 0
local skipped = 0
local i = #layer
while i > 0 do
local item = layer[i]
i = i - 1
local text_length = vim.fn.strchars(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
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)
end
table.insert(result, item)
taken = taken + text_length
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)
end
table.insert(result, item)
taken = taken + text_length
end
end
return result
end
local fade_content = function(layer, fade_char_count)
local text = layer[#layer].text
if not text or #text == 0 then
return
end
local hl = layer[#layer].highlight or "Normal"
local fade = {
highlights.get_faded_highlight_group(hl, 0.68),
highlights.get_faded_highlight_group(hl, 0.6),
highlights.get_faded_highlight_group(hl, 0.35),
}
for i = 3, 1, -1 do
if #text >= i and fade_char_count >= i then
layer[#layer].text = text:sub(1, -i - 1)
for j = i, 1, -1 do
-- force no padding for each faded character
local entry = { text = text:sub(-j, -j), highlight = fade[i - j + 1], no_padding = true }
table.insert(layer, entry)
end
break
end
end
end
local try_fade_content = function(layer, fade_char_count)
local success, err = pcall(fade_content, layer, fade_char_count)
if not success then
log.debug("Error while trying to fade content: ", err)
end
end
local merge_content = function(context)
-- Heres the idea:
-- * Starting backwards from the layer with the highest zindex
-- set the left and right tables to the content of the layer
-- * If a layer has more content than will fit, the left side will be truncated.
-- * If the available space is not used up, move on to the next layer
-- * With each subsequent layer, if the length of that layer is greater then the existing
-- length for that side (left or right), then clip that layer and append whatver portion is
-- not covered up to the appropriate side.
-- * Check again to see if we have used up the available width, short circuit if we have.
-- * Repeat until all layers have been merged.
-- * Join the left and right tables together and return.
--
local remaining_width = context.container_width
local left, right = {}, {}
local left_width, right_width = 0, 0
local wanted_width = 0
if context.left_padding and context.left_padding > 0 then
table.insert(left, { text = string.rep(" ", context.left_padding) })
remaining_width = remaining_width - context.left_padding
left_width = left_width + context.left_padding
wanted_width = wanted_width + context.left_padding
end
if context.right_padding and context.right_padding > 0 then
remaining_width = remaining_width - context.right_padding
wanted_width = wanted_width + context.right_padding
end
local keys = utils.get_keys(context.grouped_by_zindex, true)
if type(keys) ~= "table" then
return {}
end
local i = #keys
while i > 0 do
local key = keys[i]
local layer = context.grouped_by_zindex[key]
i = i - 1
if utils.truthy(layer.right) then
local width = calc_rendered_width(layer.right)
wanted_width = wanted_width + width
if remaining_width > 0 then
context.has_right_content = true
if width > remaining_width then
local truncated = truncate_layer_keep_right(layer.right, right_width, remaining_width)
vim.list_extend(right, truncated)
remaining_width = 0
else
remaining_width = remaining_width - width
vim.list_extend(right, layer.right)
right_width = right_width + width
end
end
end
if utils.truthy(layer.left) then
local width = calc_rendered_width(layer.left)
wanted_width = wanted_width + width
if remaining_width > 0 then
if width > remaining_width then
local truncated = truncate_layer_keep_left(layer.left, left_width, remaining_width)
if context.enable_character_fade then
try_fade_content(truncated, 3)
end
vim.list_extend(left, truncated)
remaining_width = 0
else
remaining_width = remaining_width - width
if context.enable_character_fade and not context.auto_expand_width then
local fade_chars = 3 - remaining_width
if fade_chars > 0 then
try_fade_content(layer.left, fade_chars)
end
end
vim.list_extend(left, layer.left)
left_width = left_width + width
end
end
end
if remaining_width == 0 and not context.auto_expand_width then
i = 0
break
end
end
if remaining_width > 0 and #right > 0 then
table.insert(left, { text = string.rep(" ", remaining_width) })
end
local result = {}
vim.list_extend(result, left)
-- we do not pad between left and right side
if #right >= 1 then
right[1].no_padding = true
end
vim.list_extend(result, right)
context.merged_content = result
log.trace("wanted width: ", wanted_width, " actual width: ", context.container_width)
context.wanted_width = math.max(wanted_width, context.wanted_width)
end
M.render = function(config, node, state, available_width)
local context = {
wanted_width = 0,
max_width = 0,
grouped_by_zindex = {},
available_width = available_width,
left_padding = config.left_padding,
right_padding = config.right_padding,
enable_character_fade = config.enable_character_fade,
auto_expand_width = state.window.auto_expand_width and state.window.position ~= "float",
}
render_content(config, node, state, context)
calc_container_width(config, node, state, context)
merge_content(context)
if context.has_right_content then
state.has_right_content = true
end
-- we still want padding between this container and the previous component
if #context.merged_content > 0 then
context.merged_content[1].no_padding = false
end
return context.merged_content, context.wanted_width
end
return M