You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
406 lines
9.9 KiB
406 lines
9.9 KiB
--- @since 25.5.28 |
|
-- 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 = "<Down>" }, { on = "<Up>" }, { on = "<Left>" }, { on = "<Right>" } |
|
} |
|
|
|
-- 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 = "<Down>" }, { on = "<Up>" }, { on = "<Left>" }, { on = "<Right>" } |
|
} |
|
|
|
-- stylua: ignore |
|
local DIRECTION_KEYS = { |
|
{ on = "j" }, { on = "k" }, { on = "<Down>" }, { on = "<Up>" }, |
|
-- 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(_) |
|
if ui.render then |
|
ui.render() |
|
else |
|
ya.render() |
|
end |
|
|
|
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) |
|
if ui.render then |
|
ui.render() |
|
else |
|
ya.render() |
|
end |
|
|
|
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 |
|
|
|
-- TODO: REMOVE THIS IN NEXT RELEASE |
|
local bg_style |
|
if type(style.main.bg) == "function" then |
|
bg_style = style.main:bg() |
|
else |
|
bg_style = style.main.bg |
|
end |
|
|
|
return ui.Line { |
|
ui.Span(separator_open):fg(bg_style), |
|
motion_span:style(style.main), |
|
ui.Span(separator_close):fg(bg_style), |
|
ui.Span(" "), |
|
} |
|
end |
|
end) |
|
|
|
local render_numbers = ya.sync(function(_, mode) |
|
if ui.render then |
|
ui.render() |
|
else |
|
ya.render() |
|
end |
|
|
|
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.Align.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 == "<Down>" then |
|
return "j" |
|
elseif dir == "<Up>" then |
|
return "k" |
|
elseif dir == "<Left>" then |
|
return "h" |
|
elseif dir == "<Right>" 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, |
|
}
|
|
|