mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-04 08:50:05 +08:00
332 lines
11 KiB
Lua
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
|