From 31f443be36f8ba23e27e6a1e497ba131df89113d Mon Sep 17 00:00:00 2001 From: Matt Nish-Lapidus Date: Wed, 26 Feb 2025 22:18:59 -0500 Subject: [PATCH] some stuff --- flake.lock | 48 +-- .../emenel/dotfiles/dot_config/starship.toml | 8 + .../emenel/dotfiles/dot_config/yazi/init.lua | 16 + .../dot_config/yazi/plugins/fg.yazi/main.lua | 274 +++++++++++++++++ .../yazi/plugins/jump-to-char.yazi/main.lua | 32 -- .../yazi/plugins/open-with-cmd.yazi/main.lua | 20 ++ .../yazi/plugins/restore.yazi/main.lua | 280 ++++++++++++++++++ .../yazi/plugins/starship.yazi/LICENSE | 21 ++ .../yazi/plugins/starship.yazi/README.md | 109 +++++++ .../yazi/plugins/starship.yazi/main.lua | 136 +++++++++ .../yazi/plugins/starship.yazi/stylua.toml | 10 + .../yazi/plugins/what-size.yazi/main.lua | 72 +++++ modules/home/desktop.nix | 5 +- modules/home/shikane.nix | 17 -- modules/home/yazi.nix | 21 +- 15 files changed, 992 insertions(+), 77 deletions(-) create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/fg.yazi/main.lua delete mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/jump-to-char.yazi/main.lua create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/open-with-cmd.yazi/main.lua create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/restore.yazi/main.lua create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/LICENSE create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/README.md create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/main.lua create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/stylua.toml create mode 100644 homes/emenel/dotfiles/dot_config/yazi/plugins/what-size.yazi/main.lua diff --git a/flake.lock b/flake.lock index 709f228..e969734 100644 --- a/flake.lock +++ b/flake.lock @@ -68,11 +68,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1740500634, - "narHash": "sha256-uCC1g2xo01Q9WS6ivR9iBqO+9baoaY5op6f1EZz46IE=", + "lastModified": 1740622367, + "narHash": "sha256-7TpBfGb24DrQrdW94T5x0vHN0GF2I50fkP+YX5r6JqE=", "owner": "nix-community", "repo": "emacs-overlay", - "rev": "d82d304bcd1398e223dee8de927db024dced5128", + "rev": "ee8442abce734e9a1d0e5824818062bcbf358e73", "type": "github" }, "original": { @@ -322,11 +322,11 @@ ] }, "locked": { - "lastModified": 1740494361, - "narHash": "sha256-Dd/GhJ9qKmUwuhgt/PAROG8J6YdU2ZjtJI9SQX5sVQI=", + "lastModified": 1740606115, + "narHash": "sha256-GKe3vrIWcei4gSTckEzHr5Zf/g9NSofmsAnbkNYU+lM=", "owner": "nix-community", "repo": "home-manager", - "rev": "74f0a8546e3f2458c870cf90fc4b38ac1f498b17", + "rev": "6be185eb76295e7562f5bf2da42afe374b8beb15", "type": "github" }, "original": { @@ -711,11 +711,11 @@ "xwayland-satellite-unstable": "xwayland-satellite-unstable" }, "locked": { - "lastModified": 1740480783, - "narHash": "sha256-5l/WnJ4BELbckzTd1rmTlEGbcqBf71K2tx6pCNb2xM8=", + "lastModified": 1740592142, + "narHash": "sha256-v+Qg8V0UHkXCDSgqKowqMyJR2LGKIJGA0HbwCRgZN/0=", "owner": "sodiboo", "repo": "niri-flake", - "rev": "0da1abb83ef2a37fd885de79730759486a407c41", + "rev": "259a8cc3e351d0a34063ae857d3c730b1ae4ad56", "type": "github" }, "original": { @@ -744,11 +744,11 @@ "niri-unstable": { "flake": false, "locked": { - "lastModified": 1740476031, - "narHash": "sha256-8YuYgIzExIAenYMaSQTP7zYBzaJPN83pGRrcwQCochY=", + "lastModified": 1740587638, + "narHash": "sha256-/BQ67VCF0ZpqCvxmVR18HdnqFy81ABWaKjz1FFwL65g=", "owner": "YaLTeR", "repo": "niri", - "rev": "c153349c62ed44762bf2ae8be6d5812faa9d5c6d", + "rev": "693d9355386c6217bb9cca5cb30c2b4248f19d8c", "type": "github" }, "original": { @@ -892,11 +892,11 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1740339700, - "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=", + "lastModified": 1740463929, + "narHash": "sha256-4Xhu/3aUdCKeLfdteEHMegx5ooKQvwPHNkOgNCXQrvc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195", + "rev": "5d7db4668d7a0c6cc5fc8cf6ef33b008b2b1ed8b", "type": "github" }, "original": { @@ -908,11 +908,11 @@ }, "nixpkgs-stable_2": { "locked": { - "lastModified": 1740339700, - "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=", + "lastModified": 1740463929, + "narHash": "sha256-4Xhu/3aUdCKeLfdteEHMegx5ooKQvwPHNkOgNCXQrvc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195", + "rev": "5d7db4668d7a0c6cc5fc8cf6ef33b008b2b1ed8b", "type": "github" }, "original": { @@ -940,11 +940,11 @@ }, "nixpkgs-stable_4": { "locked": { - "lastModified": 1740339700, - "narHash": "sha256-cbrw7EgQhcdFnu6iS3vane53bEagZQy/xyIkDWpCgVE=", + "lastModified": 1740463929, + "narHash": "sha256-4Xhu/3aUdCKeLfdteEHMegx5ooKQvwPHNkOgNCXQrvc=", "owner": "nixos", "repo": "nixpkgs", - "rev": "04ef94c4c1582fd485bbfdb8c4a8ba250e359195", + "rev": "5d7db4668d7a0c6cc5fc8cf6ef33b008b2b1ed8b", "type": "github" }, "original": { @@ -1393,11 +1393,11 @@ "rust-overlay": "rust-overlay_2" }, "locked": { - "lastModified": 1740496374, - "narHash": "sha256-DJaJ4jAIex650tuoeudOVCBCfdT/3cZ7z/QJ7TpKI2c=", + "lastModified": 1740594495, + "narHash": "sha256-Q339KulMU5xw/WKdCoHq/3Mr+09Yne+gglsNhBro1zE=", "owner": "sxyazi", "repo": "yazi", - "rev": "235f6888d467cbd469afe3da45ee048d9d38ee27", + "rev": "20c99c6a06a7aa48c2e13fc0b6763d77c44c9b1a", "type": "github" }, "original": { diff --git a/homes/emenel/dotfiles/dot_config/starship.toml b/homes/emenel/dotfiles/dot_config/starship.toml index 08d450c..a2541f8 100644 --- a/homes/emenel/dotfiles/dot_config/starship.toml +++ b/homes/emenel/dotfiles/dot_config/starship.toml @@ -2,6 +2,14 @@ format = "$all$line_break$character" +[env_var.yazi_level] +description = "Indicate the shell was launched by `yazi`" +variable = "YAZI_LEVEL" +# πŸ¦†: U+1f986 Duck +# Alternatively: "ξž• ": U+E795 +symbol = "πŸ¦†" +format = '[ $symbol ]($style)' + [directory] truncation_length = 0 truncate_to_repo = false diff --git a/homes/emenel/dotfiles/dot_config/yazi/init.lua b/homes/emenel/dotfiles/dot_config/yazi/init.lua index e0c6bfc..febb762 100644 --- a/homes/emenel/dotfiles/dot_config/yazi/init.lua +++ b/homes/emenel/dotfiles/dot_config/yazi/init.lua @@ -2,10 +2,26 @@ require("git"):setup() require("yatline-symlink"):setup() require("dir-rules"):setup() +require("starship"):setup { + config_file = "~/.config/starship.toml", +} + require("smart-enter"):setup { open_multi = true, } +require("fg"):setup({ + default_action = "menu", +}) + +-- ~/.config/yazi/init.lua +THEME.git = THEME.git or {} +THEME.git.added_sign = "A" +THEME.git.ignored_sign = "I" +THEME.git.updated_sign = "U" +THEME.git.modified_sign = "M" +THEME.git.deleted_sign = "D" + require("yatline"):setup({ --theme = my_theme, section_separator = { open = "", close = "" }, diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/fg.yazi/main.lua b/homes/emenel/dotfiles/dot_config/yazi/plugins/fg.yazi/main.lua new file mode 100644 index 0000000..4785c0c --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/fg.yazi/main.lua @@ -0,0 +1,274 @@ +local toggle_ui = ya.sync(function(self) + if self.children then + Modal:children_remove(self.children) + self.children = nil + else + self.children = Modal:children_add(self, 10) + end + ya.render() +end) + +local init_ui_data = ya.sync(function(self,file_url) + self.opt = {"nvim", "jump"} + self.title = "fg" + self.title_color = "#82ab3a" + self.cursor = 0 + self.file_url = file_url and file_url or "" + ya.render() +end) + +local set_option = ya.sync(function(self,enable) + if enable then + self.active_opt = self.opt[self.cursor+1] + else + self.active_opt = nil + end +end) + +local get_option = ya.sync(function(self) + return self.active_opt +end) + +local get_default_action = ya.sync(function(self) + return self.default_action +end) + +local update_cursor = ya.sync(function(self, cursor) + self.cursor = ya.clamp(0, self.cursor + cursor, 1) + ya.render() +end) + +local M = { + keys = { + { on = "q", run = "quit" }, + { on = "", run = "quit" }, + { on = "", run = "select" }, + + + { on = "k", run = "up" }, + { on = "j", run = "down" }, + + + { on = "", run = "up" }, + { on = "", run = "down" }, + + }, +} + +function M:new(area) + self:layout(area) + return self +end + +function M:layout(area) + local chunks = ui.Layout() + :constraints({ + ui.Constraint.Percentage(10), + ui.Constraint.Percentage(80), + ui.Constraint.Percentage(10), + }) + :split(area) + + local chunks = ui.Layout() + :direction(ui.Layout.HORIZONTAL) + :constraints({ + ui.Constraint.Percentage(10), + ui.Constraint.Percentage(80), + ui.Constraint.Percentage(10), + }) + :split(chunks[2]) + + self._area = chunks[2] +end + +local function splitAndGetNth(inputstr, sep, index) + if sep == nil then + sep = "%s" + end + local count = 0 + local start = 1 + while true do + local sepStart, sepEnd = string.find(inputstr, sep, start) + if not sepStart then + break + end + count = count + 1 + if count == index then + return string.sub(inputstr, start, sepStart - 1) + end + start = sepEnd + 1 + end + if index == 1 then + return inputstr + end + return nil -- ε¦‚ζžœζ²‘ζœ‰θΆ³ε€Ÿηš„εˆ†ε‰²ιƒ¨εˆ†οΌŒθΏ”ε›žnil +end + +local state = ya.sync(function() return tostring(cx.active.current.cwd) end) + +local function fail(s, ...) ya.notify { title = "Fzf", content = string.format(s, ...), timeout = 5, level = "error" } end + +function M:entry(job) + local args = job.args + local _permit = ya.hide() + local cwd = state() + local shell_value = ya.target_family() == "windows" and "nu" or os.getenv("SHELL"):match(".*/(.*)") + local cmd_args = "" + + local preview_cmd = [===[line={2} && begin=$( if [[ $line -lt 7 ]]; then echo $((line-1)); else echo 6; fi ) && bat --highlight-line={2} --color=always --line-range $((line-begin)):$((line+10)) {1}]===] + if ya.target_family() == "windows" then + preview_cmd = [[bat --highlight-line={2} --color=always --line-range {2}: {1}]] + elseif shell_value == "fish" then + preview_cmd = [[set line {2} && set begin ( test $line -lt 7 && echo (math "$line-1") || echo 6 ) && bat --highlight-line={2} --color=always --line-range (math "$line-$begin"):(math "$line+10") {1}]] + elseif shell_value == "nu" then + preview_cmd = [[let line = ({2} | into int); let begin = if $line < 7 { $line - 1 } else { 6 }; bat --highlight-line={2} --color=always --line-range $'($line - $begin):($line + 10)' {1}]] + end + if ya.target_family() == "windows" and args[1] == "fzf" then + cmd_args = [[fzf --preview="bat --color=always {}"]] + elseif ya.target_family() == "windows" and args[1] == "rg" then + local rg_prefix = [[rg --colors "path:fg:blue" --colors "line:fg:red" --colors "column:fg:yellow" --column --line-number --no-heading --color=always --smart-case ]] + cmd_args = [[fzf --ansi --disabled --bind "start:reload:]] + .. rg_prefix + .. [[{q}" --bind "change:reload:]] + .. rg_prefix + .. [[{q}" --delimiter ":" --preview "]] + .. preview_cmd + .. [[" --preview-window "up,60%" --nth "3.."]] + elseif ya.target_family() == "windows" then + cmd_args = [[rg --color=always --line-number --no-heading --smart-case "" | fzf --ansi --preview="]] .. preview_cmd .. [[" --delimiter=":" --preview-window="up:60%" --nth="3.."]] + elseif args[1] == "fzf" then + cmd_args = [[fzf --preview="bat --color=always {}"]] + elseif args[1] == "rg" and shell_value == "fish" then + cmd_args = [[ + RG_PREFIX="rg --colors 'path:fg:blue' --colors 'line:fg:red' --colors 'column:fg:yellow' --column --line-number --no-heading --color=always --smart-case " \ + fzf --ansi --disabled \ + --bind "start:reload:$RG_PREFIX {q}" \ + --bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \ + --delimiter : \ + --preview ']] .. preview_cmd .. [[' \ + --preview-window 'up,60%' \ + --nth '3..' + ]] + elseif args[1] == "rg" and (shell_value == "bash" or shell_value == "zsh") then + cmd_args = [[ + RG_PREFIX="rg --colors 'path:fg:blue' --colors 'line:fg:red' --colors 'column:fg:yellow' --column --line-number --no-heading --color=always --smart-case " + fzf --ansi --disabled \ + --bind "start:reload:$RG_PREFIX {q}" \ + --bind "change:reload:sleep 0.1; $RG_PREFIX {q} || true" \ + --delimiter : \ + --preview ']] .. preview_cmd .. [[' \ + --preview-window 'up,60%' \ + --nth '3..' + ]] + elseif args[1] == "rg" and shell_value == "nu" then + local rg_prefix = "rg --colors 'path:fg:blue' --colors 'line:fg:red' --colors 'column:fg:yellow' --column --line-number --no-heading --color=always --smart-case " + cmd_args = [[fzf --ansi --disabled --bind "start:reload:]] + .. rg_prefix + .. [[{q}" --bind "change:reload:sleep 100ms; try { ]] + .. rg_prefix + .. [[{q} }" --delimiter : --preview ']] + .. preview_cmd + .. [[' --preview-window 'up,60%' --nth '3..']] + else + cmd_args = [[rg --color=always --line-number --no-heading --smart-case '' | fzf --ansi --preview=']] .. preview_cmd .. [[' --delimiter=':' --preview-window='up:60%' --nth='3..']] + end + + local child, err = + Command(shell_value):args({"-c", cmd_args}):cwd(cwd):stdin(Command.INHERIT):stdout(Command.PIPED):stderr(Command.INHERIT):spawn() + + if not child then + return fail("Spawn `rfzf` failed with error code %s. Do you have it installed?", err) + end + + local output, err = child:wait_with_output() + if not output then + return fail("Cannot read `fzf` output, error code %s", err) + elseif not output.status.success and output.status.code ~= 130 then + return fail("`fzf` exited with error code %s", output.status.code) + end + + if output.stdout == "" then + return + end + + local target = output.stdout:gsub("\n$", "") + + local file_url = splitAndGetNth(target,":",1) + local line_number = splitAndGetNth(target,":",2) + line_number = line_number and line_number or 1 + init_ui_data(cwd.."/"..file_url) + local default_action = get_default_action() + + if (default_action == "menu" or default_action == nil) and args[1] ~= "fzf" then + _permit:drop() + toggle_ui() + while true do + local cand = self.keys[ya.which { cands = self.keys, silent = true }] + if cand then + if cand.run == "quit" then + set_option(false) + toggle_ui() + break + elseif cand.run == "select" then + set_option(true) + toggle_ui() + break + elseif cand.run == "down" then + update_cursor(1) + elseif cand.run == "up" then + update_cursor(-1) + end + end + end + _permit = ya.hide() + end + + if (default_action == "nvim" or get_option() == "nvim" ) and args[1] ~= "fzf" then + os.execute("nvim +"..line_number.." -n "..file_url) + elseif (default_action == "jump" or get_option() == "jump" or args[1] == "fzf") and file_url ~= "" then + ya.manager_emit(file_url:match("[/\\]$") and "cd" or "reveal", { file_url }) + else + return + end + +end + +function M:reflow() return { self } end + +function M:redraw() + local rows = {} + + rows[1] = ui.Row { "open with nvim" } + rows[2] = ui.Row { "reach at yazi" } + return { + ui.Clear(self._area), + ui.Border(ui.Border.ALL) + :area(self._area) + :type(ui.Border.ROUNDED) + :style(ui.Style():fg("#82ab3a")) + :title(ui.Line(self.title):align(ui.Line.CENTER):fg(self.title_color)), + ui.Table(rows) + :area(self._area:pad(ui.Pad(1, 2, 1, 2))) + :header(ui.Row({ "Action for:"..self.file_url }):style(ui.Style():bold():fg("#e73c80"))) + :row(self.cursor) + :row_style(ui.Style():fg("#82ab3a"):underline()), + } +end + + +function M.fail(s, ...) + ya.manager_emit("plugin", {"mount", args = "refresh" }) + ya.notify { title = "fg", content = string.format(s, ...), timeout = 10, level = "error" } +end + +function M:click() end + +function M:scroll() end + +function M:touch() end + +function M:setup(config) + self.default_action = (config and config.default_action) and config.default_action or "menu" +end + +return M diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/jump-to-char.yazi/main.lua b/homes/emenel/dotfiles/dot_config/yazi/plugins/jump-to-char.yazi/main.lua deleted file mode 100644 index 91ec94d..0000000 --- a/homes/emenel/dotfiles/dot_config/yazi/plugins/jump-to-char.yazi/main.lua +++ /dev/null @@ -1,32 +0,0 @@ ---- @since 25.2.7 - -local AVAILABLE_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789." - -local changed = ya.sync(function(st, new) - local b = st.last ~= new - st.last = new - return b or not cx.active.finder -end) - -local escape = function(s) return s == "." and "\\." or s end - -return { - entry = function() - local cands = {} - for i = 1, #AVAILABLE_CHARS do - cands[#cands + 1] = { on = AVAILABLE_CHARS:sub(i, i) } - end - - local idx = ya.which { cands = cands, silent = true } - if not idx then - return - end - - local kw = escape(cands[idx].on) - if changed(kw) then - ya.manager_emit("find_do", { "^" .. kw }) - else - ya.manager_emit("find_arrow", {}) - end - end, -} diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/open-with-cmd.yazi/main.lua b/homes/emenel/dotfiles/dot_config/yazi/plugins/open-with-cmd.yazi/main.lua new file mode 100644 index 0000000..fee405f --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/open-with-cmd.yazi/main.lua @@ -0,0 +1,20 @@ +return { + entry = function(_, job) + local block = job.args[1] and job.args[1] == "block" + + local value, event = ya.input({ + title = block and "Open with (block):" or "Open with:", + position = { "hovered", y = 1, w = 50 }, + }) + + if event == 1 then + local s = ya.target_family() == "windows" and " %*" or ' "$@"' + ya.manager_emit("shell", { + value .. s, + block = block, + orphan = true, + confirm = true, + }) + end + end, +} diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/restore.yazi/main.lua b/homes/emenel/dotfiles/dot_config/yazi/plugins/restore.yazi/main.lua new file mode 100644 index 0000000..059205d --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/restore.yazi/main.lua @@ -0,0 +1,280 @@ +--- @since 25.2.7 + +local M = {} +local shell = os.getenv("SHELL") or "" +local PackageName = "Restore" +local function success(s, ...) + ya.notify({ title = PackageName, content = string.format(s, ...), timeout = 5, level = "info" }) +end + +local function fail(s, ...) + ya.notify({ title = PackageName, content = string.format(s, ...), timeout = 5, level = "error" }) +end + +---@enum STATE +local STATE = { + POSITION = "position", + SHOW_CONFIRM = "show_confirm", + THEME = "theme", +} + +local set_state = ya.sync(function(state, key, value) + if state then + state[key] = value + else + state = {} + state[key] = value + end +end) + +local get_state = ya.sync(function(state, key) + if state then + return state[key] + else + return nil + end +end) + +---@enum File_Type +local File_Type = { + File = "file", + Dir = "dir_all", + None_Exist = "unknown", +} + +---@alias TRASHED_ITEM {trash_index: number, trashed_date_time: string, trashed_path: string, type: File_Type} Item in trash list + +function get_basename(filepath) + return filepath:match("^.+/(.+)$") or filepath +end + +local get_cwd = ya.sync(function() + return tostring(cx.active.current.cwd) +end) + +local function path_quote(path) + local result = "'" .. string.gsub(path, "'", "'\\''") .. "'" + return result +end + +local function get_file_type(path) + local cha, _ = fs.cha(Url(path)) + if cha then + return cha.is_dir and File_Type.Dir or File_Type.File + else + return File_Type.None_Exist + end +end + +local function get_trash_volume() + local cwd = get_cwd() + local trash_volumes_stream, cmr_err = + Command("trash-list"):args({ "--volumes" }):stdout(Command.PIPED):stderr(Command.PIPED):output() + + local matched_vol_path = nil + if trash_volumes_stream then + local matched_vol_length = 0 + for vol in trash_volumes_stream.stdout:gmatch("[^\r\n]+") do + local vol_length = utf8.len(vol) or 0 + if cwd:sub(1, vol_length) == vol and vol_length > matched_vol_length then + matched_vol_path = vol + matched_vol_length = vol_length + end + end + if not matched_vol_path then + fail("Can't get trash directory") + end + else + fail("Failed to start `trash-list` with error: `%s`. Do you have `trash-cli` installed?", cmr_err) + end + return matched_vol_path +end + +---get list of latest files/folders trashed +---@param curr_working_volume currently working volume +---@return TRASHED_ITEM[]|nil +local function get_latest_trashed_items(curr_working_volume) + ---@type TRASHED_ITEM[] + local restorable_items = {} + local fake_enter = Command("printf"):stderr(Command.PIPED):stdout(Command.PIPED):spawn():take_stdout() + local trash_list_stream, err_cmd = Command(shell) + :args({ "-c", "trash-restore " .. path_quote(curr_working_volume) }) + :stdin(fake_enter) + :stdout(Command.PIPED) + :stderr(Command.PIPED) + :output() + + if trash_list_stream then + ---@type TRASHED_ITEM[] + local trash_list = {} + for line in trash_list_stream.stdout:gmatch("[^\r\n]+") do + -- remove leading spaces + line = line:match("^%s*(.+)$") + local trash_index, item_date, item_path = line:match("^(%d+) (%S+ %S+) (.+)$") + if item_date and item_path and trash_index ~= nil then + table.insert(trash_list, { + trash_index = tonumber(trash_index), + trashed_date_time = item_date, + trashed_path = item_path, + type = File_Type.None_Exist, + }) + end + end + + if #trash_list == 0 then + success("Nothing left to restore") + return + end + + local last_item_datetime = trash_list[#trash_list].trashed_date_time + + for _, trash_item in ipairs(trash_list) do + if trash_item then + if trash_item.trashed_date_time == last_item_datetime then + trash_item.type = get_file_type(trash_item.trashed_path) + table.insert(restorable_items, trash_item) + end + end + end + else + fail("Failed to start `trash-restore` with error: `%s`. Do you have `trash-cli` installed?", err_cmd) + return + end + return restorable_items + -- return newest_trashed_items +end + +---@param trash_list TRASHED_ITEM[] +local function filter_none_exised_paths(trash_list) + ---@type TRASHED_ITEM[] + local existed_trash_items = {} + for _, v in ipairs(trash_list) do + if v.type ~= File_Type.None_Exist then + table.insert(existed_trash_items, v) + end + end + return existed_trash_items +end + +local function restore_files(curr_working_volume, start_index, end_index) + if type(start_index) ~= "number" or type(end_index) ~= "number" or start_index < 0 or end_index < 0 then + fail("Failed to restore file(s): out of range") + return + end + + ya.manager_emit("shell", { + "echo " .. ya.quote(start_index .. "-" .. end_index) .. " | trash-restore --overwrite " .. path_quote( + curr_working_volume + ), + confirm = true, + }) + local file_to_restore_count = end_index - start_index + 1 + success("Restored " .. tostring(file_to_restore_count) .. " file" .. (file_to_restore_count > 1 and "s" or "")) +end + +function M:setup(opts) + if opts and opts.position and type(opts.position) == "table" then + set_state(STATE.POSITION, opts.position) + else + set_state(STATE.POSITION, { "center", w = 70, h = 40 }) + end + if opts and opts.show_confirm then + set_state(STATE.SHOW_CONFIRM, opts.show_confirm) + else + set_state(STATE.SHOW_CONFIRM, false) + end + if opts and opts.theme and type(opts.theme) == "table" then + set_state(STATE.THEME, opts.theme) + else + set_state(STATE.THEME, {}) + end +end + +---@param trash_list TRASHED_ITEM[] +local function get_components(trash_list) + local theme = get_state(STATE.THEME) or {} + theme.list_item = theme.list_item or { + odd = "blue", + even = "blue", + } + local trashed_items_components = {} + for idx, item in pairs(trash_list) do + local fg_color = theme.list_item.odd or "blue" + if idx % 2 == 0 then + fg_color = theme.list_item.even or "blue" + end + table.insert( + trashed_items_components, + ui.Line({ + ui.Span(" "), + ui.Span(item.trashed_path):fg(fg_color), + }):align(ui.Line.LEFT) + ) + end + return trashed_items_components +end + +function M:entry() + local curr_working_volume = get_trash_volume() + if not curr_working_volume then + return + end + local trashed_items = get_latest_trashed_items(curr_working_volume) + if trashed_items == nil then + return + end + local collided_items = filter_none_exised_paths(trashed_items) + local overwrite_confirmed = true + local show_confirm = get_state(STATE.SHOW_CONFIRM) + local pos = get_state(STATE.POSITION) + pos = pos or { "center", w = 70, h = 40 } + + local theme = get_state(STATE.THEME) or {} + theme.title = theme.title or "blue" + theme.header = theme.header or "green" + theme.header_warning = theme.header_warning or "yellow" + if ya.confirm and show_confirm then + local continue_restore = ya.confirm({ + title = ui.Line("Restore files/folders"):fg(theme.title):bold(), + content = ui.Text({ + ui.Line(""), + ui.Line("The following files and folders are going to be restored:"):fg(theme.header), + ui.Line(""), + table.unpack(get_components(trashed_items)), + }) + :align(ui.Text.LEFT) + :wrap(ui.Text.WRAP), + --TODO: still wating for API :/ + -- list = ui.List({ + -- table.unpack(get_components(trashed_items)), + -- }), + + pos = pos, + }) + -- stopping + if not continue_restore then + return + end + end + + -- show Confirm dialog with list of collided items + if #collided_items > 0 then + overwrite_confirmed = ya.confirm({ + title = ui.Line("Restore files/folders"):fg(theme.title):bold(), + content = ui.Text({ + ui.Line(""), + ui.Line("The following files and folders are existed, overwrite?"):fg(theme.header_warning), + ui.Line(""), + table.unpack(get_components(collided_items)), + }) + :align(ui.Text.LEFT) + :wrap(ui.Text.WRAP), + pos = pos, + }) + end + if overwrite_confirmed then + restore_files(curr_working_volume, trashed_items[1].trash_index, trashed_items[#trashed_items].trash_index) + end +end + +return M diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/LICENSE b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/LICENSE new file mode 100644 index 0000000..c03ce66 --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Rolv Apneseth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/README.md b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/README.md new file mode 100644 index 0000000..e5d0097 --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/README.md @@ -0,0 +1,109 @@ +# starship.yazi + +Starship prompt plugin for [Yazi](https://github.com/sxyazi/yazi) + + + +## Requirements + +- [Yazi](https://github.com/sxyazi/yazi) +- [starship](https://github.com/starship/starship) + +## Installation + +```bash +ya pack -a Rolv-Apneseth/starship +``` + +### Manual + +```sh +# Linux / MacOS +git clone https://github.com/Rolv-Apneseth/starship.yazi.git ~/.config/yazi/plugins/starship.yazi +# Windows +git clone https://github.com/Rolv-Apneseth/starship.yazi.git %AppData%\yazi\config\plugins\starship.yazi +``` + +## Usage + +Add this to `~/.config/yazi/init.lua`: + +```lua +require("starship"):setup() +``` + +Make sure you have [starship](https://github.com/starship/starship) installed and in your `PATH`. + +## Config + +Here is an example with all available config options: + +```lua +require("starship"):setup({ + -- Hide flags (such as filter, find and search). This is recommended for starship themes which + -- are intended to go across the entire width of the terminal. + hide_flags = false, -- Default: false + -- Whether to place flags after the starship prompt. False means the flags will be placed before the prompt. + flags_after_prompt = true, -- Default: true + -- Custom starship configuration file to use + config_file = "~/.config/starship_full.toml", -- Default: nil +}) +``` + +## Extra + +If you use a `starship` theme with a background colour, it might look a bit to cramped on just the one line `Yazi` gives the header by default. To fix this, you can add this to your `init.lua`: + +
+Click to expand + +```lua +local old_build = Tab.build + +Tab.build = function(self, ...) + local bar = function(c, x, y) + if x <= 0 or x == self._area.w - 1 then + return ui.Bar(ui.Bar.TOP):area(ui.Rect.default) + end + + return ui.Bar(ui.Bar.TOP) + :area(ui.Rect({ + x = x, + y = math.max(0, y), + w = ya.clamp(0, self._area.w - x, 1), + h = math.min(1, self._area.h), + })) + :symbol(c) + end + + local c = self._chunks + self._chunks = { + c[1]:padding(ui.Padding.y(1)), + c[2]:padding(ui.Padding(c[1].w > 0 and 0 or 1, c[3].w > 0 and 0 or 1, 1, 1)), + c[3]:padding(ui.Padding.y(1)), + } + + local style = THEME.manager.border_style + self._base = ya.list_merge(self._base or {}, { + ui.Border(ui.Border.ALL):area(self._area):type(ui.Border.ROUNDED):style(style), + ui.Bar(ui.Bar.RIGHT):area(self._chunks[1]):style(style), + ui.Bar(ui.Bar.LEFT):area(self._chunks[1]):style(style), + + bar("┬", c[1].right - 1, c[1].y), + bar("β”΄", c[1].right - 1, c[1].bottom - 1), + bar("┬", c[2].right, c[2].y), + bar("β”΄", c[2].right, c[2].bottom - 1), + }) + + old_build(self, ...) +end +``` + +
+ +> [!NOTE] +> This works by overriding your `Tab.build` function so make sure this is the only place you're doing that in your config. For example, this would be incompatible with the [full-border plugin](https://github.com/yazi-rs/plugins/tree/main/full-border.yazi) + +## Thanks + +- [sxyazi](https://github.com/sxyazi) for providing the code for this plugin and the demo video [in this comment](https://github.com/sxyazi/yazi/issues/767#issuecomment-1977082834) diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/main.lua b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/main.lua new file mode 100644 index 0000000..4048508 --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/main.lua @@ -0,0 +1,136 @@ +--- @since 25.2.7 + +-- For development +--[[ local function notify(message) ]] +--[[ ya.notify({ title = "Starship", content = message, timeout = 3 }) ]] +--[[ end ]] + +local save = ya.sync(function(st, _cwd, output) + st.output = output + ya.render() +end) + +-- Helper function for accessing the `config_file` state variable +---@return string +local get_config_file = ya.sync(function(st) + return st.config_file +end) + +return { + ---User arguments for setup method + ---@class SetupArgs + ---@field config_file string Absolute path to a starship config file + ---@field hide_flags boolean Whether to hide all flags (such as filter and search). Recommended for themes which are intended to take the full width of the terminal. + ---@field flags_after_prompt boolean Whether to place flags (such as filter and search) after the starship prompt. By default this is true. + + --- Setup plugin + --- @param st table State + --- @param args SetupArgs|nil + setup = function(st, args) + local hide_flags = false + local flags_after_prompt = true + + -- Check setup args + if args ~= nil then + if args.config_file ~= nil then + local url = Url(args.config_file) + if url.is_regular then + local config_file = args.config_file + + -- Manually replace '~' and '$HOME' at the start of the path with the OS environment variable + local home = os.getenv("HOME") + if home then + home = tostring(home) + config_file = config_file:gsub("^~", home):gsub("^$HOME", home) + end + + st.config_file = config_file + end + end + + if args.hide_flags ~= nil then + hide_flags = args.hide_flags + end + + if args.flags_after_prompt ~= nil then + flags_after_prompt = args.flags_after_prompt + end + end + + -- Replace default header widget + Header:children_remove(1, Header.LEFT) + Header:children_add(function(self) + local max = self._area.w - self._right_width + if max <= 0 then + return "" + end + + if hide_flags or not st.output then + return ui.Line.parse(st.output or "") + end + + -- Split `st.output` at the first line break (or keep as is if none was found) + local output = st.output:match("([^\n]*)\n?") or st.output + + local flags = self:flags() + if flags_after_prompt then + output = output .. " " .. flags + else + output = flags .. " " .. output + end + + return ui.Line.parse(output) + end, 1000, Header.LEFT) + + -- Pass current working directory and custom config path (if specified) to the plugin's entry point + ---Callback for subscribers to update the prompt + local callback = function() + local cwd = cx.active.current.cwd + if st.cwd ~= cwd then + st.cwd = cwd + + if ya.confirm then + -- >= yazi 25.2.7 + ya.manager_emit("plugin", { + st._id, + ya.quote(tostring(cwd), true), + }) + else + -- < yazi 25.2.7 + ya.manager_emit("plugin", { + st._id, + args = ya.quote(tostring(cwd), true), + }) + end + end + end + + -- Subscribe to events + ps.sub("cd", callback) + ps.sub("tab", callback) + end, + + entry = function(_, job_or_args) + -- yazi 2024-11-29 changed the way arguments are passed to the plugin + -- entry point. They were moved inside {args = {...}}. If the user is using + -- a version before this change, they can use the old implementation. + -- https://github.com/sxyazi/yazi/pull/1966 + local args = job_or_args.args or job_or_args + local command = Command("starship") + :arg("prompt") + :stdin(Command.INHERIT) + :cwd(args[1]) + :env("STARSHIP_SHELL", "") + + -- Point to custom starship config + local config_file = get_config_file() + if config_file then + command = command:env("STARSHIP_CONFIG", config_file) + end + + local output = command:output() + if output then + save(args[1], output.stdout:gsub("^%s+", "")) + end + end, +} diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/stylua.toml b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/stylua.toml new file mode 100644 index 0000000..4bcc241 --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/starship.yazi/stylua.toml @@ -0,0 +1,10 @@ +column_width = 100 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 4 +quote_style = "AutoPreferDouble" +call_parentheses = "Always" +collapse_simple_statement = "Never" + +[sort_requires] +enabled = false diff --git a/homes/emenel/dotfiles/dot_config/yazi/plugins/what-size.yazi/main.lua b/homes/emenel/dotfiles/dot_config/yazi/plugins/what-size.yazi/main.lua new file mode 100644 index 0000000..e84e1c1 --- /dev/null +++ b/homes/emenel/dotfiles/dot_config/yazi/plugins/what-size.yazi/main.lua @@ -0,0 +1,72 @@ + +-- function to get paths of selected elements or current directory +-- of no elements are selected +local get_paths = ya.sync(function() + local paths = {} + -- get selected files + for _, u in pairs(cx.active.selected) do + paths[#paths + 1] = tostring(u) + end + -- if no files are selected, get current directory + if #paths == 0 then + if cx.active.current.cwd then + paths[1] = tostring(cx.active.current.cwd) + else + ya.err("what-size would return nil paths") + end + end + return paths +end) + +-- Function to get total size from du output +local get_total_size = function(s) + local lines = {} + for line in s:gmatch("[^\n]+") do lines[#lines + 1] = line end + local last_line = lines[#lines] + local last_line_parts = {} + for part in last_line:gmatch("%S+") do last_line_parts[#last_line_parts + 1] = part end + local total_size = last_line_parts[1] + return total_size +end + +-- Function to format file size +local function format_size(size) + local units = { "B", "KB", "MB", "GB", "TB" } + local unit_index = 1 + + while size > 1024 and unit_index < #units do + size = size / 1024 + unit_index = unit_index + 1 + end + + return string.format("%.2f %s", size, units[unit_index]) +end + +return { + entry = function(self, job) + -- defaults not to use clipboard, use it only if required by the user + local clipboard = job.args.clipboard or job.args[1] == '-c' + local items = get_paths() + + local cmd = "du" + local output, err = Command(cmd):arg("-scb"):args(items):output() + if not output then + ya.err("Failed to run diff, error: " .. err) + else + local total_size = get_total_size(output.stdout) + local formatted_size = format_size(tonumber(total_size)) + + local notification_content = "Total size: " .. formatted_size + if clipboard then + ya.clipboard(formatted_size) + notification_content = notification_content .. "\nCopied to clipboard." + end + + ya.notify { + title = "What size", + content = notification_content, + timeout = 5, + } + end + end, +} diff --git a/modules/home/desktop.nix b/modules/home/desktop.nix index 62e8f9c..49f11c8 100644 --- a/modules/home/desktop.nix +++ b/modules/home/desktop.nix @@ -123,8 +123,9 @@ protonplus fstl - nwg-look + librewolf + # screen recording and streaming kooha obs-studio obs-studio-plugins.wlrobs @@ -202,7 +203,7 @@ categories = [ "System" ]; }; mirror-laptop = { - name = "mirror laptop to external display"; + name = "mirror laptop"; exec = "/home/emene/.local/bin/mirror-laptop"; categories = [ "System" ]; }; diff --git a/modules/home/shikane.nix b/modules/home/shikane.nix index c918fee..c02f029 100644 --- a/modules/home/shikane.nix +++ b/modules/home/shikane.nix @@ -72,23 +72,6 @@ in { } ]; } - { - name = "laptop-mirror"; - exec = [ "notify-send shikane \"Profile $SHIKANE_PROFILE_NAME has been applied\"" "wl-present mirror eDP-1 & sleep .5; wl-present fullscreen-output HDMI-A-1; wl-present fullscreen" ]; - output = [ - { - enable = true; - search = [ "m=TL140ADXP04-0" "s=" "v=Thermotrex Corporation" ]; - mode = "2560x1600@240Hz"; - position = "0,0"; - adaptive_sync = true; - } - { - enable = true; - search = "n/HDMI-[ABC]-[1-9]"; - } - ]; - } ]; }; } diff --git a/modules/home/yazi.nix b/modules/home/yazi.nix index 8449a9d..1784414 100644 --- a/modules/home/yazi.nix +++ b/modules/home/yazi.nix @@ -7,6 +7,7 @@ initLua = ../../homes/emenel/dotfiles/dot_config/yazi/init.lua; keymap = { manager.prepend_keymap = [ + { on = "!"; run = "shell '$SHELL' --block"; desc = "Open shell here"; } { on = ["c" "d"]; run = "shell 'ripdrag \"$@\" -x 2>/dev/null &' --confirm"; desc = "Drag selection";} { on = ["c" "c"]; run = "yank"; desc = "Copy file"; } { on = ["c" "n"]; run = "copy filename"; desc = "Copy file name"; } @@ -25,7 +26,6 @@ { on = "M"; run = "plugin mount"; } { on = "p"; run = "plugin smart-paste"; desc = "Paste into the hovered directory or CWD"; } { on = ["g" "l"]; run = "plugin lazygit"; desc = "lazygit"; } - { on = "F"; run = "plugin jump-to-char"; desc = "jump to char"; } { on = "k"; run = "plugin arrow -1"; desc = "up"; } { on = "j"; run = "plugin arrow 1"; desc = "down"; } { on = ""; run = "plugin arrow -1"; desc = "up"; } @@ -36,7 +36,15 @@ { on = ""; run = "plugin smart-enter"; desc = "Enter the child directory, or open the file"; } { on = ""; run = "plugin smart-enter"; desc = "Enter the child directory, or open the file"; } { on = ""; run = "remove"; } - { on = ["t" "i"]; run = "inspect"; desc = "inspect"; } + + { on = [ "t" "s" ]; run = "plugin what-size"; desc = "Calc size of selection or cwd"; } + + { on = ["d" "u"]; run = "plugin restore"; desc = "Restore last deleted files/folders"; } + { on = ["d" "d"]; run = "remove"; desc = "Delete files/folders"; } + + { on = ["F" "g"]; run = "plugin fg"; desc = "Find file by content (fuzzy)"; } + { on = ["F" "G"]; run = "plugin fg rg"; desc = "Find file by content (ripgrep)"; } + { on = ["F" "f"]; run = "plugin fg fzf"; desc = "Find file by name (fuzzy)"; } ]; }; settings = { @@ -46,6 +54,10 @@ show_symlink = true; }; opener = { + open = [ + { run = "plugin open-with-cmd --args=block"; desc = "Open with command in the terminal"; } + { run = "plugin open-with-cmd"; desc = "Open with command"; } + ]; edit = [ { run = "emacsclient -r -n \"$@\""; desc = "open in current emacsclient"; orphan = true; } ]; @@ -68,6 +80,9 @@ { mime = "application/x-xz"; run = "ouch"; } { mime = "application/octet-stream"; run = "ouch"; } + { name = "*.el"; run = "code"; } + { name = "*.lisp"; run = "code"; } + { name = "*.org"; run = "glow"; } { name = "*"; run = "file-extra-metadata"; } { name = "*.md"; run = "glow"; } { mime = "{image,audio,video}/*"; run = "mediainfo";} @@ -94,6 +109,8 @@ name = "*/"; run = "git"; } + { id = "simple-tag"; name = "*"; run = "simple-tag"; } + { id = "simple-tag"; name = "*/"; run = "simple-tag"; } ]; }; };