diff --git a/.config/nvim/init.lua b/.config/nvim/init.lua
new file mode 100644
index 0000000..0297e90
--- /dev/null
+++ b/.config/nvim/init.lua
@@ -0,0 +1,2 @@
+-- lazy vim package manager
+require("config.lazy")
diff --git a/.config/nvim/lua/config/lazy.lua b/.config/nvim/lua/config/lazy.lua
new file mode 100644
index 0000000..2514b1b
--- /dev/null
+++ b/.config/nvim/lua/config/lazy.lua
@@ -0,0 +1,92 @@
+-- Bootstrap lazy.nvim
+local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
+if not (vim.uv or vim.loop).fs_stat(lazypath) then
+ local lazyrepo = "https://github.com/folke/lazy.nvim.git"
+ local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
+ if vim.v.shell_error ~= 0 then
+ vim.api.nvim_echo({
+ { "Failed to clone lazy.nvim:\n", "ErrorMsg" },
+ { out, "WarningMsg" },
+ { "\nPress any key to exit..." },
+ }, true, {})
+ vim.fn.getchar()
+ os.exit(1)
+ end
+end
+vim.opt.rtp:prepend(lazypath)
+
+-- Make sure to setup `mapleader` and `maplocalleader` before
+-- loading lazy.nvim so that mappings are correct.
+-- This is also a good place to setup other settings (vim.opt)
+vim.g.mapleader = "'"
+vim.g.maplocalleader = "\\"
+
+-- Setup lazy.nvim
+require("lazy").setup({
+ spec = {
+ -- import your plugins
+ { import = "plugins" },
+ },
+ -- Configure any other settings here. See the documentation for more details.
+ -- colorscheme that will be used when installing plugins.
+ {colorscheme = "dracula"},
+ -- automatically check for plugin updates
+ checker = { enabled = true },
+})
+-- vim script
+vim.cmd[[colorscheme dracula]]
+vim.cmd[[syntax enable]]
+vim.cmd[[set number noswapfile hlsearch ignorecase incsearch cursorline]]
+vim.cmd[[autocmd BufNewFile,BufRead ~/.config/waybar/config,*.json set ft=javascript]]
+vim.cmd[[hi Normal guibg=NONE ctermbg=NONE]]
+vim.cmd[[highlight CursorLine ctermbg=black]]
+vim.cmd[[set noshowmode]]
+-- lualine
+require('lualine').setup {
+ options = {
+ icons_enabled = true,
+ theme = 'dracula',
+ component_separators = { left = '', right = ''},
+ section_separators = { left = '', right = ''},
+ disabled_filetypes = {
+ statusline = {},
+ winbar = {},
+ },
+ ignore_focus = {},
+ always_divide_middle = true,
+ always_show_tabline = true,
+ globalstatus = false,
+ refresh = {
+ statusline = 100,
+ tabline = 100,
+ winbar = 100,
+ }
+ },
+ sections = {
+ lualine_a = {'mode'},
+ lualine_b = {'branch', 'diff', 'diagnostics'},
+ lualine_c = {'filename'},
+ lualine_x = {'encoding', 'fileformat', 'filetype'},
+ lualine_y = {'progress'},
+ lualine_z = {'location'}
+ },
+ inactive_sections = {
+ lualine_a = {},
+ lualine_b = {},
+ lualine_c = {'filename'},
+ lualine_x = {'location'},
+ lualine_y = {},
+ lualine_z = {}
+ },
+ tabline = {
+ lualine_a = {'buffers'},
+ lualine_b = {'branch'},
+ lualine_c = {'filename'},
+ lualine_x = {},
+ lualine_y = {},
+ lualine_z = {'tabs'}
+ },
+ winbar = {},
+ inactive_winbar = {},
+ extensions = {}
+}
diff --git a/.config/nvim/lua/plugins/coq.lua b/.config/nvim/lua/plugins/coq.lua
new file mode 100644
index 0000000..2ce438a
--- /dev/null
+++ b/.config/nvim/lua/plugins/coq.lua
@@ -0,0 +1,32 @@
+-- lualine
+return {
+{
+"neovim/nvim-lspconfig", -- REQUIRED: for native Neovim LSP integration
+ lazy = false, -- REQUIRED: tell lazy.nvim to start this plugin at startup
+ dependencies = {
+ -- main one
+ { "ms-jpq/coq_nvim", branch = "coq" },
+
+ -- 9000+ Snippets
+ { "ms-jpq/coq.artifacts", branch = "artifacts" },
+
+ -- lua & third party sources -- See https://github.com/ms-jpq/coq.thirdparty
+ -- Need to **configure separately**
+ { 'ms-jpq/coq.thirdparty', branch = "3p" }
+ -- - shell repl
+ -- - nvim lua api
+ -- - scientific calculator
+ -- - comment banner
+ -- - etc
+ },
+ init = function()
+ vim.g.coq_settings = {
+ auto_start = "shut-up", -- if you want to start COQ at startup
+ -- Your COQ settings here
+ }
+ end,
+ config = function()
+ -- Your LSP settings here
+ end,
+ }
+}
diff --git a/.config/nvim/lua/plugins/dracula.lua b/.config/nvim/lua/plugins/dracula.lua
new file mode 100644
index 0000000..70f0c3b
--- /dev/null
+++ b/.config/nvim/lua/plugins/dracula.lua
@@ -0,0 +1,7 @@
+-- lualine
+return {
+{
+ "Mofiqul/dracula.nvim",
+ lazy = false
+ }
+}
diff --git a/.config/nvim/lua/plugins/lualine.lua b/.config/nvim/lua/plugins/lualine.lua
new file mode 100644
index 0000000..f9a55c4
--- /dev/null
+++ b/.config/nvim/lua/plugins/lualine.lua
@@ -0,0 +1,8 @@
+-- lualine
+return {
+{
+ "nvim-lualine/lualine.nvim",
+ dependencies = { 'nvim-tree/nvim-web-devicons' },
+ lazy = false
+ }
+}
diff --git a/.config/yazi/flavors/dracula.yazi/flavor.toml b/.config/yazi/flavors/dracula.yazi/flavor.toml
new file mode 100644
index 0000000..01ded4a
--- /dev/null
+++ b/.config/yazi/flavors/dracula.yazi/flavor.toml
@@ -0,0 +1,170 @@
+# vim:fileencoding=utf-8:foldmethod=marker
+
+# : Manager {{{
+
+[manager]
+cwd = { fg = "#8be9fd" }
+
+# Hovered
+hovered = { reversed = true }
+preview_hovered = { underline = true }
+
+# Find
+find_keyword = { fg = "#f1fa8c", bold = true, italic = true, underline = true }
+find_position = { fg = "#ff79c6", bg = "reset", bold = true, italic = true }
+
+# Marker
+marker_copied = { fg = "#50fa7b", bg = "#50fa7b" }
+marker_cut = { fg = "#ff5555", bg = "#ff5555" }
+marker_marked = { fg = "#8be9fd", bg = "#8be9fd" }
+marker_selected = { fg = "#f1fa8c", bg = "#f1fa8c" }
+
+# Tab
+tab_active = { reversed = true }
+tab_inactive = {}
+tab_width = 1
+
+# Count
+count_copied = { fg = "#282a36", bg = "#50fa7b" }
+count_cut = { fg = "#282a36", bg = "#ff5555" }
+count_selected = { fg = "#282a36", bg = "#f1fa8c" }
+
+# Border
+border_symbol = "│"
+border_style = { fg = "#7282b5" }
+
+# : }}}
+
+
+# : Mode {{{
+
+[mode]
+
+normal_main = { fg = "#282a36", bg = "#bd93f9", bold = true }
+normal_alt = { fg = "#bd93f9", bg = "#44475a" }
+
+# Select mode
+select_main = { fg = "#282a36", bg = "#8be9fd", bold = true }
+select_alt = { fg = "#8be9fd", bg = "#44475a" }
+
+# Unset mode
+unset_main = { fg = "#282a36", bg = "#ffb86c", bold = true }
+unset_alt = { fg = "#ffb86c", bg = "#44475a" }
+
+# : }}}
+
+
+# : Status bar {{{
+
+[status]
+# Permissions
+perm_sep = { fg = "#7282b5" }
+perm_type = { fg = "#bd93f9" }
+perm_read = { fg = "#f1fa8c" }
+perm_write = { fg = "#ff5555" }
+perm_exec = { fg = "#50fa7b" }
+
+# Progress
+progress_label = { fg = "#ffffff", bold = true }
+progress_normal = { fg = "#bd93f9", bg = "#63667d" }
+progress_error = { fg = "#ff5555", bg = "#63667d" }
+
+# : }}}
+
+
+# : Pick {{{
+
+[pick]
+border = { fg = "#bd93f9" }
+active = { fg = "#ff79c6", bold = true }
+inactive = {}
+
+# : }}}
+
+
+# : Input {{{
+
+[input]
+border = { fg = "#bd93f9" }
+title = {}
+value = {}
+selected = { reversed = true }
+
+# : }}}
+
+
+# : Completion {{{
+
+[cmp]
+border = { fg = "#bd93f9" }
+
+# : }}}
+
+
+# : Tasks {{{
+
+[tasks]
+border = { fg = "#bd93f9" }
+title = {}
+hovered = { fg = "#ff79c6", underline = true }
+
+# : }}}
+
+
+# : Which {{{
+
+[which]
+mask = { bg = "#44475a" }
+cand = { fg = "#8be9fd" }
+rest = { fg = "#8998c9" }
+desc = { fg = "#ff79c6" }
+separator = " "
+separator_style = { fg = "#83869c" }
+
+# : }}}
+
+
+# : Help {{{
+
+[help]
+on = { fg = "#8be9fd" }
+run = { fg = "#ff79c6" }
+hovered = { reversed = true, bold = true }
+footer = { fg = "#44475a", bg = "#f8f8f2" }
+
+# : }}}
+
+
+# : Notify {{{
+
+[notify]
+title_info = { fg = "#50fa7b" }
+title_warn = { fg = "#f1fa8c" }
+title_error = { fg = "#ff5555" }
+
+# : }}}
+
+
+# : File-specific styles {{{
+
+[filetype]
+
+rules = [
+ # Images
+ { mime = "image/*", fg = "#8be9fd" },
+
+ # Media
+ { mime = "{audio,video}/*", fg = "#f1fa8c" },
+
+ # Archives
+ { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#ff79c6" },
+
+ # Documents
+ { mime = "application/{pdf,doc,rtf}", fg = "#50fa7b" },
+
+ # Fallback
+ { name = "*", fg = "#f8f8f2" },
+ { name = "*/", fg = "#bd93f9" }
+]
+
+# : }}}
diff --git a/.config/yazi/init.lua b/.config/yazi/init.lua
new file mode 100644
index 0000000..99e606a
--- /dev/null
+++ b/.config/yazi/init.lua
@@ -0,0 +1,59 @@
+-- full border
+require("full-border"):setup()
+-- size and time linemode
+function Linemode:size_and_mtime()
+ local time = math.floor(self._file.cha.mtime or 0)
+ if time == 0 then
+ time = ""
+ elseif os.date("%Y", time) == os.date("%Y") then
+ time = os.date("%b %d %H:%M", time)
+ else
+ time = os.date("%b %d %Y", time)
+ end
+
+ local size = self._file:size()
+ return string.format("%s %s", size and ya.readable_size(size) or "-", time)
+end
+
+-- show symlinks
+Status:children_add(function(self)
+ local h = self._current.hovered
+ if h and h.link_to then
+ return " -> " .. tostring(h.link_to)
+ else
+ return ""
+ end
+end, 3300, Status.LEFT)
+-- Show user/group of files in status bar
+Status:children_add(function()
+ local h = cx.active.current.hovered
+ if h == nil or ya.target_family() ~= "unix" then
+ return ""
+ end
+
+ return ui.Line {
+ ui.Span(ya.user_name(h.cha.uid) or tostring(h.cha.uid)):fg("magenta"),
+ ":",
+ ui.Span(ya.group_name(h.cha.gid) or tostring(h.cha.gid)):fg("magenta"),
+ " ",
+ }
+end, 500, Status.RIGHT)
+-- Show username and hostname in header
+Header:children_add(function()
+ if ya.target_family() ~= "unix" then
+ return ""
+ end
+ return ui.Span(ya.user_name() .. "@" .. ya.host_name() .. ":"):fg("blue")
+end, 500, Header.LEFT)
+-- startship prompt
+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
+})
+-- git for yazi
+require("git"):setup()
diff --git a/.config/yazi/keymap.toml b/.config/yazi/keymap.toml
new file mode 100644
index 0000000..a175477
--- /dev/null
+++ b/.config/yazi/keymap.toml
@@ -0,0 +1,108 @@
+[[manager.prepend_keymap]]
+on = "!"
+run = 'shell "$SHELL" --block'
+desc = "Open shell here"
+
+[[manager.prepend_keymap]]
+on = "T"
+run = "plugin toggle-pane min-preview"
+desc = "Show or hide the preview pane"
+
+[[manager.prepend_keymap]]
+on = [ "g", "d" ]
+run = "cd /mnt/M2/Downloads"
+desc = "Downloads"
+
+[[manager.prepend_keymap]]
+on = [ "g", "b" ]
+run = "cd /mnt/Backups"
+desc = "Backups"
+
+[[manager.prepend_keymap]]
+on = [ "g", "v" ]
+run = "cd /media/Games"
+desc = "Games"
+
+[[manager.prepend_keymap]]
+on = [ "g", "s" ]
+run = "cd /mnt/SSD"
+desc = "SSD"
+
+[[manager.prepend_keymap]]
+on = [ "g", "m" ]
+run = "cd /mnt/M2"
+desc = "M2"
+
+[[manager.prepend_keymap]]
+on = [ "g", "n" ]
+run = "cd /mnt/M2/Nextcloud"
+desc = "Nextcloud"
+
+[[manager.prepend_keymap]]
+on = [ "g", "r" ]
+run = "cd /run/media"
+desc = "Media"
+
+[[manager.prepend_keymap]]
+on = [ "g", "a" ]
+run = "cd /mnt/M2/Downloads/AUR"
+desc = "AUR"
+
+[[manager.prepend_keymap]]
+on = [ "g", "!" ]
+run = "cd ~/.bin/sh"
+desc = "Scripts"
+
+[[manager.prepend_keymap]]
+on = [ "g", "t" ]
+run = "cd ~/.local/share/Trash/files"
+desc = "Trash"
+
+[[manager.prepend_keymap]]
+on = [ "g", "y" ]
+run = "cd ~/Sync"
+desc = "Sync"
+
+[[manager.prepend_keymap]]
+on = [ "c", "m" ]
+run = "plugin chmod"
+desc = "Chmod on selected files"
+
+[[manager.prepend_keymap]]
+on = ["C"]
+run = "plugin ouch"
+desc = "Compress with ouch"
+
+[[manager.prepend_keymap]]
+on = "2"
+run = "plugin smart-switch 1"
+desc = "Switch or create tab 2"
+
+[[manager.prepend_keymap]]
+on = "3"
+run = "plugin smart-switch 2"
+desc = "Switch or create tab 3"
+
+[[manager.prepend_keymap]]
+on = "4"
+run = "plugin smart-switch 3"
+desc = "Switch or create tab 4"
+
+[[manager.prepend_keymap]]
+on = "5"
+run = "plugin smart-switch 4"
+desc = "Switch or create tab 5"
+
+[[manager.prepend_keymap]]
+on = "t"
+run = "plugin smart-tab"
+desc = "Create a tab and enter the hovered directory"
+
+[[manager.prepend_keymap]]
+on = [ "g", "R" ]
+run = 'shell -- ya emit cd "$(git rev-parse --show-toplevel)"'
+desc = "Repo root"
+
+[[manager.prepend_keymap]]
+on = "q"
+run = "plugin confirm-quit"
diff --git a/.config/yazi/plugins/chmod.yazi/LICENSE b/.config/yazi/plugins/chmod.yazi/LICENSE
new file mode 100644
index 0000000..fb5b1d6
--- /dev/null
+++ b/.config/yazi/plugins/chmod.yazi/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 yazi-rs
+
+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/.config/yazi/plugins/chmod.yazi/README.md b/.config/yazi/plugins/chmod.yazi/README.md
new file mode 100644
index 0000000..e8a6614
--- /dev/null
+++ b/.config/yazi/plugins/chmod.yazi/README.md
@@ -0,0 +1,28 @@
+# chmod.yazi
+
+Execute `chmod` on the selected files to change their mode. This plugin is only available on Unix platforms since it relies on [`chmod(2)`](https://man7.org/linux/man-pages/man2/chmod.2.html).
+
+https://github.com/yazi-rs/plugins/assets/17523360/7aa3abc2-d057-498c-8473-a6282c59c464
+
+## Installation
+
+```sh
+ya pack -a yazi-rs/plugins:chmod
+```
+
+## Usage
+
+Add this to your `~/.config/yazi/keymap.toml`:
+
+```toml
+[[manager.prepend_keymap]]
+on = [ "c", "m" ]
+run = "plugin chmod"
+desc = "Chmod on selected files"
+```
+
+Make sure the c => m key is not used elsewhere.
+
+## License
+
+This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
diff --git a/.config/yazi/plugins/chmod.yazi/main.lua b/.config/yazi/plugins/chmod.yazi/main.lua
new file mode 100644
index 0000000..ad565c6
--- /dev/null
+++ b/.config/yazi/plugins/chmod.yazi/main.lua
@@ -0,0 +1,41 @@
+--- @since 25.2.26
+
+local selected_or_hovered = ya.sync(function()
+ local tab, paths = cx.active, {}
+ for _, u in pairs(tab.selected) do
+ paths[#paths + 1] = tostring(u)
+ end
+ if #paths == 0 and tab.current.hovered then
+ paths[1] = tostring(tab.current.hovered.url)
+ end
+ return paths
+end)
+
+return {
+ entry = function()
+ ya.mgr_emit("escape", { visual = true })
+
+ local urls = selected_or_hovered()
+ if #urls == 0 then
+ return ya.notify { title = "Chmod", content = "No file selected", level = "warn", timeout = 5 }
+ end
+
+ local value, event = ya.input {
+ title = "Chmod:",
+ position = { "top-center", y = 3, w = 40 },
+ }
+ if event ~= 1 then
+ return
+ end
+
+ local status, err = Command("chmod"):arg(value):args(urls):spawn():wait()
+ if not status or not status.success then
+ ya.notify {
+ title = "Chmod",
+ content = string.format("Chmod on selected files failed, error: %s", status and status.code or err),
+ level = "error",
+ timeout = 5,
+ }
+ end
+ end,
+}
diff --git a/.config/yazi/plugins/confirm-quit.yazi/main.lua b/.config/yazi/plugins/confirm-quit.yazi/main.lua
new file mode 100644
index 0000000..6394880
--- /dev/null
+++ b/.config/yazi/plugins/confirm-quit.yazi/main.lua
@@ -0,0 +1,18 @@
+local count = ya.sync(function() return #cx.tabs end)
+
+local function entry()
+ if count() < 2 then
+ return ya.mgr_emit("quit", {})
+ end
+
+ local yes = ya.confirm {
+ pos = { "center", w = 60, h = 10 },
+ title = "Quit?",
+ content = ui.Text("There are multiple tabs open. Quit anyway?"):wrap(ui.Text.WRAP),
+ }
+ if yes then
+ ya.mgr_emit("quit", {})
+ end
+end
+
+return { entry = entry }
diff --git a/.config/yazi/plugins/full-border.yazi/LICENSE b/.config/yazi/plugins/full-border.yazi/LICENSE
new file mode 100644
index 0000000..fb5b1d6
--- /dev/null
+++ b/.config/yazi/plugins/full-border.yazi/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 yazi-rs
+
+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/.config/yazi/plugins/full-border.yazi/README.md b/.config/yazi/plugins/full-border.yazi/README.md
new file mode 100644
index 0000000..6e78bd4
--- /dev/null
+++ b/.config/yazi/plugins/full-border.yazi/README.md
@@ -0,0 +1,32 @@
+# full-border.yazi
+
+Add a full border to Yazi to make it look fancier.
+
+
+
+## Installation
+
+```sh
+ya pack -a yazi-rs/plugins:full-border
+```
+
+## Usage
+
+Add this to your `init.lua` to enable the plugin:
+
+```lua
+require("full-border"):setup()
+```
+
+Or you can customize the border type:
+
+```lua
+require("full-border"):setup {
+ -- Available values: ui.Border.PLAIN, ui.Border.ROUNDED
+ type = ui.Border.ROUNDED,
+}
+```
+
+## License
+
+This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
diff --git a/.config/yazi/plugins/full-border.yazi/main.lua b/.config/yazi/plugins/full-border.yazi/main.lua
new file mode 100644
index 0000000..abb1c3e
--- /dev/null
+++ b/.config/yazi/plugins/full-border.yazi/main.lua
@@ -0,0 +1,43 @@
+--- @since 25.2.26
+
+local function setup(_, opts)
+ local type = opts and opts.type or ui.Border.ROUNDED
+ local old_build = Tab.build
+
+ Tab.build = function(self, ...)
+ local bar = function(c, x, y)
+ if x <= 0 or x == self._area.w - 1 or th.mgr.border_symbol ~= "│" then
+ return ui.Bar(ui.Bar.TOP)
+ 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]:pad(ui.Pad.y(1)),
+ c[2]:pad(ui.Pad(1, c[3].w > 0 and 0 or 1, 1, c[1].w > 0 and 0 or 1)),
+ c[3]:pad(ui.Pad.y(1)),
+ }
+
+ local style = th.mgr.border_style
+ self._base = ya.list_merge(self._base or {}, {
+ ui.Border(ui.Border.ALL):area(self._area):type(type):style(style),
+ ui.Bar(ui.Bar.RIGHT):area(self._chunks[1]):style(style),
+ ui.Bar(ui.Bar.LEFT):area(self._chunks[3]):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
+end
+
+return { setup = setup }
diff --git a/.config/yazi/plugins/git.yazi/LICENSE b/.config/yazi/plugins/git.yazi/LICENSE
new file mode 100644
index 0000000..fb5b1d6
--- /dev/null
+++ b/.config/yazi/plugins/git.yazi/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 yazi-rs
+
+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/.config/yazi/plugins/git.yazi/README.md b/.config/yazi/plugins/git.yazi/README.md
new file mode 100644
index 0000000..4c5b07c
--- /dev/null
+++ b/.config/yazi/plugins/git.yazi/README.md
@@ -0,0 +1,78 @@
+# git.yazi
+
+> [!NOTE]
+> Yazi v25.2.26 or later is required for this plugin to work.
+
+Show the status of Git file changes as linemode in the file list.
+
+https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576
+
+## Installation
+
+```sh
+ya pack -a yazi-rs/plugins:git
+```
+
+## Setup
+
+Add the following to your `~/.config/yazi/init.lua`:
+
+```lua
+require("git"):setup()
+```
+
+And register it as fetchers in your `~/.config/yazi/yazi.toml`:
+
+```toml
+[[plugin.prepend_fetchers]]
+id = "git"
+name = "*"
+run = "git"
+
+[[plugin.prepend_fetchers]]
+id = "git"
+name = "*/"
+run = "git"
+```
+
+## Advanced
+
+You can customize the [Style](https://yazi-rs.github.io/docs/plugins/layout#style) of the status sign with:
+
+- `th.git.modified`
+- `th.git.added`
+- `th.git.untracked`
+- `th.git.ignored`
+- `th.git.deleted`
+- `th.git.updated`
+
+For example:
+
+```lua
+-- ~/.config/yazi/init.lua
+th.git = th.git or {}
+th.git.modified = ui.Style():fg("blue")
+th.git.deleted = ui.Style():fg("red"):bold()
+```
+
+You can also customize the text of the status sign with:
+
+- `th.git.modified_sign`
+- `th.git.added_sign`
+- `th.git.untracked_sign`
+- `th.git.ignored_sign`
+- `th.git.deleted_sign`
+- `th.git.updated_sign`
+
+For example:
+
+```lua
+-- ~/.config/yazi/init.lua
+th.git = th.git or {}
+th.git.modified_sign = "M"
+th.git.deleted_sign = "D"
+```
+
+## License
+
+This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
diff --git a/.config/yazi/plugins/git.yazi/main.lua b/.config/yazi/plugins/git.yazi/main.lua
new file mode 100644
index 0000000..d8f365a
--- /dev/null
+++ b/.config/yazi/plugins/git.yazi/main.lua
@@ -0,0 +1,228 @@
+--- @since 25.4.4
+
+local WINDOWS = ya.target_family() == "windows"
+
+-- The code of supported git status,
+-- also used to determine which status to show for directories when they contain different statuses
+-- see `bubble_up`
+local CODES = {
+ excluded = 100, -- ignored directory
+ ignored = 6, -- ignored file
+ untracked = 5,
+ modified = 4,
+ added = 3,
+ deleted = 2,
+ updated = 1,
+ unknown = 0,
+}
+
+local PATTERNS = {
+ { "!$", CODES.ignored },
+ { "?$", CODES.untracked },
+ { "[MT]", CODES.modified },
+ { "[AC]", CODES.added },
+ { "D", CODES.deleted },
+ { "U", CODES.updated },
+ { "[AD][AD]", CODES.updated },
+}
+
+local function match(line)
+ local signs = line:sub(1, 2)
+ for _, p in ipairs(PATTERNS) do
+ local path, pattern, code = nil, p[1], p[2]
+ if signs:find(pattern) then
+ path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4)
+ path = WINDOWS and path:gsub("/", "\\") or path
+ end
+ if not path then
+ elseif path:find("[/\\]$") then
+ -- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down`
+ return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2)
+ else
+ return code, path
+ end
+ end
+end
+
+local function root(cwd)
+ local is_worktree = function(url)
+ local file, head = io.open(tostring(url)), nil
+ if file then
+ head = file:read(8)
+ file:close()
+ end
+ return head == "gitdir: "
+ end
+
+ repeat
+ local next = cwd:join(".git")
+ local cha = fs.cha(next)
+ if cha and (cha.is_dir or is_worktree(next)) then
+ return tostring(cwd)
+ end
+ cwd = cwd.parent
+ until not cwd
+end
+
+local function bubble_up(changed)
+ local new, empty = {}, Url("")
+ for path, code in pairs(changed) do
+ if code ~= CODES.ignored then
+ local url = Url(path).parent
+ while url and url ~= empty do
+ local s = tostring(url)
+ new[s] = (new[s] or CODES.unknown) > code and new[s] or code
+ url = url.parent
+ end
+ end
+ end
+ return new
+end
+
+local function propagate_down(excluded, cwd, repo)
+ local new, rel = {}, cwd:strip_prefix(repo)
+ for _, path in ipairs(excluded) do
+ if rel:starts_with(path) then
+ -- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded`
+ new[tostring(cwd)] = CODES.excluded
+ elseif cwd == repo:join(path).parent then
+ -- If `path` is a direct subdirectory of `cwd`, mark it as `ignored`
+ new[path] = CODES.ignored
+ else
+ -- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance
+ end
+ end
+ return new
+end
+
+local add = ya.sync(function(st, cwd, repo, changed)
+ st.dirs[cwd] = repo
+ st.repos[repo] = st.repos[repo] or {}
+ for path, code in pairs(changed) do
+ if code == CODES.unknown then
+ st.repos[repo][path] = nil
+ elseif code == CODES.excluded then
+ -- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering
+ st.dirs[path] = CODES.excluded
+ else
+ st.repos[repo][path] = code
+ end
+ end
+ ya.render()
+end)
+
+local remove = ya.sync(function(st, cwd)
+ local repo = st.dirs[cwd]
+ if not repo then
+ return
+ end
+
+ ya.render()
+ st.dirs[cwd] = nil
+ if not st.repos[repo] then
+ return
+ end
+
+ for _, r in pairs(st.dirs) do
+ if r == repo then
+ return
+ end
+ end
+ st.repos[repo] = nil
+end)
+
+local function setup(st, opts)
+ st.dirs = {} -- Mapping between a directory and its corresponding repository
+ st.repos = {} -- Mapping between a repository and the status of each of its files
+
+ opts = opts or {}
+ opts.order = opts.order or 1500
+
+ local t = th.git or {}
+ local styles = {
+ [CODES.ignored] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("darkgray"),
+ [CODES.untracked] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("magenta"),
+ [CODES.modified] = t.modified and ui.Style(t.modified) or ui.Style():fg("yellow"),
+ [CODES.added] = t.added and ui.Style(t.added) or ui.Style():fg("green"),
+ [CODES.deleted] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("red"),
+ [CODES.updated] = t.updated and ui.Style(t.updated) or ui.Style():fg("yellow"),
+ }
+ local signs = {
+ [CODES.ignored] = t.ignored_sign or "",
+ [CODES.untracked] = t.untracked_sign or "?",
+ [CODES.modified] = t.modified_sign or "",
+ [CODES.added] = t.added_sign or "",
+ [CODES.deleted] = t.deleted_sign or "",
+ [CODES.updated] = t.updated_sign or "",
+ }
+
+ Linemode:children_add(function(self)
+ local url = self._file.url
+ local repo = st.dirs[tostring(url.base)]
+ local code
+ if repo then
+ code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)]
+ end
+
+ if not code or signs[code] == "" then
+ return ""
+ elseif self._file.is_hovered then
+ return ui.Line { " ", signs[code] }
+ else
+ return ui.Line { " ", ui.Span(signs[code]):style(styles[code]) }
+ end
+ end, opts.order)
+end
+
+local function fetch(_, job)
+ local cwd = job.files[1].url.base
+ local repo = root(cwd)
+ if not repo then
+ remove(tostring(cwd))
+ return true
+ end
+
+ local paths = {}
+ for _, file in ipairs(job.files) do
+ paths[#paths + 1] = tostring(file.url)
+ end
+
+ -- stylua: ignore
+ local output, err = Command("git")
+ :cwd(tostring(cwd))
+ :args({ "--no-optional-locks", "-c", "core.quotePath=", "status", "--porcelain", "-unormal", "--no-renames", "--ignored=matching" })
+ :args(paths)
+ :stdout(Command.PIPED)
+ :output()
+ if not output then
+ return true, Err("Cannot spawn `git` command, error: %s", err)
+ end
+
+ local changed, excluded = {}, {}
+ for line in output.stdout:gmatch("[^\r\n]+") do
+ local code, path = match(line)
+ if code == CODES.excluded then
+ excluded[#excluded + 1] = path
+ else
+ changed[path] = code
+ end
+ end
+
+ if job.files[1].cha.is_dir then
+ ya.dict_merge(changed, bubble_up(changed))
+ end
+ ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo)))
+
+ -- Reset the status of any files that don't appear in the output of `git status` to `unknown`,
+ -- so that cleaning up outdated statuses from `st.repos`
+ for _, path in ipairs(paths) do
+ local s = path:sub(#repo + 2)
+ changed[s] = changed[s] or CODES.unknown
+ end
+
+ add(tostring(cwd), repo, changed)
+
+ return false
+end
+
+return { setup = setup, fetch = fetch }
diff --git a/.config/yazi/plugins/ouch.yazi/LICENSE b/.config/yazi/plugins/ouch.yazi/LICENSE
new file mode 100644
index 0000000..3f9d766
--- /dev/null
+++ b/.config/yazi/plugins/ouch.yazi/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 ndtoan96
+
+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/.config/yazi/plugins/ouch.yazi/README.md b/.config/yazi/plugins/ouch.yazi/README.md
new file mode 100644
index 0000000..e09b3df
--- /dev/null
+++ b/.config/yazi/plugins/ouch.yazi/README.md
@@ -0,0 +1,79 @@
+# ouch.yazi
+
+[ouch](https://github.com/ouch-org/ouch) plugin for [Yazi](https://github.com/sxyazi/yazi).
+
+
+
+## Features
+- Archive preview
+- Compression
+
+## Installation
+
+### Yazi package manager
+```bash
+ya pack -a ndtoan96/ouch
+```
+
+### Git
+```bash
+# Linux/macOS
+git clone https://github.com/ndtoan96/ouch.yazi.git ~/.config/yazi/plugins/ouch.yazi
+
+# Windows with cmd
+git clone https://github.com/ndtoan96/ouch.yazi.git %AppData%\yazi\config\plugins\ouch.yazi
+
+# Windows with powershell
+git clone https://github.com/ndtoan96/ouch.yazi.git "$($env:APPDATA)\yazi\config\plugins\ouch.yazi"
+```
+
+Make sure you have [ouch](https://github.com/ouch-org/ouch) installed and in your `PATH`.
+
+## Usage
+
+### Preview
+For archive preview, add this to your `yazi.toml`:
+
+```toml
+[plugin]
+prepend_previewers = [
+ # Archive previewer
+ { mime = "application/*zip", run = "ouch" },
+ { mime = "application/x-tar", run = "ouch" },
+ { mime = "application/x-bzip2", run = "ouch" },
+ { mime = "application/x-7z-compressed", run = "ouch" },
+ { mime = "application/x-rar", run = "ouch" },
+ { mime = "application/x-xz", run = "ouch" },
+ { mime = "application/xz", run = "ouch" },
+]
+```
+
+Now go to an archive on Yazi, you should see the archive's content in the preview pane. You can use `J` and `K` to roll up and down the preview.
+
+If you want to change the icon or the style of text, you can modify the `peek` function in `init.lua` file (all of them are stored in the `lines` variable).
+
+### Compression
+For compession, add this to your `keymap.toml`:
+
+```toml
+[[manager.prepend_keymap]]
+on = ["C"]
+run = "plugin ouch"
+desc = "Compress with ouch"
+```
+
+The plugin uses `zip` format by default. You can change the format when you name the output file, `ouch` will detect format based on file extension.
+
+And, for example, if you would like to set `7z` as default format, you can use `plugin ouch 7z`.
+
+### Decompression
+This plugin does not provide a decompression feature because it already is supported by Yazi.
+To decompress with `ouch`, configure the opener in `yazi.toml`.
+
+```toml
+[opener]
+extract = [
+ { run = 'ouch d -y "%*"', desc = "Extract here with ouch", for = "windows" },
+ { run = 'ouch d -y "$@"', desc = "Extract here with ouch", for = "unix" },
+]
+```
diff --git a/.config/yazi/plugins/ouch.yazi/main.lua b/.config/yazi/plugins/ouch.yazi/main.lua
new file mode 100644
index 0000000..f642eb3
--- /dev/null
+++ b/.config/yazi/plugins/ouch.yazi/main.lua
@@ -0,0 +1,148 @@
+local M = {}
+
+function M:peek(job)
+ local child = Command("ouch")
+ :args({ "l", "-t", "-y", tostring(job.file.url) })
+ :stdout(Command.PIPED)
+ :stderr(Command.PIPED)
+ :spawn()
+ local limit = job.area.h
+ local file_name = string.match(tostring(job.file.url), ".*[/\\](.*)")
+ local lines = string.format("📁 \x1b[2m%s\x1b[0m\n", file_name)
+ local num_lines = 1
+ local num_skip = 0
+ repeat
+ local line, event = child:read_line()
+ if event == 1 then
+ ya.err(tostring(event))
+ elseif event ~= 0 then
+ break
+ end
+
+ if line:find('Archive', 1, true) ~= 1 and line:find('[INFO]', 1, true) ~= 1 then
+ if num_skip >= job.skip then
+ lines = lines .. line
+ num_lines = num_lines + 1
+ else
+ num_skip = num_skip + 1
+ end
+ end
+ until num_lines >= limit
+
+ child:start_kill()
+ if job.skip > 0 and num_lines < limit then
+ ya.manager_emit(
+ "peek",
+ { tostring(math.max(0, job.skip - (limit - num_lines))), only_if = tostring(job.file.url), upper_bound = "" }
+ )
+ else
+ ya.preview_widgets(job, { ui.Text(lines):area(job.area) })
+ end
+end
+
+function M:seek(job)
+ local h = cx.active.current.hovered
+ if h and h.url == job.file.url then
+ local step = math.floor(job.units * job.area.h / 10)
+ ya.manager_emit("peek", {
+ math.max(0, cx.active.preview.skip + step),
+ only_if = tostring(job.file.url),
+ })
+ end
+end
+
+-- Check if file exists
+local function file_exists(name)
+ local f = io.open(name, "r")
+ if f ~= nil then
+ io.close(f)
+ return true
+ else
+ return false
+ end
+end
+
+-- Get the files that need to be compressed and infer a default archive name
+local get_compression_target = ya.sync(function()
+ local tab = cx.active
+ local default_name
+ local paths = {}
+ if #tab.selected == 0 then
+ if tab.current.hovered then
+ local name = tab.current.hovered.name
+ default_name = name
+ table.insert(paths, name)
+ else
+ return
+ end
+ else
+ default_name = tab.current.cwd.name
+ for _, url in pairs(tab.selected) do
+ table.insert(paths, tostring(url))
+ end
+ -- The compression targets are aquired, now unselect them
+ ya.manager_emit("escape", {})
+ end
+ return paths, default_name
+end)
+
+local function invoke_compress_command(paths, name)
+ local cmd_output, err_code = Command("ouch")
+ :args({ "c", "-y" })
+ :args(paths)
+ :arg(name)
+ :stderr(Command.PIPED)
+ :output()
+ if err_code ~= nil then
+ ya.notify({
+ title = "Failed to run ouch command",
+ content = "Status: " .. err_code,
+ timeout = 5.0,
+ level = "error",
+ })
+ elseif not cmd_output.status.success then
+ ya.notify({
+ title = "Compression failed: status code " .. cmd_output.status.code,
+ content = cmd_output.stderr,
+ timeout = 5.0,
+ level = "error",
+ })
+ end
+end
+
+function M:entry(job)
+ local default_fmt = job.args[1]
+ if default_fmt == nil then
+ default_fmt = "zip"
+ end
+
+ ya.manager_emit("escape", { visual = true })
+
+ -- Get the files that need to be compressed and infer a default archive name
+ local paths, default_name = get_compression_target()
+
+ -- Get archive name from user
+ local output_name, name_event = ya.input({
+ title = "Create archive:",
+ value = default_name .. "." .. default_fmt,
+ position = { "top-center", y = 3, w = 40 },
+ })
+ if name_event ~= 1 then
+ return
+ end
+
+ -- Get confirmation if file exists
+ if file_exists(output_name) then
+ local confirm, confirm_event = ya.input({
+ title = "Overwrite " .. output_name .. "? (y/N)",
+ position = { "top-center", y = 3, w = 40 },
+ })
+ if not (confirm_event == 1 and confirm:lower() == "y") then
+ return
+ end
+ end
+
+ invoke_compress_command(paths, output_name)
+end
+
+return M
diff --git a/.config/yazi/plugins/smart-switch.yazi/main.lua b/.config/yazi/plugins/smart-switch.yazi/main.lua
new file mode 100644
index 0000000..aae94f4
--- /dev/null
+++ b/.config/yazi/plugins/smart-switch.yazi/main.lua
@@ -0,0 +1,13 @@
+--- @sync entry
+local function entry(_, job)
+ local cur = cx.active.current
+ for _ = #cx.tabs, job.args[1] do
+ ya.mgr_emit("tab_create", { cur.cwd })
+ if cur.hovered then
+ ya.mgr_emit("reveal", { cur.hovered.url })
+ end
+ end
+ ya.mgr_emit("tab_switch", { job.args[1] })
+end
+
+return { entry = entry }
diff --git a/.config/yazi/plugins/smart-tab.yazi/main.lua b/.config/yazi/plugins/smart-tab.yazi/main.lua
new file mode 100644
index 0000000..107f706
--- /dev/null
+++ b/.config/yazi/plugins/smart-tab.yazi/main.lua
@@ -0,0 +1,7 @@
+--- @sync entry
+return {
+ entry = function()
+ local h = cx.active.current.hovered
+ ya.mgr_emit("tab_create", h and h.cha.is_dir and { h.url } or { current = true })
+ end,
+}
diff --git a/.config/yazi/plugins/starship.yazi/LICENSE b/.config/yazi/plugins/starship.yazi/LICENSE
new file mode 100644
index 0000000..c03ce66
--- /dev/null
+++ b/.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/.config/yazi/plugins/starship.yazi/README.md b/.config/yazi/plugins/starship.yazi/README.md
new file mode 100644
index 0000000..b2b4dd8
--- /dev/null
+++ b/.config/yazi/plugins/starship.yazi/README.md
@@ -0,0 +1,108 @@
+# starship.yazi
+
+Starship prompt plugin for [Yazi](https://github.com/sxyazi/yazi)
+
+
+
+## Requirements
+
+- [Yazi](https://github.com/sxyazi/yazi) (v25.4.8+)
+- [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]:pad(ui.Pad.y(1)),
+ c[2]:pad(ui.Pad(1, c[3].w > 0 and 0 or 1, 1, c[1].w > 0 and 0 or 1)),
+ c[3]:pad(ui.Pad.y(1)),
+ }
+
+ local style = th.mgr.border_style
+ self._base = ya.list_merge(self._base or {}, {
+ 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/.config/yazi/plugins/starship.yazi/main.lua b/.config/yazi/plugins/starship.yazi/main.lua
new file mode 100644
index 0000000..c4d3943
--- /dev/null
+++ b/.config/yazi/plugins/starship.yazi/main.lua
@@ -0,0 +1,123 @@
+--- @since 25.4.8
+
+-- 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
+
+ ya.manager_emit("plugin", {
+ st._id,
+ ya.quote(tostring(cwd), true),
+ })
+ end
+ end
+
+ -- Subscribe to events
+ ps.sub("cd", callback)
+ ps.sub("tab", callback)
+ end,
+
+ entry = function(_, job)
+ local args = job.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/.config/yazi/plugins/toggle-pane.yazi/LICENSE b/.config/yazi/plugins/toggle-pane.yazi/LICENSE
new file mode 100644
index 0000000..fb5b1d6
--- /dev/null
+++ b/.config/yazi/plugins/toggle-pane.yazi/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 yazi-rs
+
+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/.config/yazi/plugins/toggle-pane.yazi/README.md b/.config/yazi/plugins/toggle-pane.yazi/README.md
new file mode 100644
index 0000000..d00c169
--- /dev/null
+++ b/.config/yazi/plugins/toggle-pane.yazi/README.md
@@ -0,0 +1,78 @@
+# toggle-pane.yazi
+
+Toggle the show, hide, and maximize states for different panes: parent, current, and preview. It respects the user's [`ratio` settings](https://yazi-rs.github.io/docs/configuration/yazi#manager.ratio)!
+
+Assume the user's `ratio` is $$[A, B, C]$$, that is, $$\text{parent}=A, \text{current}=B, \text{preview}=C$$:
+
+- `min-parent`: Toggles between $$0$$ and $$A$$ - the parent is either completely hidden or showed with width $$A$$.
+- `max-parent`: Toggles between $$A$$ and $$\infty$$ - the parent is either showed with width $$A$$ or fills the entire screen.
+- `min-current`: Toggles between $$0$$ and $$B$$ - the current is either completely hidden or showed with width $$B$$.
+- `max-current`: Toggles between $$B$$ and $$\infty$$ - the current is either showed with width $$B$$ or fills the entire screen.
+- `min-preview`: Toggles between $$0$$ and $$C$$ - the preview is either completely hidden or showed with width $$C$$.
+- `max-preview`: Toggles between $$C$$ and $$\infty$$ - the preview is either showed with width $$C$$ or fills the entire screen.
+- `reset`: Resets to the user's configured `ratio`.
+
+## Installation
+
+```sh
+ya pack -a yazi-rs/plugins:toggle-pane
+```
+
+## Usage
+
+Hide/Show preview:
+
+```toml
+# keymap.toml
+[[manager.prepend_keymap]]
+on = "T"
+run = "plugin toggle-pane min-preview"
+desc = "Show or hide the preview pane"
+```
+
+Maximize/Restore preview:
+
+```toml
+# keymap.toml
+[[manager.prepend_keymap]]
+on = "T"
+run = "plugin toggle-pane max-preview"
+desc = "Maximize or restore the preview pane"
+```
+
+You can replace `preview` with `current` or `parent` to toggle the other panes.
+
+## Advanced
+
+In addition to triggering the plugin with a keypress, you can also trigger it in your `init.lua` file:
+
+```lua
+if os.getenv("NVIM") then
+ require("toggle-pane"):entry("min-preview")
+end
+```
+
+In the example above, when it detects that you're [using Yazi in nvim](https://yazi-rs.github.io/docs/resources#vim), the preview is hidden by default — you can always press `T` (or any key you've bound) to show it again.
+
+## Tips
+
+This plugin only maximizes the "available preview area", without actually changing the content size.
+
+This means that the appearance of your preview largely depends on the previewer you are using.
+However, most previewers tend to make the most of the available space, so this usually isn't an issue.
+
+For image previews, you may want to tune up the [`max_width`][max-width] and [`max_height`][max-height] options in your `yazi.toml`:
+
+```toml
+[preview]
+# Change them to your desired values
+max_width = 1000
+max_height = 1000
+```
+
+[max-width]: https://yazi-rs.github.io/docs/configuration/yazi/#preview.max_width
+[max-height]: https://yazi-rs.github.io/docs/configuration/yazi/#preview.max_height
+
+## License
+
+This plugin is MIT-licensed. For more information, check the [LICENSE](LICENSE) file.
diff --git a/.config/yazi/plugins/toggle-pane.yazi/main.lua b/.config/yazi/plugins/toggle-pane.yazi/main.lua
new file mode 100644
index 0000000..b408692
--- /dev/null
+++ b/.config/yazi/plugins/toggle-pane.yazi/main.lua
@@ -0,0 +1,51 @@
+--- @since 25.2.26
+--- @sync entry
+
+local function entry(st, job)
+ local R = rt.mgr.ratio
+ job = type(job) == "string" and { args = { job } } or job
+
+ st.parent = st.parent or R.parent
+ st.current = st.current or R.current
+ st.preview = st.preview or R.preview
+
+ local act, to = string.match(job.args[1] or "", "(.-)-(.+)")
+ if act == "min" then
+ st[to] = st[to] == R[to] and 0 or R[to]
+ elseif act == "max" then
+ local max = st[to] == 65535 and R[to] or 65535
+ st.parent = st.parent == 65535 and R.parent or st.parent
+ st.current = st.current == 65535 and R.current or st.current
+ st.preview = st.preview == 65535 and R.preview or st.preview
+ st[to] = max
+ end
+
+ if not st.old then
+ st.old = Tab.layout
+ Tab.layout = function(self)
+ local all = st.parent + st.current + st.preview
+ self._chunks = ui.Layout()
+ :direction(ui.Layout.HORIZONTAL)
+ :constraints({
+ ui.Constraint.Ratio(st.parent, all),
+ ui.Constraint.Ratio(st.current, all),
+ ui.Constraint.Ratio(st.preview, all),
+ })
+ :split(self._area)
+ end
+ end
+
+ if not act then
+ Tab.layout, st.old = st.old, nil
+ st.parent, st.current, st.preview = nil, nil, nil
+ end
+
+ -- TODO: remove this in the future
+ if ya.emit then
+ ya.emit("app:resize", {})
+ else
+ ya.app_emit("resize", {})
+ end
+end
+
+return { entry = entry }
diff --git a/.config/yazi/theme.toml b/.config/yazi/theme.toml
new file mode 100644
index 0000000..b91f47c
--- /dev/null
+++ b/.config/yazi/theme.toml
@@ -0,0 +1,2 @@
+[flavor]
+dark = "dracula"
diff --git a/.config/yazi/yazi.toml b/.config/yazi/yazi.toml
new file mode 100644
index 0000000..49c88e1
--- /dev/null
+++ b/.config/yazi/yazi.toml
@@ -0,0 +1,30 @@
+[manager]
+show_hidden = true
+linemode = "size_and_mtime"
+
+[plugin]
+prepend_previewers = [
+ # Archive previewer
+ { mime = "application/*zip", run = "ouch" },
+ { mime = "application/x-tar", run = "ouch" },
+ { mime = "application/x-bzip2", run = "ouch" },
+ { mime = "application/x-7z-compressed", run = "ouch" },
+ { mime = "application/x-rar", run = "ouch" },
+ { mime = "application/x-xz", run = "ouch" },
+ { mime = "application/xz", run = "ouch" },
+]
+
+[[plugin.prepend_fetchers]]
+id = "git"
+name = "*"
+run = "git"
+
+[[plugin.prepend_fetchers]]
+id = "git"
+name = "*/"
+run = "git"
+
+[opener]
+extract = [
+ { run = 'ouch d -y "$@"', desc = "Extract here with ouch", for = "unix" },
+]