commit a5329b6832888a7e7fdfd7080995978a5c5495ff Author: pika Date: Sun Nov 10 13:31:23 2024 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98c2775 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +flavors diff --git a/README.md b/README.md new file mode 100644 index 0000000..f4015b4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Yazi configuration diff --git a/flavors/onedark.yazi b/flavors/onedark.yazi new file mode 160000 index 0000000..fa1da70 --- /dev/null +++ b/flavors/onedark.yazi @@ -0,0 +1 @@ +Subproject commit fa1da70556a5654f5d40d063a95e55ecc63b3ef7 diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..81b95fc --- /dev/null +++ b/init.lua @@ -0,0 +1,6 @@ +require("full-border"):setup({ + -- Available values: ui.Border.PLAIN, ui.Border.ROUNDED + type = ui.Border.ROUNDED, +}) + +require("git"):setup() diff --git a/keymap.toml b/keymap.toml new file mode 100644 index 0000000..d3fa971 --- /dev/null +++ b/keymap.toml @@ -0,0 +1,329 @@ +# A TOML linter such as https://taplo.tamasfe.dev/ can use this schema to validate your config. +# If you encounter any issues, please make an issue at https://github.com/yazi-rs/schemas. +"$schema" = "https://yazi-rs.github.io/schemas/keymap.json" + +[manager] + +keymap = [ + { on = "", run = "escape", desc = "Exit visual mode, clear selected, or cancel search" }, + { on = "", run = "escape", desc = "Exit visual mode, clear selected, or cancel search" }, + { on = "q", run = "quit", desc = "Exit the process" }, + { on = "Q", run = "quit --no-cwd-file", desc = "Exit the process without writing cwd-file" }, + { on = "", run = "close", desc = "Close the current tab, or quit if it is last tab" }, + { on = "", run = "suspend", desc = "Suspend the process" }, + + # Hopping + { on = "k", run = "arrow -1", desc = "Move cursor up" }, + { on = "j", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -50%", desc = "Move cursor up half page" }, + { on = "", run = "arrow 50%", desc = "Move cursor down half page" }, + { on = "", run = "arrow -100%", desc = "Move cursor up one page" }, + { on = "", run = "arrow 100%", desc = "Move cursor down one page" }, + + { on = "", run = "arrow -50%", desc = "Move cursor up half page" }, + { on = "", run = "arrow 50%", desc = "Move cursor down half page" }, + { on = "", run = "arrow -100%", desc = "Move cursor up one page" }, + { on = "", run = "arrow 100%", desc = "Move cursor down one page" }, + + { on = [ "g", "g" ], run = "arrow -99999999", desc = "Move cursor to the top" }, + { on = "G", run = "arrow 99999999", desc = "Move cursor to the bottom" }, + + # Navigation + { on = "h", run = "leave", desc = "Go back to the parent directory" }, + { on = "l", run = "enter", desc = "Enter the child directory" }, + + { on = "", run = "leave", desc = "Go back to the parent directory" }, + { on = "", run = "enter", desc = "Enter the child directory" }, + + { on = "H", run = "back", desc = "Go back to the previous directory" }, + { on = "L", run = "forward", desc = "Go forward to the next directory" }, + + # Seeking + { on = "K", run = "seek -5", desc = "Seek up 5 units in the preview" }, + { on = "J", run = "seek 5", desc = "Seek down 5 units in the preview" }, + + # Selection + { on = "", run = [ "select --state=none", "arrow 1" ], desc = "Toggle the current selection state" }, + { on = "v", run = "visual_mode", desc = "Enter visual mode (selection mode)" }, + { on = "V", run = "visual_mode --unset", desc = "Enter visual mode (unset mode)" }, + { on = "", run = "select_all --state=true", desc = "Select all files" }, + { on = "", run = "select_all --state=none", desc = "Inverse selection of all files" }, + + # Operation + { on = "o", run = "open", desc = "Open selected files" }, + { on = "O", run = "open --interactive", desc = "Open selected files interactively" }, + { on = "", run = "open", desc = "Open selected files" }, + { on = "", run = "open --interactive", desc = "Open selected files interactively" }, + { on = "y", run = "yank", desc = "Yank selected files (copy)" }, + { on = "x", run = "yank --cut", desc = "Yank selected files (cut)" }, + { on = "p", run = "paste", desc = "Paste yanked files" }, + { on = "P", run = "paste --force", desc = "Paste yanked files (overwrite if the destination exists)" }, + { on = "-", run = "link", desc = "Symlink the absolute path of yanked files" }, + { on = "_", run = "link --relative", desc = "Symlink the relative path of yanked files" }, + { on = "", run = "hardlink", desc = "Hardlink yanked files" }, + { on = "Y", run = "unyank", desc = "Cancel the yank status" }, + { on = "X", run = "unyank", desc = "Cancel the yank status" }, + { on = "d", run = "remove", desc = "Trash selected files" }, + { on = "D", run = "remove --permanently", desc = "Permanently delete selected files" }, + { on = "a", run = "create", desc = "Create a file (ends with / for directories)" }, + { on = "r", run = "rename --cursor=before_ext", desc = "Rename selected file(s)" }, + { on = ";", run = "shell --interactive", desc = "Run a shell command" }, + { on = ":", run = "shell --block --interactive", desc = "Run a shell command (block until finishes)" }, + { on = "", run = "hidden toggle", desc = "Toggle the visibility of hidden files" }, + { on = "s", run = "search fd", desc = "Search files by name using fd" }, + { on = "S", run = "search rg", desc = "Search files by content using ripgrep" }, + { on = "", run = "escape --search", desc = "Cancel the ongoing search" }, + { on = "z", run = "plugin zoxide", desc = "Jump to a directory using zoxide" }, + { on = "Z", run = "plugin fzf", desc = "Jump to a directory or reveal a file using fzf" }, + + # Linemode + { on = [ "m", "s" ], run = "linemode size", desc = "Set linemode to size" }, + { on = [ "m", "p" ], run = "linemode permissions", desc = "Set linemode to permissions" }, + { on = [ "m", "c" ], run = "linemode ctime", desc = "Set linemode to ctime" }, + { on = [ "m", "m" ], run = "linemode mtime", desc = "Set linemode to mtime" }, + { on = [ "m", "o" ], run = "linemode owner", desc = "Set linemode to owner" }, + { on = [ "m", "n" ], run = "linemode none", desc = "Set linemode to none" }, + + # Copy + { on = [ "c", "c" ], run = "copy path", desc = "Copy the file path" }, + { on = [ "c", "d" ], run = "copy dirname", desc = "Copy the directory path" }, + { on = [ "c", "f" ], run = "copy filename", desc = "Copy the filename" }, + { on = [ "c", "n" ], run = "copy name_without_ext", desc = "Copy the filename without extension" }, + + # Filter + { on = "f", run = "filter --smart", desc = "Filter files" }, + + # Find + { on = "/", run = "find --smart", desc = "Find next file" }, + { on = "?", run = "find --previous --smart", desc = "Find previous file" }, + { on = "n", run = "find_arrow", desc = "Go to the next found" }, + { on = "N", run = "find_arrow --previous", desc = "Go to the previous found" }, + + # Sorting + { on = [ ",", "m" ], run = [ "sort modified --reverse=no", "linemode mtime" ], desc = "Sort by modified time" }, + { on = [ ",", "M" ], run = [ "sort modified --reverse", "linemode mtime" ], desc = "Sort by modified time (reverse)" }, + { on = [ ",", "c" ], run = [ "sort created --reverse=no", "linemode ctime" ], desc = "Sort by created time" }, + { on = [ ",", "C" ], run = [ "sort created --reverse", "linemode ctime" ], desc = "Sort by created time (reverse)" }, + { on = [ ",", "e" ], run = "sort extension --reverse=no", desc = "Sort by extension" }, + { on = [ ",", "E" ], run = "sort extension --reverse", desc = "Sort by extension (reverse)" }, + { on = [ ",", "a" ], run = "sort alphabetical --reverse=no", desc = "Sort alphabetically" }, + { on = [ ",", "A" ], run = "sort alphabetical --reverse", desc = "Sort alphabetically (reverse)" }, + { on = [ ",", "n" ], run = "sort natural --reverse=no", desc = "Sort naturally" }, + { on = [ ",", "N" ], run = "sort natural --reverse", desc = "Sort naturally (reverse)" }, + { on = [ ",", "s" ], run = [ "sort size --reverse=no", "linemode size" ], desc = "Sort by size" }, + { on = [ ",", "S" ], run = [ "sort size --reverse", "linemode size" ], desc = "Sort by size (reverse)" }, + { on = [ ",", "r" ], run = "sort random --reverse=no", desc = "Sort randomly" }, + + # Goto + { on = [ "g", "h" ], run = "cd ~", desc = "Go to the home directory" }, + { on = [ "g", "c" ], run = "cd ~/.config", desc = "Go to the config directory" }, + { on = [ "g", "d" ], run = "cd ~/Downloads", desc = "Go to the downloads directory" }, + { on = [ "g", "" ], run = "cd --interactive", desc = "Go to a directory interactively" }, + + # Tabs + { on = "t", run = "tab_create --current", desc = "Create a new tab with CWD" }, + + { on = "1", run = "tab_switch 0", desc = "Switch to the first tab" }, + { on = "2", run = "tab_switch 1", desc = "Switch to the second tab" }, + { on = "3", run = "tab_switch 2", desc = "Switch to the third tab" }, + { on = "4", run = "tab_switch 3", desc = "Switch to the fourth tab" }, + { on = "5", run = "tab_switch 4", desc = "Switch to the fifth tab" }, + { on = "6", run = "tab_switch 5", desc = "Switch to the sixth tab" }, + { on = "7", run = "tab_switch 6", desc = "Switch to the seventh tab" }, + { on = "8", run = "tab_switch 7", desc = "Switch to the eighth tab" }, + { on = "9", run = "tab_switch 8", desc = "Switch to the ninth tab" }, + + { on = "[", run = "tab_switch -1 --relative", desc = "Switch to the previous tab" }, + { on = "]", run = "tab_switch 1 --relative", desc = "Switch to the next tab" }, + + { on = "{", run = "tab_swap -1", desc = "Swap current tab with previous tab" }, + { on = "}", run = "tab_swap 1", desc = "Swap current tab with next tab" }, + + # Tasks + { on = "w", run = "tasks_show", desc = "Show task manager" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[tasks] + +keymap = [ + { on = "", run = "close", desc = "Close task manager" }, + { on = "", run = "close", desc = "Close task manager" }, + { on = "", run = "close", desc = "Close task manager" }, + { on = "w", run = "close", desc = "Close task manager" }, + + { on = "k", run = "arrow -1", desc = "Move cursor up" }, + { on = "j", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "inspect", desc = "Inspect the task" }, + { on = "x", run = "cancel", desc = "Cancel the task" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[select] + +keymap = [ + { on = "", run = "close", desc = "Cancel selection" }, + { on = "", run = "close", desc = "Cancel selection" }, + { on = "", run = "close", desc = "Cancel selection" }, + { on = "", run = "close --submit", desc = "Submit the selection" }, + + { on = "k", run = "arrow -1", desc = "Move cursor up" }, + { on = "j", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[input] + +keymap = [ + { on = "", run = "close", desc = "Cancel input" }, + { on = "", run = "close --submit", desc = "Submit input" }, + { on = "", run = "escape", desc = "Go back the normal mode, or cancel input" }, + { on = "", run = "escape", desc = "Go back the normal mode, or cancel input" }, + + # Mode + { on = "i", run = "insert", desc = "Enter insert mode" }, + { on = "a", run = "insert --append", desc = "Enter append mode" }, + { on = "I", run = [ "move -999", "insert" ], desc = "Move to the BOL, and enter insert mode" }, + { on = "A", run = [ "move 999", "insert --append" ], desc = "Move to the EOL, and enter append mode" }, + { on = "v", run = "visual", desc = "Enter visual mode" }, + { on = "V", run = [ "move -999", "visual", "move 999" ], desc = "Enter visual mode and select all" }, + + # Character-wise movement + { on = "h", run = "move -1", desc = "Move back a character" }, + { on = "l", run = "move 1", desc = "Move forward a character" }, + { on = "", run = "move -1", desc = "Move back a character" }, + { on = "", run = "move 1", desc = "Move forward a character" }, + { on = "", run = "move -1", desc = "Move back a character" }, + { on = "", run = "move 1", desc = "Move forward a character" }, + + # Word-wise movement + { on = "b", run = "backward", desc = "Move back to the start of the current or previous word" }, + { on = "w", run = "forward", desc = "Move forward to the start of the next word" }, + { on = "e", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" }, + { on = "", run = "backward", desc = "Move back to the start of the current or previous word" }, + { on = "", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" }, + + # Line-wise movement + { on = "0", run = "move -999", desc = "Move to the BOL" }, + { on = "$", run = "move 999", desc = "Move to the EOL" }, + { on = "", run = "move -999", desc = "Move to the BOL" }, + { on = "", run = "move 999", desc = "Move to the EOL" }, + { on = "", run = "move -999", desc = "Move to the BOL" }, + { on = "", run = "move 999", desc = "Move to the EOL" }, + + # Delete + { on = "", run = "backspace", desc = "Delete the character before the cursor" }, + { on = "", run = "backspace --under", desc = "Delete the character under the cursor" }, + { on = "", run = "backspace", desc = "Delete the character before the cursor" }, + { on = "", run = "backspace --under", desc = "Delete the character under the cursor" }, + + # Kill + { on = "", run = "kill bol", desc = "Kill backwards to the BOL" }, + { on = "", run = "kill eol", desc = "Kill forwards to the EOL" }, + { on = "", run = "kill backward", desc = "Kill backwards to the start of the current word" }, + { on = "", run = "kill forward", desc = "Kill forwards to the end of the current word" }, + + # Cut/Yank/Paste + { on = "d", run = "delete --cut", desc = "Cut the selected characters" }, + { on = "D", run = [ "delete --cut", "move 999" ], desc = "Cut until the EOL" }, + { on = "c", run = "delete --cut --insert", desc = "Cut the selected characters, and enter insert mode" }, + { on = "C", run = [ "delete --cut --insert", "move 999" ], desc = "Cut until the EOL, and enter insert mode" }, + { on = "x", run = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" }, + { on = "y", run = "yank", desc = "Copy the selected characters" }, + { on = "p", run = "paste", desc = "Paste the copied characters after the cursor" }, + { on = "P", run = "paste --before", desc = "Paste the copied characters before the cursor" }, + + # Undo/Redo + { on = "u", run = "undo", desc = "Undo the last operation" }, + { on = "", run = "redo", desc = "Redo the last operation" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[confirm] +keymap = [ + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close", desc = "Cancel the confirm" }, + { on = "", run = "close --submit", desc = "Submit the confirm" }, + + { on = "n", run = "close", desc = "Cancel the confirm" }, + { on = "y", run = "close --submit", desc = "Submit the confirm" }, + + { on = "k", run = "arrow -1", desc = "Move cursor up" }, + { on = "j", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[completion] + +keymap = [ + { on = "", run = "close", desc = "Cancel completion" }, + { on = "", run = "close --submit", desc = "Submit the completion" }, + { on = "", run = [ "close --submit", "close_input --submit" ], desc = "Submit the completion and input" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + # Help + { on = "~", run = "help", desc = "Open help" }, + { on = "", run = "help", desc = "Open help" }, +] + +[help] + +keymap = [ + { on = "", run = "escape", desc = "Clear the filter, or hide the help" }, + { on = "", run = "escape", desc = "Clear the filter, or hide the help" }, + { on = "q", run = "close", desc = "Exit the process" }, + { on = "", run = "close", desc = "Hide the help" }, + + # Navigation + { on = "k", run = "arrow -1", desc = "Move cursor up" }, + { on = "j", run = "arrow 1", desc = "Move cursor down" }, + + { on = "", run = "arrow -1", desc = "Move cursor up" }, + { on = "", run = "arrow 1", desc = "Move cursor down" }, + + # Filtering + { on = "f", run = "filter", desc = "Apply a filter for the help items" }, +] + +[[manager.prepend_keymap]] +on = "!" +run = 'shell "$SHELL" --block --confirm' +desc = "Open shell here" diff --git a/package.toml b/package.toml new file mode 100644 index 0000000..10f7ca1 --- /dev/null +++ b/package.toml @@ -0,0 +1,8 @@ +[plugin] +deps = [ + { use = "yazi-rs/plugins:full-border", rev = "dc11879" }, + { use = "yazi-rs/plugins:git", rev = "4fcd737" }, +] + +[flavor] +deps = [] diff --git a/plugins/full-border.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/plugins/full-border.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY new file mode 100644 index 0000000..e69de29 diff --git a/plugins/full-border.yazi/LICENSE b/plugins/full-border.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/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/plugins/full-border.yazi/README.md b/plugins/full-border.yazi/README.md new file mode 100644 index 0000000..6e78bd4 --- /dev/null +++ b/plugins/full-border.yazi/README.md @@ -0,0 +1,32 @@ +# full-border.yazi + +Add a full border to Yazi to make it look fancier. + +![full-border](https://github.com/yazi-rs/plugins/assets/17523360/ef81b560-2465-4d36-abf2-5d21dcb7b987) + +## 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/plugins/full-border.yazi/init.lua b/plugins/full-border.yazi/init.lua new file mode 100644 index 0000000..958ae67 --- /dev/null +++ b/plugins/full-border.yazi/init.lua @@ -0,0 +1,50 @@ +-- TODO: remove this once v0.4 is released +local v4 = function(typ, area, ...) + if typ == "bar" then + return ui.Table and ui.Bar(...):area(area) or ui.Bar(area, ...) + else + return ui.Table and ui.Border(...):area(area) or ui.Border(area, ...) + end +end + +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 then + return v4("bar", ui.Rect.default, ui.Bar.TOP) + end + + return v4( + "bar", + 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) }, + ui.Bar.TOP + ):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 {}, { + v4("border", self._area, ui.Border.ALL):type(type):style(style), + v4("bar", self._chunks[1], ui.Bar.RIGHT):style(style), + v4("bar", self._chunks[3], ui.Bar.LEFT):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/plugins/git.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY b/plugins/git.yazi/DO_NOT_MODIFY_ANYTHING_IN_THIS_DIRECTORY new file mode 100644 index 0000000..e69de29 diff --git a/plugins/git.yazi/LICENSE b/plugins/git.yazi/LICENSE new file mode 100644 index 0000000..fb5b1d6 --- /dev/null +++ b/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/plugins/git.yazi/README.md b/plugins/git.yazi/README.md new file mode 100644 index 0000000..fad9ce4 --- /dev/null +++ b/plugins/git.yazi/README.md @@ -0,0 +1,81 @@ +# git.yazi + +> [!NOTE] +> Yazi v0.3.3 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 + +> [!NOTE] +> This section currently requires Yazi nightly that includes https://github.com/sxyazi/yazi/pull/1637 + +You can customize the [Style](https://yazi-rs.github.io/docs/plugins/layout#style) of the status sign with: + +- `THEME.git.modified` +- `THEME.git.added` +- `THEME.git.untracked` +- `THEME.git.ignored` +- `THEME.git.deleted` +- `THEME.git.updated` + +For example: + +```lua +-- ~/.config/yazi/init.lua +THEME.git = THEME.git or {} +THEME.git.modified = ui.Style():fg("blue") +THEME.git.deleted = ui.Style():fg("red"):bold() +``` + +You can also customize the text of the status sign with: + +- `THEME.git.modified_sign` +- `THEME.git.added_sign` +- `THEME.git.untracked_sign` +- `THEME.git.ignored_sign` +- `THEME.git.deleted_sign` +- `THEME.git.updated_sign` + +For example: + +```lua +-- ~/.config/yazi/init.lua +THEME.git = THEME.git or {} +THEME.git.modified_sign = "M" +THEME.git.deleted_sign = "D" +``` + +## License + +This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file. diff --git a/plugins/git.yazi/init.lua b/plugins/git.yazi/init.lua new file mode 100644 index 0000000..3cf79a8 --- /dev/null +++ b/plugins/git.yazi/init.lua @@ -0,0 +1,208 @@ +local WIN = ya.target_family() == "windows" +local PATS = { + { "[MT]", 6 }, -- Modified + { "[AC]", 5 }, -- Added + { "?$", 4 }, -- Untracked + { "!$", 3 }, -- Ignored + { "D", 2 }, -- Deleted + { "U", 1 }, -- Updated + { "[AD][AD]", 1 }, -- Updated +} + +local function match(line) + local signs = line:sub(1, 2) + for _, p in ipairs(PATS) do + local path + if signs:find(p[1]) then + path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4) + path = WIN and path:gsub("/", "\\") or path + end + if not path then + elseif path:find("[/\\]$") then + return p[2] == 3 and 30 or p[2], path:sub(1, -2) + else + return p[2], 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 k, v in pairs(changed) do + if v ~= 3 and v ~= 30 then + local url = Url(k):parent() + while url and url ~= empty do + local s = tostring(url) + new[s] = (new[s] or 0) > v and new[s] or v + url = url:parent() + end + end + end + return new +end + +local function propagate_down(ignored, cwd, repo) + local new, rel = {}, cwd:strip_prefix(repo) + for k, v in pairs(ignored) do + if v == 30 then + if rel:starts_with(k) then + new[tostring(repo:join(rel))] = 30 + elseif cwd == repo:join(k):parent() then + new[k] = 3 + end + 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 k, v in pairs(changed) do + if v == 0 then + st.repos[repo][k] = nil + elseif v == 30 then + st.dirs[k] = "" + else + st.repos[repo][k] = v + end + end + ya.render() +end) + +local remove = ya.sync(function(st, cwd) + local dir = st.dirs[cwd] + if not dir then + return + end + + ya.render() + st.dirs[cwd] = nil + if not st.repos[dir] then + return + end + + for _, r in pairs(st.dirs) do + if r == dir then + return + end + end + st.repos[dir] = nil +end) + +local function setup(st, opts) + st.dirs = {} + st.repos = {} + + opts = opts or {} + opts.order = opts.order or 1500 + + -- Chosen by ChatGPT fairly, PRs are welcome to adjust them + local t = THEME.git or {} + local styles = { + [6] = t.modified and ui.Style(t.modified) or ui.Style():fg("#ffa500"), + [5] = t.added and ui.Style(t.added) or ui.Style():fg("#32cd32"), + [4] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("#a9a9a9"), + [3] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("#696969"), + [2] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("#ff4500"), + [1] = t.updated and ui.Style(t.updated) or ui.Style():fg("#1e90ff"), + } + local signs = { + [6] = t.modified_sign and t.modified_sign or "", + [5] = t.added_sign and t.added_sign or "", + [4] = t.untracked_sign and t.untracked_sign or "", + [3] = t.ignored_sign and t.ignored_sign or "", + [2] = t.deleted_sign and t.deleted_sign or "", + [1] = t.updated_sign and t.updated_sign or "U", + } + + Linemode:children_add(function(self) + local url = self._file.url + local dir = st.dirs[tostring(url:parent())] + local change + if dir then + change = dir == "" and 3 or st.repos[dir][tostring(url):sub(#dir + 2)] + end + + if not change or signs[change] == "" then + return ui.Line("") + elseif self._file:is_hovered() then + return ui.Line { ui.Span(" "), ui.Span(signs[change]) } + else + return ui.Line { ui.Span(" "), ui.Span(signs[change]):style(styles[change]) } + end + end, opts.order) +end + +local function fetch(self) + local cwd = self.files[1].url:parent() + local repo = root(cwd) + if not repo then + remove(tostring(cwd)) + return 1 + end + + local paths = {} + for _, f in ipairs(self.files) do + paths[#paths + 1] = tostring(f.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 + ya.err("Cannot spawn git command, error code " .. tostring(err)) + return 0 + end + + local changed, ignored = {}, {} + for line in output.stdout:gmatch("[^\r\n]+") do + local sign, path = match(line) + if sign == 30 then + ignored[path] = sign + else + changed[path] = sign + end + end + + if self.files[1].cha.is_dir then + ya.dict_merge(changed, bubble_up(changed)) + ya.dict_merge(changed, propagate_down(ignored, cwd, Url(repo))) + else + ya.dict_merge(changed, propagate_down(ignored, cwd, Url(repo))) + end + + for _, p in ipairs(paths) do + local s = p:sub(#repo + 2) + changed[s] = changed[s] or 0 + end + add(tostring(cwd), repo, changed) + + return 3 +end + +return { setup = setup, fetch = fetch } diff --git a/theme.toml b/theme.toml new file mode 100644 index 0000000..91c506b --- /dev/null +++ b/theme.toml @@ -0,0 +1,2 @@ +[flavor] +use = "onedark" diff --git a/yazi.toml b/yazi.toml new file mode 100644 index 0000000..7ff8381 --- /dev/null +++ b/yazi.toml @@ -0,0 +1,42 @@ +[manager] +sort_dir_first = true + +[preview] +wrap = "yes" +image_quality = 90 +sixel_fraction = 15 + +[opener] +play = [ + { run = 'mpv "$@"', orphan = true, for = "unix" }, +] +edit = [ + { run = '$EDITOR "$@"', block = true, for = "unix" }, +] +open = [ + { run = 'xdg-open "$@"', desc = "Open" }, +] + +[open] +prepend_rules = [ + { name = "*.json", use = "edit" }, + + # Multiple openers for a single rule + { name = "*.html", use = [ "open", "edit" ] }, +] +append_rules = [ + { name = "*", use = "nvim" }, +] + +[tasks] +image_alloc = 0 + +[[plugin.prepend_fetchers]] +id = "git" +name = "*" +run = "git" + +[[plugin.prepend_fetchers]] +id = "git" +name = "*/" +run = "git"