function Status:mode() local mode = tostring(self._tab.mode):upper() local style = th.mode.normal_main return ui.Line { ui.Span(" " .. mode .. " "):style(style), }endfunction Status:filename() local h = self._current.hovered if not h then return "" end return ui.Span(" " .. h.name .. " ")endfunction Status:position() local cursor = self._current.cursor local length = #self._current.files return ui.Span(string.format(" %d/%d ", cursor + 1, length))end
3
Implement redraw
Combine elements:
function Status:redraw() local left = ui.Line { self:mode(), self:filename(), } local right = ui.Line { self:position(), } return { ui.Text(""):area(self._area):style(th.status.overall), ui.Line(left):area(self._area), ui.Line(right):area(self._area):align(ui.Align.RIGHT), }endfunction Status:reflow() return { self }end
You can add custom elements to existing components:
-- In init.luafunction Status:custom_element() local h = self._current.hovered if not h then return ui.Span("") end -- Show file size with custom formatting local size = h:size() or h.cha.len or 0 return ui.Span(string.format(" 📦 %s ", ya.readable_size(size)))end-- Add to left sideStatus:children_add(function(self) return self:custom_element()end, 500, Status.LEFT)
Status = { LEFT = 0, RIGHT = 1, _id = "status", _inc = 1000, _left = { { "mode", id = 1, order = 1000 }, { "size", id = 2, order = 2000 }, { "name", id = 3, order = 3000 }, }, _right = { { "perm", id = 4, order = 1000 }, { "percent", id = 5, order = 2000 }, { "position", id = 6, order = 3000 }, },}function Status:new(area, tab) return setmetatable({ _area = area, _tab = tab, _current = tab.current, }, { __index = self })endfunction Status:style() local m = th.mode if self._tab.mode.is_select then return { main = m.select_main, alt = m.select_alt } elseif self._tab.mode.is_unset then return { main = m.unset_main, alt = m.unset_alt } else return { main = m.normal_main, alt = m.normal_alt } endendfunction Status:mode() local mode = tostring(self._tab.mode):sub(1, 3):upper() local style = self:style() return ui.Line { ui.Span(" " .. mode .. " "):style(style.main), }endfunction Status:size() local h = self._current.hovered local size = h and (h:size() or h.cha.len) or 0 local style = self:style() return ui.Line { ui.Span(" " .. ya.readable_size(size) .. " "):style(style.alt), }endfunction Status:name() local h = self._current.hovered return h and (" " .. ui.printable(h.name)) or ""endfunction Status:perm() local h = self._current.hovered if not h then return "" end local perm = h.cha:perm() if not perm then return "" end local spans = {} for i = 1, #perm do local c = perm:sub(i, i) local style = th.status.perm_type if c == "-" or c == "?" then style = th.status.perm_sep elseif c == "r" then style = th.status.perm_read elseif c == "w" then style = th.status.perm_write elseif c == "x" or c == "s" or c == "S" or c == "t" or c == "T" then style = th.status.perm_exec end spans[i] = ui.Span(c):style(style) end return ui.Line(spans)endfunction Status:position() local cursor = self._current.cursor local length = #self._current.files local style = self:style() return ui.Line { ui.Span(string.format(" %2d/%-2d ", math.min(cursor + 1, length), length)):style(style.main), }endfunction Status:children_redraw(side) local lines = {} for _, c in ipairs(side == self.RIGHT and self._right or self._left) do lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self) end return ui.Line(lines)endfunction Status:reflow() return { self }endfunction Status:redraw() local left = self:children_redraw(self.LEFT) local right = self:children_redraw(self.RIGHT) return { ui.Text(""):area(self._area):style(th.status.overall), ui.Line(left):area(self._area), ui.Line(right):area(self._area):align(ui.Align.RIGHT), }endreturn Status
The Entity component controls how individual files are displayed:
Entity = { _children = { { "icon", id = 1, order = 1000 }, { "highlights", id = 2, order = 2000 }, { "symlink", id = 3, order = 3000 }, },}function Entity:new(file) return setmetatable({ _file = file }, { __index = self })endfunction Entity:icon() local icon = self._file:icon() if not icon then return "" end return ui.Line(icon.text .. " "):style(icon.style)endfunction Entity:highlights() local name = self._file.name local highlights = self._file:highlights() if not highlights or #highlights == 0 then return ui.printable(name) end -- Render with search highlights local spans, last = {}, 0 for _, h in ipairs(highlights) do if h[1] > last then spans[#spans + 1] = ui.printable(name:sub(last + 1, h[1])) end spans[#spans + 1] = ui.Span( ui.printable(name:sub(h[1] + 1, h[2])) ):style(th.mgr.find_keyword) last = h[2] end if last < #name then spans[#spans + 1] = ui.printable(name:sub(last + 1)) end return ui.Line(spans)endfunction Entity:symlink() local to = self._file.link_to return to and ui.Span(" -> " .. to):style(th.mgr.symlink_target) or ""endfunction Entity:redraw() local lines = {} for _, c in ipairs(self._children) do lines[#lines + 1] = (type(c[1]) == "string" and self[c[1]] or c[1])(self) end return ui.Line(lines):style(self:style())endfunction Entity:style() local s = self._file:style() or ui.Style() if not self._file.is_hovered then return s end return s:patch(th.indicator.current)endreturn Entity
local style = ui.Style() :fg("#ff0000") -- Foreground color :bg("#000000") -- Background color :fg("red") -- Named colors :bg("reset") -- Reset to default
function MyComponent:click(event, up) if up then return -- Ignore mouse up end if event.is_left then -- Left click elseif event.is_right then -- Right click elseif event.is_middle then -- Middle click end -- Access position local x, y = event.x, event.yendfunction MyComponent:scroll(event, step) -- step > 0: scroll down -- step < 0: scroll upend
function Status:new(area, tab) local me = setmetatable({ _area = area }, { __index = self }) me._cached = me:compute_expensive() -- Cache on init return meend
Check for nil safely
Always validate before accessing:
function Status:name() local h = self._current.hovered if not h then return "" -- Safe fallback end return h.nameend