diff options
Diffstat (limited to 'config/yazi/plugins/yatline.yazi/main.lua')
| -rw-r--r-- | config/yazi/plugins/yatline.yazi/main.lua | 1464 |
1 files changed, 1464 insertions, 0 deletions
diff --git a/config/yazi/plugins/yatline.yazi/main.lua b/config/yazi/plugins/yatline.yazi/main.lua new file mode 100644 index 00000000..eda7213b --- /dev/null +++ b/config/yazi/plugins/yatline.yazi/main.lua @@ -0,0 +1,1464 @@ +--- @since 25.12.29 +--- @diagnostic disable: undefined-global, undefined-field +--- @alias Mode Mode Comes from Yazi. +--- @alias Rect Rect Comes from Yazi. +--- @alias Paragraph Paragraph Comes from Yazi. +--- @alias Line Line Comes from Yazi. +--- @alias Span Span Comes from Yazi. +--- @alias Color Color Comes from Yazi. + +--==================-- +-- Type Declaration -- +--==================-- + +--- @enum Side +local Side = { + LEFT = 0, -- The left side of either the header-line or status-line. [ LEFT ... ] + RIGHT = 1, -- The right side of either the header-line or status-line. [ ... RIGHT] +} + +--- @enum SeparatorType +local SeparatorType = { + OUTER = 0, -- Separators on the outer side of sections. [ c o | c o | c o ... ] or [ ... o c | o c | o c ] + INNER = 1, -- Separators on the inner side of sections. [ c i c | c i c | c i c ... ] or [ ... c i c | c i c | c i c ] +} + +--- @enum ComponentType +local ComponentType = { + A = 0, -- Components on the first section. [ A | | ... ] or [ ... | | A ] + B = 1, -- Components on the second section. [ | B | ... ] or [ ... | B | ] + C = 2, -- Components on the third section. [ | | C ... ] or [ ... C | | ] +} + +--- @alias Colored [string, Color] Stores text and its foreground color. +--- @alias Coloreds Colored[] The array of Coloreds. + +--- @generic T +--- @alias T T Type of the component. + +--- @class Yatline +--- @field config YatlineConfig Configuration of Yatline. +--- @field string? {} Table that stores string components. +--- @field line? {} Table that stores Line components. +--- @field coloreds? {} Table that stores Coloreds components. +Yatline = {} + +--- @class (exact) ComponentConfig +--- @field type string Defines the type of the component (T). +--- @field custom? boolean Toggles the usage of a function defined or name field. +--- @field name string | T Either defined function name or variable of defined type T. +--- @field params? {} Contains the parameters that can be used by the function called. + +--- @class (exact) SideConfig +--- @field section_a ComponentConfig[] Array of configuration of components in the first section. +--- @field section_b ComponentConfig[] Array of configuration of components in the second section. +--- @field section_c ComponentConfig[] Array of configuration of components in the third section. + +--- @class (exact) LineConfig +--- @field left SideConfig Configuration of the left side of the line. +--- @field right SideConfig Configuration of the right side of the line. + +--- @class (exact) YatlineConfig +--- @field section_separator {open: string, close: string} Separators that are between sections. +--- @field part_separator {open: string, close: string} Separators that are between components. +--- @field inverse_separator {open: string, close: string} Separators that are used when foreground color of separator is reset. +--- @field padding {inner: integer, outer: integer} Number of space padding surronding the component. +--- @field style_a {bg: Color, fg: Color, bg_mode: {normal: Color, select: Color, un_set: Color}} Style of the first section. +--- @field style_b {bg: Color, fg: Color} Style of the second section. +--- @field style_c {bg: Color, fg: Color} Style of the third section. +--- @field permissions_t_fg Color Foreground color of the type of permission. +--- @field permissions_r_fg Color Foreground color of the read permission. +--- @field permissions_w_fg Color Foreground color of the write permission. +--- @field permissions_x_fg Color Foreground color of the execute permission. +--- @field permissions_s_fg Color Foreground color of the separators between permission. +--- @field tab_width integer Maximum tab width of the tabs component. +--- @field selected {icon: string, fg: Color} Configuration for the count of files that selected. +--- @field copied {icon: string, fg: Color} Configuration for the count of files that copied. +--- @field cut {icon: string, fg: Color} Configuration for the count of files that cut. +--- @field files {icon: string, fg: Color} Configuration for the count of files in the active tab. +--- @field filtereds {icon: string, fg: Color} Configuration for the count of files in the active tab that are filtered. +--- @field total {icon: string, fg: Color} Configuration for the count of progress tasks that finished. +--- @field success {icon: string, fg: Color} Configuration for the count of progress tasks that successed. +--- @field failed {icon: string, fg: Color} Configuration for the count of progress tasks that failed. +--- @field show_background boolean Toggle the visibility of the background where no component exists. +--- @field display_header_line boolean Toggle the visibility of the header-line. +--- @field display_status_line boolean Toggle the visibility of the status-line. +--- @field component_positions string[] Arrange positions of the Yazi sections. +--- @field header_line LineConfig Configuration of header-line components. +--- @field status_line LineConfig Configuration of status-line components. +Yatline.config = { + section_separator = { open = "", close = "" }, + part_separator = { open = "", close = "" }, + inverse_separator = { open = "", close = "" }, + + padding = { inner = 1, outer = 1 }, + + style_a = { + bg = "white", + fg = "black", + bg_mode = { + normal = "white", + select = "brightyellow", + un_set = "brightred", + }, + }, + style_b = { bg = "brightblack", fg = "brightwhite" }, + style_c = { bg = "black", fg = "brightwhite" }, + + permissions_t_fg = "green", + permissions_r_fg = "yellow", + permissions_w_fg = "red", + permissions_x_fg = "cyan", + permissions_s_fg = "white", + + tab_width = 20, + + selected = { icon = "", fg = "yellow" }, + copied = { icon = "", fg = "green" }, + cut = { icon = "", fg = "red" }, + + files = { icon = "", fg = "blue" }, + filtereds = { icon = "", fg = "magenta" }, + + total = { icon = "", fg = "yellow" }, + success = { icon = "", fg = "green" }, + failed = { icon = "", fg = "red" }, + + show_background = true, + + display_header_line = true, + display_status_line = true, + + component_positions = { "header", "tab", "status" }, + + header_line = { + left = { + section_a = { + { type = "line", name = "tabs" }, + }, + section_b = {}, + section_c = {}, + }, + right = { + section_a = { + { type = "string", name = "date", params = { "%A, %d %B %Y" } }, + }, + section_b = { + { type = "string", name = "date", params = { "%X" } }, + }, + section_c = {}, + }, + }, + + status_line = { + left = { + section_a = { + { type = "string", name = "tab_mode" }, + }, + section_b = { + { type = "string", name = "hovered_size" }, + }, + section_c = { + { type = "string", name = "hovered_path" }, + { type = "coloreds", name = "count" }, + }, + }, + right = { + section_a = { + { type = "string", name = "cursor_position" }, + }, + section_b = { + { type = "string", name = "cursor_percentage" }, + }, + section_c = { + { type = "string", name = "hovered_file_extension", params = { true } }, + { type = "coloreds", name = "permissions" }, + }, + }, + }, +} + +--=================-- +-- Component Setup -- +--=================-- + +--- Sets the background of style_a according to the tab's mode. +--- @param mode Mode The mode of the active tab. +--- @see cx.active.mode To get the active tab's mode. +local function set_mode_style(mode) + if mode.is_select then + Yatline.config.style_a.bg = Yatline.config.style_a.bg_mode.select + elseif mode.is_unset then + Yatline.config.style_a.bg = Yatline.config.style_a.bg_mode.un_set + else + Yatline.config.style_a.bg = Yatline.config.style_a.bg_mode.normal + end +end + +--- Helper function to apply style table to a component +--- @param component Span The component to style +--- @param style table The style table with fg and/or bg fields +local function apply_style_table(component, style) + if not style then + return component + end + -- Apply manually + if style.fg then + component:fg(style.fg) + end + if style.bg then + component:bg(style.bg) + end + if style.bold then + component:bold() + end + if style.dim then + component:dim() + end + if style.italic then + component:italic() + end + if style.underline then + component:underline() + end + if style.blink then + component:blink() + end + if style.blink_rapid then + component:blink_rapid() + end + if style.reverse then + component:reverse() + end + if style.hidden then + component:hidden() + end + if style.crossed then + component:crossed() + end + + return component +end + +--- Helper function to apply style table to a component +--- @param component Span The component to style +--- @param style table The style table with fg and/or bg fields +local function apply_style_table(component, style) + if not style then + return component + end + -- Apply manually + if style.fg then component:fg(style.fg) end + if style.bg then component:bg(style.bg) end + if style.bold then component:bold() end + if style.dim then component:dim() end + if style.italic then component:italic() end + if style.underline then component:underline() end + if style.blink then component:blink() end + if style.blink_rapid then component:blink_rapid() end + if style.reverse then component:reverse() end + if style.hidden then component:hidden() end + if style.crossed then component:crossed() end + return component +end + +--- Sets the style of the component according to the its type. +--- @param component Span Component that will be styled. +--- @param component_type ComponentType Which section component will be in [ a | b | c ]. +--- @see Style To see how to style, in Yazi's documentation. +local function set_component_style(component, component_type) + if component_type == ComponentType.A then + apply_style_table(component, Yatline.config.style_a):bold() + elseif component_type == ComponentType.B then + apply_style_table(component, Yatline.config.style_b) + else + apply_style_table(component, Yatline.config.style_c) + end +end + +--- Surronds component with paddings. +--- @param component string | Span | Line Component that will be connected to paddings. +--- @param component_type ComponentType Which section component will be in [ a | b | c ]. +--- @param in_side Side Left or right side of the either header-line or status-line. +--- @return Line line A Line which is a component that has padding. +local function connect_padding(component, component_type, in_side) + local inner = ui.Span(string.rep(" ", Yatline.config.padding.inner)) + local outer = ui.Span(string.rep(" ", Yatline.config.padding.outer)) + + set_mode_style(cx.active.mode) + set_component_style(inner, component_type) + set_component_style(outer, component_type) + + if in_side == Side.LEFT then + return ui.Line({ outer, component, inner }) + else + return ui.Line({ inner, component, outer }) + end +end + +--- Connects component to a separator. +--- @param component Span Component that will be connected to separator. +--- @param in_side Side Left or right side of the either header-line or status-line. +--- @param separator_type SeparatorType Where will there be a separator in the section. +--- @param separator_style {bg: string?, fg: string?} Holds the style of the separator. +--- @return Line line A Line which may have either both component and separator, or component. +local function connect_separator(component, in_side, separator_type, separator_style) + local open, close + if + separator_type == SeparatorType.OUTER and not (separator_style.bg == "reset" and separator_style.fg == "reset") + then + open = ui.Span(Yatline.config.section_separator.open) + close = ui.Span(Yatline.config.section_separator.close) + + if separator_style.fg == "reset" then + if separator_style.bg ~= "" then + open = ui.Span(Yatline.config.inverse_separator.open) + close = ui.Span(Yatline.config.inverse_separator.close) + + separator_style.fg, separator_style.bg = separator_style.bg, separator_style.fg + else + return ui.Line({ component }) + end + end + else + open = ui.Span(Yatline.config.part_separator.open) + close = ui.Span(Yatline.config.part_separator.close) + end + + apply_style_table(open, separator_style) + apply_style_table(close, separator_style) + + if in_side == Side.LEFT then + return ui.Line({ component, close }) + else + return ui.Line({ open, component }) + end +end + +--==================-- +-- Helper Functions -- +--==================-- + +--- Gets the file name from given file extension. +--- @param file_name string The name of a file whose extension will be taken. +--- @return string file_extension Extension of a file. +local function get_file_extension(file_name) + local extension = file_name:match("^.+%.(.+)$") + + if extension == nil or extension == "" then + return "null" + else + return extension + end +end + +--- Reverse the order of given array +--- @param array Line Array which wants to be reversed. +--- @return table reversed Reversed ordered given array. +local function reverse_order(array) + local reversed = {} + for i = #array, 1, -1 do + table.insert(reversed, array[i]) + end + + return reversed +end + +--- the number of characters in a UTF-8 string +--- @param s string The string to process. +--- @return integer The number of characters in the string. +local function utf8len(s) + -- count the number of non-continuing bytes + return select(2, s:gsub("[^\128-\193]", "")) +end + +--- like string.sub() but i, j are utf8 strings +--- a utf8-safe string.sub() +--- @param s string The string to process. +--- @param i integer The start position. +--- @param j integer The end position. +--- @return string The substring. +local function utf8sub(s, i, j) + -- pattern for matching UTF-8 characters + local pattern = "[%z\1-\127\194-\244][\128-\191]*" + + -- helper function for position calculation + --- @param pos integer The position of the character. + --- @param len integer The length of the string. + --- @return integer The relative position of the character. + local function posrelat(pos, len) + if pos < 0 then + pos = len + pos + 1 + end + return pos + end + + -- helper function to iterate over UTF-8 chars + local function chars(_s, no_subs) + local function map(f) + local _i = 0 + if no_subs then + for b, e in _s:gmatch("()" .. pattern .. "()") do + _i = _i + 1 + local c = e - b + f(_i, c, b) + end + else + for b, c in _s:gmatch("()(" .. pattern .. ")") do + _i = _i + 1 + f(_i, c, b) + end + end + end + return coroutine.wrap(function() + return map(coroutine.yield) + end) + end + + local l = utf8len(s) + + i = posrelat(i, l) + j = j and posrelat(j, l) or l + + if i < 1 then + i = 1 + end + if j > l then + j = l + end + + if i > j then + return "" + end + + local diff = j - i + local iter = chars(s, true) + + -- advance up to i + for _ = 1, i - 1 do + iter() + end + + local c, b = select(2, iter()) + + -- becareful with the edge case of empty string + if not b then + return "" + end + + -- i and j are the same, single-character sub + if diff == 0 then + return string.sub(s, b, b + c - 1) + end + + i = b + + -- advance up to j + for _ = 1, diff - 1 do + iter() + end + + c, b = select(2, iter()) + + return string.sub(s, i, b + c - 1) +end + +--- Trims the filename if it is longer than the max_length. +--- @param filename string The name of a file which will be trimmed. +--- @param max_length integer Maximum length of the filename. +--- @param trim_length integer Length of the trimmed filename. +--- @return string trimmed_filename Trimmed filename. +local function trim_filename(filename, max_length, trim_length) + if not max_length or not trim_length then + return filename + end + + -- Count UTF-8 characters + local len = utf8len(filename) + + if len <= max_length then + return filename + end + + if len <= trim_length * 2 then + return filename + end + + return utf8sub(filename, 1, trim_length) .. "..." .. utf8sub(filename, len - trim_length + 1, len) +end + +--========================-- +-- Component String Group -- +--========================-- + +Yatline.string = {} +Yatline.string.get = {} +Yatline.string.has_separator = true + +--- Creates a component from given string according to other parameters. +--- @param string string The text which will be shown inside of the component. +--- @param component_type ComponentType Which section component will be in [ a | b | c ]. +--- @return Line line Customized Line which follows desired style of the parameters. +--- @see set_mode_style To know how mode style selected. +--- @see set_component_style To know how component style applied. +function Yatline.string.create(string, component_type) + local span = ui.Span(string) + set_mode_style(cx.active.mode) + set_component_style(span, component_type) + + return ui.Line({ span }) +end + +--- Gets the hovered file's name of the current active tab. +--- @param trimmed? boolean Whether to trim the filename if it's too long (default: false) +--- @param max_length? integer Maximum length of the filename (default: 24) +--- @param trim_length? integer Length of each end when trimming (default: 10) +--- @param show_symlink? boolean Whether to show symlink target (default: false) +--- @return string name Current active tab's hovered file's name +function Yatline.string.get:hovered_name(trimmed, max_length, trim_length, show_symlink) + trimmed = trimmed or false + max_length = max_length or 24 + trim_length = trim_length or 10 + show_symlink = show_symlink or false + + local hovered = cx.active.current.hovered + if not hovered then + return "" + end + + local link_delimiter = " -> " + local linked = (show_symlink and hovered.link_to ~= nil) and (link_delimiter .. tostring(hovered.link_to)) or "" + + if trimmed then + local trimmed_name = trim_filename(hovered.name, max_length, trim_length) + local trimmed_linked = #linked ~= 0 + and link_delimiter .. trim_filename( + string.sub(linked, #link_delimiter + 1, -1), + max_length, + trim_length + ) + or "" + return trimmed_name .. trimmed_linked + else + return hovered.name .. linked + end +end + +--- Gets the hovered file's path of the current active tab. +--- @param trimmed? boolean Whether to trim the file path if it's too long (default: false) +--- @param max_length? integer Maximum length of the file path (default: 24) +--- @param trim_length? integer Length of each end when trimming (default: 10) +--- @return string path Current active tab's hovered file's path. +function Yatline.string.get:hovered_path(trimmed, max_length, trim_length) + trimmed = trimmed or false + max_length = max_length or 24 + trim_length = trim_length or 10 + + local hovered = cx.active.current.hovered + if not hovered then + return "" + end + + if trimmed then + return trim_filename(ya.readable_path(tostring(hovered.url)), max_length, trim_length) + else + return ya.readable_path(tostring(hovered.url)) + end +end + +--- Gets the hovered file's size of the current active tab. +--- @return string size Current active tab's hovered file's size. +function Yatline.string.get:hovered_size() + local hovered = cx.active.current.hovered + if hovered then + return ya.readable_size(hovered:size() or hovered.cha.len) + else + return "" + end +end + +--- Gets the hovered file's path of the current active tab. +--- @return string mime Current active tab's hovered file's mime. +function Yatline.string.get:hovered_mime() + local hovered = cx.active.current.hovered + if hovered then + return hovered:mime() + else + return "" + end +end + +--- Gets the hovered file's user and group ownership of the current active tab. +--- Unix-like systems only. +--- @return string ownership Current active tab's hovered file's user and group ownership. +function Yatline.string.get:hovered_ownership() + local hovered = cx.active.current.hovered + + if hovered then + if not hovered.cha.uid or not hovered.cha.gid then + return "" + end + + local username = ya.user_name(hovered.cha.uid) or tostring(hovered.cha.uid) + local groupname = ya.group_name(hovered.cha.gid) or tostring(hovered.cha.gid) + + return username .. ":" .. groupname + else + return "" + end +end + +--- Gets the hovered file's extension of the current active tab. +--- @param show_icon boolean Whether or not an icon will be shown. +--- @return string file_extension Current active tab's hovered file's extension. +function Yatline.string.get:hovered_file_extension(show_icon) + local hovered = cx.active.current.hovered + + if hovered then + local cha = hovered.cha + + local name + if cha.is_dir then + name = "dir" + else + name = get_file_extension(hovered.url.name) + end + + if show_icon then + local icon = hovered:icon().text + return icon .. " " .. name + else + return name + end + else + return "" + end +end + +--- Gets the path of the current active tab. +--- @param trimmed? boolean Whether to trim the current active tab's path if it's too long (default: false) +--- @param max_length? integer Maximum length of the current active tab's path (default: 24) +--- @param trim_length? integer Length of each end when trimming (default: 10) +--- @return string path Current active tab's path. +function Yatline.string.get:tab_path(trimmed, max_length, trim_length) + trimmed = trimmed or false + max_length = max_length or 24 + trim_length = trim_length or 10 + + local cwd = cx.active.current.cwd + local filter = cx.active.current.files.filter + local finder = cx.active.finder + + local t = {} + if cwd.is_search then + t[#t + 1] = string.format("search: %s", cwd.domain) + end + if filter then + t[#t + 1] = string.format("filter: %s", filter) + end + if finder then + t[#t + 1] = string.format("find: %s", finder) + end + + local suffix + if #t ~= 0 then + suffix = " (" .. table.concat(t, ", ") .. ")" + else + suffix = "" + end + + if trimmed then + return trim_filename(ya.readable_path(tostring(cwd)), max_length, trim_length) .. suffix + else + return ya.readable_path(tostring(cwd)) .. suffix + end +end + +--- Gets the filtered query. +--- @param key? string Key value that indicates filtered query (default: "filter:") +--- @return string query Filtered query. +function Yatline.string.get:filter_query(key) + key = key or "filter:" + + local filter = cx.active.current.files.filter + + if filter then + return string.format("%s %s", key, tostring(filter)) + else + return "" + end +end + +--- Gets the searched query. +--- @param key? string Key value that indicates searched query (default: "search:") +--- @return string query Searched query. +function Yatline.string.get:search_query(key) + key = key or "search:" + + local cwd = cx.active.current.cwd + + if cwd.is_search then + return string.format("%s %s", key, cwd.domain) + else + return "" + end +end + +--- Gets the finded query. +--- @param key? string Key value that indicates finded query (default: "find:") +--- @return string query Finded query. +function Yatline.string.get:finder_query(key) + key = key or "find:" + + local finder = cx.active.finder + + if finder then + return string.format("%s %s", key, tostring(finder)) + else + return "" + end +end + +--- Gets the mode of active tab. +--- @return string mode Active tab's mode. +function Yatline.string.get:tab_mode() + local mode = tostring(cx.active.mode):upper() + if mode == "UNSET" then + mode = "UN-SET" + end + + return mode +end + +--- Gets the number of files in the current active tab. +--- @return string num_files Number of files in the current active tab. +function Yatline.string.get:tab_num_files() + return tostring(#cx.active.current.files) +end + +--- Gets the cursor position in the current active tab. +--- @return string cursor_position Current active tab's cursor position. +function Yatline.string.get:cursor_position() + local cursor = cx.active.current.cursor + local length = #cx.active.current.files + + if length ~= 0 then + return string.format("%d/%d", cursor + 1, length) + else + return "0" + end +end + +--- Gets the cursor position as percentage which is according to the number of files inside of current active tab. +--- @return string percentage Percentage of current active tab's cursor position and number of percentages. +function Yatline.string.get:cursor_percentage() + local percentage = 0 + local cursor = cx.active.current.cursor + local length = #cx.active.current.files + if cursor ~= 0 and length ~= 0 then + percentage = math.floor((cursor + 1) * 100 / length) + end + + if percentage == 0 then + return "Top" + elseif percentage == 100 then + return "Bot" + else + return string.format("%d%%", percentage) + end +end + +--- Gets the local date or time values. +--- @param format string Format for giving desired date or time values. +--- @return string date Date or time values. +--- @see os.date To see how format works. +function Yatline.string.get:date(format) + return tostring(os.date(format)) +end + +--======================-- +-- Component Line Group -- +--======================-- + +Yatline.line = {} +Yatline.line.get = {} +Yatline.line.has_separator = false + +--- To follow component group naming and functions, returns the given line without any changes. +--- @param line Line The line already pre-defined. +--- @param component_type ComponentType Which section component will be in [ a | b | c ]. Will not be used. +--- @return Line line The given line as an input. +function Yatline.line.create(line, component_type) + return line +end + +--- Creates and returns line component for tabs. +--- @param side? string Left or right side of the either header-line or status-line. +--- @return Line line Customized Line which contains tabs. +--- @see set_mode_style To know how mode style selected. +--- @see set_component_style To know how component style applied. +--- @see connect_padding To know how components have paddings. +--- @see connect_separator To know how component and separator connected. +function Yatline.line.get:tabs(side) + side = side or "left" + + local tabs = #cx.tabs + local lines = {} + + local in_side + if side == "left" then + in_side = Side.LEFT + else + in_side = Side.RIGHT + end + + for i = 1, tabs do + local text = tostring(i) + if Yatline.config.tab_width > 2 then + text = ui.truncate(text .. " " .. cx.tabs[i].name, { max = Yatline.config.tab_width }) + end + + local separator_style = { bg = nil, fg = nil } + if i == cx.tabs.idx then + local tab = connect_padding(text, ComponentType.A, in_side) + set_mode_style(cx.tabs[i].mode) + set_component_style(tab, ComponentType.A) + + if Yatline.config.style_a.bg ~= "reset" or Yatline.config.show_background then + separator_style.fg = Yatline.config.style_a.bg + if Yatline.config.show_background then + separator_style.bg = Yatline.config.style_c.bg + end + + lines[#lines + 1] = connect_separator(tab, in_side, SeparatorType.OUTER, separator_style) + else + separator_style.fg = Yatline.config.style_a.fg + + lines[#lines + 1] = connect_separator(tab, in_side, SeparatorType.INNER, separator_style) + end + else + local tab = ui.Span(text) + local inner = ui.Span(string.rep(" ", Yatline.config.padding.inner)) + local outer = ui.Span(string.rep(" ", Yatline.config.padding.outer)) + + if Yatline.config.show_background then + set_component_style(inner, ComponentType.C) + set_component_style(outer, ComponentType.C) + set_component_style(tab, ComponentType.C) + else + apply_style_table(tab, { fg = Yatline.config.style_c.fg }) + end + + if in_side == Side.LEFT then + tab = ui.Line({ outer, tab, inner }) + else + tab = ui.Line({ inner, tab, outer }) + end + + if i == cx.tabs.idx - 1 then + set_mode_style(cx.tabs[i + 1].mode) + + local open, close + if + Yatline.config.style_a.bg ~= "reset" + or (Yatline.config.show_background and Yatline.config.style_c.bg ~= "reset") + then + if + not Yatline.config.show_background + or (Yatline.config.show_background and Yatline.config.style_c.bg == "reset") + then + separator_style.fg = Yatline.config.style_a.bg + if Yatline.config.show_background then + separator_style.bg = Yatline.config.style_c.bg + end + + open = ui.Span(Yatline.config.inverse_separator.open) + close = ui.Span(Yatline.config.inverse_separator.close) + else + separator_style.bg = Yatline.config.style_a.bg + if Yatline.config.show_background then + separator_style.fg = Yatline.config.style_c.bg + end + + open = ui.Span(Yatline.config.section_separator.open) + close = ui.Span(Yatline.config.section_separator.close) + end + else + separator_style.fg = Yatline.config.style_c.fg + + open = ui.Span(Yatline.config.part_separator.open) + close = ui.Span(Yatline.config.part_separator.close) + end + + apply_style_table(open, separator_style) + apply_style_table(close, separator_style) + + if in_side == Side.LEFT then + lines[#lines + 1] = ui.Line({ tab, close }) + else + lines[#lines + 1] = ui.Line({ open, tab }) + end + else + separator_style.fg = Yatline.config.style_c.fg + if Yatline.config.show_background then + separator_style.bg = Yatline.config.style_c.bg + end + + lines[#lines + 1] = connect_separator(tab, in_side, SeparatorType.INNER, separator_style) + end + end + end + + if in_side == Side.RIGHT then + return ui.Line(reverse_order(lines)) + else + return ui.Line(lines) + end +end + +--==========================-- +-- Component Coloreds Group -- +--==========================-- + +Yatline.coloreds = {} +Yatline.coloreds.get = {} +Yatline.coloreds.has_separator = true + +--- Creates a component from given Coloreds according to other parameters. +--- The component it created, can contain multiple strings with different foreground color. +--- @param coloreds Coloreds The array which contains an array which contains text which will be shown inside of the component and its foreground color. +--- @param component_type ComponentType Which section component will be in [ a | b | c ]. +--- @return Line line Customized Line which follows desired style of the parameters. +--- @see set_mode_style To know how mode style selected. +--- @see set_component_style To know how component style applied. +function Yatline.coloreds.create(coloreds, component_type) + set_mode_style(cx.active.mode) + + local spans = {} + for i, colored in ipairs(coloreds) do + local span = ui.Span(colored[1]) + set_component_style(span, component_type) + span:fg(colored[2]) + + spans[i] = span + end + + return ui.Line(spans) +end + +--- Gets the hovered file's permissions of the current active tab. +--- Unix-like systems only. +--- @return Coloreds? coloreds Current active tab's hovered file's permissions +function Yatline.coloreds.get:permissions() + local hovered = cx.active.current.hovered + + if hovered then + local perm = hovered.cha:perm() + + if perm then + local coloreds = {} + + for i = 1, #perm do + local c = perm:sub(i, i) + + local fg = Yatline.config.permissions_t_fg + if c == "-" then + fg = Yatline.config.permissions_s_fg + elseif c == "r" then + fg = Yatline.config.permissions_r_fg + elseif c == "w" then + fg = Yatline.config.permissions_w_fg + elseif c == "x" or c == "s" or c == "S" or c == "t" or c == "T" then + fg = Yatline.config.permissions_x_fg + end + + table.insert(coloreds, { c, fg }) + end + + return coloreds + else + return nil + end + else + return nil + end +end + +--- Gets the number of selected and yanked files and also number of files or filtered files of the active tab. +--- @param filter? boolean Whether or not number of files (or filtered files) will be shown. +--- @param zero_check? boolean Whether or not counts will be shown if count is zero. +--- @return Coloreds? coloreds Active tab's number of selected and yanked files and also number of files or filtered files +function Yatline.coloreds.get:count(filter, zero_check) + filter = filter or false + zero_check = zero_check or false + + local num_yanked = #cx.yanked + local num_selected = #cx.active.selected + local num_files = #cx.active.current.files + + local coloreds = {} + + if filter then + local files_count_fg, files_count_icon + if cx.active.current.files.filter or cx.active.current.cwd.is_search then + files_count_fg = Yatline.config.filtereds.fg + files_count_icon = Yatline.config.filtereds.icon + else + files_count_fg = Yatline.config.files.fg + files_count_icon = Yatline.config.files.icon + end + + if (zero_check and num_files > 0) or not zero_check then + table.insert(coloreds, { string.format("%s %d", files_count_icon, num_files), files_count_fg }) + end + end + + if (zero_check and num_selected > 0) or not zero_check then + if #coloreds > 0 then + table.insert(coloreds, { " ", Yatline.config.selected.fg }) + end + + table.insert( + coloreds, + { string.format("%s %d", Yatline.config.selected.icon, num_selected), Yatline.config.selected.fg } + ) + end + + if (zero_check and num_yanked > 0) or not zero_check then + local yanked_fg, yanked_icon + if cx.yanked.is_cut then + yanked_fg = Yatline.config.cut.fg + yanked_icon = Yatline.config.cut.icon + else + yanked_fg = Yatline.config.copied.fg + yanked_icon = Yatline.config.copied.icon + end + + if #coloreds > 0 then + table.insert(coloreds, { " ", yanked_fg }) + end + + table.insert(coloreds, { string.format("%s %d", yanked_icon, num_yanked), yanked_fg }) + end + + if #coloreds > 0 then + return coloreds + else + return nil + end +end + +--- Gets the number of task states. +--- @param zero_check? boolean Whether or not counts will be shown if count is zero. +--- @return Coloreds? coloreds Number of task states. +function Yatline.coloreds.get:task_states(zero_check) + zero_check = zero_check or false + + local summary = cx.tasks.summary + local coloreds = {} + + if (zero_check and summary.total > 0) or not zero_check then + table.insert( + coloreds, + { string.format("%s %d", Yatline.config.total.icon, summary.total), Yatline.config.total.fg } + ) + end + + if (zero_check and summary.success > 0) or not zero_check then + if #coloreds > 0 then + table.insert(coloreds, { " ", Yatline.config.success.fg }) + end + + table.insert( + coloreds, + { string.format("%s %d", Yatline.config.success.icon, summary.success), Yatline.config.success.fg } + ) + end + + if (zero_check and summary.failed > 0) or not zero_check then + if #coloreds > 0 then + table.insert(coloreds, { " ", Yatline.config.failed.fg }) + end + + table.insert( + coloreds, + { string.format("%s %d", Yatline.config.failed.icon, summary.failed), Yatline.config.failed.fg } + ) + end + + if #coloreds > 0 then + return coloreds + else + return nil + end +end + +--- Gets colored which contains string based component's string and desired foreground color. +--- @param component_name string String based component's name. +--- @param fg Color Desired foreground color. +--- @param params? table Array of parameters of string based component. It is optional. +--- @return Coloreds? coloreds Array of solely array of string based component's string and desired foreground color. +function Yatline.coloreds.get:string_based_component(component_name, fg, params) + local getter = Yatline.string.get[component_name] + + if getter then + local output + if params then + output = getter(Yatline.string.get, table.unpack(params)) + else + output = getter() + end + + if output ~= nil and output ~= "" then + return { { output, fg } } + end + end + + return nil +end + +--===============-- +-- Configuration -- +--===============-- + +--- Configure separators if it is need to be added to the components. +--- Connects them with each component. +--- @param section_components [Line, boolean][] Array of components in one of the sections. +--- @param component_type ComponentType Which section component will be in [ a | b | c ]. +--- @param in_side Side Left or right side of the either header-line or status-line. +--- @param num_section_b_components integer Number of components in section-b. +--- @param num_section_c_components integer Number of components in section-c. +--- @return table section_line_components Array of line components whether or not connected with separators. +--- @see connect_padding To know how components have paddings. +--- @see connect_separator To know how component and separator connected. +local function config_components_separators( + section_components, + component_type, + in_side, + num_section_b_components, + num_section_c_components +) + local num_section_components = #section_components + local section_line_components = {} + for i, component in ipairs(section_components) do + if component[2] == true then -- Does component have separator? + local separator_style = { bg = nil, fg = nil } + + local separator_type + if i ~= num_section_components then -- Does component is not at the end of the section? + separator_type = SeparatorType.INNER + + if component_type == ComponentType.A then + separator_style = Yatline.config.style_a + elseif component_type == ComponentType.B then + separator_style = Yatline.config.style_b + else + separator_style = Yatline.config.style_c + end + else -- Does component is at the end of the section? + separator_type = SeparatorType.OUTER + + if component_type == ComponentType.A then + separator_style.fg = Yatline.config.style_a.bg + elseif component_type == ComponentType.B then + separator_style.fg = Yatline.config.style_b.bg + else + separator_style.fg = Yatline.config.style_c.bg + end + + if component_type == ComponentType.A and num_section_b_components ~= 0 then + separator_style.bg = Yatline.config.style_b.bg + elseif num_section_c_components == 0 or component_type == ComponentType.C then + if Yatline.config.show_background then + separator_style.bg = Yatline.config.style_c.bg + end + else + separator_style.bg = Yatline.config.style_c.bg + end + end + + component[1] = connect_padding(component[1], component_type, in_side) + section_line_components[i] = connect_separator(component[1], in_side, separator_type, separator_style) + else + section_line_components[i] = component[1] + end + end + + return section_line_components +end + +--- Creates configured section according to its components' config. +--- @param section ComponentConfig[] Array of components' config in a section. +--- @param component_type ComponentType Which section that components will be. +--- @return [Line, boolean][] section_components Configured components array whose components are in section. +local function config_section(section, component_type) + --- @type [Line, boolean][] + local section_components = {} + + for _, component in ipairs(section) do + local component_group = Yatline[component.type] + + if component_group then -- Does component group exist? + if component.custom then -- Does component is custom? + if component.name ~= nil and component.name ~= "" and #component.name ~= 0 then -- Does component name is valid? + section_components[#section_components + 1] = -- Insert component to the table. + { component_group.create(component.name, component_type), component_group.has_separator } + end + else + local getter = component_group.get[component.name] -- Get component function that will be called. + + if getter then -- Does function exist? + local output -- Output of the function. + if component.params then -- Does component function has parameters? + output = getter(component_group.get, table.unpack(component.params)) + else + output = getter() + end + + if output ~= nil and output ~= "" then -- Does component is not empty? + section_components[#section_components + 1] = -- Insert component to the table. + { component_group.create(output, component_type), component_group.has_separator } + end + end + end + end + end + + return section_components +end + +--- Automatically creates and configures either header-line or status-line. +--- @param side SideConfig Configuration of either left or right side. +--- @param in_side Side Which side components will be. +--- @return Line left_line Consist of components that are in left side of the line. +--- @return Line right_line Consist of components that are in right side of the line. +--- @see config_section To know how components are gotten from sections' config. +--- @see config_components_separators To know how components are connected with separators. +local function config_line(side, in_side) + -- Configures components of sections. + local section_a_components = config_section(side.section_a, ComponentType.A) + local section_b_components = config_section(side.section_b, ComponentType.B) + local section_c_components = config_section(side.section_c, ComponentType.C) + + local num_section_b_components = #section_b_components + local num_section_c_components = #section_c_components + + -- Connects components of section by separators. + local section_a_line_components = config_components_separators( + section_a_components, + ComponentType.A, + in_side, + num_section_b_components, + num_section_c_components + ) + local section_b_line_components = config_components_separators( + section_b_components, + ComponentType.B, + in_side, + num_section_b_components, + num_section_c_components + ) + local section_c_line_components = config_components_separators( + section_c_components, + ComponentType.C, + in_side, + num_section_b_components, + num_section_c_components + ) + + if in_side == Side.RIGHT then -- Reverse the order of the components if it is in the right side. + section_a_line_components = reverse_order(section_a_line_components) + section_b_line_components = reverse_order(section_b_line_components) + section_c_line_components = reverse_order(section_c_line_components) + end + + -- Combines components of section into single components. + local section_a_line = ui.Line(section_a_line_components) + local section_b_line = ui.Line(section_b_line_components) + local section_c_line = ui.Line(section_c_line_components) + + if in_side == Side.LEFT then + return ui.Line({ section_a_line, section_b_line, section_c_line }) + else + return ui.Line({ section_c_line, section_b_line, section_a_line }) + end +end + +--- Checks if either header-line or status-line contains components. +--- @param line LineConfig Configuration of either header-line or status-line. +--- @return boolean show_line Returns yes if it contains components, otherwise returns no. +local function show_line(line) + for _, side in pairs(line) do + for _, section in pairs(side) do + if #section ~= 0 then + return true + end + end + end + + return false +end + +--- Creates and configures paragraph which is used as left or right of either +--- header-line or status-line. +--- @param area Rect The area where paragraph will be placed in. +--- @param line? Line The line which used in paragraph. It is optional. +--- @return Paragraph paragraph Configured parapgraph. +local function config_paragraph(area, line) + local line_array = { line } or {} + if Yatline.config.show_background then + return apply_style_table(ui.Text(line_array):area(area), Yatline.config.style_c) + else + return ui.Text(line_array):area(area) + end +end + +return { + setup = function(_, config, pre_theme) + if config then + -- Fills the sections that are not given if the line exists. + for _, line in ipairs({ "header_line", "status_line" }) do + if config[line] then + for _, side in ipairs({ "left", "right" }) do + if config[line][side] then + for _, section in ipairs({ "section_a", "section_b", "section_c" }) do + config[line][side][section] = config[line][side][section] or {} + end + else + config[line][side] = {} + for _, section in ipairs({ "section_a", "section_b", "section_c" }) do + config[line][side][section] = {} + end + end + end + end + end + + -- Get the current theme according to the light/dark mode. (default: config.theme) + config.theme = (not rt.term.light and config.theme_dark) + or (rt.term.light and config.theme_light) + or config.theme + + -- Extracts theme fields to the config unless that fields does not exists. + if config.theme then + for key, value in pairs(config.theme) do + if not config[key] then + config[key] = value + end + end + end + + -- Extracts config fields to the YatlineConfig if that fields exists. + for key, value in pairs(config) do + if Yatline.config[key] then + Yatline.config[key] = value + end + end + end + + -- Extracts pre_theme fields to the YatlineConfig if that fields exists. + if pre_theme then + for key, value in pairs(pre_theme) do + if Yatline.config[key] then + Yatline.config[key] = value + end + end + end + + if Yatline.config.display_header_line then -- Controls displaying header-line. + if show_line(Yatline.config.header_line) then -- Controls recoding of header-line. + -- Empties default Yazi header-line. + Header._left = {} + Header._right = {} + + Header.redraw = function(self) + -- Gets Yazi components. + local right = self:children_redraw(self.RIGHT) + self._right_width = right:width() + local left = self:children_redraw(self.LEFT) + + -- Gets Yatline components. + local left_line = config_line(Yatline.config.header_line.left, Side.LEFT) + local right_line = config_line(Yatline.config.header_line.right, Side.RIGHT) + + return { + config_paragraph(self._area, ui.Line({ left_line, left })), -- Styles left_line if show_background set. + ui.Line({ right, right_line }):area(self._area):align(ui.Align.RIGHT), + } + end + end + else + Header.redraw = function() + return {} + end + end + + if Yatline.config.display_status_line then -- Controls displaying status-line. + if show_line(Yatline.config.status_line) then -- Controls recoding of status-line. + -- Empties default Yazi status-line. + Status._left = {} + Status._right = {} + + Status.redraw = function(self) + -- Gets Yazi components. + local left = self:children_redraw(self.LEFT) + local right = self:children_redraw(self.RIGHT) + + -- Gets Yatline components. + local left_line = config_line(Yatline.config.status_line.left, Side.LEFT) + local right_line = config_line(Yatline.config.status_line.right, Side.RIGHT) + + local sum_right = ui.Line({ right, right_line }) -- Needed for error prevention. + return { + config_paragraph(self._area, ui.Line({ left_line, left })), -- Styles left_line if show_background set. + sum_right:area(self._area):align(ui.Align.RIGHT), + table.unpack(ui.redraw(Progress:new(self._area, sum_right:width()))), -- Inserts Progress bar. + } + end + end + else + Status.redraw = function() + return {} + end + end + + Root.layout = function(self) + local constraints = {} + -- Sets Yazi layout according to the given positions. + for _, component in ipairs(Yatline.config.component_positions) do + if + (component == "header" and Yatline.config.display_header_line) + or (component == "status" and Yatline.config.display_status_line) + then + table.insert(constraints, ui.Constraint.Length(1)) + elseif component == "tab" then + table.insert(constraints, ui.Constraint.Fill(1)) + end + end + + self._chunks = ui.Layout():direction(ui.Layout.VERTICAL):constraints(constraints):split(self._area) + end + + Root.build = function(self) + local childrens = {} + -- Fills the layout according to the given positions. + local i = 1 + for _, component in ipairs(Yatline.config.component_positions) do + if component == "header" and Yatline.config.display_header_line then + table.insert(childrens, Header:new(self._chunks[i], cx.active)) + i = i + 1 + elseif component == "tab" then + table.insert(childrens, Tab:new(self._chunks[i], cx.active)) + i = i + 1 + elseif component == "status" and Yatline.config.display_status_line then + table.insert(childrens, Status:new(self._chunks[i], cx.active)) + i = i + 1 + end + end + + table.insert(childrens, Modal:new(self._area)) + + self._children = childrens + end + end, +} |
