--- @since 25.4.8 -- stylua: ignore local MOTIONS_AND_OP_KEYS = { { on = "0" }, { on = "1" }, { on = "2" }, { on = "3" }, { on = "4" }, { on = "5" }, { on = "6" }, { on = "7" }, { on = "8" }, { on = "9" }, -- commands { on = "d" }, { on = "v" }, { on = "y" }, { on = "x" }, -- tab commands { on = "t" }, { on = "L" }, { on = "H" }, { on = "w" }, { on = "W" }, { on = "<" }, { on = ">" }, { on = "~" }, -- movement { on = "g" }, { on = "j" }, { on = "k" }, { on = "h" }, { on = "l" }, { on = "" }, { on = "" }, { on = "" }, { on = "" } } -- stylua: ignore local MOTION_KEYS = { { on = "0" }, { on = "1" }, { on = "2" }, { on = "3" }, { on = "4" }, { on = "5" }, { on = "6" }, { on = "7" }, { on = "8" }, { on = "9" }, -- movement { on = "g" }, { on = "j" }, { on = "k" }, { on = "h" }, { on = "l" }, { on = "" }, { on = "" }, { on = "" }, { on = "" } } -- stylua: ignore local DIRECTION_KEYS = { { on = "j" }, { on = "k" }, { on = "" }, { on = "" }, -- tab movement { on = "t" } } local SHOW_NUMBERS_ABSOLUTE = 0 local SHOW_NUMBERS_RELATIVE = 1 local SHOW_NUMBERS_RELATIVE_ABSOLUTE = 2 local ENTER_MODE_FIRST = 0 local ENTER_MODE_CACHE = 1 local ENTER_MODE_CACHE_OR_FIRST = 2 ----------------------------------------------- ----------------- R E N D E R ----------------- ----------------------------------------------- local render_motion_setup = ya.sync(function(_) ya.render() Status.motion = function() return ui.Span("") end Status.children_redraw = function(self, side) local lines = {} if side == self.RIGHT then lines[1] = self:motion(self) end 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) end -- TODO: check why it doesn't work line this -- Status:children_add(function() return ui.Span("") end, 1000, Status.RIGHT) end) local render_motion = ya.sync(function(_, motion_num, motion_cmd) ya.render() Status.motion = function(self) if not motion_num then return ui.Span("") end local style = self:style() local motion_span if not motion_cmd then motion_span = ui.Span(string.format(" %3d ", motion_num)) else motion_span = ui.Span(string.format(" %3d%s ", motion_num, motion_cmd)) end local status_config = th.status local separator_open = status_config.sep_right.open local separator_close = status_config.sep_right.close return ui.Line { ui.Span(separator_open):fg(style.main.bg), motion_span:style(style.main), ui.Span(separator_close):fg(style.main.bg), ui.Span(" "), } end end) local render_numbers = ya.sync(function(_, mode) ya.render() Entity.number = function(_, index, total, file, hovered) local idx if mode == SHOW_NUMBERS_RELATIVE then idx = math.abs(hovered - index) elseif mode == SHOW_NUMBERS_ABSOLUTE then idx = file.idx else -- SHOW_NUMBERS_RELATIVE_ABSOLUTE if hovered == index then idx = file.idx else idx = math.abs(hovered - index) end end local num_format = "%" .. #tostring(total) .. "d" -- emulate vim's hovered offset if hovered == index then return ui.Span(string.format(num_format .. " ", idx)) else return ui.Span(string.format(" " .. num_format, idx)) end end Current.redraw = function(self) local files = self._folder.window if #files == 0 then return self:empty() end local hovered_index for i, f in ipairs(files) do if f.is_hovered then hovered_index = i break end end local entities, linemodes = {}, {} for i, f in ipairs(files) do linemodes[#linemodes + 1] = Linemode:new(f):redraw() local entity = Entity:new(f) entities[#entities + 1] = ui.Line({ Entity:number(i, #self._folder.files, f, hovered_index), entity:redraw() }) :style(entity:style()) end return { ui.List(entities):area(self._area), ui.Text(linemodes):area(self._area):align(ui.Text.RIGHT), } end end) local function render_clear() render_motion() end ----------------------------------------------- --------- C O M M A N D P A R S E R --------- ----------------------------------------------- local get_keys = ya.sync(function(state) return state._only_motions and MOTION_KEYS or MOTIONS_AND_OP_KEYS end) local function normal_direction(dir) if dir == "" then return "j" elseif dir == "" then return "k" elseif dir == "" then return "h" elseif dir == "" then return "l" end return dir end local function get_cmd(first_char, keys) local last_key local lines = first_char or "" while true do render_motion(tonumber(lines)) local key = ya.which { cands = keys, silent = true } if not key then return nil, nil, nil end last_key = keys[key].on if not tonumber(last_key) then last_key = normal_direction(last_key) break end lines = lines .. last_key end render_motion(tonumber(lines), last_key) -- command direction local direction if last_key == "g" or last_key == "v" or last_key == "d" or last_key == "y" or last_key == "x" then DIRECTION_KEYS[#DIRECTION_KEYS + 1] = { on = last_key, } local direction_key = ya.which { cands = DIRECTION_KEYS, silent = true } if not direction_key then return nil, nil, nil end direction = DIRECTION_KEYS[direction_key].on direction = normal_direction(direction) end return tonumber(lines), last_key, direction end local function is_tab_command(command) local tab_commands = { "t", "L", "H", "w", "W", "<", ">", "~" } for _, cmd in ipairs(tab_commands) do if command == cmd then return true end end return false end local get_active_tab = ya.sync(function(_) return cx.tabs.idx end) local get_cache_or_first_dir = ya.sync(function(state) if state._enter_mode == ENTER_MODE_CACHE then return nil elseif state._enter_mode == ENTER_MODE_CACHE_OR_FIRST then local hovered_file = cx.active.current.hovered if hovered_file ~= nil and hovered_file.cha.is_dir then return cx.active.current.cursor end end local files = cx.active.current.files local index = 1 for i = 1, #files do if files[i].cha.is_dir then index = i break end end return index - 1 end) ----------------------------------------------- ---------- E N T R Y / S E T U P ---------- ----------------------------------------------- return { entry = function(_, job) local initial_value local args = job.args -- this is checking if the argument is a valid number if #args > 0 then initial_value = tostring(tonumber(args[1])) if initial_value == "nil" then return end end local lines, cmd, direction = get_cmd(initial_value, get_keys()) if not lines or not cmd then -- command was cancelled render_clear() return end if cmd == "g" then if direction == "g" then ya.mgr_emit("arrow", { "top" }) ya.mgr_emit("arrow", { lines - 1 }) render_clear() return elseif direction == "j" then cmd = "j" elseif direction == "k" then cmd = "k" elseif direction == "t" then ya.mgr_emit("tab_switch", { lines - 1 }) render_clear() return else -- no valid direction render_clear() return end end if cmd == "j" then ya.mgr_emit("arrow", { lines }) elseif cmd == "k" then ya.mgr_emit("arrow", { -lines }) elseif cmd == "h" then for _ = 1, lines do ya.mgr_emit("leave", {}) end elseif cmd == "l" then for _ = 1, lines do ya.mgr_emit("enter", {}) local file_idx = get_cache_or_first_dir() if file_idx then ya.mgr_emit("arrow", { "top" }) ya.mgr_emit("arrow", { file_idx }) end end elseif is_tab_command(cmd) then if cmd == "t" then for _ = 1, lines do ya.mgr_emit("tab_create", {}) end elseif cmd == "H" then ya.mgr_emit("tab_switch", { -lines, relative = true }) elseif cmd == "L" then ya.mgr_emit("tab_switch", { lines, relative = true }) elseif cmd == "w" then ya.mgr_emit("tab_close", { lines - 1 }) elseif cmd == "W" then local curr_tab = get_active_tab() local del_tab = curr_tab + lines - 1 for _ = curr_tab, del_tab do ya.mgr_emit("tab_close", { curr_tab - 1 }) end ya.mgr_emit("tab_switch", { curr_tab - 1 }) elseif cmd == "<" then ya.mgr_emit("tab_swap", { -lines }) elseif cmd == ">" then ya.mgr_emit("tab_swap", { lines }) elseif cmd == "~" then local jump = lines - get_active_tab() ya.mgr_emit("tab_swap", { jump }) end else ya.mgr_emit("visual_mode", {}) -- invert direction when user specifies it if direction == "k" then ya.mgr_emit("arrow", { -lines }) elseif direction == "j" then ya.mgr_emit("arrow", { lines }) else ya.mgr_emit("arrow", { lines - 1 }) end ya.mgr_emit("escape", {}) if cmd == "d" then ya.mgr_emit("remove", {}) elseif cmd == "y" then ya.mgr_emit("yank", {}) elseif cmd == "x" then ya.mgr_emit("yank", { cut = true }) end end render_clear() end, setup = function(state, args) if not args then return end -- initialize state variables state._only_motions = args["only_motions"] or false if args["show_motion"] then render_motion_setup() end if args["enter_mode"] == "cache" then state._enter_mode = ENTER_MODE_CACHE elseif args["enter_mode"] == "first" then state._enter_mode = ENTER_MODE_FIRST elseif args["enter_mode"] == "cache_or_first" then state._enter_mode = ENTER_MODE_CACHE_OR_FIRST else state._enter_mode = ENTER_MODE_CACHE_OR_FIRST end if args["show_numbers"] == "absolute" then render_numbers(SHOW_NUMBERS_ABSOLUTE) elseif args["show_numbers"] == "relative" then render_numbers(SHOW_NUMBERS_RELATIVE) elseif args["show_numbers"] == "relative_absolute" then render_numbers(SHOW_NUMBERS_RELATIVE_ABSOLUTE) end end, }