fixing yazi plugins

This commit is contained in:
Matt Nish-Lapidus 2025-04-04 14:45:27 -04:00
parent d618ecf02e
commit 38f162562e
17 changed files with 43 additions and 822 deletions

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "modules/home/yazi/plugins/yazi-plugins"]
path = modules/home/yazi/plugins/yazi-plugins
url = git@github.com:yazi-rs/plugins.git

42
flake.lock generated
View file

@ -70,11 +70,11 @@
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1743700520,
"narHash": "sha256-FTgOgaZks+xkyqqukNOe7FXHPshCYmQ38wxzxJ6T3RQ=",
"lastModified": 1743783783,
"narHash": "sha256-6SYZkfwTEyuLkM2wal5s5CiYrnFJ6Pa9bA/Y6FZ4fcA=",
"owner": "nix-community",
"repo": "emacs-overlay",
"rev": "8c76e5792ef50a67e89ad54e792769f89d721f4a",
"rev": "441e7ba4e4a128422b85039fe6b9662b259ba5eb",
"type": "github"
},
"original": {
@ -357,11 +357,11 @@
]
},
"locked": {
"lastModified": 1743648554,
"narHash": "sha256-23JFd+zd2GamTTdnGuFVeIg8x8C3hLpQJRh/PGTORzo=",
"lastModified": 1743788974,
"narHash": "sha256-2LeVyQZI2wTkSzMLvnN/kJjXVWp4HCVUoq17Bv8TNTk=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "107352dde4ff3c01cb5a0b3fe17f5beef37215bc",
"rev": "0f5908daf890c3d7e7052bef1d6deb0f2710aaa1",
"type": "github"
},
"original": {
@ -744,11 +744,11 @@
"xwayland-satellite-unstable": "xwayland-satellite-unstable"
},
"locked": {
"lastModified": 1743644801,
"narHash": "sha256-z8x/j/RuDBo/5lNt3XYatKRpIMFMHVE2HK7TKVxYn+c=",
"lastModified": 1743778191,
"narHash": "sha256-Fc4kJeTawHVUd+TTpqLJRvvNusTTwqWE1vLz9IpH0NA=",
"owner": "sodiboo",
"repo": "niri-flake",
"rev": "f3fca85fe72c70d58f44f4c6ad2f27a91aa54d0d",
"rev": "612390f7f196eec4b832e3fca17719d412f5f69c",
"type": "github"
},
"original": {
@ -777,11 +777,11 @@
"niri-unstable": {
"flake": false,
"locked": {
"lastModified": 1743492917,
"narHash": "sha256-OqLDg0Ody1HX23hgjvjIkfZPNhYKxbkj/ONcDjdD4Ik=",
"lastModified": 1743774669,
"narHash": "sha256-xrg3m1RP7mvBi0sLPJjnn9UiCqN+NKqU94DZJMoaXZU=",
"owner": "YaLTeR",
"repo": "niri",
"rev": "60034a57efd9c8130b05797b37cbc187a8c13145",
"rev": "e8da89a430f4af0accfe80efe286b2cffd20a4aa",
"type": "github"
},
"original": {
@ -1013,11 +1013,11 @@
},
"nixpkgs-stable_4": {
"locked": {
"lastModified": 1743576891,
"narHash": "sha256-vXiKURtntURybE6FMNFAVpRPr8+e8KoLPrYs9TGuAKc=",
"lastModified": 1743703532,
"narHash": "sha256-s1KLDALEeqy+ttrvqV3jx9mBZEvmthQErTVOAzbjHZs=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "44a69ed688786e98a101f02b712c313f1ade37ab",
"rev": "bdb91860de2f719b57eef819b5617762f7120c70",
"type": "github"
},
"original": {
@ -1255,11 +1255,11 @@
]
},
"locked": {
"lastModified": 1743604509,
"narHash": "sha256-Hf5aYGP3hP+uNbcd4NrEMUAR+1o518uGzoeVyMzzJwo=",
"lastModified": 1743756170,
"narHash": "sha256-2b11EYa08oqDmF3zEBLkG1AoNn9rB1k39ew/T/mSvbU=",
"owner": "Mic92",
"repo": "sops-nix",
"rev": "4521de68fba1a36fae8caebce3d6e047179661f7",
"rev": "cff8437c5fe8c68fc3a840a21bf1f4dc801da40d",
"type": "github"
},
"original": {
@ -1562,11 +1562,11 @@
"rust-overlay": "rust-overlay_3"
},
"locked": {
"lastModified": 1743669585,
"narHash": "sha256-uAOZg2vsMooemIpV0jdgy7JicuVNBbJl2gRN7m/N0ZM=",
"lastModified": 1743786211,
"narHash": "sha256-WW/qwf6wVFWNJGMRbmDT4l/K8KMlX7+69jwd7mBHgwc=",
"owner": "sxyazi",
"repo": "yazi",
"rev": "b725b86be13ffc127094dbb981d667d3d7d8c64a",
"rev": "75fad253c0948a06bcfed57b99ca60ecca39a0c3",
"type": "github"
},
"original": {

View file

@ -58,28 +58,24 @@
tree
emoji-picker
sox
imgcat
ghostscript
playerctl
tesseract
dockfmt
trash-cli
poppler_utils
fishPlugins.foreign-env
fishPlugins.fzf-fish
fishPlugins.fifc
fishPlugins.bass
fishPlugins.autopair
fishPlugins.forgit
fishPlugins.colored-man-pages
sox
imgcat
ghostscript
playerctl
tesseract
dockfmt
trash-cli
poppler_utils
fossil
television
];
programs = {
@ -99,6 +95,7 @@
};
interactiveShellInit = ''
bind --erase \ct
fzf_configure_bindings --variables=\e\cv
'';
plugins = [
{
@ -141,7 +138,6 @@
git = true;
icons = "auto";
colors = "auto";
# extraOptions = [ "--header" ];
};
fd = {
enable = true;

View file

@ -10,8 +10,6 @@
{ 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"; }
{ on = ["c" "N"]; run = "copy name_without_ext"; desc = "Copy file name without extension"; }
{ on = "y"; run = ["shell 'for path in \"$@\"; do echo \"file://$path\"; done | wl-copy -t text/uri-list'\n" "yank"]; }
{ on = ["g" "r"]; run = "shell 'ya emit cd \"$(git rev-parse --show-toplevel)\"'\n"; desc = "Go to top of git repo"; }
{ on = ["g" "p"]; run = "cd ~/Projects"; desc = "Go to ~/Projects"; }
@ -26,15 +24,12 @@
{ 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 = "k"; run = "plugin arrow -1"; desc = "up"; }
{ on = "j"; run = "plugin arrow 1"; desc = "down"; }
{ on = "<Up>"; run = "plugin arrow -1"; desc = "up"; }
{ on = "<Down>"; run = "plugin arrow 1"; desc = "down"; }
# { on = "k"; run = "arrow -1"; desc = "up"; }
# { on = "j"; run = "arrow 1"; desc = "down"; }
# { on = "<Up>"; run = "arrow -1"; desc = "up"; }
# { on = "<Down>"; run = "arrow 1"; desc = "down"; }
{ on = "C"; run = "plugin ouch zip"; }
{ on = "T"; run = "plugin smart-tab"; desc = "Create a tab and enter the hovered directory"; }
{ on = "l"; run = "plugin smart-enter"; desc = "Enter the child directory, or open the file"; }
{ on = "<Enter>"; run = "plugin smart-enter"; desc = "Enter the child directory, or open the file"; }
{ on = "<Right>"; run = "plugin smart-enter"; desc = "Enter the child directory, or open the file"; }
{ on = "<Backspace>"; run = "remove"; }
{ on = [ "t" "s" ]; run = "plugin what-size"; desc = "Calc size of selection or cwd"; }
@ -42,9 +37,8 @@
{ 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)"; }
{ on = "f"; run = "find --smart"; desc = "Find file"; }
{ on = "F"; run = "filter --smart"; desc = "Find file"; }
];
};
settings = {

View file

@ -6,22 +6,10 @@ 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 = "" },

View file

@ -1,8 +0,0 @@
--- @sync entry
return {
entry = function(_, job)
local current = cx.active.current
local new = (current.cursor + job.args[1]) % #current.files
ya.manager_emit("arrow", { new - current.cursor })
end,
}

View file

@ -1,21 +0,0 @@
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.

View file

@ -1,28 +0,0 @@
# 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 <kbd>c</kbd> => <kbd>m</kbd> key is not used elsewhere.
## License
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.

View file

@ -1,39 +0,0 @@
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.manager_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,
}

View file

@ -1,21 +0,0 @@
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.

View file

@ -1,78 +0,0 @@
# git.yazi
> [!NOTE]
> Yazi v0.4.1 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:
- `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.

View file

@ -1,217 +0,0 @@
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 ""
elseif self._file:is_hovered() then
return ui.Line { " ", signs[change] }
else
return ui.Line { " ", ui.Span(signs[change]):style(styles[change]) }
end
end, opts.order)
end
local function fetch(_, job)
local cwd = job.files[1].url:parent()
local repo = root(cwd)
if not repo then
remove(tostring(cwd))
if not ya.__250127 then -- TODO: remove this
return 1
end
return true
end
local paths = {}
for _, f in ipairs(job.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
if not ya.__250127 then -- TODO: remove this
ya.err("Cannot spawn git command, error: " .. err)
return 0
end
return true, Err("Cannot spawn `git` command, error: %s", err)
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 job.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)
if not ya.__250127 then -- TODO: remove this
return 3
end
return false
end
return { setup = setup, fetch = fetch }

View file

@ -1,21 +0,0 @@
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.

View file

@ -1,51 +0,0 @@
# mount.yazi
> [!NOTE]
> The plugin is currently very experimental, and the newest Yazi nightly is required for it to work.
A mount manager for Yazi, providing disk mount, unmount, and eject functionality.
Supported platforms:
- Linux with [`udisksctl`](https://github.com/storaged-project/udisks) and [`lsblk`](https://github.com/util-linux/util-linux)
- macOS with `diskutil`
https://github.com/user-attachments/assets/c6f780ab-458b-420f-85cf-2fc45fcfe3a2
## Installation
```sh
ya pack -a yazi-rs/plugins:mount
```
## Usage
Add this to your `~/.config/yazi/keymap.toml`:
```toml
[[manager.prepend_keymap]]
on = "M"
run = "plugin mount"
```
Available keybindings:
| Key binding | Alternate key | Action |
| ------------ | ------------- | --------------------- |
| <kbd>q</kbd> | - | Quit the plugin |
| <kbd>k</kbd> | <kbd></kbd> | Move up |
| <kbd>j</kbd> | <kbd></kbd> | Move down |
| <kbd>l</kbd> | <kbd></kbd> | Enter the mount point |
| <kbd>m</kbd> | - | Mount the partition |
| <kbd>u</kbd> | - | Unmount the partition |
| <kbd>e</kbd> | - | Eject the disk |
## TODO
- Custom keybindings
- Windows support (I don't have an Windows machine for testing, PRs welcome!)
- Support mount, unmount, and eject the entire disk
## License
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.

View file

@ -1,266 +0,0 @@
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 subscribe = ya.sync(function(self)
ps.unsub("mount")
ps.sub("mount", function() ya.manager_emit("plugin", { self._id, args = "refresh" }) end)
end)
local update_partitions = ya.sync(function(self, partitions)
self.partitions = partitions
self.cursor = math.max(0, math.min(self.cursor or 0, #self.partitions - 1))
ya.render()
end)
local active_partition = ya.sync(function(self) return self.partitions[self.cursor + 1] end)
local update_cursor = ya.sync(function(self, cursor)
if #self.partitions == 0 then
self.cursor = 0
else
self.cursor = ya.clamp(0, self.cursor + cursor, #self.partitions - 1)
end
ya.render()
end)
local M = {
keys = {
{ on = "q", run = "quit" },
{ on = "k", run = "up" },
{ on = "j", run = "down" },
{ on = "l", run = { "enter", "quit" } },
{ on = "<Up>", run = "up" },
{ on = "<Down>", run = "down" },
{ on = "<Right>", run = { "enter", "quit" } },
{ on = "m", run = "mount" },
{ on = "u", run = "unmount" },
{ on = "e", run = "eject" },
},
}
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
function M:entry(job)
if job.args[1] == "refresh" then
return update_partitions(self.obtain())
end
toggle_ui()
update_partitions(self.obtain())
subscribe()
local tx1, rx1 = ya.chan("mpsc")
local tx2, rx2 = ya.chan("mpsc")
function producer()
while true do
local cand = self.keys[ya.which { cands = self.keys, silent = true }] or { run = {} }
for _, r in ipairs(type(cand.run) == "table" and cand.run or { cand.run }) do
tx1:send(r)
if r == "quit" then
toggle_ui()
return
end
end
end
end
function consumer1()
repeat
local run = rx1:recv()
if run == "quit" then
tx2:send(run)
break
elseif run == "up" then
update_cursor(-1)
elseif run == "down" then
update_cursor(1)
elseif run == "enter" then
local active = active_partition()
if active and active.dist then
ya.manager_emit("cd", { active.dist })
end
else
tx2:send(run)
end
until not run
end
function consumer2()
repeat
local run = rx2:recv()
if run == "quit" then
break
elseif run == "mount" then
self.operate("mount")
elseif run == "unmount" then
self.operate("unmount")
elseif run == "eject" then
self.operate("eject")
end
until not run
end
ya.join(producer, consumer1, consumer2)
end
function M:reflow() return { self } end
function M:redraw()
local rows = {}
for _, p in ipairs(self.partitions or {}) do
if p.sub == "" then
rows[#rows + 1] = ui.Row { p.main }
else
rows[#rows + 1] = ui.Row { p.sub, p.label or "", p.dist or "", p.fstype or "" }
end
end
return {
ui.Clear(self._area),
ui.Border(ui.Border.ALL)
:area(self._area)
:type(ui.Border.ROUNDED)
:style(ui.Style():fg("blue"))
:title(ui.Line("Mount"):align(ui.Line.CENTER)),
ui.Table(rows)
:area(self._area:pad(ui.Pad(1, 2, 1, 2)))
:header(ui.Row({ "Src", "Label", "Dist", "FSType" }):style(ui.Style():bold()))
:row(self.cursor)
:row_style(ui.Style():fg("blue"):underline())
:widths {
ui.Constraint.Length(20),
ui.Constraint.Length(20),
ui.Constraint.Percentage(70),
ui.Constraint.Length(10),
},
}
end
function M.obtain()
local tbl = {}
local last
for _, p in ipairs(fs.partitions()) do
local main, sub
if ya.target_os() == "macos" then
main, sub = p.src:match("^(/dev/disk%d+)(.+)$")
elseif p.src:find("/dev/nvme", 1, true) == 1 then -- /dev/nvme0n1p1
main, sub = p.src:match("^(/dev/nvme%d+n%d+)(p%d+)$")
elseif p.src:find("/dev/sd", 1, true) == 1 then -- /dev/sda1
main, sub = p.src:match("^(/dev/sd[a-z])(%d+)$")
end
if sub then
if last ~= main then
last, tbl[#tbl + 1] = main, { src = main, main = main, sub = "" }
end
p.main, p.sub, tbl[#tbl + 1] = main, " " .. sub, p
end
end
table.sort(M.fillin(tbl), function(a, b)
if a.main == b.main then
return a.sub < b.sub
else
return a.main > b.main
end
end)
return tbl
end
function M.fillin(tbl)
if ya.target_os() ~= "linux" then
return tbl
end
local sources, indices = {}, {}
for i, p in ipairs(tbl) do
if p.sub ~= "" and not p.fstype then
sources[#sources + 1], indices[p.src] = p.src, i
end
end
if #sources == 0 then
return tbl
end
local output, err = Command("lsblk"):args({ "-p", "-o", "name,fstype", "-J" }):args(sources):output()
if err then
ya.dbg("Failed to fetch filesystem types for unmounted partitions: " .. err)
return tbl
end
local t = ya.json_decode(output and output.stdout or "")
for _, p in ipairs(t and t.blockdevices or {}) do
tbl[indices[p.name]].fstype = p.fstype
end
return tbl
end
function M.operate(type)
local active = active_partition()
if not active then
return
elseif active.sub == "" then
return -- TODO: mount/unmount main disk
end
local output, err
if ya.target_os() == "macos" then
output, err = Command("diskutil"):args({ type, active.src }):output()
end
if ya.target_os() == "linux" then
if type == "eject" then
Command("udisksctl"):args({ "unmount", "-b", active.src }):status()
output, err = Command("udisksctl"):args({ "power-off", "-b", active.src }):output()
else
output, err = Command("udisksctl"):args({ type, "-b", active.src }):output()
end
end
if not output then
M.fail("Failed to %s `%s`: %s", type, active.src, err)
elseif not output.status.success then
M.fail("Failed to %s `%s`: %s", type, active.src, output.stderr)
end
end
function M.fail(s, ...) ya.notify { title = "Mount", content = string.format(s, ...), timeout = 10, level = "error" } end
function M:click() end
function M:scroll() end
function M:touch() end
return M

View file

@ -1,11 +0,0 @@
--- @since 25.2.7
--- @sync entry
local function setup(self, opts) self.open_multi = opts.open_multi end
local function entry(self)
local h = cx.active.current.hovered
ya.manager_emit(h and h.cha.is_dir and "enter" or "open", { hovered = not self.open_multi })
end
return { entry = entry, setup = setup }

@ -0,0 +1 @@
Subproject commit 9a095057d698aaaedc4dd23d638285bd3fd647e9