diff --git a/lua/windline/init.lua b/lua/windline/init.lua index 5ed65f1..0d5816e 100644 --- a/lua/windline/init.lua +++ b/lua/windline/init.lua @@ -10,8 +10,8 @@ local click_utils = require('windline.click_utils') M.state = M.state or { - mode = {}, -- vim mode {normal insert} - comp = {}, -- component state it will reset on begin render + mode = {}, -- vim mode {normal insert} + comp = {}, -- component state it will reset on begin render config = {}, runtime_colors = {}, -- some colors name added by function add_component } @@ -117,7 +117,7 @@ M.show_global = function(bufnr, winid) if vim.g.statusline_winid == winid then if utils.is_in_table(M.state.config.global_skip_filetypes, ft) or ( - api.nvim_win_get_config(winid).relative ~= '' + api.nvim_win_get_config(winid).relative ~= '' and not check_line.global_show_float ) then @@ -239,6 +239,9 @@ M.get_colors = function(reload) colors = vim.tbl_extend('force', colors, func_color(colors)) end end + for key, c in ipairs(colors) do + colors[key] = string.lower(c) + end M.state.colors = colors assert(colors ~= nil, 'a colors_name on setup function should return a value') return colors @@ -329,7 +332,6 @@ M.setup_event = function() callback = function() M.on_set_laststatus() end }) api.nvim_create_user_command("WindLineBenchmark", "lua require('windline.benchmark').benchmark()", {}) - end M.remove_status_by_ft = function(filetypes) @@ -380,7 +382,7 @@ end --- position == left add component before a first divider (%=) --- position == right add component after a first divider (%=) --- auto_remove auto remove old component with same name ---- colors_name table a modifer colors to add to a new component +--- colors_name table a modifier colors to add to a new component --- autocmd boolean It use an auto command to add component to default statusline. M.add_component = function(component, opt) if opt.autocmd then @@ -427,7 +429,7 @@ M.add_component = function(component, opt) end M.setup_hightlight(M.state.colors) else - vim.api.nvim_echo({ { string.format("Cant' find a position %s", opt.position), 'ErrorMsg' } }, true, {}) + vim.api.nvim_echo({ { string.format("Can't find a position %s", opt.position), 'ErrorMsg' } }, true, {}) end end diff --git a/lua/wltabline/init.lua b/lua/wltabline/init.lua index 900b841..53f2f01 100644 --- a/lua/wltabline/init.lua +++ b/lua/wltabline/init.lua @@ -84,15 +84,15 @@ end -- stylua: ignore local default_config = { template = { - select = { '', { 'NormalFg', 'NormalBg', 'bold' } }, + select = { '', { 'NormalFg', 'NormalBg', 'bold' } }, select_start = { separators.block_thin .. ' ', { 'blue', 'NormalBg' } }, - select_end = { ' ', { 'NormalFg', 'NormalBg' } }, - select_fill = { ' ', { 'NormalFg', 'NormalBg' } }, - normal = { '', { 'TabLineFg', 'TabLineBg' } }, - normal_start = { ' ', { 'TabLineFg', 'TabLineBg' } }, - normal_end = { ' ', { 'TabLineFg', 'TabLineBg' } }, - normal_select = { ' ', { 'TabLineFg', 'TabLineBg' } }, - normal_last = { ' ', { 'TabLineFg', 'TabLineBg' } }, + select_end = { ' ', { 'NormalFg', 'NormalBg' } }, + select_fill = { ' ', { 'NormalFg', 'NormalBg' } }, + normal = { '', { 'TabLineFg', 'TabLineBg' } }, + normal_start = { ' ', { 'TabLineFg', 'TabLineBg' } }, + normal_end = { ' ', { 'TabLineFg', 'TabLineBg' } }, + normal_select = { ' ', { 'TabLineFg', 'TabLineBg' } }, + normal_last = { ' ', { 'TabLineFg', 'TabLineBg' } }, }, click = true, tab_end = { @@ -100,7 +100,7 @@ local default_config = { }, } -local tab_template = function(template) +M.tab_template = function(template) local hl_colors = {} local sep_text = {} for key, value in pairs(template) do @@ -118,9 +118,9 @@ local tab_template = function(template) hl_end = 'select_last' end return { - { sep_text.select_start, 'select_start' }, + { sep_text.select_start, 'select_start' }, { M.tab_name(data.tab_index), 'select' }, - { text_end, hl_end }, + { text_end, hl_end }, } else local hl_end = 'normal_end' @@ -134,9 +134,9 @@ local tab_template = function(template) hl_end = 'normal_select' end return { - { text_start, 'normal_start' }, + { text_start, 'normal_start' }, { M.tab_name(data.tab_index), 'normal' }, - { text_end, hl_end }, + { text_end, hl_end }, } end end, @@ -145,7 +145,7 @@ end M.setup = function(opts) opts = vim.tbl_deep_extend('force', default_config, opts or {}) - opts.tab_template = opts.tab_template or tab_template(opts.template or {}) + opts.tab_template = opts.tab_template or M.tab_template(opts.template or {}) WindLine.hl_data = {} _G.WindLine.tabline = { setup_hightlight = M.setup_hightlight, @@ -156,7 +156,7 @@ M.setup = function(opts) M.setup_hightlight(opts.colors) utils.hl_create() end - vim.cmd([[set tabline=%!v:lua.WindLine.tabline.show()]]) + vim.opt.tabline = '%!v:lua.WindLine.tabline.show()' end return M diff --git a/lua/wltmuxline/init.lua b/lua/wltmuxline/init.lua new file mode 100644 index 0000000..c9e491e --- /dev/null +++ b/lua/wltmuxline/init.lua @@ -0,0 +1,218 @@ +local M = {} +local api = vim.api +local tmux_component = require('wltmuxline.tmux_component') + +local uv = vim.uv or vim.loop + +---@class tmux_line +---@field original string +---@field tmux 'status-left'|'status-right' +---@field tabline boolean +---@field stdin? uv_pipe_t +---@field job? uv_process_t +---@field shutdown function +---@field setup function +---@field restore function +---@field last_status string +---@field active tmux_component[]|tmux_component +---@field data? any + +---@class tmux_config +---@field is_run boolean +---@field statuslines tmux_line[] +local default_config = { + autocmd = { "BufEnter", "TabEnter" }, + is_run = true, + statuslines = {} +} + +---@type tmux_config +---@diagnostic disable-next-line: missing-fields +local tmux_state = {} + +---@param line tmux_line +local function render_tabline(line) + local total_tab = vim.fn.tabpagenr('$') + local status = '' + local tab_select = vim.fn.tabpagenr() + for i = 1, total_tab, 1 do + local data = { + is_selection = i == tab_select, + is_next_select = ((i + 1) == tab_select), + is_tab_finish = i == total_tab, + template = line.data, + tab_index = i, + } + status = status .. line.active[1]:render(data) + end + line.last_status = status + return status +end + +---@param line tmux_line +local function render_status_line(line) + tmux_component.reset() + local status = '' + local winid = api.nvim_get_current_win() + local bufnr = api.nvim_get_current_buf() + local win_width = vim.o.columns + for _, comp in pairs(line.active) do + status = status .. comp:render(bufnr, winid, win_width) + end + line.last_status = status + return status +end + + +M.update = function() + if not tmux_state.is_run then return false end + for _, line in pairs(tmux_state.statuslines) do + local text = line.tabline and render_tabline(line) or render_status_line(line) + if text ~= '' and line.stdin then + line.stdin:write(text .. '\n') + end + end +end + +M.stop = function() + tmux_state.is_run = false + for _, line in pairs(tmux_state.statuslines) do + if line.job then + line.shutdown() + line.restore() + end + end +end + +M.tmux_set = function() + for _, line in pairs(tmux_state.statuslines) do + line.setup() + end + M.update() +end + +M.tmux_restore = function() + for _, line in pairs(tmux_state.statuslines) do + line.restore(true) + end +end + +---@param line tmux_line +---@param colors table +local function init_tmux(line, colors) + assert(line.tmux == 'status-left' or line.tmux == 'status-right', ' tmux config is wrong') + -- compile component + if #line.active > 0 then + for key, value in pairs(line.active) do + local comp = tmux_component.create(value) + line.active[key] = comp + comp:setup_hl(colors) + end + end + + local original = vim.fn.systemlist( + string.format([[sh -c 'tmux display-message -p "#{%s}"']], line.tmux))[1] + + local session_id = vim.env.TMUX:match(',(%d*)$') + local tmux_pipe = vim.env.TMUX:match('/tmp/tmux%-%d*/default') + local pipe_path = string.format('%s-$%s-%s', tmux_pipe, session_id, line.tmux) + local function setup() + local tmux_set_command = string.format([[tmux set -g %s '#(cat #{socket_path}-\#{session_id}-%s)']], + line.tmux, line.tmux) + vim.fn.systemlist(tmux_set_command); + end + local function restore(verify) + if verify then + local check = vim.fn.readfile(pipe_path, '', 1) + if check and check[1] ~= line.last_status then + return + end + end + pcall(vim.fn.system, + string.format('tmux set -g %s %s', line.tmux, vim.fn.shellescape(line.original))) + end + + local stdin = uv.new_pipe(false) + local job_script = string.format( + [[while IFS=''$\n'' read -r l; do echo "$l" > '%s';tmux refresh-client -S; done]], + pipe_path + ) + + local job = uv.spawn("bash", + { cwd = uv.cwd(), stdio = { stdin }, args = { "-c", job_script } } + ) + setup() + if not job then return end + line.original = original + line.stdin = stdin + line.job = job + line.restore = vim.schedule_wrap(restore) + line.setup = vim.schedule_wrap(setup) + line.shutdown = function() + if stdin and stdin.is_active then stdin:close() end + if job and job.is_active then job:close() end + line.stdin = nil + line.job = nil + end +end + + +---@param opts tmux_config +M.setup = function(opts) + if not vim.env.TMUX then return end + opts = vim.tbl_deep_extend('force', default_config, opts or {}) + tmux_state = opts + if #opts.statuslines == 0 then + return vim.notify('tmuxline: you need to input statuslines on setup') + end + M.stop() + local group = vim.api.nvim_create_augroup('WindlineTmuxLine', { clear = true }) + vim.api.nvim_create_autocmd('FocusGained', { + group = group, + pattern = "*", + callback = function() + tmux_state.is_run = true + M.tmux_set() + end + }) + vim.api.nvim_create_autocmd('FocusLost', { + group = group, + pattern = "*", + callback = function() + tmux_state.is_run = false + vim.schedule(M.tmux_restore) + end + }) + vim.api.nvim_create_autocmd('VimLeavePre', { + group = group, + pattern = "*", + callback = vim.schedule_wrap(M.stop) + }) + vim.api.nvim_create_autocmd( + tmux_state.autocmd, + { group = group, pattern = '*', callback = M.update } + ) + vim.api.nvim_create_autocmd('ColorScheme', { + group = group, + pattern = "*", + callback = function() + local colors = require('windline').get_colors() + for _, line in pairs(tmux_state.statuslines) do + if (line.component) then line.component:setup_hl(colors) end + if (line.active) then + for _, comp in pairs(line.active) do + comp:setup_hl(colors) + end + end + end + M.update() + end + }) + local colors = require('windline').get_colors() + for _, line in pairs(tmux_state.statuslines) do + init_tmux(line, colors) + end + tmux_state.is_run = true +end + +return M diff --git a/lua/wltmuxline/tmux_component.lua b/lua/wltmuxline/tmux_component.lua new file mode 100644 index 0000000..ecdd91f --- /dev/null +++ b/lua/wltmuxline/tmux_component.lua @@ -0,0 +1,85 @@ +local base_component = require('windline.component') +local get_hl_color = require('windline.themes').get_hl_color + +---@class tmux_component +local tmux_component = {} +local state = WindLine.state + +local last_hl = {} + +local render_text = function(text, highlight) + if text == nil or text == '' then + return '' + end + if not highlight or highlight == '' or (highlight[1] == last_hl[1] and highlight[2] == last_hl[2]) then + return text + end + last_hl = highlight + return string.format('#[fg=%s,bg=%s]%s', highlight[1], highlight[2], text) +end + +function tmux_component:make_hl(hl) + local colors = state.colors + if type(hl) == 'table' then + if colors[hl[1]] or colors[hl[2]] then + return { colors[hl[1]], colors[hl[2]] } + end + else + if colors[hl] then return colors[hl] end + local fg, bg = get_hl_color(hl) + if fg and bg then return { fg, bg } end + end + return hl +end + +function tmux_component:setup_hl(colors) + local hl_data = {} + local hl_colors = self.hl_colors + if hl_colors then + if type(hl_colors[1]) == 'string' or type(hl_colors[2]) == 'string' then + self.hl_data = { colors[hl_colors[1]], colors[hl_colors[2]] } + self.hl = self.hl_data + return + end + for key, value in pairs(hl_colors) do + if type(value) == 'table' then + hl_data[key] = { colors[value[1]], colors[value[2]] } + else + local fg, bg = get_hl_color(value) + hl_data[key] = { fg, bg } + end + end + end + self.hl_data = hl_data +end + +function tmux_component:render(bufnr, winid, width) + self.bufnr = bufnr + local hl_data = self.hl_data or {} + local comps = self.text(bufnr, winid, width) + if type(comps) == 'table' then + local result = '' + for _, child in pairs(comps) do + local text, hl = child[1], child[2] + if type(text) == 'function' then + text = child[1](bufnr, winid, width) + end + if type(hl) == 'string' then + hl = hl_data[hl] or hl + end + result = result .. render_text(text, self:make_hl(hl)) + end + return result + end + return render_text(comps, self:make_hl(self.hl)) +end + +return { + ---@return tmux_component + create = function(...) + local cmp = base_component.create(...) + ---@diagnostic disable-next-line: return-type-mismatch + return setmetatable(cmp, { __index = tmux_component }) + end, + reset = function() last_hl = {} end +}