--- @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, }