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:
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
|
||||
Reference in new issue
Block a user