restertet yazi setup

new file:   herdr/.config/herdr/sessions/home/session-history.json
	new file:   herdr/.config/herdr/sessions/home/session.json
	new file:   kanata/.config/systemd/user/default.target.wants/duplicati.service
	new file:   pi/.pi/agent/extensions/atuin.ts
	new file:   yazi/.config/yazi/bookmark
	modified:   yazi/.config/yazi/init.lua
	modified:   yazi/.config/yazi/keymap.toml
	modified:   yazi/.config/yazi/package.toml
	renamed:    yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE -> yazi/.config/yazi/plugins/chmod.yazi/LICENSE
	new file:   yazi/.config/yazi/plugins/chmod.yazi/README.md
	new file:   yazi/.config/yazi/plugins/chmod.yazi/main.lua
	new file:   yazi/.config/yazi/plugins/eza-preview.yazi/LICENSE
	new file:   yazi/.config/yazi/plugins/eza-preview.yazi/README.md
	new file:   yazi/.config/yazi/plugins/eza-preview.yazi/main.lua
	modified:   yazi/.config/yazi/plugins/fchar.yazi/README.md
	modified:   yazi/.config/yazi/plugins/fchar.yazi/main.lua
	deleted:    yazi/.config/yazi/plugins/file-extra-metadata.yazi/main.lua
	modified:   yazi/.config/yazi/plugins/mediainfo.yazi/LICENSE
	modified:   yazi/.config/yazi/plugins/mediainfo.yazi/README.md
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/audio-old.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/const.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/image-old.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/image-stable.lua
	modified:   yazi/.config/yazi/plugins/mediainfo.yazi/main.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/utils.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/video-old.lua
	new file:   yazi/.config/yazi/plugins/mediainfo.yazi/video-stable.lua
	modified:   yazi/.config/yazi/plugins/mime-ext.yazi/README.md
	new file:   yazi/.config/yazi/plugins/mime-ext.yazi/local.lua
	modified:   yazi/.config/yazi/plugins/mime-ext.yazi/main.lua
	new file:   yazi/.config/yazi/plugins/mime-ext.yazi/remote.lua
	modified:   yazi/.config/yazi/plugins/rich-preview.yazi/main.lua
	modified:   yazi/.config/yazi/plugins/smart-enter.yazi/README.md
	deleted:    yazi/.config/yazi/plugins/whoosh.yazi/.luarc.json
	modified:   yazi/.config/yazi/plugins/whoosh.yazi/LICENSE
	deleted:    yazi/.config/yazi/plugins/whoosh.yazi/README-RU.md
	modified:   yazi/.config/yazi/plugins/whoosh.yazi/README.md
	modified:   yazi/.config/yazi/plugins/whoosh.yazi/bookmarks
	deleted:    yazi/.config/yazi/plugins/whoosh.yazi/image/history.png
	deleted:    yazi/.config/yazi/plugins/whoosh.yazi/image/plugin.png
	modified:   yazi/.config/yazi/plugins/whoosh.yazi/main.lua
	modified:   yazi/.config/yazi/yazi.toml
	renamed:    yazi/.config/yazi/plugins/full-border.yazi/LICENSE -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE
	renamed:    yazi/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/LICENSE-tmtheme
	renamed:    yazi/.config/yazi/flavors/catppuccin-mocha.yazi/README.md -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/README.md
	renamed:    yazi/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/flavor.toml
	renamed:    yazi/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/preview.png
	renamed:    yazi/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/catppuccin-mocha.yazi/tmtheme.xml
	renamed:    yazi/.config/yazi/flavors/dracula.yazi -> yazi_1/.config/yazi/flavors/dracula.yazi
	renamed:    yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE
	renamed:    yazi/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/LICENSE-tmtheme
	renamed:    yazi/.config/yazi/flavors/everforest-medium.yazi/README.md -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/README.md
	renamed:    yazi/.config/yazi/flavors/everforest-medium.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/flavor.toml
	renamed:    yazi/.config/yazi/flavors/everforest-medium.yazi/preview.png -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/preview.png
	renamed:    yazi/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/everforest-medium.yazi/tmtheme.xml
	new file:   yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE
	new file:   yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/LICENSE-tmtheme
	new file:   yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/README.md
	new file:   yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/flavor.toml
	new file:   yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/preview.png
	new file:   yazi_1/.config/yazi/flavors/gruvbox-dark.yazi/tmtheme.xml
	renamed:    yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE
	renamed:    yazi/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/LICENSE-tmtheme
	renamed:    yazi/.config/yazi/flavors/gruvbox-material.yazi/README.md -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/README.md
	renamed:    yazi/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/flavor.toml
	renamed:    yazi/.config/yazi/flavors/gruvbox-material.yazi/preview.png -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/preview.png
	renamed:    yazi/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/gruvbox-material.yazi/tmtheme.xml
	renamed:    yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE -> yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE
	renamed:    yazi/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/kanagawa.yazi/LICENSE-tmtheme
	renamed:    yazi/.config/yazi/flavors/kanagawa.yazi/README.md -> yazi_1/.config/yazi/flavors/kanagawa.yazi/README.md
	renamed:    yazi/.config/yazi/flavors/kanagawa.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/kanagawa.yazi/flavor.toml
	renamed:    yazi/.config/yazi/flavors/kanagawa.yazi/preview.png -> yazi_1/.config/yazi/flavors/kanagawa.yazi/preview.png
	renamed:    yazi/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/kanagawa.yazi/tmtheme.xml
	renamed:    yazi/.config/yazi/flavors/monokai.yazi/LICENSE -> yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE
	renamed:    yazi/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/monokai.yazi/LICENSE-tmtheme
	renamed:    yazi/.config/yazi/flavors/monokai.yazi/README.md -> yazi_1/.config/yazi/flavors/monokai.yazi/README.md
	renamed:    yazi/.config/yazi/flavors/monokai.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/monokai.yazi/flavor.toml
	renamed:    yazi/.config/yazi/flavors/monokai.yazi/preview.png -> yazi_1/.config/yazi/flavors/monokai.yazi/preview.png
	renamed:    yazi/.config/yazi/flavors/monokai.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/monokai.yazi/tmtheme.xml
	renamed:    yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE
	renamed:    yazi/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/LICENSE-tmtheme
	renamed:    yazi/.config/yazi/flavors/rose-pine-moon.yazi/README.md -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/README.md
	renamed:    yazi/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/flavor.toml
	renamed:    yazi/.config/yazi/flavors/rose-pine-moon.yazi/preview.png -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/preview.png
	renamed:    yazi/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/rose-pine-moon.yazi/tmtheme.xml
	renamed:    yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE
	renamed:    yazi/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/LICENSE-tmtheme
	renamed:    yazi/.config/yazi/flavors/tokyo-night.yazi/README.md -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/README.md
	renamed:    yazi/.config/yazi/flavors/tokyo-night.yazi/flavor.toml -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/flavor.toml
	renamed:    yazi/.config/yazi/flavors/tokyo-night.yazi/preview.png -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/preview.png
	renamed:    yazi/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml -> yazi_1/.config/yazi/flavors/tokyo-night.yazi/tmtheme.xml
	new file:   yazi_1/.config/yazi/init.lua
	new file:   yazi_1/.config/yazi/keymap.toml
	renamed:    yazi/.config/yazi/omp/omp.json -> yazi_1/.config/yazi/omp/omp.json
	renamed:    yazi/.config/yazi/omp/omp.toml -> yazi_1/.config/yazi/omp/omp.toml
	renamed:    yazi/.config/yazi/omp/omp2.toml -> yazi_1/.config/yazi/omp/omp2.toml
	renamed:    yazi/.config/yazi/omp/omp_rose-pine.toml -> yazi_1/.config/yazi/omp/omp_rose-pine.toml
	new file:   yazi_1/.config/yazi/package.toml
	renamed:    yazi/.config/yazi/plugins/bunny.yazi/LICENSE -> yazi_1/.config/yazi/plugins/bunny.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/bunny.yazi/README.md -> yazi_1/.config/yazi/plugins/bunny.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/bunny.yazi/main.lua -> yazi_1/.config/yazi/plugins/bunny.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/bypass.yazi/LICENSE -> yazi_1/.config/yazi/plugins/bypass.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/bypass.yazi/README.md -> yazi_1/.config/yazi/plugins/bypass.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/bypass.yazi/main.lua -> yazi_1/.config/yazi/plugins/bypass.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/dupes.yazi/LICENSE -> yazi_1/.config/yazi/plugins/dupes.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/dupes.yazi/README.md -> yazi_1/.config/yazi/plugins/dupes.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/dupes.yazi/main.lua -> yazi_1/.config/yazi/plugins/dupes.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/fazif.yazi/LICENSE -> yazi_1/.config/yazi/plugins/fazif.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/fazif.yazi/README.md -> yazi_1/.config/yazi/plugins/fazif.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/fazif.yazi/faziffd -> yazi_1/.config/yazi/plugins/fazif.yazi/faziffd
	renamed:    yazi/.config/yazi/plugins/fazif.yazi/faziffdr -> yazi_1/.config/yazi/plugins/fazif.yazi/faziffdr
	renamed:    yazi/.config/yazi/plugins/fazif.yazi/fazifrg -> yazi_1/.config/yazi/plugins/fazif.yazi/fazifrg
	renamed:    yazi/.config/yazi/plugins/fazif.yazi/fazifrga -> yazi_1/.config/yazi/plugins/fazif.yazi/fazifrga
	renamed:    yazi/.config/yazi/plugins/fazif.yazi/main.lua -> yazi_1/.config/yazi/plugins/fazif.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/fchar.yazi/LICENSE
	new file:   yazi_1/.config/yazi/plugins/fchar.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/fchar.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE -> yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/file-extra-metadata.yazi/README.md -> yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/file-extra-metadata.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/git.yazi/LICENSE -> yazi_1/.config/yazi/plugins/full-border.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/full-border.yazi/README.md -> yazi_1/.config/yazi/plugins/full-border.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/full-border.yazi/main.lua -> yazi_1/.config/yazi/plugins/full-border.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/mount.yazi/LICENSE -> yazi_1/.config/yazi/plugins/git.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/git.yazi/README.md -> yazi_1/.config/yazi/plugins/git.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/git.yazi/main.lua -> yazi_1/.config/yazi/plugins/git.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/git.yazi/types.lua -> yazi_1/.config/yazi/plugins/git.yazi/types.lua
	renamed:    yazi/.config/yazi/plugins/lazygit.yazi/LICENSE -> yazi_1/.config/yazi/plugins/lazygit.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/lazygit.yazi/README.md -> yazi_1/.config/yazi/plugins/lazygit.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/lazygit.yazi/main.lua -> yazi_1/.config/yazi/plugins/lazygit.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/open-with-cmd.yazi/LICENSE -> yazi_1/.config/yazi/plugins/mediainfo.yazi/LICENSE
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-old.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/adobe-stable.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-14-23.png
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-09-15-39.png
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-51-11.png
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/assets/2025-02-15-16-52-39.png
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-old.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/audio-stable.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/const.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/image-old.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/image-stable.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-old.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/none-media-preview-stable.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/utils.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/video-old.lua
	new file:   yazi_1/.config/yazi/plugins/mediainfo.yazi/video-stable.lua
	renamed:    yazi/.config/yazi/plugins/piper.yazi/LICENSE -> yazi_1/.config/yazi/plugins/mime-ext.yazi/LICENSE
	new file:   yazi_1/.config/yazi/plugins/mime-ext.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/mime-ext.yazi/local.lua
	new file:   yazi_1/.config/yazi/plugins/mime-ext.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/mime-ext.yazi/remote.lua
	renamed:    yazi/.config/yazi/plugins/rsync.yazi/LICENSE -> yazi_1/.config/yazi/plugins/mount.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/mount.yazi/README.md -> yazi_1/.config/yazi/plugins/mount.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/mount.yazi/cross.lua
	renamed:    yazi/.config/yazi/plugins/mount.yazi/main.lua -> yazi_1/.config/yazi/plugins/mount.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/mount.yazi/sudo.lua
	renamed:    yazi/.config/yazi/plugins/omp.yazi -> yazi_1/.config/yazi/plugins/omp.yazi
	renamed:    yazi/.config/yazi/plugins/open-git-remote.yazi/LICENSE -> yazi_1/.config/yazi/plugins/open-git-remote.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/open-git-remote.yazi/README.md -> yazi_1/.config/yazi/plugins/open-git-remote.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/open-git-remote.yazi/main.lua -> yazi_1/.config/yazi/plugins/open-git-remote.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/open-with-cmd.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/open-with-cmd.yazi/README.md -> yazi_1/.config/yazi/plugins/open-with-cmd.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/open-with-cmd.yazi/main.lua -> yazi_1/.config/yazi/plugins/open-with-cmd.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/ouch.yazi/LICENSE -> yazi_1/.config/yazi/plugins/ouch.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/ouch.yazi/README.md -> yazi_1/.config/yazi/plugins/ouch.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/ouch.yazi/main.lua -> yazi_1/.config/yazi/plugins/ouch.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/toggle-pane.yazi/LICENSE -> yazi_1/.config/yazi/plugins/piper.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/piper.yazi/README.md -> yazi_1/.config/yazi/plugins/piper.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/piper.yazi/main.lua -> yazi_1/.config/yazi/plugins/piper.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/projects.yazi/LICENSE -> yazi_1/.config/yazi/plugins/projects.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/projects.yazi/README.md -> yazi_1/.config/yazi/plugins/projects.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/projects.yazi/json.lua -> yazi_1/.config/yazi/plugins/projects.yazi/json.lua
	renamed:    yazi/.config/yazi/plugins/projects.yazi/main.lua -> yazi_1/.config/yazi/plugins/projects.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/recycle-bin.yazi/LICENSE -> yazi_1/.config/yazi/plugins/recycle-bin.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/recycle-bin.yazi/README.md -> yazi_1/.config/yazi/plugins/recycle-bin.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/recycle-bin.yazi/main.lua -> yazi_1/.config/yazi/plugins/recycle-bin.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/rich-preview.yazi/LICENSE
	new file:   yazi_1/.config/yazi/plugins/rich-preview.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/rich-preview.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/vcs-files.yazi/LICENSE -> yazi_1/.config/yazi/plugins/rsync.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/rsync.yazi/README.md -> yazi_1/.config/yazi/plugins/rsync.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/rsync.yazi/assets/demo.gif -> yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.gif
	renamed:    yazi/.config/yazi/plugins/rsync.yazi/assets/demo.webm -> yazi_1/.config/yazi/plugins/rsync.yazi/assets/demo.webm
	renamed:    yazi/.config/yazi/plugins/rsync.yazi/main.lua -> yazi_1/.config/yazi/plugins/rsync.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/simple-tag.yazi/LICENSE -> yazi_1/.config/yazi/plugins/simple-tag.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/simple-tag.yazi/README.md -> yazi_1/.config/yazi/plugins/simple-tag.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/simple-tag.yazi/main.lua -> yazi_1/.config/yazi/plugins/simple-tag.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/smart-enter.yazi/LICENSE
	new file:   yazi_1/.config/yazi/plugins/smart-enter.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/smart-enter.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/smart-paste.yazi/LICENSE -> yazi_1/.config/yazi/plugins/smart-paste.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/smart-paste.yazi/README.md -> yazi_1/.config/yazi/plugins/smart-paste.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/smart-paste.yazi/main.lua -> yazi_1/.config/yazi/plugins/smart-paste.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/toggle-pane.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/toggle-pane.yazi/README.md -> yazi_1/.config/yazi/plugins/toggle-pane.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/toggle-pane.yazi/main.lua -> yazi_1/.config/yazi/plugins/toggle-pane.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/vcs-files.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/vcs-files.yazi/README.md -> yazi_1/.config/yazi/plugins/vcs-files.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/vcs-files.yazi/main.lua -> yazi_1/.config/yazi/plugins/vcs-files.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/what-size.yazi/LICENSE -> yazi_1/.config/yazi/plugins/what-size.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/what-size.yazi/README.md -> yazi_1/.config/yazi/plugins/what-size.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/what-size.yazi/main.lua -> yazi_1/.config/yazi/plugins/what-size.yazi/main.lua
	new file:   yazi_1/.config/yazi/plugins/whoosh.yazi
	new file:   yazi_1/.config/yazi/plugins/wise-enter.yazi/LICENSE
	new file:   yazi_1/.config/yazi/plugins/wise-enter.yazi/README.md
	new file:   yazi_1/.config/yazi/plugins/wise-enter.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/yafg.yazi/LICENSE -> yazi_1/.config/yazi/plugins/yafg.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/yafg.yazi/README.md -> yazi_1/.config/yazi/plugins/yafg.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/yafg.yazi/main.lua -> yazi_1/.config/yazi/plugins/yafg.yazi/main.lua
	renamed:    yazi/.config/yazi/plugins/yaziline.yazi/LICENSE -> yazi_1/.config/yazi/plugins/yaziline.yazi/LICENSE
	renamed:    yazi/.config/yazi/plugins/yaziline.yazi/README.md -> yazi_1/.config/yazi/plugins/yaziline.yazi/README.md
	renamed:    yazi/.config/yazi/plugins/yaziline.yazi/main.lua -> yazi_1/.config/yazi/plugins/yaziline.yazi/main.lua
	renamed:    yazi/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json -> yazi_1/.config/yazi/tags/home/liph/.local/share/Trash/files/tags.json
	renamed:    yazi/.config/yazi/tags/home/liph/Downloads/tags.json -> yazi_1/.config/yazi/tags/home/liph/Downloads/tags.json
	renamed:    yazi/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json -> yazi_1/.config/yazi/tags/home/liph/programming/webdev/html/static/tags.json
	new file:   yazi_1/.config/yazi/tags/home/liph/scripts/tags.json
	renamed:    yazi/.config/yazi/tags/home/liph/tags.json -> yazi_1/.config/yazi/tags/home/liph/tags.json
	new file:   yazi_1/.config/yazi/yazi.toml
This commit is contained in:
liph committed 2026-08-29 08:59:31 +02:00
1 parent d6fd64c8af
commit a8a1b5a718
226 files changed
+17541 -3631

No files matched your search

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Mshnwq
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.
@@ -0,0 +1,221 @@
# Dupes Plugin for Yazi
Duplicate files plugin for [Yazi](https://github.com/sxyazi/yazi) file manager, leveraging [jdupes](https://codeberg.org/jbruchon/jdupes) for fast and efficient duplicate detection.
> [!NOTE]
> The latest release of Yazi is required at the moment.
https://github.com/user-attachments/assets/1d87bb52-d326-4720-834c-baac74008d7d
## Features
- 🔍 **Fast Duplicate Detection** - Uses jdupes for high-performance scanning
- 👁️ **Visual Preview** - See duplicates directly in Yazi with deletion markers
- 🧪 **Dry Run Mode** - Preview what will be deleted before applying changes
- ⚙️ **Profile System** - Define custom scanning or override profiles for different use cases
- 💾 **Export Results** - Save scan results to JSON or text files
- 🔒 **Safe by Default** - Confirmation required for destructive operations
## Requirements
- [Yazi](https://github.com/sxyazi/yazi) file manager (v25.5.31+)
- [jdupes](https://codeberg.org/jbruchon/jdupes) command-line tool
## Installation
**Using yazi package manager:**
```sh
ya pkg add mshnwq/dupes
```
**Using nix home-manager (nixpkgs): (recommended)**
```nix
{ pkgs, ... }: {
programs.yazi = {
enable = true;
plugins = {
# jdupes is self contained within
dupes = pkgs.yaziPlugins.dupes
};
};
}
```
**Using nix home-manager (github):**
```nix
{ pkgs, ... }: {
home.packages = with pkgs; [ jdupes ];
programs.yazi = {
enable = true;
plugins = {
dupes = pkgs.fetchFromGitHub {
owner = "mshnwq";
repo = "dupes.yazi";
rev = "4666b6f299c2257c011f622319ae97fab8adbabe";
hash = "sha256-v9xuSY/i/trIHHbOPbijd0AmcUb2vufNL9BSjBE6+Vo=";
};
};
};
}
```
**Manual installation:**
```sh
git clone https://github.com/mshnwq/dupes.yazi.git ~/.config/yazi/plugins/dupes.yazi
```
## Usage
Configuration in `~/.config/yazi/init.lua`:
```lua
require("dupes"):setup {
-- Global settings
save_op = false, -- Save results to file by default
-- auto_confirm = true, -- Skip confirmation for apply (use with caution!)
profiles = {
-- Interactive mode: recursively scan and display duplicates
interactive = {
args = { "-r" },
},
-- Apply mode: recursively scan and DELETE duplicates
apply = {
args = { "-r", "-N", "-d" },
save_op = true, -- Save results before deletion
},
-- Custom profile example (uncomment to use)
-- custom = {
-- args = { "-r", "-s", }, -- Your custom jdupes flags
-- },
},
}
```
Keybindings in `~/.config/yazi/keymap.toml`:
```toml
[[manager.prepend_keymap]]
on = ["<A-J>", "i"]
run = "plugin dupes interactive"
desc = "Run dupes interactive"
[[manager.prepend_keymap]]
on = ["<A-J>", "o"]
run = "plugin dupes override"
desc = "Run dupes override"
[[manager.prepend_keymap]]
on = ["<A-J>", "d"]
run = "plugin dupes dry"
desc = "Run dupes dry"
[[manager.prepend_keymap]]
on = ["<A-J>", "a"]
run = "plugin dupes apply"
desc = "Run dupes apply"
# [[mgr.prepend_keymap]]
# on = ["<A-J>", "c"]
# run = "plugin dupes custom"
# desc = "Run dupes custom"
```
### Profiles
#### 🔍 Interactive Mode
Scans for duplicates and displays them in Yazi. Safe to use - no files are deleted.
```lua
interactive = {
args = { "-r" }, -- Recursive scan
}
```
#### 🧪 Dry Run Mode (inherits apply mode)
Shows what **would** be deleted using the same settings as apply mode. Files marked 'X' will be deleted. The first file in each duplicate set is kept.
- Normal files: displayed normally (will be kept)
- Files marked 'X': will be deleted
#### ⚠️ Apply Mode
**DESTRUCTIVE OPERATION** - Permanently deletes duplicate files.
```lua
apply = {
args = { "-r", "-N", "-d" },
save_op = true, -- Saves results to dupes.json
}
```
#### 🛠️ Override Mode
Opens an input dialog where you can enter custom jdupes arguments for one-time scans.
**Example inputs:**
- `-r -s` - Recursive, follow symlinks
## Configuration Options
### Global Settings
```lua
require("dupes"):setup {
save_op = false, -- Default: don't save results to file
auto_confirm = false, -- Default: prompt before deletion
profiles = {
-- Your profiles here
},
}
```
### Profile Settings
Each profile can have:
```lua
profile_name = {
args = { "-r", "-N" }, -- jdupes command arguments
save_op = true, -- Override global save_op for this profile
}
```
### Common jdupes Flags
| Flag | Description |
|------|-------------|
| `-r` | Recursive search in subdirectories |
| `-d` | **DELETE duplicates (DESTRUCTIVE!)** |
| `-j` | JSON output (added by plugin) |
| `-s` | Follows symbolic links |
See `jdupes --help` for complete flag list.
## Custom Examples
### Example: Find duplicates in current directory only
```lua
shallow = {
args = {}, -- No -r flag, current directory only
}
```
## Output Files
When `save_op = true`, results are saved to:
- **dupes.json** - JSON format
- **dupes.txt** - Text format (when destructive)
Files are saved in the current working directory.
## License
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
## Credits
- [Yazi](https://github.com/sxyazi/yazi) file manager
- [jdupes](https://codeberg.org/jbruchon/jdupes) for duplicate detection
- [boydaihungst](https://github.com/boydaihungst) for help on Yazi's [Discord](https://discord.gg/qfADduSdJu) channel
## See Also
- [jdupes Documentation](https://codeberg.org/jbruchon/jdupes)
- [Yazi Plugins](https://yazi-rs.github.io/docs/plugins/overview)
@@ -0,0 +1,600 @@
--- Dupes Plugin: Dynamic jdupes runner for Yazi file manager
--- @since 25.5.31
--- @description Finds and manages duplicate files using jdupes
local M = {}
-- ============================================================================
-- STATE MANAGEMENT
-- ============================================================================
--- Thread-safe state setter
--- @param state table The global state object
--- @param key string State key to set
--- @param value any Value to store
local set_state = ya.sync(function(state, key, value) state[key] = value end)
--- Thread-safe state getter
--- @param state table The global state object
--- @param key string State key to retrieve
--- @return any The stored value
local get_state = ya.sync(function(state, key) return state[key] end)
--- Get current working directory from Yazi context
--- @return string Current working directory path
local get_cwd = ya.sync(function() return cx.active.current.cwd end)
-- ============================================================================
-- DISPLAY FUNCTIONS
-- ============================================================================
--- Display duplicate files in Yazi's file list
--- @param _cwd string Current working directory
--- @param data table Parsed JSON data from jdupes output
--- @param style boolean If true, shows dry-run preview with deletion markers
local function display_dupes(_cwd, data, style)
-- Validate input data structure
if not data or not data.matchSets then
ya.dbg("Invalid or missing matchSets in JSON data")
ya.notify {
title = "Dupes Plugin",
content = "Invalid JSON data: no matchSets",
level = "error",
timeout = 5,
}
return
end
ya.dbg(string.format("Entering display_dupes, cwd: %s", _cwd))
-- Create unique ID for this file operation
local id = ya.id("ft")
-- Set search context title based on mode
local cwd
if style then
cwd = _cwd:into_search("Duplicate files Dry Run Preview 'X means will be deleted'")
else
cwd = _cwd:into_search("Duplicate files")
end
-- Navigate to the search view
ya.emit("cd", { Url(cwd) })
-- Initialize empty file list
ya.emit("update_files", {
op = fs.op("part", { id = id, url = Url(cwd), files = {} }),
})
-- Build file list from duplicate sets
local files = {}
for i, matchSet in ipairs(data.matchSets) do
local dupe_set = string.format("dup-set-%02d", i)
ya.dbg(string.format("Processing group: %s", dupe_set))
-- Process each file in the duplicate set
for j, fileObj in ipairs(matchSet.fileList) do
local url = Url(cwd):join(fileObj.filePath)
local cha = fs.cha(url, true)
local file
if style then
-- In dry-run mode, mark files for deletion (except first one)
if j == 1 then
-- First file in set: keep it (normal display)
file = File { url = url, cha = cha }
else
-- Subsequent files: mark as dummy (will be deleted)
local dcha = Cha {
kind = 16,
is_dummy = true,
len = cha.len,
gid = cha.gid,
uid = cha.uid,
atime = cha.atime,
btime = cha.btime,
mtime = cha.mtime,
perm = cha.perm,
}
file = File { url = url, cha = dcha }
end
else
-- Normal mode: display all files equally
file = File { url = url, cha = cha }
end
table.insert(files, file)
ya.dbg(string.format("[Dupes] Added file %s", file.url))
end
end
-- Update the file list in Yazi
ya.emit("update_files", {
op = fs.op("part", { id = id, url = Url(cwd), files = files }),
})
-- NOTE: Finalization with 'done' operation breaks file ordering
-- Keeping these commented for future investigation
-- ya.emit("update_files", {
-- op = fs.op("done", {
-- id = id,
-- url = Url(cwd),
-- cha = Cha({ kind = 16, mode = tonumber("100644", 8) })
-- })
-- })
-- Show notification with results
local mode_text = style and " (DRY RUN)" or ""
ya.notify {
title = "Dupes Plugin",
content = string.format("Found %d files%s", #files, mode_text),
level = "info",
timeout = 3,
}
end
-- ============================================================================
-- FILE SAVING FUNCTIONS
-- ============================================================================
--- Save jdupes output to file (JSON or plain text)
--- @param cwd string Directory to save the file in
--- @param stdout string Raw output from jdupes
--- @param format string Output format: "json" or "txt"
--- @return boolean success True if file was saved successfully
local function save_output(cwd, stdout, format)
-- Validate format
format = format or "json"
if format ~= "json" and format ~= "txt" then
ya.err(string.format("Invalid format '%s', defaulting to 'json'", format))
format = "json"
end
-- Construct file path
local file_path = string.format("%s/dupes.%s", cwd, format)
-- Attempt to write file
local file, err = io.open(file_path, "w")
if not file then
ya.err(string.format("Failed to write dupes.%s to %s: %s", format, file_path, err or "unknown error"))
ya.notify {
title = "Dupes Plugin",
content = string.format("Failed to save file: %s", err or "unknown error"),
level = "error",
timeout = 5,
}
return false
end
-- Write content and close
file:write(stdout)
file:close()
-- Success notification
ya.notify {
title = "Dupes Plugin",
content = string.format("Saved dupes.%s to %s", format, file_path),
level = "info",
timeout = 3,
}
return true
end
-- ============================================================================
-- COMMAND BUILDING AND VALIDATION
-- ============================================================================
--- Build final command arguments with proper flag handling
--- @param args table User-provided arguments
--- @param no_json boolean Whether to exclude JSON flag
--- @return table Final argument list
local function build_command_args(args, no_json)
-- Check if JSON flag already exists
local has_j_flag = false
for _, arg in ipairs(args) do
if arg:match("^%-[^-]*j") or arg == "-j" then
has_j_flag = true
break
end
end
-- Build final argument list
local final_args = {}
-- Add JSON flag if needed and not disabled
if not has_j_flag and not no_json then
table.insert(final_args, "-j")
end
-- Add all user arguments
for _, arg in ipairs(args) do
table.insert(final_args, arg)
end
return final_args
end
--- Execute shell command with proper error handling
--- @param cmdline string Base command (e.g., "jdupes")
--- @param args table Command arguments
--- @return table Command result with status, stdout, stderr
local function execute_command(cmdline, args)
local argstr = table.concat(args, " ")
local full_cmd = string.format("%s . %s 2>&1", cmdline, argstr)
ya.dbg(string.format("Executing: %s", full_cmd))
local cmd = Command(get_state("shell")):arg({ "-c", full_cmd }):stdout(Command.PIPED):stderr(Command.PIPED):output()
-- Log execution results
ya.dbg(string.format("Exit code: %s", tostring(cmd.status.code)))
if cmd.stdout and #cmd.stdout > 0 then
ya.dbg(string.format("STDOUT length: %d bytes", #cmd.stdout))
end
if cmd.stderr and #cmd.stderr > 0 then
ya.err(string.format("STDERR:\n%s", cmd.stderr))
end
return cmd
end
-- ============================================================================
-- CORE EXECUTION FUNCTION
-- ============================================================================
--- Execute jdupes with given profile configuration
--- @param profile_name string Name of the profile being run
--- @param conf table Configuration with cmdline and args
--- @param save_to_file boolean Whether to save output to file
--- @param no_display boolean Whether to skip displaying results in Yazi
--- @param no_json boolean Whether to output plain text instead of JSON
local function run_dupes(profile_name, conf, save_to_file, no_display, no_json)
-- Validate configuration
if not conf then
ya.err(string.format("ERROR: Profile '%s' configuration is nil!", profile_name))
return
end
if not conf.cmdline then
ya.err(string.format("ERROR: Profile '%s' missing cmdline", profile_name))
return
end
-- Extract configuration with defaults
local cmdline = conf.cmdline
local args = conf.args or {}
save_to_file = save_to_file or false
no_display = no_display or false
no_json = no_json or false
-- Build command arguments
local final_args = build_command_args(args, no_json)
-- Execute command
local cwd = get_cwd()
local cmd = execute_command(cmdline, final_args)
-- Handle command results
if cmd.status.success then
-- Save output to file if requested
if save_to_file then
local format = no_json and "txt" or "json"
save_output(cwd, cmd.stdout, format)
end
-- Display results in Yazi unless suppressed
if not no_display and not no_json then
local parsed_data = ya.json_decode(cmd.stdout)
if parsed_data then
local is_dry_run = (profile_name == "dry")
display_dupes(cwd, parsed_data, is_dry_run)
else
ya.err("Failed to parse JSON output from jdupes")
ya.notify {
title = "Dupes Plugin",
content = "Failed to parse command output",
level = "error",
timeout = 5,
}
return
end
end
-- Success notification
ya.notify {
title = "Dupes Plugin",
content = string.format("Profile '%s' completed successfully", profile_name),
level = "info",
timeout = 3,
}
else
-- Error notification with details
local error_msg = cmd.stderr or "(no error details available)"
ya.notify {
title = "Dupes Plugin",
content = string.format("Command failed:\n%s", error_msg),
level = "error",
timeout = 5,
}
end
end
-- ============================================================================
-- PROFILE HANDLING
-- ============================================================================
--- Create dry-run configuration from apply profile
--- @param apply_conf table Apply profile configuration
--- @return table Dry-run configuration
local function create_dry_config(apply_conf)
local dry_args = {}
-- Copy all arguments except destructive ones
for _, arg in ipairs(apply_conf.args) do
if arg ~= "-d" and arg ~= "--delete" then
table.insert(dry_args, arg)
else
ya.dbg(string.format("Excluded destructive flag: %s", arg))
end
end
return {
cmdline = apply_conf.cmdline,
args = dry_args,
}
end
--- Parse custom arguments from user input
--- @param input string Space-separated argument string
--- @return table Parsed arguments
local function parse_custom_args(input)
local args = {}
for arg in input:gmatch("%S+") do
table.insert(args, arg)
end
return args
end
-- ============================================================================
-- UTILITY FUNCTIONS
-- ============================================================================
--- Recursively dump table contents for debugging
--- @param tbl table Table to dump
--- @param indent number Current indentation level
--- @return table Array of formatted lines
local function dump_table(tbl, indent)
indent = indent or 0
local lines = {}
local prefix = string.rep(" ", indent)
for k, v in pairs(tbl or {}) do
if type(v) == "table" then
table.insert(lines, string.format("%s%s = {", prefix, tostring(k)))
for _, line in ipairs(dump_table(v, indent + 1)) do
table.insert(lines, line)
end
table.insert(lines, string.format("%s},", prefix))
else
table.insert(lines, string.format("%s%s = %q,", prefix, tostring(k), tostring(v)))
end
end
return lines
end
--- Count table entries
--- @param tbl table Table to count
--- @return number Number of entries
local function count_table(tbl)
local count = 0
for _ in pairs(tbl or {}) do
count = count + 1
end
return count
end
-- ============================================================================
-- PLUGIN LIFECYCLE FUNCTIONS
-- ============================================================================
--- Initialize the plugin with user configuration
--- Called from yazi/init.lua
--- @param opts table Configuration options
function M:setup(opts)
opts = opts or {}
-- Set global options with defaults
set_state("save_op", opts.save_op or false)
set_state("auto_confirm", opts.auto_confirm or false)
set_state("cmdline", "jdupes")
set_state("shell", "bash")
ya.dbg("Dupes Plugin Setup: Starting profile processing")
-- Process and store user-defined profiles
local profiles = {}
if opts.profiles then
for profile_name, profile_conf in pairs(opts.profiles) do
-- Validate profile configuration
if not profile_conf then
ya.err(string.format("Profile '%s' has nil configuration, skipping", profile_name))
else
local profile = {
cmdline = profile_conf.cmdline or get_state("cmdline"),
args = profile_conf.args or {},
-- Profile-specific save_op overrides global setting
save_op = profile_conf.save_op ~= nil and profile_conf.save_op or get_state("save_op"),
}
profiles[profile_name] = profile
-- Log detailed profile configuration
local profile_lines = dump_table(profile)
ya.dbg(string.format("Setup profile '%s':\n%s", tostring(profile_name), table.concat(profile_lines, "\n")))
end
end
else
ya.dbg("Dupes Plugin Setup: No profiles provided in opts.profiles")
end
set_state("profiles", profiles)
-- Log summary
local profile_count = count_table(profiles)
ya.dbg(string.format("Dupes Plugin Setup: Complete with %d profiles", profile_count))
-- Register custom linemode to show deletion markers
-- Priority 1500 ensures it runs before most other linemodes
Linemode:children_add(function(self)
if not self._file.cha.is_dummy then
-- Normal file: no marker
return ""
elseif self._file.is_hovered then
-- Hovered dummy file: show black background 'X'
return ui.Line { " ", ui.Span("X"):style(ui.Style():bg("black")) }
else
-- Non-hovered dummy file: show red 'X'
return ui.Line({ " ", "X" }):style(ui.Style():fg("red"))
end
end, 1500)
end
--- Main entry point when plugin is invoked
--- @param job string|table Profile name or job configuration
function M:entry(job)
-- Exit visual mode if active
ya.mgr_emit("escape", { visual = true })
-- Parse profile name from job argument
local profile_name = "interactive" -- default profile
if type(job) == "string" then
profile_name = job
elseif type(job) == "table" then
profile_name = (type(job.args) == "table" and job.args[1])
or (type(job.args) == "string" and job.args)
or "interactive"
end
ya.dbg(string.format("== Invoked profile: %s ==", profile_name))
-- ========================================================================
-- SPECIAL PROFILE: override
-- Allows user to input custom jdupes arguments interactively
-- ========================================================================
if profile_name == "override" then
ya.dbg("INFO: Waiting for custom input")
local ov_args, ok = ya.input {
title = "Dupes Override - Enter custom args",
value = "-j",
position = { "top-center", y = 3, w = 45 },
}
if ok ~= 1 then
ya.dbg("Override cancelled by user")
return
end
-- Parse and execute custom arguments
local p_args = parse_custom_args(ov_args)
run_dupes("override", {
cmdline = get_state("cmdline"),
args = p_args,
}, get_state("save_op"))
return
end
-- ========================================================================
-- SPECIAL PROFILE: dry
-- Inherits 'apply' profile but removes destructive flags for preview
-- ========================================================================
if profile_name == "dry" then
local profiles = get_state("profiles")
local apply_conf = profiles["apply"]
if not apply_conf then
ya.notify {
title = "Dupes Plugin",
content = "Cannot run dry: 'apply' profile missing.",
level = "error",
timeout = 4,
}
return
end
-- Create dry-run configuration
local dry_conf = create_dry_config(apply_conf)
dry_conf.save_op = apply_conf.save_op
run_dupes("dry", dry_conf, dry_conf.save_op)
return
end
-- ========================================================================
-- REGULAR PROFILE EXECUTION
-- ========================================================================
local profiles = get_state("profiles")
local conf = profiles[profile_name]
if not conf then
ya.err(string.format("ERROR: Profile '%s' not found", profile_name))
ya.notify {
title = "Dupes Plugin",
content = string.format("Profile not found: %s", profile_name),
level = "error",
timeout = 3,
}
return
end
-- ========================================================================
-- SPECIAL PROFILE: apply
-- Destructive operation - requires confirmation and runs dry preview first
-- ========================================================================
if profile_name == "apply" then
-- Check for auto-confirm or prompt user
local apply_confirm = get_state("auto_confirm")
or ya.confirm {
pos = { "center", y = -8, w = 36, h = 8 },
title = "Confirm Deduplication operation?",
content = ui.Text {
ui.Line(""),
ui.Line("This will DELETE duplicate files!"):style(ui.Style():fg("red")),
ui.Line("If unsure, try dry run first"):style(th.confirm.content),
ui.Line(""),
},
}
if not apply_confirm then
ya.dbg("Apply cancelled by user")
ya.notify {
title = "Dupes Plugin",
content = "Operation cancelled",
level = "info",
timeout = 3,
}
return
end
ya.dbg("Apply confirmed by user")
-- First, run silent dry-run preview for logging
local dry_conf = create_dry_config(conf)
run_dupes("dry", dry_conf, conf.save_op, true) -- no_display = true
-- Then execute actual deletion
run_dupes(profile_name, conf, conf.save_op, true, true) -- no_display, no_json
return
end
-- ========================================================================
-- STANDARD PROFILE EXECUTION
-- For 'interactive' and other custom profiles
-- ========================================================================
run_dupes(profile_name, conf, conf.save_op)
end
return M