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
No files matched your search
@@ -0,0 +1 @@
|
||||
Desktop /home/liph/Desktop/ d
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Catppuccin
|
||||
|
||||
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.
|
||||
@@ -1,36 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Catppuccin Mocha Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||
</h3>
|
||||
|
||||
## 👀 Preview
|
||||
|
||||
<img src="preview.png" width="600" />
|
||||
|
||||
## 🎨 Installation
|
||||
|
||||
```sh
|
||||
ya pkg add yazi-rs/flavors:catppuccin-mocha
|
||||
```
|
||||
|
||||
## ⚙️ Usage
|
||||
|
||||
Set the content of your `theme.toml` to enable it as your _dark_ flavor:
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
dark = "catppuccin-mocha"
|
||||
```
|
||||
|
||||
Make sure your `theme.toml` doesn't contain anything other than `[flavor]`, unless you want to override certain styles of this flavor.
|
||||
|
||||
See the [Yazi flavor documentation](https://yazi-rs.github.io/docs/flavors/overview) for more details.
|
||||
|
||||
## 📜 License
|
||||
|
||||
The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed.
|
||||
|
||||
Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details.
|
||||
@@ -1,187 +0,0 @@
|
||||
# vim:fileencoding=utf-8:foldmethod=marker
|
||||
|
||||
# : Manager {{{
|
||||
|
||||
[mgr]
|
||||
cwd = { fg = "#94e2d5" }
|
||||
|
||||
# TODO: remove
|
||||
# Hovered
|
||||
hovered = { reversed = true }
|
||||
preview_hovered = { underline = true }
|
||||
|
||||
# Find
|
||||
find_keyword = { fg = "#f9e2af", bold = true, italic = true, underline = true }
|
||||
find_position = { fg = "#f5c2e7", bg = "reset", bold = true, italic = true }
|
||||
|
||||
# Marker
|
||||
marker_copied = { fg = "#a6e3a1", bg = "#a6e3a1" }
|
||||
marker_cut = { fg = "#f38ba8", bg = "#f38ba8" }
|
||||
marker_marked = { fg = "#94e2d5", bg = "#94e2d5" }
|
||||
marker_selected = { fg = "#f9e2af", bg = "#f9e2af" }
|
||||
|
||||
# Count
|
||||
count_copied = { fg = "#1e1e2e", bg = "#a6e3a1" }
|
||||
count_cut = { fg = "#1e1e2e", bg = "#f38ba8" }
|
||||
count_selected = { fg = "#1e1e2e", bg = "#f9e2af" }
|
||||
|
||||
# Border
|
||||
border_symbol = "│"
|
||||
border_style = { fg = "#7f849c" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tabs {{{
|
||||
|
||||
[tabs]
|
||||
active = { fg = "#1e1e2e", bg = "#89b4fa", bold = true }
|
||||
inactive = { fg = "#89b4fa", bg = "#313244" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Mode {{{
|
||||
|
||||
[mode]
|
||||
|
||||
normal_main = { fg = "#1e1e2e", bg = "#89b4fa", bold = true }
|
||||
normal_alt = { fg = "#89b4fa", bg = "#313244" }
|
||||
|
||||
# Select mode
|
||||
select_main = { fg = "#1e1e2e", bg = "#94e2d5", bold = true }
|
||||
select_alt = { fg = "#94e2d5", bg = "#313244" }
|
||||
|
||||
# Unset mode
|
||||
unset_main = { fg = "#1e1e2e", bg = "#f2cdcd", bold = true }
|
||||
unset_alt = { fg = "#f2cdcd", bg = "#313244" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Status bar {{{
|
||||
|
||||
[status]
|
||||
# Permissions
|
||||
perm_sep = { fg = "#7f849c" }
|
||||
perm_type = { fg = "#89b4fa" }
|
||||
perm_read = { fg = "#f9e2af" }
|
||||
perm_write = { fg = "#f38ba8" }
|
||||
perm_exec = { fg = "#a6e3a1" }
|
||||
|
||||
# Progress
|
||||
progress_label = { fg = "#ffffff", bold = true }
|
||||
progress_normal = { fg = "#a6e3a1", bg = "#45475a" }
|
||||
progress_error = { fg = "#f9e2af", bg = "#f38ba8" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Pick {{{
|
||||
|
||||
[pick]
|
||||
border = { fg = "#89b4fa" }
|
||||
active = { fg = "#f5c2e7", bold = true }
|
||||
inactive = {}
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Input {{{
|
||||
|
||||
[input]
|
||||
border = { fg = "#89b4fa" }
|
||||
title = {}
|
||||
value = {}
|
||||
selected = { reversed = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Completion {{{
|
||||
|
||||
[cmp]
|
||||
border = { fg = "#89b4fa" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tasks {{{
|
||||
|
||||
[tasks]
|
||||
border = { fg = "#89b4fa" }
|
||||
title = {}
|
||||
hovered = { fg = "#f5c2e7", bold = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Which {{{
|
||||
|
||||
[which]
|
||||
mask = { bg = "#313244" }
|
||||
cand = { fg = "#94e2d5" }
|
||||
rest = { fg = "#9399b2" }
|
||||
desc = { fg = "#f5c2e7" }
|
||||
separator = " "
|
||||
separator_style = { fg = "#585b70" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Help {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "#94e2d5" }
|
||||
run = { fg = "#f5c2e7" }
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "#313244", bg = "#cdd6f4" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Spotter {{{
|
||||
|
||||
[spot]
|
||||
border = { fg = "#89b4fa" }
|
||||
title = { fg = "#89b4fa" }
|
||||
tbl_col = { fg = "#94e2d5" }
|
||||
tbl_cell = { fg = "#f5c2e7", bg = "#45475a" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Notification {{{
|
||||
|
||||
[notify]
|
||||
title_info = { fg = "#a6e3a1" }
|
||||
title_warn = { fg = "#f9e2af" }
|
||||
title_error = { fg = "#f38ba8" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : File-specific styles {{{
|
||||
|
||||
[filetype]
|
||||
|
||||
rules = [
|
||||
# Image
|
||||
{ mime = "image/*", fg = "#94e2d5" },
|
||||
# Media
|
||||
{ mime = "{audio,video}/*", fg = "#f9e2af" },
|
||||
# Archive
|
||||
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#f5c2e7" },
|
||||
# Document
|
||||
{ mime = "application/{pdf,doc,rtf}", fg = "#a6e3a1" },
|
||||
# Virtual file system
|
||||
{ mime = "vfs/{absent,stale}", fg = "#9399b2" },
|
||||
# Fallback
|
||||
{ url = "*", fg = "#cdd6f4" },
|
||||
{ url = "*/", fg = "#89b4fa" },
|
||||
# TODO: remove
|
||||
{ name = "*", fg = "#cdd6f4" },
|
||||
{ name = "*/", fg = "#89b4fa" }
|
||||
]
|
||||
|
||||
# : }}}
|
||||
|
Before Width: | Height: | Size: 660 KiB |
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Chromium Oxide
|
||||
|
||||
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.
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Mitchell Hanberg
|
||||
|
||||
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.
|
||||
@@ -1,45 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Example Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||
</h3>
|
||||
|
||||
## 👀 Preview
|
||||
|
||||
<img src="preview.png" width="600" />
|
||||
|
||||
## 🎨 Installation
|
||||
|
||||
```bash
|
||||
ya pkg add Chromium-3-Oxide/everforest-medium
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```bash
|
||||
ya pack -a Chromium-3-Oxide/everforest-medium
|
||||
```
|
||||
|
||||
## ⚙️ Usage
|
||||
|
||||
Add the these lines to your `theme.toml` configuration file to use it:
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
dark = "everforest-medium"
|
||||
```
|
||||
|
||||
For Yazi versions before 0.4:
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
use = "everforest-medium"
|
||||
```
|
||||
|
||||
## 📜 License
|
||||
|
||||
The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed.
|
||||
|
||||
Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details.
|
||||
@@ -1,229 +0,0 @@
|
||||
# vim:fileencoding=utf-8:foldmethod=marker
|
||||
|
||||
# : Manager {{{
|
||||
|
||||
[mgr]
|
||||
cwd = { fg = "#7fbbb3" }
|
||||
|
||||
# Hovered
|
||||
hovered = { reversed = true }
|
||||
preview_hovered = { underline = true }
|
||||
|
||||
# Find
|
||||
find_keyword = { fg = "#dbbc7f", bold = true, italic = true, underline = true }
|
||||
find_position = { fg = "#d699b6", bg = "reset", bold = true, italic = true }
|
||||
|
||||
# Symlink
|
||||
symlink_target = { italic = true }
|
||||
|
||||
# Marker
|
||||
marker_copied = { fg = "#a7c080", bg = "#a7c080" }
|
||||
marker_cut = { fg = "#e67e80", bg = "#e67e80" }
|
||||
marker_marked = { fg = "#7fbbb3", bg = "#7fbbb3" }
|
||||
marker_selected = { fg = "#dbbc7f", bg = "#dbbc7f" }
|
||||
|
||||
# Count
|
||||
count_copied = { fg = "#343f44", bg = "#a7c080" }
|
||||
count_cut = { fg = "#343f44", bg = "#e67e80" }
|
||||
count_selected = { fg = "#343f44", bg = "#dbbc7f" }
|
||||
|
||||
# Border
|
||||
border_symbol = "│"
|
||||
border_style = { fg = "#4f585e" }
|
||||
|
||||
# Highlighting
|
||||
syntect_theme = ""
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tabs {{{
|
||||
|
||||
[tabs]
|
||||
active = { bg = "#7fbbb3", bold = true }
|
||||
inactive = { fg = "#7fbbb3", bg = "#4f585e" }
|
||||
|
||||
# Separator
|
||||
sep_inner = { open = "", close = "" }
|
||||
sep_outer = { open = "", close = "" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Mode {{{
|
||||
|
||||
[mode]
|
||||
normal_main = { fg = "#3d484d", bg = "#a7c080", bold = true }
|
||||
normal_alt = { fg = "#7fbbb3", bg = "#4f585e", bold = true }
|
||||
|
||||
# Select mode
|
||||
select_main = { fg = "#3d484d", bg = "#e67e80", bold = true }
|
||||
select_alt = { fg = "#7fbbb3", bg = "#4f585e", bold = true }
|
||||
|
||||
# Unset mode
|
||||
unset_main = { fg = "#3d484d", bg = "#7fbbb3", bold = true }
|
||||
unset_alt = { fg = "#7fbbb3", bg = "#4f585e", bold = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Status bar {{{
|
||||
|
||||
[status]
|
||||
overall = {}
|
||||
sep_left = { open = "", close = "" }
|
||||
sep_right = { open = "", close = "" }
|
||||
|
||||
# Permissions
|
||||
permissions_s = { fg = "#2d353b" }
|
||||
permissions_t = { fg = "#a7c080" }
|
||||
permissions_r = { fg = "#dbbc7f" }
|
||||
permissions_w = { fg = "#e67e80" }
|
||||
permissions_x = { fg = "#7fbbb3" }
|
||||
|
||||
# Progress
|
||||
progress_label = { bold = true }
|
||||
progress_normal = { fg = "#7fbbb3", bg = "#232a2e" }
|
||||
progress_error = { fg = "#e67e80", bg = "#232a2e" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Which {{{
|
||||
|
||||
[which]
|
||||
cols = 3
|
||||
mask = { bg = "#2d353b" }
|
||||
cand = { fg = "#7fbbb3" }
|
||||
rest = { fg = "#2d353b" }
|
||||
desc = { fg = "#d699b6" }
|
||||
separator = " "
|
||||
separator_style = { fg = "#2d353b" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Confirmation {{{
|
||||
|
||||
[confirm]
|
||||
border = { fg = "#7fbbb3" }
|
||||
title = { fg = "#7fbbb3" }
|
||||
body = {}
|
||||
list = {}
|
||||
btn_yes = { reversed = true }
|
||||
btn_no = {}
|
||||
btn_labels = [ " [Y]es ", " (N)o " ]
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Spotter {{{
|
||||
|
||||
[spot]
|
||||
border = { fg = "#7fbbb3" }
|
||||
title = { fg = "#7fbbb3" }
|
||||
|
||||
# Table
|
||||
tbl_col = { fg = "#7fbbb3" }
|
||||
tbl_cell = { fg = "#dbbc7f", reversed = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Notification {{{
|
||||
|
||||
[notify]
|
||||
title_info = { fg = "#a7c080" }
|
||||
title_warn = { fg = "#dbbc7f" }
|
||||
title_error = { fg = "#e67e80" }
|
||||
|
||||
# Icons
|
||||
icon_info = ""
|
||||
icon_warn = ""
|
||||
icon_error = ""
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Picker {{{
|
||||
|
||||
[pick]
|
||||
border = { fg = "#7fbbb3" }
|
||||
active = { fg = "#d699b6", bold = true }
|
||||
inactive = {}
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Input {{{
|
||||
|
||||
[input]
|
||||
border = { fg = "#7fbbb3" }
|
||||
title = {}
|
||||
value = {}
|
||||
selected = { reversed = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Completion {{{
|
||||
|
||||
[cmp]
|
||||
border = { fg = "#7fbbb3" }
|
||||
active = { reversed = true }
|
||||
inactive = {}
|
||||
|
||||
# Icons
|
||||
icon_file = ""
|
||||
icon_folder = ""
|
||||
icon_command = ""
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Task manager {{{
|
||||
|
||||
[tasks]
|
||||
border = { fg = "#7fbbb3" }
|
||||
title = {}
|
||||
hovered = { fg = "#d699b6", underline = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Help menu {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "#7fbbb3" }
|
||||
run = { fg = "#d699b6" }
|
||||
desc = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "#2d353b", bg = "#d3c6aa" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : File-specific styles {{{
|
||||
|
||||
[filetype]
|
||||
|
||||
rules = [
|
||||
# Images
|
||||
{ mime = "image/*", fg = "#7fbbb3" },
|
||||
|
||||
# Media
|
||||
{ mime = "{audio,video}/*", fg = "#d699b6" },
|
||||
|
||||
# Archives
|
||||
{ mime = "application/*zip", fg = "#e67e80" },
|
||||
{ mime = "application/x-{tar,bzip*,7z-compressed,xz,rar}", fg = "#e67e80" },
|
||||
|
||||
# Documents
|
||||
{ mime = "application/{pdf,doc,rtf,vnd.*}", fg = "#7fbbb3" },
|
||||
|
||||
# Fallback
|
||||
{ name = "*", fg = "#83c092" },
|
||||
{ name = "*/", fg = "#a7c080" }
|
||||
]
|
||||
|
||||
# : }}}
|
||||
|
Before Width: | Height: | Size: 77 KiB |
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 - Matthew Dong
|
||||
|
||||
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.
|
||||
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -1,42 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Gruvbox Material Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||
</h3>
|
||||
|
||||
## 👀 Preview
|
||||
|
||||

|
||||
|
||||
## 🎨 Installation
|
||||
|
||||
### Using package manager
|
||||
|
||||
```bash
|
||||
ya pkg add matt-dong-123/gruvbox-material
|
||||
```
|
||||
|
||||
### Manual install
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
git clone https://github.com/your-username/gruvbox-material.yazi.git ~/.config/yazi/flavors/gruvbox-material.yazi
|
||||
|
||||
# Windows
|
||||
git clone https://github.com/your-username/gruvbox-material.yazi.git %AppData%\yazi\config\flavors\gruvbox-material.yazi
|
||||
```
|
||||
|
||||
## ⚙️ Usage
|
||||
|
||||
Add the these lines to your `theme.toml` configuration file to use it:
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
dark = "gruvbox-material"
|
||||
```
|
||||
|
||||
## 📜 License
|
||||
|
||||
Check the [LICENSE](LICENSE) file for more details.
|
||||
@@ -1,214 +0,0 @@
|
||||
# vim:fileencoding=utf-8:foldmethod=marker
|
||||
|
||||
# : Manager {{{
|
||||
|
||||
[mgr]
|
||||
cwd = { fg = "#89b482" }
|
||||
|
||||
# Find
|
||||
find_keyword = { fg = "#d8a657", bold = true, italic = true, underline = true }
|
||||
find_position = { fg = "#d3869b", bg = "reset", bold = true, italic = true }
|
||||
|
||||
# Marker
|
||||
marker_copied = { fg = "#a9b665", bg = "#a9b665" }
|
||||
marker_cut = { fg = "#ea6962", bg = "#ea6962" }
|
||||
marker_marked = { fg = "#89b482", bg = "#89b482" }
|
||||
marker_selected = { fg = "#d8a657", bg = "#d8a657" }
|
||||
|
||||
# Count
|
||||
count_copied = { fg = "#282828", bg = "#a9b665" }
|
||||
count_cut = { fg = "#282828", bg = "#ea6962" }
|
||||
count_selected = { fg = "#282828", bg = "#d8a657" }
|
||||
|
||||
# Border
|
||||
border_symbol = "│"
|
||||
border_style = { fg = "#928374" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tabs {{{
|
||||
|
||||
[tabs]
|
||||
active = { fg = "#282828", bg = "#7daea3", bold = true }
|
||||
inactive = { fg = "#7daea3", bg = "#32302f" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Mode {{{
|
||||
|
||||
[mode]
|
||||
|
||||
normal_main = { fg = "#282828", bg = "#7daea3", bold = true }
|
||||
normal_alt = { fg = "#7daea3", bg = "#32302f" }
|
||||
|
||||
# Select mode
|
||||
select_main = { fg = "#282828", bg = "#89b482", bold = true }
|
||||
select_alt = { fg = "#89b482", bg = "#32302f" }
|
||||
|
||||
# Unset mode
|
||||
unset_main = { fg = "#282828", bg = "#ea6962", bold = true }
|
||||
unset_alt = { fg = "#ea6962", bg = "#32302f" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Status bar {{{
|
||||
|
||||
[status]
|
||||
# Permissions
|
||||
perm_sep = { fg = "#928374" }
|
||||
perm_type = { fg = "#7daea3" }
|
||||
perm_read = { fg = "#d8a657" }
|
||||
perm_write = { fg = "#ea6962" }
|
||||
perm_exec = { fg = "#a9b665" }
|
||||
|
||||
# Progress
|
||||
progress_label = { fg = "#ddc7a1", bold = true }
|
||||
progress_normal = { fg = "#a9b665", bg = "#45403d" }
|
||||
progress_error = { fg = "#d8a657", bg = "#ea6962" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Pick {{{
|
||||
|
||||
[pick]
|
||||
border = { fg = "#7daea3" }
|
||||
active = { fg = "#d3869b", bold = true }
|
||||
inactive = {}
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Input {{{
|
||||
|
||||
[input]
|
||||
border = { fg = "#7daea3" }
|
||||
title = {}
|
||||
value = {}
|
||||
selected = { reversed = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Completion {{{
|
||||
|
||||
[cmp]
|
||||
border = { fg = "#7daea3" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tasks {{{
|
||||
|
||||
[tasks]
|
||||
border = { fg = "#7daea3" }
|
||||
title = {}
|
||||
hovered = { fg = "#d3869b", bold = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Which {{{
|
||||
|
||||
[which]
|
||||
mask = { bg = "#32302f" }
|
||||
cand = { fg = "#89b482" }
|
||||
rest = { fg = "#a89984" }
|
||||
desc = { fg = "#d3869b" }
|
||||
separator = " "
|
||||
separator_style = { fg = "#7c6f64" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Help {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "#89b482" }
|
||||
run = { fg = "#d3869b" }
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "#32302f", bg = "#d4be98" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Spotter {{{
|
||||
|
||||
[spot]
|
||||
border = { fg = "#7daea3" }
|
||||
title = { fg = "#7daea3" }
|
||||
tbl_col = { fg = "#89b482" }
|
||||
tbl_cell = { fg = "#d3869b", bg = "#45403d" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Notification {{{
|
||||
|
||||
[notify]
|
||||
title_info = { fg = "#a9b665" }
|
||||
title_warn = { fg = "#d8a657" }
|
||||
title_error = { fg = "#ea6962" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : File-specific styles {{{
|
||||
|
||||
[filetype]
|
||||
|
||||
rules = [
|
||||
# Image
|
||||
{ mime = "image/*", fg = "#89b482" },
|
||||
# Media
|
||||
{ mime = "{audio,video}/*", fg = "#d8a657" },
|
||||
# Archive
|
||||
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#d3869b" },
|
||||
# Document
|
||||
{ mime = "application/{pdf,doc,rtf}", fg = "#a9b665" },
|
||||
# Virtual file system
|
||||
{ mime = "vfs/{absent,stale}", fg = "#a89984" },
|
||||
# Fallback
|
||||
{ url = "*", fg = "#d4be98" },
|
||||
{ url = "*/", fg = "#7daea3" },
|
||||
]
|
||||
|
||||
# : }}}
|
||||
|
||||
[icon]
|
||||
|
||||
dirs = [
|
||||
{ name = ".config", text = "", fg = "#d3869b" },
|
||||
{ name = ".git", text = "", fg = "#89b482" },
|
||||
{ name = ".github", text = "", fg = "#7daea3" },
|
||||
{ name = ".npm", text = "", fg = "#7daea3" },
|
||||
{ name = "Desktop", text = "", fg = "#89b482" },
|
||||
{ name = "Development", text = "", fg = "#89b482" },
|
||||
{ name = "Documents", text = "", fg = "#89b482" },
|
||||
{ name = "Downloads", text = "", fg = "#89b482" },
|
||||
{ name = "Library", text = "", fg = "#89b482" },
|
||||
{ name = "Movies", text = "", fg = "#89b482" },
|
||||
{ name = "Music", text = "", fg = "#89b482" },
|
||||
{ name = "Pictures", text = "", fg = "#89b482" },
|
||||
{ name = "Public", text = "", fg = "#89b482" },
|
||||
{ name = "Videos", text = "", fg = "#89b482" },
|
||||
]
|
||||
conds = [
|
||||
# Special files
|
||||
{ if = "orphan", text = "", fg = "#d4be98" },
|
||||
{ if = "link", text = "", fg = "#928374" },
|
||||
{ if = "block", text = "", fg = "#ea6962" },
|
||||
{ if = "char", text = "", fg = "#ea6962" },
|
||||
{ if = "fifo", text = "", fg = "#ea6962" },
|
||||
{ if = "sock", text = "", fg = "#ea6962" },
|
||||
{ if = "sticky", text = "", fg = "#ea6962" },
|
||||
{ if = "dummy", text = "", fg = "#ea6962" },
|
||||
|
||||
# Fallback
|
||||
{ if = "dir", text = "", fg = "#7daea3" },
|
||||
{ if = "exec", text = "", fg = "#a9b665" },
|
||||
{ if = "!dir", text = "", fg = "#d4be98" },
|
||||
]
|
||||
|
Before Width: | Height: | Size: 226 KiB |
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Dmitry Nefedov
|
||||
|
||||
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.
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Dmitry Nefedov
|
||||
|
||||
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.
|
||||
@@ -1,36 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Kanagawa Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||
</h3>
|
||||
|
||||
## 👀 Preview
|
||||
|
||||
<img src="preview.png" width="600" />
|
||||
|
||||
## 🎨 Installation
|
||||
|
||||
```bash
|
||||
ya pkg add dangooddd/kanagawa
|
||||
```
|
||||
|
||||
## ⚙️ Usage
|
||||
|
||||
> [!Note]
|
||||
> To achieve the look like in the screenshot you also need to install
|
||||
> [full-border](https://github.com/yazi-rs/plugins/tree/main/full-border.yazi) plugin!
|
||||
|
||||
Add these lines to your `theme.toml` configuration file to use it:
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
dark = "kanagawa"
|
||||
```
|
||||
|
||||
## 📜 License
|
||||
|
||||
The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed.
|
||||
|
||||
Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details.
|
||||
@@ -1,143 +0,0 @@
|
||||
## Color palette
|
||||
## Check https://github.com/rebelot/kanagawa.nvim for more details
|
||||
# sumiInk0 "#16161d" dark bg
|
||||
# sumiInk3 "#1f1f28" bg
|
||||
# sumiInk4 "#2a2a37"
|
||||
# sumiInk6 "#54546d"
|
||||
# fujiWhite "#dcd7ba" fg
|
||||
# oldWhite "#c8c093" dark fg
|
||||
# fujiGray "#727169"
|
||||
# peachRed "#ff5d62"
|
||||
# autumnRed "#c34043"
|
||||
# crystalBlue "#7e9cd8"
|
||||
# waveRed "#e46876"
|
||||
# carpYellow "#e6c384"
|
||||
# springGreen "#98bb6c"
|
||||
# autumnGreen "#76946a"
|
||||
# oniViolet "#957fb8"
|
||||
# waveAqua1 "#6a9589"
|
||||
# surimiOrange "#ffa066"
|
||||
# waveAqua2 "#7aa89f"
|
||||
|
||||
[mgr]
|
||||
marker_copied = { fg = "#98bb6c", bg = "#98bb6c" }
|
||||
marker_cut = { fg = "#e46876", bg = "#e46876" }
|
||||
marker_marked = { fg = "#957fb8", bg = "#957fb8" }
|
||||
marker_selected = { fg = "#ffa066", bg = "#ffa066" }
|
||||
|
||||
cwd = { fg = "#e6c384" }
|
||||
|
||||
find_keyword = { fg = "#ffa066", bg = "#1f1f28" }
|
||||
find_position = {}
|
||||
|
||||
count_copied = { fg = "#1f1f28", bg = "#98bb6c" }
|
||||
count_cut = { fg = "#1f1f28", bg = "#e46876" }
|
||||
count_selected = { fg = "#1f1f28", bg = "#ffa066" }
|
||||
|
||||
border_symbol = "│"
|
||||
border_style = { fg = "#dcd7ba" }
|
||||
|
||||
[indicator]
|
||||
parent = { reversed = true }
|
||||
current = { reversed = true }
|
||||
preview = { reversed = true }
|
||||
|
||||
[tabs]
|
||||
active = { fg = "#1f1f28", bg = "#7e9cd8" }
|
||||
inactive = { fg = "#7e9cd8", bg = "#2a2a37" }
|
||||
|
||||
sep_inner = { open = "", close = "" }
|
||||
sep_outer = { open = "", close = "" }
|
||||
|
||||
|
||||
[mode]
|
||||
normal_main = { fg = "#1f1f28", bg = "#7e9cd8" }
|
||||
normal_alt = { fg = "#7e9cd8", bg = "#2a2a37" }
|
||||
select_main = { fg = "#1f1f28", bg = "#957fb8" }
|
||||
select_alt = { fg = "#957fb8", bg = "#2a2a37" }
|
||||
unset_main = { fg = "#1f1f28", bg = "#e6c384" }
|
||||
unset_alt = { fg = "#e6c384", bg = "#2a2a37" }
|
||||
|
||||
|
||||
[status]
|
||||
sep_left = { open = "", close = "" }
|
||||
sep_right = { open = "", close = "" }
|
||||
overall = { fg = "#c8c093", bg = "#16161d" }
|
||||
|
||||
progress_label = { fg = "#7e9cd8", bold = true }
|
||||
progress_normal = { fg = "#2a2a37", bg = "#1f1f28" }
|
||||
progress_error = { fg = "#2a2a37", bg = "#1f1f28" }
|
||||
|
||||
perm_type = { fg = "#98bb6c" }
|
||||
perm_read = { fg = "#e6c384" }
|
||||
perm_write = { fg = "#ff5d62" }
|
||||
perm_exec = { fg = "#7aa89f" }
|
||||
perm_sep = { fg = "#957fb8" }
|
||||
|
||||
|
||||
[pick]
|
||||
border = { fg = "#7fb4ca" }
|
||||
active = { fg = "#957fb8", bold = true }
|
||||
inactive = {}
|
||||
|
||||
[input]
|
||||
border = { fg = "#7fb4ca" }
|
||||
title = {}
|
||||
value = {}
|
||||
selected = { reversed = true }
|
||||
|
||||
[completion]
|
||||
border = { fg = "#7fb4ca" }
|
||||
active = { reversed = true }
|
||||
inactive = {}
|
||||
|
||||
[tasks]
|
||||
border = { fg = "#7fb4ca" }
|
||||
title = {}
|
||||
hovered = { fg = "#957fb8" }
|
||||
|
||||
[which]
|
||||
cols = 2
|
||||
separator = " - "
|
||||
separator_style = { fg = "#727169" }
|
||||
mask = { bg = "#16161d" }
|
||||
rest = { fg = "#727169" }
|
||||
cand = { fg = "#7e9cd8" }
|
||||
desc = { fg = "#54546d" }
|
||||
|
||||
[help]
|
||||
on = { fg = "#7aa89f" }
|
||||
run = { fg = "#957fb8" }
|
||||
desc = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "#1f1f28", bg = "#dcd7ba" }
|
||||
|
||||
[notify]
|
||||
title_info = { fg = "#98bb6c" }
|
||||
title_warn = { fg = "#e6c384" }
|
||||
title_error = { fg = "#ff5d62" }
|
||||
|
||||
[filetype]
|
||||
rules = [
|
||||
# images
|
||||
{ mime = "image/*", fg = "#e6c384" },
|
||||
|
||||
# media
|
||||
{ mime = "{audio,video}/*", fg = "#957fb8" },
|
||||
|
||||
# archives
|
||||
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#e46876" },
|
||||
|
||||
# documents
|
||||
{ mime = "application/{pdf,doc,rtf,vnd.*}", fg = "#6a9589" },
|
||||
|
||||
# broken links
|
||||
{ url = "*", is = "orphan", fg = "#c34043" },
|
||||
|
||||
# executables
|
||||
{ url = "*", is = "exec", fg = "#76946a" },
|
||||
|
||||
# fallback
|
||||
{ url = "*", fg = "#dcd7ba" },
|
||||
{ url = "*/", fg = "#7e9cd8" },
|
||||
]
|
||||
|
Before Width: | Height: | Size: 163 KiB |
@@ -1,448 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Kanagawa</string>
|
||||
<key>settings</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#1F1F28</string>
|
||||
<key>caret</key>
|
||||
<string>#C8C093</string>
|
||||
<key>foreground</key>
|
||||
<string>#DCD7BA</string>
|
||||
<key>invisibles</key>
|
||||
<string>#54546D</string>
|
||||
<key>lineHighlight</key>
|
||||
<string>#2D4F67</string>
|
||||
<key>selection</key>
|
||||
<string>#2D4F67</string>
|
||||
<key>findHighlight</key>
|
||||
<string>#2D4F67</string>
|
||||
<key>selectionBorder</key>
|
||||
<string>#222218</string>
|
||||
<key>gutterForeground</key>
|
||||
<string>#54546D</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Comment</string>
|
||||
<key>scope</key>
|
||||
<string>comment</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#727169</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>String</string>
|
||||
<key>scope</key>
|
||||
<string>string</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#98BB6C</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Number</string>
|
||||
<key>scope</key>
|
||||
<string>constant.numeric</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#D27E99</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Built-in constant</string>
|
||||
<key>scope</key>
|
||||
<string>constant.language</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#FFA066</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>User-defined constant</string>
|
||||
<key>scope</key>
|
||||
<string>constant.character, constant.other</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#E6C384</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Variable</string>
|
||||
<key>scope</key>
|
||||
<string>variable</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>#E6C384</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Ruby's @variable</string>
|
||||
<key>scope</key>
|
||||
<string>variable.other.readwrite.instance</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#E6C384</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>String interpolation</string>
|
||||
<key>scope</key>
|
||||
<string>constant.character.escaped, constant.character.escape, string source, string source.ruby</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#C0A36E</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Keyword</string>
|
||||
<key>scope</key>
|
||||
<string>keyword</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#E46876</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Storage</string>
|
||||
<key>scope</key>
|
||||
<string>storage</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#957FB8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Storage type</string>
|
||||
<key>scope</key>
|
||||
<string>storage.type</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#957FB8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Class name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#7AA89F</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Inherited class</string>
|
||||
<key>scope</key>
|
||||
<string>entity.other.inherited-class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#7AA89F</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Function name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.function</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#7E9CD8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Function argument</string>
|
||||
<key>scope</key>
|
||||
<string>variable.parameter</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#b8b4d0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Tag name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.tag</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#7FB4CA</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Tag attribute</string>
|
||||
<key>scope</key>
|
||||
<string>entity.other.attribute-name</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#E6C384</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library function</string>
|
||||
<key>scope</key>
|
||||
<string>support.function</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#7FB4CA</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library constant</string>
|
||||
<key>scope</key>
|
||||
<string>support.constant</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#7FB4CA</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library class/type</string>
|
||||
<key>scope</key>
|
||||
<string>support.type, support.class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#7AA89F</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library variable</string>
|
||||
<key>scope</key>
|
||||
<string>support.other.variable</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#FFA066</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Invalid</string>
|
||||
<key>scope</key>
|
||||
<string>invalid</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string/>
|
||||
<key>foreground</key>
|
||||
<string>#FF5D62</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Invalid deprecated</string>
|
||||
<key>scope</key>
|
||||
<string>invalid.deprecated</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#717C7C</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>JSON String</string>
|
||||
<key>scope</key>
|
||||
<string>meta.structure.dictionary.json string.quoted.double.json</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#957FB8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>diff.header</string>
|
||||
<key>scope</key>
|
||||
<string>meta.diff, meta.diff.header</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#7E9CD8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>diff.deleted</string>
|
||||
<key>scope</key>
|
||||
<string>markup.deleted</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#43242B</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>diff.inserted</string>
|
||||
<key>scope</key>
|
||||
<string>markup.inserted</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#2B3328</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>diff.changed</string>
|
||||
<key>scope</key>
|
||||
<string>markup.changed</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#49443C</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>scope</key>
|
||||
<string>constant.numeric.line-number.find-in-files - match</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#54546D</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>scope</key>
|
||||
<string>entity.name.filename</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#C8C093</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>scope</key>
|
||||
<string>message.error</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#E82424</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>JSON Punctuation</string>
|
||||
<key>scope</key>
|
||||
<string>punctuation.definition.string.begin.json - meta.structure.dictionary.value.json, punctuation.definition.string.end.json - meta.structure.dictionary.value.json</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#9CABCA</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>JSON Structure</string>
|
||||
<key>scope</key>
|
||||
<string>meta.structure.dictionary.json string.quoted.double.json</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#957FB8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>JSON String</string>
|
||||
<key>scope</key>
|
||||
<string>meta.structure.dictionary.value.json string.quoted.double.json</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#ffffff</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Escape Characters</string>
|
||||
<key>scope</key>
|
||||
<string>constant.character.escape</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#FF5D62</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Regular Expressions</string>
|
||||
<key>scope</key>
|
||||
<string>string.regexp</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#E6C384</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>uuid</key>
|
||||
<string>592FC036-6BB7-4676-A2F5-2894D48C8E33</string>
|
||||
<key>colorSpaceName</key>
|
||||
<string>sRGB</string>
|
||||
<key>semanticClass</key>
|
||||
<string>theme.dark.kanagawa</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,37 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Monokai flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||
</h3>
|
||||
|
||||
## 👀 Preview
|
||||
|
||||
<img src="preview.png" width="600" />
|
||||
|
||||
## 🎨 Installation
|
||||
|
||||
|
||||
```sh
|
||||
ya pkg add malick-tammal/monokai
|
||||
```
|
||||
|
||||
## ⚙️ Usage
|
||||
|
||||
Add it to your `theme.toml` as dark flavor:
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
dark = "monokai"
|
||||
```
|
||||
|
||||
Make sure your `theme.toml` doesn't contain anything other than `[flavor]`, unless you want to override certain styles of this flavor.
|
||||
|
||||
See the [Yazi flavor documentation](https://yazi-rs.github.io/docs/flavors/overview) for more details.
|
||||
|
||||
## 📜 License
|
||||
|
||||
The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed.
|
||||
|
||||
Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details.
|
||||
@@ -1,182 +0,0 @@
|
||||
#----------------------------------------------------------
|
||||
#-- HACK: Monokai
|
||||
#----------------------------------------------------------
|
||||
|
||||
[mgr]
|
||||
cwd = { fg = "#78DCE8" }
|
||||
|
||||
# Hovered
|
||||
hovered = { reversed = true }
|
||||
preview_hovered = { underline = true }
|
||||
|
||||
# Find
|
||||
find_symbol = " Finding: "
|
||||
find_keyword = { fg = "#FF6188", bold = true, italic = true, underline = true }
|
||||
find_position = { fg = "#AB9DF2", bg = "reset", bold = true, italic = true }
|
||||
|
||||
# Symlink
|
||||
symlink_target = { fg = "#78DCE8", italic = true }
|
||||
|
||||
# Marker
|
||||
marker_copied = { fg = "#A9DC76", bg = "#A9DC76" }
|
||||
marker_cut = { fg = "lightred", bg = "lightred" }
|
||||
marker_marked = { fg = "#AB9DF2", bg = "#AB9DF2" }
|
||||
marker_selected = { fg = "#FF6188", bg = "#FF6188" }
|
||||
|
||||
# Count
|
||||
count_copied = { fg = "#221F22", bg = "#A9DC76" }
|
||||
count_cut = { fg = "#221F22", bg = "#FF6188" }
|
||||
count_selected = { fg = "#221F22", bg = "#FFD866" }
|
||||
|
||||
# Border
|
||||
border_symbol = "│"
|
||||
border_style = { fg = "#727072" }
|
||||
|
||||
# Tabs
|
||||
[tabs]
|
||||
active = { fg = "#221F22", bg = "#FFD866", bold = true }
|
||||
inactive = { fg = "#FFD866", bg = "#221F22" }
|
||||
|
||||
# Separator
|
||||
sep_inner = { open = "", close = "" }
|
||||
sep_outer = { open = "", close = "" }
|
||||
|
||||
# Modes
|
||||
[mode]
|
||||
# Normal mode
|
||||
normal_main = { fg = "#2D2A2E", bg = "#FFD866", bold = true }
|
||||
normal_alt = { fg = "#FFD866", bg = "#403E41" }
|
||||
|
||||
# Select mode
|
||||
select_main = { fg = "#2D2A2E", bg = "#AB9DF2", bold = true }
|
||||
select_alt = { fg = "#AB9DF2", bg = "#403E41" }
|
||||
|
||||
# Unset mode
|
||||
unset_main = { bg = "#FF6188", bold = true }
|
||||
unset_alt = { fg = "#FF6188", bg = "gray" }
|
||||
|
||||
# Status bar
|
||||
[status]
|
||||
overall = {}
|
||||
sep_left = { open = "", close = "" }
|
||||
sep_right = { open = "", close = "" }
|
||||
|
||||
# Permissions
|
||||
perm_sep = { fg = "#727072" }
|
||||
perm_type = { fg = "#A9DC76" }
|
||||
perm_read = { fg = "#FFD866" }
|
||||
perm_write = { fg = "#FF6188" }
|
||||
perm_exec = { fg = "#78DCE8" }
|
||||
|
||||
# Progress
|
||||
progress_label = { bold = true }
|
||||
progress_normal = { fg = "#78DCE8", bg = "#2D2A2E" }
|
||||
progress_error = { fg = "#FF6188", bg = "#2D2A2E" }
|
||||
|
||||
# Which
|
||||
[which]
|
||||
cols = 3
|
||||
mask = { bg = "#2D2A2E" }
|
||||
cand = { fg = "lightcyan" }
|
||||
rest = { fg = "#727072" }
|
||||
desc = { fg = "lightmagenta" }
|
||||
separator = " "
|
||||
separator_style = { fg = "#727072" }
|
||||
|
||||
# Confirmation
|
||||
[confirm]
|
||||
border = { fg = "#FFD866" }
|
||||
title = { fg = "#FFD866" }
|
||||
content = {}
|
||||
list = {}
|
||||
btn_yes = { reversed = true }
|
||||
btn_no = {}
|
||||
btn_labels = [" [Y]es ", " (N)o "]
|
||||
|
||||
# Spotter
|
||||
[spot]
|
||||
border = { fg = "#78DCE8" }
|
||||
title = { fg = "#78DCE8" }
|
||||
|
||||
# Table
|
||||
tbl_col = { fg = "#78DCE8" }
|
||||
tbl_cell = { fg = "#FFD866", reversed = true }
|
||||
|
||||
# Norifications
|
||||
[notify]
|
||||
title_info = { fg = "#A9DC76" }
|
||||
title_warn = { fg = "#FFD866" }
|
||||
title_error = { fg = "#FF6188" }
|
||||
|
||||
# Icons
|
||||
icon_info = ""
|
||||
icon_warn = ""
|
||||
icon_error = ""
|
||||
|
||||
# Picker
|
||||
[pick]
|
||||
border = { fg = "#78DCE8" }
|
||||
active = { fg = "#AB9DF2", bold = true }
|
||||
inactive = {}
|
||||
|
||||
# Input
|
||||
[input]
|
||||
border = { fg = "#78DCE8" }
|
||||
title = {}
|
||||
value = {}
|
||||
selected = { reversed = true }
|
||||
|
||||
# Completion
|
||||
[cmp]
|
||||
border = { fg = "#78DCE8" }
|
||||
active = { reversed = true }
|
||||
inactive = {}
|
||||
|
||||
# Icons
|
||||
icon_file = ""
|
||||
icon_folder = ""
|
||||
icon_command = ""
|
||||
|
||||
# : Task manager {{{
|
||||
[tasks]
|
||||
border = { fg = "#78DCE8" }
|
||||
title = {}
|
||||
hovered = { fg = "#AB9DF2", underline = true }
|
||||
|
||||
# Help menu
|
||||
[help]
|
||||
on = { fg = "#78DCE8" }
|
||||
run = { fg = "#AB9DF2" }
|
||||
desc = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "#221F22", bg = "#FDFFF1" }
|
||||
|
||||
# File-specific styles
|
||||
[filetype]
|
||||
rules = [
|
||||
# Images
|
||||
{ mime = "image/*", fg = "#FFD866" },
|
||||
|
||||
# Media
|
||||
{ mime = "{audio,video}/*", fg = "#A9DC76" },
|
||||
|
||||
# Archives
|
||||
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#FF6188" },
|
||||
|
||||
# Documents
|
||||
{ mime = "application/{pdf,doc,rtf}", fg = "#78DCE8" },
|
||||
|
||||
# Empty files
|
||||
{ mime = "inode/empty", fg = "#FF6188" },
|
||||
|
||||
# Special files
|
||||
{ url = "*", is = "orphan", bg = "#FF6188" },
|
||||
{ url = "*", is = "exec", fg = "#A9DC76" },
|
||||
|
||||
# Dummy files
|
||||
{ url = "*", is = "dummy", bg = "#FF6188" },
|
||||
{ url = "*/", is = "dummy", bg = "#FF6188" },
|
||||
|
||||
# Fallback
|
||||
{ url = "*/", fg = "#FC9767", bold = true },
|
||||
]
|
||||
|
Before Width: | Height: | Size: 797 KiB |
@@ -1,300 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>gutterSettings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#49483E</string>
|
||||
<key>divider</key>
|
||||
<string>#75715E</string>
|
||||
<key>foreground</key>
|
||||
<string>#75715E</string>
|
||||
</dict>
|
||||
<key>name</key>
|
||||
<string>Monokai</string>
|
||||
<key>semanticClass</key>
|
||||
<string>theme.dark.monokai</string>
|
||||
<key>settings</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#272822</string>
|
||||
<key>caret</key>
|
||||
<string>#F8F8F0</string>
|
||||
<key>foreground</key>
|
||||
<string>#F8F8F2</string>
|
||||
<key>invisibles</key>
|
||||
<string>#49483E</string>
|
||||
<key>lineHighlight</key>
|
||||
<string>#49483E</string>
|
||||
<key>selection</key>
|
||||
<string>#49483E</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Comment</string>
|
||||
<key>scope</key>
|
||||
<string>comment</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#75715E</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>String</string>
|
||||
<key>scope</key>
|
||||
<string>string</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#E6DB74</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Number</string>
|
||||
<key>scope</key>
|
||||
<string>constant.numeric</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#AE81FF</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Built-in constant</string>
|
||||
<key>scope</key>
|
||||
<string>constant.language</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#AE81FF</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>User-defined constant</string>
|
||||
<key>scope</key>
|
||||
<string>constant.character, constant.other</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#AE81FF</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Variable</string>
|
||||
<key>scope</key>
|
||||
<string>variable</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Keyword</string>
|
||||
<key>scope</key>
|
||||
<string>keyword</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#F92672</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Storage</string>
|
||||
<key>scope</key>
|
||||
<string>storage</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#F92672</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Storage type</string>
|
||||
<key>scope</key>
|
||||
<string>storage.type</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic</string>
|
||||
<key>foreground</key>
|
||||
<string>#66D9EF</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Class name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>underline</string>
|
||||
<key>foreground</key>
|
||||
<string>#A6E22E</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Inherited class</string>
|
||||
<key>scope</key>
|
||||
<string>entity.other.inherited-class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic underline</string>
|
||||
<key>foreground</key>
|
||||
<string>#A6E22E</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Function name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.function</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#A6E22E</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Function argument</string>
|
||||
<key>scope</key>
|
||||
<string>variable.parameter</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic</string>
|
||||
<key>foreground</key>
|
||||
<string>#FD971F</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Tag name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.tag</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#F92672</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Tag attribute</string>
|
||||
<key>scope</key>
|
||||
<string>entity.other.attribute-name</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#A6E22E</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library function</string>
|
||||
<key>scope</key>
|
||||
<string>support.function</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#66D9EF</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library constant</string>
|
||||
<key>scope</key>
|
||||
<string>support.constant</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#66D9EF</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library class/type</string>
|
||||
<key>scope</key>
|
||||
<string>support.type, support.class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic</string>
|
||||
<key>foreground</key>
|
||||
<string>#66D9EF</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library variable</string>
|
||||
<key>scope</key>
|
||||
<string>support.other.variable</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Invalid</string>
|
||||
<key>scope</key>
|
||||
<string>invalid</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#F92672</string>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#F8F8F0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Invalid deprecated</string>
|
||||
<key>scope</key>
|
||||
<string>invalid.deprecated</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#AE81FF</string>
|
||||
<key>foreground</key>
|
||||
<string>#F8F8F0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>uuid</key>
|
||||
<string>D8D5E82E-3D5B-46B5-B38E-8C841C21347D</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Rosé Pine
|
||||
|
||||
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.
|
||||
@@ -1,36 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Rosé Pine Moon Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||
</h3>
|
||||
|
||||
## 👀 Preview
|
||||
|
||||
<img src="preview.png" width="600" />
|
||||
|
||||
## 🎨 Installation
|
||||
|
||||
```sh
|
||||
ya pkg add Mintass/rose-pine-moon
|
||||
```
|
||||
|
||||
## ⚙️ Usage
|
||||
|
||||
To set it as your dark flavor, change the content of your `theme.toml` to:
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
dark = "rose-pine-moon"
|
||||
```
|
||||
|
||||
Make sure your `theme.toml` doesn't contain anything other than `[flavor]`, unless you want to override certain styles of this flavor.
|
||||
|
||||
See the [Yazi flavor documentation](https://yazi-rs.github.io/docs/flavors/overview) for more details.
|
||||
|
||||
## 📜 License
|
||||
|
||||
The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed.
|
||||
|
||||
Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details.
|
||||
@@ -1,253 +0,0 @@
|
||||
# vim:fileencoding=utf-8:foldmethod=marker
|
||||
|
||||
# : App {{{
|
||||
|
||||
[app]
|
||||
overall = { bg = "#232136" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Manager {{{
|
||||
|
||||
[mgr]
|
||||
cwd = { fg = "#9ccfd8" }
|
||||
|
||||
# Find
|
||||
find_keyword = { fg = "#f6c177", bold = true, italic = true, underline = true }
|
||||
find_position = { fg = "#c4a7e7", bg = "reset", bold = true, italic = true }
|
||||
|
||||
# Symlink
|
||||
symlink_target = { italic = true }
|
||||
|
||||
# Marker
|
||||
marker_copied = { fg = "#3e8fb0", bg = "#3e8fb0" }
|
||||
marker_cut = { fg = "#eb6f92", bg = "#eb6f92" }
|
||||
marker_marked = { fg = "#ea9a97", bg = "#ea9a97" }
|
||||
marker_selected = { fg = "#f6c177", bg = "#f6c177" }
|
||||
|
||||
# Count
|
||||
count_copied = { fg = "#e0def4", bg = "#3e8fb0" }
|
||||
count_cut = { fg = "#e0def4", bg = "#eb6f92" }
|
||||
count_selected = { fg = "#232136", bg = "#f6c177" }
|
||||
|
||||
# Border
|
||||
border_symbol = "│"
|
||||
border_style = { fg = "#56526e" }
|
||||
|
||||
# Highlighting
|
||||
syntect_theme = ""
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tabs {{{
|
||||
|
||||
[tabs]
|
||||
active = { fg = "#e0def4", bg = "#3e8fb0", bold = true }
|
||||
inactive = { fg = "#3e8fb0", bg = "#e0def4" }
|
||||
|
||||
# Separator
|
||||
sep_inner = { open = "", close = "" }
|
||||
sep_outer = { open = "", close = "" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Mode {{{
|
||||
|
||||
[mode]
|
||||
normal_main = { fg = "#232136", bg = "#9ccfd8", bold = true }
|
||||
normal_alt = { fg = "#232136", bg = "#e0def4", bold = true }
|
||||
|
||||
# Select mode
|
||||
select_main = { fg = "#232136", bg = "#eb6f92", bold = true }
|
||||
select_alt = { fg = "#232136", bg = "#e0def4", bold = true }
|
||||
|
||||
# Unset mode
|
||||
unset_main = { fg = "#232136", bg = "#eb6f92", bold = true }
|
||||
unset_alt = { fg = "#232136", bg = "#e0def4", bold = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Indicator of hovered file {{{
|
||||
|
||||
[indicator]
|
||||
parent = { reversed = true }
|
||||
current = { reversed = true }
|
||||
preview = { underline = true }
|
||||
padding = { open = "", close = "" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Status bar {{{
|
||||
|
||||
[status]
|
||||
overall = { bg = "#232136" }
|
||||
sep_left = { open = "", close = "" }
|
||||
sep_right = { open = "", close = "" }
|
||||
|
||||
# Permissions
|
||||
perm_sep = { fg = "#6e6a86" }
|
||||
perm_type = { fg = "#c4a7e7" }
|
||||
perm_read = { fg = "#f6c177" }
|
||||
perm_write = { fg = "#eb6f92" }
|
||||
perm_exec = { fg = "#9ccfd8" }
|
||||
|
||||
# Progress
|
||||
progress_label = { fg = "#e0def4", bold = true }
|
||||
progress_normal = { fg = "#3e8fb0", bg = "#2a283e" }
|
||||
progress_error = { fg = "#eb6f92", bg = "#2a283e" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Which {{{
|
||||
|
||||
[which]
|
||||
cols = 3
|
||||
mask = { bg = "#2a273f" }
|
||||
cand = { fg = "#9ccfd8" }
|
||||
rest = { fg = "#6e6a86" }
|
||||
desc = { fg = "#f6c177" }
|
||||
separator = " "
|
||||
separator_style = { fg = "#6e6a86" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Confirmation {{{
|
||||
|
||||
[confirm]
|
||||
border = { fg = "#56526e" }
|
||||
title = { fg = "#eb6f92", bold = true }
|
||||
body = { fg = "#e0def4" }
|
||||
list = { fg = "#e0def4" }
|
||||
btn_yes = { fg = "#232136", bg = "#3e8fb0", bold = true }
|
||||
btn_no = { fg = "#232136", bg = "#e0def4", bold = true }
|
||||
btn_labels = [ " [y]es ", " (n)o " ]
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Spotter {{{
|
||||
|
||||
[spot]
|
||||
border = { fg = "#56526e" }
|
||||
title = { fg = "#9ccfd8", bold = true }
|
||||
|
||||
# Table
|
||||
tbl_col = { fg = "#9ccfd8" }
|
||||
tbl_cell = { fg = "#232136", bg = "#f6c177", bold = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Notification {{{
|
||||
|
||||
[notify]
|
||||
title_info = { fg = "#3e8fb0" }
|
||||
title_warn = { fg = "#f6c177" }
|
||||
title_error = { fg = "#eb6f92" }
|
||||
|
||||
# Icons
|
||||
icon_info = ""
|
||||
icon_warn = ""
|
||||
icon_error = ""
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Picker {{{
|
||||
|
||||
[pick]
|
||||
border = { fg = "#3e8fb0" }
|
||||
active = { fg = "#c4a7e7", bold = true }
|
||||
inactive = { fg = "#908caa" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Input {{{
|
||||
|
||||
[input]
|
||||
border = { fg = "#3e8fb0" }
|
||||
title = { bold = true }
|
||||
value = { fg = "#e0def4" }
|
||||
selected = { reversed = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Completion {{{
|
||||
|
||||
[cmp]
|
||||
border = { fg = "#3e8fb0" }
|
||||
active = { bg = "#e0def4", fg = "#232136", bold = true }
|
||||
inactive = { fg = "#908caa" }
|
||||
|
||||
# Icons
|
||||
icon_file = ""
|
||||
icon_folder = ""
|
||||
icon_command = ""
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Task manager {{{
|
||||
|
||||
[tasks]
|
||||
border = { fg = "#3e8fb0" }
|
||||
title = { bold = true }
|
||||
hovered = { fg = "#c4a7e7", bold = true, reversed = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Help menu {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "#9ccfd8" }
|
||||
run = { fg = "#c4a7e7" }
|
||||
desc = { fg = "#f6c177" }
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "#232136", bg = "#e0def4" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : File-specific styles {{{
|
||||
|
||||
[filetype]
|
||||
rules = [
|
||||
# Images
|
||||
{ mime = "image/*", fg = "#c4a7e7" },
|
||||
|
||||
# Media
|
||||
{ mime = "{audio,video}/*", fg = "#f6c177" },
|
||||
|
||||
# Archives
|
||||
{ mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#eb6f92" },
|
||||
|
||||
# Documents
|
||||
{ mime = "application/{pdf,doc,rtf}", fg = "#ea9a97" },
|
||||
|
||||
# Empty files
|
||||
# { mime = "inode/empty", fg = "#eb6f92" },
|
||||
|
||||
# Special files
|
||||
{ url = "*", is = "orphan", fg = "#232136", bg = "#eb6f92" },
|
||||
{ url = "*", is = "exec", fg = "#9ccfd8" },
|
||||
|
||||
# Dummy files
|
||||
{ url = "*", is = "dummy", fg = "#232136", bg = "#eb6f92" },
|
||||
{ url = "*/", is = "dummy", fg = "#232136", bg = "#eb6f92" },
|
||||
|
||||
# Fallback
|
||||
{ url = "*", fg = "#e0def4" },
|
||||
{ url = "*/", fg = "#3e8fb0" }
|
||||
]
|
||||
|
||||
# : }}}
|
||||
|
Before Width: | Height: | Size: 985 KiB |
@@ -1,316 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Rosé Pine Moon</string>
|
||||
<key>settings</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#232136</string>
|
||||
<key>caret</key>
|
||||
<string>#56526e</string>
|
||||
<key>foreground</key>
|
||||
<string>#e0def4</string>
|
||||
<key>invisibles</key>
|
||||
<string>#2a273f</string>
|
||||
<key>lineHighlight</key>
|
||||
<string>#2a283e</string>
|
||||
<key>selection</key>
|
||||
<string>#6e6a86</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Comment</string>
|
||||
<key>scope</key>
|
||||
<string>comment</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic</string>
|
||||
<key>foreground</key>
|
||||
<string>#908caa</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>String</string>
|
||||
<key>scope</key>
|
||||
<string>string, punctuation.definition.string</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#f6c177</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Number</string>
|
||||
<key>scope</key>
|
||||
<string>constant.numeric</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#f6c177</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Built-in constant</string>
|
||||
<key>scope</key>
|
||||
<string>constant.language</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>bold</string>
|
||||
<key>foreground</key>
|
||||
<string>#f6c177</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>User-defined constant</string>
|
||||
<key>scope</key>
|
||||
<string>constant.character, constant.other</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#f6c177</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Variable</string>
|
||||
<key>scope</key>
|
||||
<string>variable</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic</string>
|
||||
<key>foreground</key>
|
||||
<string>#e0def4</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Keyword</string>
|
||||
<key>scope</key>
|
||||
<string>keyword</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#3e8fb0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Storage</string>
|
||||
<key>scope</key>
|
||||
<string>storage</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#9ccfd8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Storage type</string>
|
||||
<key>scope</key>
|
||||
<string>storage.type</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#9ccfd8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Class name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>bold</string>
|
||||
<key>foreground</key>
|
||||
<string>#3e8fb0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Inherited class</string>
|
||||
<key>scope</key>
|
||||
<string>entity.other.inherited-class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic </string>
|
||||
<key>foreground</key>
|
||||
<string>#3e8fb0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Function name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.function</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>italic</string>
|
||||
<key>foreground</key>
|
||||
<string>#ea9a97</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Function argument</string>
|
||||
<key>scope</key>
|
||||
<string>variable.parameter</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#c4a7e7</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Tag name</string>
|
||||
<key>scope</key>
|
||||
<string>entity.name.tag</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>bold</string>
|
||||
<key>foreground</key>
|
||||
<string>#3e8fb0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Tag attribute</string>
|
||||
<key>scope</key>
|
||||
<string>entity.other.attribute-name</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#c4a7e7</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library function</string>
|
||||
<key>scope</key>
|
||||
<string>support.function</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>bold</string>
|
||||
<key>foreground</key>
|
||||
<string>#ea9a97</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library constant</string>
|
||||
<key>scope</key>
|
||||
<string>support.constant</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>bold</string>
|
||||
<key>foreground</key>
|
||||
<string>#f6c177</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library class/type</string>
|
||||
<key>scope</key>
|
||||
<string>support.type, support.class</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>bold</string>
|
||||
<key>foreground</key>
|
||||
<string>#9ccfd8</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Library variable</string>
|
||||
<key>scope</key>
|
||||
<string>support.other.variable</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>fontStyle</key>
|
||||
<string>bold</string>
|
||||
<key>foreground</key>
|
||||
<string>#eb6f92</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Invalid</string>
|
||||
<key>scope</key>
|
||||
<string>invalid</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#eb6f92</string>
|
||||
<key>fontStyle</key>
|
||||
<string></string>
|
||||
<key>foreground</key>
|
||||
<string>#e0def4</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Invalid deprecated</string>
|
||||
<key>scope</key>
|
||||
<string>invalid.deprecated</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>background</key>
|
||||
<string>#c4a7e7</string>
|
||||
<key>foreground</key>
|
||||
<string>#e0def4</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>name</key>
|
||||
<string>Punctuation, Operators</string>
|
||||
<key>scope</key>
|
||||
<string>punctuation, keyword.operator</string>
|
||||
<key>settings</key>
|
||||
<dict>
|
||||
<key>foreground</key>
|
||||
<string>#908caa</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>colorSpaceName</key>
|
||||
<string>sRGB</string>
|
||||
<key>semanticClass</key>
|
||||
<string>theme.dark.rosé-pine-moon</string>
|
||||
<key>author</key>
|
||||
<string>arrrgi</string>
|
||||
<key>comment</key>
|
||||
<string>All natural pine, faux fur and a bit of soho vibes for the classy minimalist</string>
|
||||
<key>uuid</key>
|
||||
<string>CC28B8FB-96BA-43EB-B71F-5AA3D3EBB0BB</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 - sxyazi
|
||||
|
||||
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.
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Himanshu
|
||||
|
||||
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.
|
||||
@@ -1,47 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
|
||||
</div>
|
||||
|
||||
<h3 align="center">
|
||||
Tokyo Night Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
|
||||
</h3>
|
||||
|
||||
## 👀 Preview
|
||||
|
||||
<img src="preview.png" width="600" />
|
||||
|
||||
## 🎨 Installation
|
||||
|
||||
### Using package manager
|
||||
|
||||
```bash
|
||||
ya pkg add BennyOe/tokyo-night
|
||||
```
|
||||
|
||||
### Manual install
|
||||
|
||||
```bash
|
||||
# Linux/macOS
|
||||
git clone https://github.com/BennyOe/tokyo-night.yazi.git ~/.config/yazi/flavors/tokyo-night.yazi
|
||||
|
||||
# Windows
|
||||
git clone https://github.com/BennyOe/tokyo-night.yazi.git %AppData%\yazi\config\flavors\tokyo-night.yazi
|
||||
```
|
||||
|
||||
## ⚙️ Usage
|
||||
|
||||
Add the these lines to your `theme.toml` configuration file to use it:
|
||||
|
||||
|
||||
```toml
|
||||
[flavor]
|
||||
use = "tokyo-night"
|
||||
# For Yazi 0.4 and above
|
||||
dark = "tokyo-night"
|
||||
```
|
||||
|
||||
## 📜 License
|
||||
|
||||
The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed.
|
||||
|
||||
Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details.
|
||||
@@ -1,201 +0,0 @@
|
||||
# vim:fileencoding=utf-8:foldmethod=marker
|
||||
|
||||
# : Manager {{{
|
||||
|
||||
[mgr]
|
||||
cwd = { fg = "#7aa2f7" }
|
||||
|
||||
# Find
|
||||
find_keyword = { fg = "#f7768e", bold = true, italic = true, underline = true }
|
||||
find_position = { fg = "#bb9af7", bg = "reset", bold = true, italic = true }
|
||||
|
||||
# Marker
|
||||
marker_copied = { fg = "#9ece6a", bg = "#9ece6a" }
|
||||
marker_cut = { fg = "#e0af68", bg = "#f7768e" }
|
||||
marker_marked = { fg = "#7aa2f7", bg = "#7dcfff" }
|
||||
marker_selected = { fg = "#e0af68", bg = "#e0af68" }
|
||||
|
||||
# Count
|
||||
count_copied = { fg = "#1a1b26", bg = "#9ece6a" }
|
||||
count_cut = { fg = "#1a1b26", bg = "#e0af68" }
|
||||
count_selected = { fg = "#1a1b26", bg = "#7aa2f7" }
|
||||
|
||||
# Border
|
||||
border_symbol = "│"
|
||||
border_style = { fg = "#414868" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tabs {{{
|
||||
|
||||
[tabs]
|
||||
active = { fg = "#292e42", bg = "#7aa2f7", bold = true }
|
||||
inactive = { fg = "#7aa2f7", bg = "#292e42" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Mode {{{
|
||||
|
||||
[mode]
|
||||
|
||||
normal_main = { fg = "#292e42", bg = "#7aa2f7", bold = true }
|
||||
normal_alt = { fg = "#7aa2f7", bg = "#292e42" }
|
||||
|
||||
# Select mode
|
||||
select_main = { fg = "#292e42", bg = "#9ece6a", bold = true }
|
||||
select_alt = { fg = "#7aa2f7", bg = "#292e42" }
|
||||
|
||||
# Unset mode
|
||||
unset_main = { fg = "#292e42", bg = "#bb9af7", bold = true }
|
||||
unset_alt = { fg = "#7aa2f7", bg = "#292e42" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Status bar {{{
|
||||
|
||||
[status]
|
||||
overall = { fg = "#7aa2f7" }
|
||||
sep_left = { open = "", close = "" }
|
||||
sep_right = { open = "", close = "" }
|
||||
|
||||
|
||||
# Progress
|
||||
progress_label = { fg = "#292e42", bold = true }
|
||||
progress_normal = { fg = "#7aa2f7", bg = "#292e42" }
|
||||
progress_error = { fg = "#f7768e", bg = "#292e42" }
|
||||
|
||||
# Permissions
|
||||
perm_sep = { fg = "#7aa2f7" }
|
||||
perm_type = { fg = "#9ece6a" }
|
||||
perm_read = { fg = "#e0af68" }
|
||||
perm_write = { fg = "#f7768e" }
|
||||
perm_exec = { fg = "#bb9af7" }
|
||||
|
||||
|
||||
# : }}}
|
||||
|
||||
# : Pick {{{
|
||||
|
||||
[pick]
|
||||
border = { fg = "#7aa2f7" }
|
||||
active = { fg = "#bb9af7", bold = true }
|
||||
inactive = {}
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Input {{{
|
||||
|
||||
[input]
|
||||
border = { fg = "#7aa2f7" }
|
||||
title = {}
|
||||
value = {}
|
||||
selected = { reversed = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Completion {{{
|
||||
|
||||
[cmp]
|
||||
border = { fg = "#7aa2f7" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Tasks {{{
|
||||
|
||||
[tasks]
|
||||
border = { fg = "#7aa2f7" }
|
||||
title = {}
|
||||
hovered = { fg = "#bb9af7", underline = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Which {{{
|
||||
|
||||
[which]
|
||||
mask = { bg = "#414868" }
|
||||
cand = { fg = "#9ece6a" }
|
||||
rest = { fg = "#a9b1d6" }
|
||||
desc = { fg = "#bb9af7" }
|
||||
separator = " "
|
||||
separator_style = { fg = "#626880" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Help {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "#9ece6a" }
|
||||
run = { fg = "#bb9af7" }
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "#1a1b26", bg = "#a9b1d6" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Spotter {{{
|
||||
|
||||
[spot]
|
||||
border = { fg = "#7aa2f7" }
|
||||
title = { fg = "#7aa2f7" }
|
||||
tbl_col = { fg = "#9ece6a" }
|
||||
tbl_cell = { fg = "#bb9af7", bg = "#292e42" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : Notify {{{
|
||||
|
||||
[notify]
|
||||
title_info = { fg = "#9ece6a" }
|
||||
title_warn = { fg = "#f7768e" }
|
||||
title_error = { fg = "#e0af68" }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
||||
# : File-specific styles {{{
|
||||
|
||||
[filetype]
|
||||
|
||||
rules = [
|
||||
# Images
|
||||
{ mime = "image/*", fg = "#e0af68" },
|
||||
|
||||
# Media
|
||||
{ mime = "video/*", fg = "#f7768e" },
|
||||
{ mime = "audio/*", fg = "#f7768e" },
|
||||
|
||||
# Archives
|
||||
{ mime = "application/zip", fg = "#bb9af7" },
|
||||
{ mime = "application/x-tar", fg = "#bb9af7" },
|
||||
{ mime = "application/x-bzip*", fg = "#bb9af7" },
|
||||
{ mime = "application/x-bzip2", fg = "#bb9af7" },
|
||||
{ mime = "application/x-7z-compressed", fg = "#bb9af7" },
|
||||
{ mime = "application/x-rar", fg = "#bb9af7" },
|
||||
{ mime = "application/x-xz", fg = "#bb9af7" },
|
||||
|
||||
# Documents
|
||||
{ mime = "application/doc", fg = "#9ece6a" },
|
||||
{ mime = "application/epub+zip", fg = "#9ece6a" },
|
||||
{ mime = "application/pdf", fg = "#9ece6a" },
|
||||
{ mime = "application/rtf", fg = "#9ece6a" },
|
||||
{ mime = "application/vnd.*", fg = "#9ece6a" },
|
||||
|
||||
# Special files
|
||||
{ mime = "*", is = "orphan", fg = "#f29cb4", bg = "#93000a" },
|
||||
{ mime = "application/*exec*", fg = "#f7768e" },
|
||||
|
||||
# Fallback
|
||||
{ url = "*", fg = "#c6d0f5" },
|
||||
{ url = "*/", fg = "#7aa2f7" },
|
||||
]
|
||||
|
||||
# : }}}
|
||||
|
Before Width: | Height: | Size: 712 KiB |
@@ -1,140 +1,32 @@
|
||||
-- Change this to switch themes
|
||||
-- local THEME = "kanagawa"
|
||||
-- local THEME = "catppuccin-mocha"
|
||||
-- local THEME = "everforest-medium"
|
||||
-- local THEME = "gruvbox-material"
|
||||
-- local THEME = "tokyo-night"
|
||||
local THEME = "rose_pine-moon"
|
||||
|
||||
-- local theme_colors = {
|
||||
-- rose_pine = {
|
||||
-- primary = "#c4a7e7", -- iris/mauve (purple-like color from rose-pine palette)
|
||||
-- secondary = "#6e6a86", -- muted/subtle (comparable to comment/gray colors)
|
||||
-- yanked = "#9ccfd8", -- foam (cyan-like accent color)
|
||||
-- cut = "#eb6f92", -- love (red-like accent color)
|
||||
-- },
|
||||
-- carbonfox = {
|
||||
-- primary = "#78a9ff",
|
||||
-- secondary = "#525252",
|
||||
-- yanked = "#3ddbd9",
|
||||
-- cut = "#ff7eb6",
|
||||
-- },
|
||||
-- kanagawa = {
|
||||
-- primary = "#7e9cd8",
|
||||
-- secondary = "#54546d",
|
||||
-- yanked = "#7aa89f",
|
||||
-- cut = "#e82424",
|
||||
-- },
|
||||
-- sonokai = {
|
||||
-- primary = "#76cce0",
|
||||
-- secondary = "#7f8490",
|
||||
-- yanked = "#fc5d7c",
|
||||
-- cut = "#f85e84",
|
||||
-- },
|
||||
-- catppuccin = {
|
||||
-- primary = "#89b4fa",
|
||||
-- secondary = "#6c7086",
|
||||
-- yanked = "#94e2d5",
|
||||
-- cut = "#f38ba8",
|
||||
-- },
|
||||
-- dracula = {
|
||||
-- primary = "#bd93f9", -- purple
|
||||
-- secondary = "#6272a4", -- comment
|
||||
-- yanked = "#8be9fd", -- cyan
|
||||
-- cut = "#ff5555", -- red
|
||||
-- },
|
||||
-- everforest = {
|
||||
-- primary = "#7fbbb3", -- aqua
|
||||
-- secondary = "#859289", -- gray
|
||||
-- yanked = "#a7c080", -- green
|
||||
-- cut = "#e67e80", -- red
|
||||
-- },
|
||||
-- gruvbox = {
|
||||
-- primary = "#7daea3", -- aqua
|
||||
-- secondary = "#7c6f64", -- gray
|
||||
-- yanked = "#a9b665", -- green
|
||||
-- cut = "#ea6962", -- red
|
||||
-- },
|
||||
-- nightfox = {
|
||||
-- primary = "#719cd6", -- blue
|
||||
-- secondary = "#738091", -- comment
|
||||
-- yanked = "#63cdcf", -- cyan
|
||||
-- cut = "#c94f6d", -- red
|
||||
-- },
|
||||
-- onedark = {
|
||||
-- primary = "#61afef", -- blue
|
||||
-- secondary = "#5c6370", -- gray
|
||||
-- yanked = "#56b6c2", -- cyan
|
||||
-- cut = "#e06c75", -- red
|
||||
-- },
|
||||
-- }
|
||||
|
||||
-- eocal c = theme_colors[THEME]
|
||||
|
||||
-- require("yaziline"):setup({
|
||||
-- color = c.primary,
|
||||
-- secondary_color = c.secondary,
|
||||
-- default_files_color = "darkgray",
|
||||
-- selected_files_color = "white",
|
||||
-- yanked_files_color = c.yanked,
|
||||
-- cut_files_color = c.cut,
|
||||
-- separator_style = "angly", -- "angly" | "curvy" | "liney" | "empty"
|
||||
-- separator_open = "",
|
||||
-- separator_close = "",
|
||||
-- separator_open_thin = "",
|
||||
-- separator_close_thin = "",
|
||||
-- separator_head = "",
|
||||
-- separator_tail = "",
|
||||
|
||||
-- select_symbol = "",
|
||||
-- yank_symbol = "",
|
||||
|
||||
-- filename_max_length = 24, -- truncate when filename > 24
|
||||
-- filename_truncate_length = 6, -- leave 6 chars on both sides
|
||||
-- filename_truncate_separator = "...",
|
||||
-- })
|
||||
|
||||
require("recycle-bin"):setup({
|
||||
trash_dir = "~/.local/share/Trash/",
|
||||
})
|
||||
|
||||
-- require("omp"):setup({ config = "/home/liph/.config/yazi/omp/omp.toml" })
|
||||
|
||||
require("mime-ext"):setup({
|
||||
-- with_files = {
|
||||
-- tex = "text/makefile",
|
||||
-- },
|
||||
|
||||
with_exts = {
|
||||
tex = "text/makefile",
|
||||
},
|
||||
})
|
||||
|
||||
require("full-border"):setup({
|
||||
type = ui.Border.ROUNDED,
|
||||
})
|
||||
|
||||
-- git-yazi plugin
|
||||
-- th.git = th.git or {}
|
||||
-- th.git.modified = ui.Style():fg("blue")
|
||||
-- th.git.deleted = ui.Style():fg("red"):bold()
|
||||
require("git"):setup()
|
||||
-- whoosh plugin
|
||||
-- You can configure your bookmarks using simplified syntax
|
||||
-- local bookmarks = {
|
||||
-- { tag = "Documents", path = "~/Documents", key = "D" },
|
||||
-- }
|
||||
--- whoosh ---
|
||||
--
|
||||
local bookmarks = {
|
||||
{ tag = "Desktop", path = "~/Desktop", key = "d" },
|
||||
{ tag = "Documents", path = "~/Documents", key = "D" },
|
||||
{ tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||
}
|
||||
|
||||
-- You can also configure bookmarks with key arrays
|
||||
-- local bookmarks = {
|
||||
-- { tag = "Desktop", path = "~/Desktop", key = { "d", "D" } },
|
||||
-- { tag = "Documents", path = "~/Documents", key = { "d", "d" } },
|
||||
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||
-- { tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||
-- }
|
||||
local bookmarks = {
|
||||
{ tag = "Desktop", path = "~/Desktop", key = { "d", "D" } },
|
||||
{ tag = "Documents", path = "~/Documents", key = { "d", "d" } },
|
||||
{ tag = "Downloads", path = "~/Downloads", key = "o" },
|
||||
}
|
||||
|
||||
-- Windows-specific bookmarks
|
||||
if ya.target_family() == "windows" then
|
||||
local home_path = os.getenv("USERPROFILE")
|
||||
table.insert(bookmarks, {
|
||||
tag = "Scoop Local",
|
||||
path = os.getenv("SCOOP") or (home_path .. "\\scoop"),
|
||||
key = "p",
|
||||
})
|
||||
table.insert(bookmarks, {
|
||||
tag = "Scoop Global",
|
||||
path = os.getenv("SCOOP_GLOBAL") or "C:\\ProgramData\\scoop",
|
||||
key = "P",
|
||||
})
|
||||
end
|
||||
|
||||
require("whoosh"):setup({
|
||||
-- Configuration bookmarks (cannot be deleted through plugin)
|
||||
@@ -153,6 +45,7 @@ require("whoosh"):setup({
|
||||
fuzzy_search = "<Space>", -- Launch fuzzy search (fzf)
|
||||
history = "<Tab>", -- Open directory history
|
||||
previous_dir = "<Backspace>", -- Jump back to the previous directory
|
||||
project_root = "-", -- Jump to the current Git repository root
|
||||
},
|
||||
|
||||
-- File path for storing user bookmarks
|
||||
@@ -185,115 +78,30 @@ require("whoosh"):setup({
|
||||
history_fzf_path_max_folder_name_length = 30, -- Maximum length for folder names in history (default 30)
|
||||
})
|
||||
|
||||
-- projetc plugin
|
||||
require("projects"):setup({
|
||||
event = {
|
||||
save = {
|
||||
enable = true,
|
||||
name = "project-saved",
|
||||
},
|
||||
load = {
|
||||
enable = true,
|
||||
name = "project-loaded",
|
||||
},
|
||||
delete = {
|
||||
enable = true,
|
||||
name = "project-deleted",
|
||||
},
|
||||
delete_all = {
|
||||
enable = true,
|
||||
name = "project-deleted-all",
|
||||
},
|
||||
merge = {
|
||||
enable = true,
|
||||
name = "project-merged",
|
||||
},
|
||||
--- mime-ext ---
|
||||
require("mime-ext.local"):setup({
|
||||
-- Expand the default filename database (lowercase), for example:
|
||||
with_files = {
|
||||
makefile = "text/makefile",
|
||||
-- ...
|
||||
},
|
||||
save = {
|
||||
method = "yazi", -- yazi | lua
|
||||
yazi_load_event = "@projects-load", -- event name when loading projects in `yazi` method
|
||||
lua_save_path = "", -- path of saved file in `lua` method, comment out or assign explicitly
|
||||
-- default value:
|
||||
-- windows: "%APPDATA%/yazi/state/projects.json"
|
||||
-- unix: "~/.local/state/yazi/projects.json"
|
||||
},
|
||||
last = {
|
||||
update_after_save = true,
|
||||
update_after_load = true,
|
||||
update_before_quit = false,
|
||||
load_after_start = false,
|
||||
},
|
||||
merge = {
|
||||
event = "projects-merge",
|
||||
quit_after_merge = false,
|
||||
},
|
||||
notify = {
|
||||
enable = true,
|
||||
title = "Projects",
|
||||
timeout = 3,
|
||||
level = "info",
|
||||
|
||||
-- Expand the default extension database (lowercase), for example:
|
||||
with_exts = {
|
||||
mk = "text/makefile",
|
||||
-- ...
|
||||
},
|
||||
|
||||
-- Empty the default filename and extension databases,
|
||||
-- use only the custom ones configured with `with_files` and `with_exts`
|
||||
custom_only = false,
|
||||
|
||||
-- If the MIME type is not in both filename and extension databases,
|
||||
-- then fallback to Yazi's preset `mime.local` plugin, which uses `file(1)`
|
||||
fallback_file1 = false,
|
||||
})
|
||||
|
||||
-- yafg plugin
|
||||
require("yafg"):setup({
|
||||
editor = "nvim", -- Editor command (default: "hx")
|
||||
args = { "--noplugin" }, -- Additional editor arguments (default: {})
|
||||
file_arg_format = "+{row} {file}", -- File argument format (default: "{file}:{row}:{col}")
|
||||
})
|
||||
|
||||
require("simple-tag"):setup({
|
||||
-- UI display mode (icon, text, hidden)
|
||||
ui_mode = "icon", -- (Optional)
|
||||
|
||||
-- Disable tag key hints (popup in bottom right corner)
|
||||
hints_disabled = false, -- (Optional)
|
||||
|
||||
-- linemode order: adjusts icon/text position. For example, if you want icon to be on the most left of linemode then set linemode_order larger than 1000.
|
||||
-- More info: https://github.com/sxyazi/yazi/blob/077faacc9a84bb5a06c5a8185a71405b0cb3dc8a/yazi-plugin/preset/components/linemode.lua#L4-L5
|
||||
linemode_order = 500, -- (Optional)
|
||||
|
||||
-- You can backup/restore this folder within the same OS (Linux, windows, or MacOS).
|
||||
-- But you can't restore backed up folder in the different OS because they use difference absolute path.
|
||||
-- save_path = -- full path to save tags folder (Optional)
|
||||
-- - Linux/MacOS: os.getenv("HOME") .. "/.config/yazi/tags"
|
||||
-- - Windows: os.getenv("APPDATA") .. "\\yazi\\config\\tags"
|
||||
|
||||
-- Set tag colors
|
||||
colors = { -- (Optional)
|
||||
-- Set this same value with `theme.toml` > [mgr] > hovered > reversed
|
||||
-- Default theme use "reversed = true".
|
||||
-- More info: https://github.com/sxyazi/yazi/blob/077faacc9a84bb5a06c5a8185a71405b0cb3dc8a/yazi-config/preset/theme-dark.toml#L25
|
||||
reversed = true, -- (Optional)
|
||||
|
||||
-- More colors: https://yazi-rs.github.io/docs/configuration/theme#types.color
|
||||
-- format: [tag key] = "color"
|
||||
["*"] = "#bf68d9", -- (Optional)
|
||||
["$"] = "green",
|
||||
["!"] = "#cc9057",
|
||||
["1"] = "cyan",
|
||||
["p"] = "red",
|
||||
},
|
||||
|
||||
-- Set tag icons. Only show when ui_mode = "icon".
|
||||
-- Any text or nerdfont icons should work as long as you use nerdfont to render yazi.
|
||||
-- Default icon from mactag.yazi: ●; Some generic icons: , ,
|
||||
-- More icon from nerd fonts: https://www.nerdfonts.com/cheat-sheet
|
||||
icons = { -- (Optional)
|
||||
-- default icon
|
||||
default = "",
|
||||
|
||||
-- format: [tag key] = "tag icon"
|
||||
["*"] = "*",
|
||||
["$"] = "",
|
||||
["!"] = "",
|
||||
["p"] = "",
|
||||
},
|
||||
})
|
||||
-- require("omp"):setup({
|
||||
-- config = "/home/liph/.config/ohmyposh/yazi-zen.json",
|
||||
-- })
|
||||
|
||||
-- fchar --
|
||||
-- default config
|
||||
require("fchar"):setup({
|
||||
-- if true: f -> file, File, FILE
|
||||
@@ -302,11 +110,18 @@ require("fchar"):setup({
|
||||
skip_symbols = true,
|
||||
-- if {"yazi-"}: f -> file, yazi-file
|
||||
skip_prefix = {},
|
||||
-- start: f -> file
|
||||
-- word: f -> file, also-file
|
||||
-- all: f -> file, also-file, alsofile, elf
|
||||
search_location = "start",
|
||||
aliases = {},
|
||||
keys = {},
|
||||
})
|
||||
|
||||
-- setup keymaps
|
||||
require("fchar"):setup({
|
||||
keys = {
|
||||
start = "f", -- ff -> file
|
||||
ext = "e", -- ef -> name.fs
|
||||
word = "F", -- Ff -> file, also-file
|
||||
all = "<C-f>", -- ^ff -> file, also-file, twofile, elf
|
||||
},
|
||||
})
|
||||
|
||||
-- aliases for German
|
||||
@@ -319,14 +134,14 @@ require("fchar"):setup({
|
||||
},
|
||||
})
|
||||
|
||||
-- you may want to turn off the search regex from
|
||||
-- showing up in the header by doing this
|
||||
--[[ you may want to turn off the search regex from showing up
|
||||
in the header by putting this in `~/.config/yazi/init.lua` ]]
|
||||
function Header:flags()
|
||||
local cwd = self._current.cwd
|
||||
local filter = self._current.files.filter
|
||||
|
||||
local t = {}
|
||||
if cwd.is_search then
|
||||
if cwd.spec.is_search then
|
||||
t[#t + 1] = string.format("search: %s", cwd.domain)
|
||||
end
|
||||
if filter then
|
||||
@@ -334,25 +149,3 @@ function Header:flags()
|
||||
end
|
||||
return #t == 0 and "" or " (" .. table.concat(t, ", ") .. ")"
|
||||
end
|
||||
|
||||
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
|
||||
-- },
|
||||
},
|
||||
})
|
||||
@@ -1,163 +0,0 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json",
|
||||
"palette": {
|
||||
"gb_bright_aqua": "#8ec07c",
|
||||
"gb_bright_blue": "#6272a4",
|
||||
"gb_bright_green": "#b8bb26",
|
||||
"gb_bright_orange": "#fe8019",
|
||||
"gb_bright_purple": "#d3869b",
|
||||
"gb_bright_red": "#fb4934",
|
||||
"gb_bright_yellow": "#fabd2f",
|
||||
"gb_dark0": "#282828",
|
||||
"gb_dark0_hard": "#1d2021",
|
||||
"gb_dark0_soft": "#32302f",
|
||||
"gb_dark1": "#3c3836",
|
||||
"gb_dark2": "#504945",
|
||||
"gb_dark3": "#665c54",
|
||||
"gb_dark4": "#7c6f64",
|
||||
"gb_dark4_256": "#7c6f64",
|
||||
"gb_faded_aqua": "#427b58",
|
||||
"gb_faded_blue": "#076678",
|
||||
"gb_faded_green": "#79740e",
|
||||
"gb_faded_orange": "#af3a03",
|
||||
"gb_faded_purple": "#8f3f71",
|
||||
"gb_faded_red": "#9d0006",
|
||||
"gb_faded_yellow": "#b57614",
|
||||
"gb_gray_244": "#928374",
|
||||
"gb_gray_245": "#928374",
|
||||
"gb_light0": "#fbf1c7",
|
||||
"gb_light0_hard": "#f9f5d7",
|
||||
"gb_light0_soft": "#f2e5bc",
|
||||
"gb_light1": "#ebdbb2",
|
||||
"gb_light2": "#d5c4a1",
|
||||
"gb_light3": "#bdae93",
|
||||
"gb_light4": "#a89984",
|
||||
"gb_light4_256": "#a89984",
|
||||
"gb_neutral_aqua": "#689d6a",
|
||||
"gb_neutral_blue": "#458588",
|
||||
"gb_neutral_green": "#98971a",
|
||||
"gb_neutral_orange": "#d65d0e",
|
||||
"gb_neutral_purple": "#b16286",
|
||||
"gb_neutral_red": "#cc241d",
|
||||
"gb_neutral_yellow": "#d79921"
|
||||
},
|
||||
"var": {
|
||||
"Dir": "",
|
||||
"Documents": "📝",
|
||||
"Downloads": "📥",
|
||||
"Duration": "\u231b",
|
||||
"Exit": "\u21a9\ufe0f",
|
||||
"GitHub": "\ueba1",
|
||||
"Home": "🏠",
|
||||
"Host": " ",
|
||||
"LeftDelim": "\ue0b3",
|
||||
"LeftSep": "\ue0b2",
|
||||
"LeftSepRound": "\ue0b6",
|
||||
"Music": "🎵",
|
||||
"Pictures": "🖼\ufe0f",
|
||||
"RightDelim": "\ue0b1",
|
||||
"RightSep": "\ue0b0",
|
||||
"RightSepRound": "\ue0b4",
|
||||
"Tag": "🔖 ",
|
||||
"Time": "🕓",
|
||||
"root": "🔴",
|
||||
"ssh": "🌐",
|
||||
"user": ""
|
||||
},
|
||||
"pwd": "osc7",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "prompt",
|
||||
"alignment": "left",
|
||||
"segments": [
|
||||
{
|
||||
"properties": {
|
||||
"cache_duration": "none"
|
||||
},
|
||||
"leading_diamond": "\ue0b6",
|
||||
"trailing_diamond": "\ue0b0",
|
||||
"template": "{{if .Root}}{{.Var.root}}{{else}}{{.Var.user}}{{end}}{{.UserName}} {{.Var.Host}}{{ .HostName }}{{ if .SSHSession }} {{.Var.ssh}}{{ end }}",
|
||||
"foreground": "p:gb_dark0_hard",
|
||||
"background": "p:gb_bright_purple",
|
||||
"type": "session",
|
||||
"style": "diamond"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"cache_duration": "none",
|
||||
"folder_icon": "\ueaf7",
|
||||
"folder_separator_icon": " \ue0b1 ",
|
||||
"home_icon": "🏠",
|
||||
"mapped_locations": {},
|
||||
"max_depth": 2,
|
||||
"max_width": 6,
|
||||
"mixed_threshold": 6,
|
||||
"style": "mixed"
|
||||
},
|
||||
"leading_diamond": "<transparent,#427b58>\ue0b0</>",
|
||||
"trailing_diamond": "\ue0b0",
|
||||
"template": "<#f1fa8c,#427b58>{{.Var.Dir}}</><#427b58,p:gb_bright_aqua>{{.Var.RightSep}}</><transparent,p:gb_bright_aqua> {{ .Path}}</>",
|
||||
"foreground": "transparent",
|
||||
"background": "p:gb_bright_aqua",
|
||||
"type": "path",
|
||||
"style": "diamond"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"branch_max_length": 35,
|
||||
"cache_duration": "none",
|
||||
"fetch_stash_count": true,
|
||||
"fetch_status": true,
|
||||
"fetch_upstream_icon": true,
|
||||
"status_formats": {
|
||||
"Added": " %d",
|
||||
"Deleted": " %d",
|
||||
"Modified": " %d",
|
||||
"Untracked": " %d",
|
||||
"Unmerged": " %d"
|
||||
}
|
||||
},
|
||||
"leading_diamond": "<transparent,background>\ue0b0</>",
|
||||
"trailing_diamond": "\ue0b0",
|
||||
"template": " {{ .UpstreamIcon }} {{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }} -> {{.Upstream}}{{ end }}{{ if .Working.Changed }}<p:gb_faded_red> {{ .Working.String }}</>{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }}{{ end }}{{ if .Staging.Changed }} <p:gb_faded_green>{{ .Staging.String }}</>{{ end }}{{ if gt .StashCount 0 }} \uf51e {{ .StashCount }}{{ end }} ",
|
||||
"foreground": "#193549",
|
||||
"background": "#fffb38",
|
||||
"type": "git",
|
||||
"style": "diamond",
|
||||
"background_templates": [
|
||||
"{{ if or (.Working.Changed) (.Staging.Changed) }}#FF9248{{ end }}",
|
||||
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}#ff4500{{ end }}",
|
||||
"{{ if gt .Ahead 0 }}#B388FF{{ end }}",
|
||||
"{{ if gt .Behind 0 }}#B388FF{{ end }}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"cache_duration": "none",
|
||||
"fetch_version": true
|
||||
},
|
||||
"template": " \ue718 {{ if .PackageManagerIcon }}{{ .PackageManagerIcon }} {{ end }}{{ .Full }} ",
|
||||
"foreground": "#ffffff",
|
||||
"powerline_symbol": "\ue0b0",
|
||||
"background": "#6CA35E",
|
||||
"type": "node",
|
||||
"style": "powerline"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"cache_duration": "none",
|
||||
"display_mode": "files",
|
||||
"fetch_virtual_env": false
|
||||
},
|
||||
"template": " \ue235 {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} ",
|
||||
"foreground": "#111111",
|
||||
"powerline_symbol": "\ue0b0",
|
||||
"background": "#FFDE57",
|
||||
"type": "python",
|
||||
"style": "powerline"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"version": 3
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
"$schema" = "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json"
|
||||
pwd = "osc7"
|
||||
version = 3
|
||||
|
||||
[palette]
|
||||
gb_bright_path = "#7daea3"
|
||||
gb_bright_git = "#d8a657"
|
||||
gb_dark0_hard = "#3e7fb0"
|
||||
gb_faded_green = "#79740e"
|
||||
gb_faded_red = "#ea9a97"
|
||||
gb_text = "#d3869b"
|
||||
|
||||
[var]
|
||||
user = ""
|
||||
ssh = "🌐"
|
||||
|
||||
# LEFT SIDE PROMPT
|
||||
[[blocks]]
|
||||
type = "prompt"
|
||||
alignment = "left"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = '{{ .HostName }} '
|
||||
foreground = 'magenta'
|
||||
background = 'transparent'
|
||||
type = 'text'
|
||||
style = 'plain'
|
||||
|
||||
[[blocks.segments]]
|
||||
template = '{{ .UserName }} '
|
||||
foreground = 'magenta'
|
||||
background = 'transparent'
|
||||
type = 'text'
|
||||
style = 'plain'
|
||||
|
||||
# [blocks.segments.properties]
|
||||
# cache_duration = "none"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = "{{ .Path }} "
|
||||
foreground = "blue"
|
||||
background = "transparent"
|
||||
type = "path"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
style = "full"
|
||||
enable_hyperlink = true
|
||||
|
||||
[[blocks.segments]]
|
||||
template = ' {{ .HEAD }}{{ if or (.Working.Changed) (.Staging.Changed) }}*{{ end }} <cyan>{{ if gt .Behind 0 }}⇣{{ end }}{{ if gt .Ahead 0 }}⇡{{ end }}</>'
|
||||
foreground = "yellow"
|
||||
background = "transparent"
|
||||
type = "git"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
fetch_status = true
|
||||
|
||||
[blocks.segments.properties.status_formats]
|
||||
Added = "+%d"
|
||||
Deleted = "-%d"
|
||||
Modified = "~%d"
|
||||
Untracked = "?%d"
|
||||
|
||||
# RIGHT SIDE PROMPT
|
||||
[[blocks]]
|
||||
type = "rprompt"
|
||||
alignment = "right"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = " {{ .Full }}"
|
||||
foreground = "p:gb_faded_green"
|
||||
background = "transparent"
|
||||
type = "node"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
display_mode = "files"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = " {{ if .Venv }}({{ .Venv }}) {{ end }}{{ .Full }}"
|
||||
foreground = "p:gb_faded_green"
|
||||
background = "transparent"
|
||||
type = "python"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
display_mode = "always"
|
||||
fetch_virtual_env = true
|
||||
|
||||
# NEWLINE FOR INPUT
|
||||
[[blocks]]
|
||||
type = "prompt"
|
||||
alignment = "left"
|
||||
newline = true
|
||||
|
||||
[[blocks.segments]]
|
||||
template = "❯ "
|
||||
foreground = "p:gb_bright_aqua"
|
||||
background = "transparent"
|
||||
type = "text"
|
||||
style = "plain"
|
||||
@@ -1,157 +0,0 @@
|
||||
"$schema" = "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json"
|
||||
pwd = "osc7"
|
||||
version = 3
|
||||
|
||||
[palette]
|
||||
gb_bright_aqua = "#8ec07c"
|
||||
gb_bright_blue = "#83a598"
|
||||
gb_bright_green = "#b8bb26"
|
||||
gb_bright_orange = "#fe8019"
|
||||
gb_bright_purple = "#d3869b"
|
||||
gb_bright_red = "#fb4934"
|
||||
gb_bright_yellow = "#fabd2f"
|
||||
gb_dark0 = "#282828"
|
||||
gb_dark0_hard = "#1d2021"
|
||||
gb_dark0_soft = "#32302f"
|
||||
gb_dark1 = "#3c3836"
|
||||
gb_dark2 = "#504945"
|
||||
gb_dark3 = "#665c54"
|
||||
gb_dark4 = "#7c6f64"
|
||||
gb_dark4_256 = "#7c6f64"
|
||||
gb_faded_aqua = "#427b58"
|
||||
gb_faded_blue = "#076678"
|
||||
gb_faded_green = "#79740e"
|
||||
gb_faded_orange = "#af3a03"
|
||||
gb_faded_purple = "#8f3f71"
|
||||
gb_faded_red = "#9d0006"
|
||||
gb_faded_yellow = "#b57614"
|
||||
gb_gray_244 = "#928374"
|
||||
gb_gray_245 = "#928374"
|
||||
gb_light0 = "#fbf1c7"
|
||||
gb_light0_hard = "#f9f5d7"
|
||||
gb_light0_soft = "#f2e5bc"
|
||||
gb_light1 = "#ebdbb2"
|
||||
gb_light2 = "#d5c4a1"
|
||||
gb_light3 = "#bdae93"
|
||||
gb_light4 = "#a89984"
|
||||
gb_light4_256 = "#a89984"
|
||||
gb_neutral_aqua = "#689d6a"
|
||||
gb_neutral_blue = "#458588"
|
||||
gb_neutral_green = "#98971a"
|
||||
gb_neutral_orange = "#d65d0e"
|
||||
gb_neutral_purple = "#b16286"
|
||||
gb_neutral_red = "#cc241d"
|
||||
gb_neutral_yellow = "#d79921"
|
||||
|
||||
[var]
|
||||
Dir = "📂"
|
||||
Documents = "📝"
|
||||
Downloads = "📥"
|
||||
Duration = "⌛"
|
||||
Exit = "↩️"
|
||||
GitHub = ""
|
||||
Home = "🏠"
|
||||
Host = " "
|
||||
LeftDelim = ""
|
||||
LeftSep = ""
|
||||
LeftSepRound = ""
|
||||
Music = "🎵"
|
||||
Pictures = "🖼️"
|
||||
RightDelim = ""
|
||||
RightSep = ""
|
||||
RightSepRound = ""
|
||||
Tag = "🔖 "
|
||||
Time = "🕓"
|
||||
root = "🔴"
|
||||
ssh = "🌐"
|
||||
user = "👤"
|
||||
|
||||
[[blocks]]
|
||||
type = "prompt"
|
||||
alignment = "left"
|
||||
|
||||
[[blocks.segments]]
|
||||
leading_diamond = ""
|
||||
trailing_diamond = ""
|
||||
template = "{{if .Root}}{{.Var.root}}{{else}}{{.Var.user}}{{end}}{{.UserName}} {{.Var.Host}}{{ .HostName }}{{ if .SSHSession }} {{.Var.ssh}}{{ end }}"
|
||||
foreground = "p:gb_dark0_hard"
|
||||
background = "p:gb_bright_purple"
|
||||
type = "session"
|
||||
style = "diamond"
|
||||
|
||||
[blocks.segments.properties]
|
||||
cache_duration = "none"
|
||||
|
||||
[[blocks.segments]]
|
||||
leading_diamond = "<transparent,#427b58></>"
|
||||
trailing_diamond = ""
|
||||
template = "<#fabd2f,#427b58>{{.Var.Dir}}</><#427b58,p:gb_bright_aqua>{{.Var.RightSep}}</><transparent,p:gb_bright_aqua> {{ .Path}}</>"
|
||||
foreground = "transparent"
|
||||
background = "p:gb_bright_aqua"
|
||||
type = "path"
|
||||
style = "diamond"
|
||||
|
||||
[blocks.segments.properties]
|
||||
cache_duration = "none"
|
||||
folder_icon = ""
|
||||
folder_separator_icon = " "
|
||||
home_icon = "🏠"
|
||||
mapped_locations = {}
|
||||
max_depth = 2
|
||||
max_width = 6
|
||||
mixed_threshold = 6
|
||||
style = "mixed"
|
||||
|
||||
[[blocks.segments]]
|
||||
leading_diamond = "<transparent,background></>"
|
||||
trailing_diamond = ""
|
||||
template = " {{ .UpstreamIcon }} {{ .HEAD }}{{if .BranchStatus }} {{ .BranchStatus }} -> {{.Upstream}}{{ end }}{{ if .Working.Changed }}<p:gb_faded_red> {{ .Working.String }}</>{{ end }}{{ if and (.Working.Changed) (.Staging.Changed) }}{{ end }}{{ if .Staging.Changed }} <p:gb_faded_green>{{ .Staging.String }}</>{{ end }}{{ if gt .StashCount 0 }} {{ .StashCount }}{{ end }} "
|
||||
foreground = "#193549"
|
||||
background = "#fffb38"
|
||||
type = "git"
|
||||
style = "diamond"
|
||||
background_templates = [
|
||||
"{{ if or (.Working.Changed) (.Staging.Changed) }}#FF9248{{ end }}",
|
||||
"{{ if and (gt .Ahead 0) (gt .Behind 0) }}#ff4500{{ end }}",
|
||||
"{{ if gt .Ahead 0 }}#B388FF{{ end }}",
|
||||
"{{ if gt .Behind 0 }}#B388FF{{ end }}",
|
||||
]
|
||||
|
||||
[blocks.segments.properties]
|
||||
branch_max_length = 35
|
||||
cache_duration = "none"
|
||||
fetch_stash_count = true
|
||||
fetch_status = true
|
||||
fetch_upstream_icon = true
|
||||
|
||||
[blocks.segments.properties.status_formats]
|
||||
Added = " %d"
|
||||
Deleted = " %d"
|
||||
Modified = " %d"
|
||||
Untracked = " %d"
|
||||
Unmerged = " %d"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = " {{ if .PackageManagerIcon }}{{ .PackageManagerIcon }} {{ end }}{{ .Full }} "
|
||||
foreground = "#ffffff"
|
||||
powerline_symbol = ""
|
||||
background = "#6CA35E"
|
||||
type = "node"
|
||||
style = "powerline"
|
||||
|
||||
[blocks.segments.properties]
|
||||
cache_duration = "none"
|
||||
fetch_version = true
|
||||
|
||||
[[blocks.segments]]
|
||||
template = " {{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }} "
|
||||
foreground = "#111111"
|
||||
powerline_symbol = ""
|
||||
background = "#FFDE57"
|
||||
type = "python"
|
||||
style = "powerline"
|
||||
|
||||
[blocks.segments.properties]
|
||||
cache_duration = "none"
|
||||
display_mode = "files"
|
||||
fetch_virtual_env = false
|
||||
@@ -1,103 +0,0 @@
|
||||
"$schema" = "https://raw.githubusercontent.com/JanDeDobbeleer/oh-my-posh/main/themes/schema.json"
|
||||
pwd = "osc7"
|
||||
version = 3
|
||||
|
||||
[palette]
|
||||
gb_bright_path = "#9ccdf8"
|
||||
gb_bright_git = "#f6c177"
|
||||
gb_dark0_hard = "#3e7fb0"
|
||||
gb_faded_green = "#79740e"
|
||||
gb_faded_red = "#ea9a97"
|
||||
gb_text = "#c4a7e7"
|
||||
|
||||
[var]
|
||||
user = ""
|
||||
ssh = "🌐"
|
||||
|
||||
# LEFT SIDE PROMPT
|
||||
[[blocks]]
|
||||
type = "prompt"
|
||||
alignment = "left"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = '{{ .HostName }} '
|
||||
foreground = 'p:gb_text'
|
||||
background = 'transparent'
|
||||
type = 'text'
|
||||
style = 'plain'
|
||||
|
||||
[[blocks.segments]]
|
||||
template = '{{ .UserName }} '
|
||||
foreground = 'p:gb_text'
|
||||
background = 'transparent'
|
||||
type = 'text'
|
||||
style = 'plain'
|
||||
|
||||
# [blocks.segments.properties]
|
||||
# cache_duration = "none"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = "{{ .Path }} "
|
||||
foreground = "p:gb_bright_path"
|
||||
background = "transparent"
|
||||
type = "path"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
style = "full"
|
||||
enable_hyperlink = true
|
||||
|
||||
[[blocks.segments]]
|
||||
template = ' {{ .HEAD }}{{ if or (.Working.Changed) (.Staging.Changed) }}*{{ end }} <cyan>{{ if gt .Behind 0 }}⇣{{ end }}{{ if gt .Ahead 0 }}⇡{{ end }}</>'
|
||||
foreground = "p:gb_bright_git"
|
||||
background = "transparent"
|
||||
type = "git"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
fetch_status = true
|
||||
|
||||
[blocks.segments.properties.status_formats]
|
||||
Added = "+%d"
|
||||
Deleted = "-%d"
|
||||
Modified = "~%d"
|
||||
Untracked = "?%d"
|
||||
|
||||
# RIGHT SIDE PROMPT
|
||||
[[blocks]]
|
||||
type = "rprompt"
|
||||
alignment = "right"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = " {{ .Full }}"
|
||||
foreground = "p:gb_faded_green"
|
||||
background = "transparent"
|
||||
type = "node"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
display_mode = "files"
|
||||
|
||||
[[blocks.segments]]
|
||||
template = " {{ if .Venv }}({{ .Venv }}) {{ end }}{{ .Full }}"
|
||||
foreground = "p:gb_faded_green"
|
||||
background = "transparent"
|
||||
type = "python"
|
||||
style = "plain"
|
||||
|
||||
[blocks.segments.properties]
|
||||
display_mode = "always"
|
||||
fetch_virtual_env = true
|
||||
|
||||
# NEWLINE FOR INPUT
|
||||
[[blocks]]
|
||||
type = "prompt"
|
||||
alignment = "left"
|
||||
newline = true
|
||||
|
||||
[[blocks.segments]]
|
||||
template = "❯ "
|
||||
foreground = "p:gb_bright_aqua"
|
||||
background = "transparent"
|
||||
type = "text"
|
||||
style = "plain"
|
||||
@@ -1,174 +1,42 @@
|
||||
[[plugin.deps]]
|
||||
use = "AnirudhG07/rich-preview"
|
||||
rev = "02597c4"
|
||||
hash = "33982a6e2d6df07619a2a0fc163a0950"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:smart-enter"
|
||||
rev = "4e55902"
|
||||
hash = "56fdabc96fc1f4d53c96eb884b02a5be"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:git"
|
||||
rev = "4e55902"
|
||||
hash = "36a484acf6a0a0219c543ccb4cee218f"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:mount"
|
||||
rev = "4e55902"
|
||||
hash = "e2d4bfccf31e5ab05f46fee4797c5215"
|
||||
rev = "c591a36"
|
||||
hash = "187cc58ba7ac3befd49c342129e6f1b6"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:mime-ext"
|
||||
rev = "4e55902"
|
||||
hash = "48a77ab785d1ee73bc2d7bbe2a4c2090"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "ndtoan96/ouch"
|
||||
rev = "cfb9140"
|
||||
hash = "b5067143415bd2d46c0dfa57319ddcef"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:piper"
|
||||
rev = "4e55902"
|
||||
hash = "2dd68af59418b631b9703a03f606b31f"
|
||||
rev = "c591a36"
|
||||
hash = "3bcc00df11efc3ae5d15f2dba860be7"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "boydaihungst/mediainfo"
|
||||
rev = "90a1652"
|
||||
hash = "7a83bfab51d1d2ba6558cc56bd52539a"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "AnirudhG07/rich-preview"
|
||||
rev = "573b275"
|
||||
hash = "c3e2871c9ef244fd181f203791f9b0d2"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "stelcodes/bunny"
|
||||
rev = "7137a44"
|
||||
hash = "58bfa473bcac8574400e573787954512"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "Lil-Dank/lazygit"
|
||||
rev = "0e56060"
|
||||
hash = "ab2e66065a94cca4886b303518e44bd8"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "MasouShizuka/projects"
|
||||
rev = "eed0657"
|
||||
hash = "ca79de2b3bb2247906e01b2261e5d2bb"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "XYenon/yafg"
|
||||
rev = "6034c67"
|
||||
hash = "11543279fd83ad4cacdfb07d67016928"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "Shallow-Seek/fazif"
|
||||
rev = "6e10488"
|
||||
hash = "c549ff30fb1d1d97c734010fe82f6295"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "GianniBYoung/rsync"
|
||||
rev = "14d2828"
|
||||
hash = "7394430ba473dce1023721622438bf3d"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "llanosrocas/yaziline"
|
||||
rev = "6266926"
|
||||
hash = "9917ab5cb9bdbab7ca7f2501f84f0f11"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "boydaihungst/file-extra-metadata"
|
||||
rev = "4fbb106"
|
||||
hash = "8765a20c837f4be5718f89ab1cf33b4"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:full-border"
|
||||
rev = "68f7d48"
|
||||
hash = "3996fc74044bc44144b323686f887e1"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "pirafrank/what-size"
|
||||
rev = "179ebf6"
|
||||
hash = "57056b9728006881d580ccabe8154a9c"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:toggle-pane"
|
||||
rev = "57f1863"
|
||||
hash = "8a7c58225816a163a6e8730c0adafbc8"
|
||||
rev = "73a3658"
|
||||
hash = "fa787a8ef785f2976ea2861f62b8c6c4"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "AminurAlam/yazi-plugins:fchar"
|
||||
rev = "658a099"
|
||||
hash = "b484ff674a30268991b67f819f4625da"
|
||||
rev = "8c2ce96"
|
||||
hash = "9f2598542a3263b5a03d7fb832b407c1"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "mshnwq/dupes"
|
||||
rev = "18a9395"
|
||||
hash = "730ddf5e1179f34ccb81f8d5ed564823"
|
||||
use = "ahkohd/eza-preview"
|
||||
rev = "e8fb6c8"
|
||||
hash = "c238595c801caa4f28553b9af8cf8804"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "uhs-robert/recycle-bin"
|
||||
rev = "69d7d4c"
|
||||
hash = "38dac2fc8f4b50bbdaa46e699720a54a"
|
||||
use = "WhoSowSee/whoosh"
|
||||
rev = "43f3202"
|
||||
hash = "da14dfe482886e756150ae4f5c2bb23"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "Rolv-Apneseth/bypass"
|
||||
rev = "c1e5fcf"
|
||||
hash = "208e77480824f62558a5b037c8569d61"
|
||||
use = "yazi-rs/plugins:chmod"
|
||||
rev = "c591a36"
|
||||
hash = "87472b05a8c420100f6a2b9cde1e7746"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "Ape/open-with-cmd"
|
||||
rev = "e3d430f"
|
||||
hash = "d29c1ffa99099181faf8c01d1f59aa4c"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:smart-paste"
|
||||
rev = "56971d0"
|
||||
hash = "75fd60b2df7115e0a6ca31a6a804d320"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "larry-oates/open-git-remote"
|
||||
rev = "95b3043"
|
||||
hash = "8d17dcc063217b558a08ed8bb5e9e8ad"
|
||||
|
||||
[[plugin.deps]]
|
||||
use = "yazi-rs/plugins:vcs-files"
|
||||
rev = "56971d0"
|
||||
hash = "5cfd9b2bd3f8ec4e3245e3426a967f82"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "yazi-rs/flavors:catppuccin-mocha"
|
||||
rev = "4a1802a"
|
||||
hash = "b951d27afe197d154f6da9683b46c5f8"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "Mintass/rose-pine-moon"
|
||||
rev = "94385fe"
|
||||
hash = "edbe0dfb5db8ff37281dba62adc7e750"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "Chromium-3-Oxide/everforest-medium"
|
||||
rev = "0158f0f"
|
||||
hash = "a7c36abf0b13289aceb1c20a17c6a63e"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "bennyyip/gruvbox-dark"
|
||||
rev = "619fdc5"
|
||||
hash = "d4c7003ec6dc4efd5d3a050d5c2f2575"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "BennyOe/tokyo-night"
|
||||
rev = "8e6296f"
|
||||
hash = "d9da921c3bd37ba32ee27a8a7a40461f"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "dangooddd/kanagawa"
|
||||
rev = "04985d1"
|
||||
hash = "c69a63981e254a3a06a11c2bf0e6af1d"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "matt-dong-123/gruvbox-material"
|
||||
rev = "6c36491"
|
||||
hash = "564f2335540ee2c440c3b671bc28ac60"
|
||||
|
||||
[[flavor.deps]]
|
||||
use = "malick-tammal/monokai"
|
||||
rev = "e59f6cb"
|
||||
hash = "dcb449d46e30dc020e4b7b59202d2448"
|
||||
[flavor]
|
||||
deps = []
|
||||
@@ -1,9 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2024 Stel Clementine
|
||||
|
||||
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.
|
||||
@@ -1,102 +0,0 @@
|
||||
# bunny.yazi 🐰
|
||||
|
||||
*🩷 Hop around your filesystem 🩷*
|
||||
|
||||
This is a bookmark plugin for [yazi](https://github.com/sxyazi/yazi) which augments the builtin bookmarking abilities into a single menu of `cd` powers designed with user experience (and cuteness) in mind. Bookmarks are referred to as *hops* because users should always feel adorable while using their terminal.
|
||||
|
||||
## Features
|
||||
|
||||
- Create persistent hops in your `init.lua` config file
|
||||
- Create ephemeral hops while using yazi
|
||||
- Hop to any directory open in another tab
|
||||
- Hop back to previous directory (history is associated with tab number)
|
||||
- Hop by fuzzy searching available hops with [fzf](https://github.com/junegunn/fzf) or similar program
|
||||
- Single menu for all functionality, therefore only one keymap is required in your `keymap.toml` file
|
||||
- Hands off: no reads or writes to your filesystem, all state is kept in memory
|
||||
|
||||
<img src="https://i.imgur.com/9OKQJUT.png" alt="bunny.yazi menu open in a terminal"/>
|
||||
|
||||
## Installation
|
||||
|
||||
### With `git`
|
||||
|
||||
```sh
|
||||
git clone https://github.com/stelcodes/bunny.yazi ~/.config/yazi/plugins/bunny.yazi
|
||||
```
|
||||
|
||||
### With `yapack`
|
||||
|
||||
```sh
|
||||
ya pack -a stelcodes/bunny
|
||||
```
|
||||
|
||||
### With Nix (Home Manager + flakes)
|
||||
|
||||
`flake.nix`:
|
||||
```nix
|
||||
inputs = {
|
||||
bunny-yazi = {
|
||||
url = "github:stelcodes/bunny.yazi";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
Home Manager config:
|
||||
```nix
|
||||
programs.yazi = {
|
||||
plugins.bunny = "${inputs.bunny-yazi}";
|
||||
initLua = ''
|
||||
require("bunny"):setup({ ... })
|
||||
'';
|
||||
keymap.mgr.prepend_keymap = [
|
||||
{ on = ";"; run = "plugin bunny"; desc = "Start bunny.yazi"; }
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
## Configuration
|
||||
`~/.config/yazi/init.lua`:
|
||||
```lua
|
||||
require("bunny"):setup({
|
||||
hops = {
|
||||
{ key = "/", path = "/", },
|
||||
{ key = "t", path = "/tmp", },
|
||||
{ key = "n", path = "/nix/store", desc = "Nix store" },
|
||||
{ key = "~", path = "~", desc = "Home" },
|
||||
{ key = "m", path = "~/Music", desc = "Music" },
|
||||
{ key = "d", path = "~/Desktop", desc = "Desktop" },
|
||||
{ key = "D", path = "~/Documents", desc = "Documents" },
|
||||
{ key = "c", path = "~/.config", desc = "Config files" },
|
||||
{ key = { "l", "s" }, path = "~/.local/share", desc = "Local share" },
|
||||
{ key = { "l", "b" }, path = "~/.local/bin", desc = "Local bin" },
|
||||
{ key = { "l", "t" }, path = "~/.local/state", desc = "Local state" },
|
||||
-- key and path attributes are required, desc is optional
|
||||
},
|
||||
desc_strategy = "path", -- If desc isn't present, use "path" or "filename", default is "path"
|
||||
ephemeral = true, -- Enable ephemeral hops, default is true
|
||||
tabs = true, -- Enable tab hops, default is true
|
||||
notify = false, -- Notify after hopping, default is false
|
||||
fuzzy_cmd = "fzf", -- Fuzzy searching command, default is "fzf"
|
||||
})
|
||||
```
|
||||
|
||||
`~/.config/yazi/keymap.toml`:
|
||||
```toml
|
||||
[[mgr.prepend_keymap]]
|
||||
desc = "Start bunny.yazi"
|
||||
on = ";"
|
||||
run = "plugin bunny"
|
||||
|
||||
# Optionally, add another keymap to immediately fuzzy search bookmarks
|
||||
[[mgr.prepend_keymap]]
|
||||
desc = "Start bunny.yazi fuzzy search"
|
||||
on = "'"
|
||||
run = "plugin bunny fuzzy"
|
||||
```
|
||||
|
||||
## Inspiration
|
||||
|
||||
[yamb.yazi](https://github.com/h-hg/yamb.yazi)
|
||||
|
||||
[nnn bookmarks](https://github.com/jarun/nnn/wiki/Basic-use-cases#add-bookmarks)
|
||||
@@ -1,385 +0,0 @@
|
||||
--- @since 25.5.28
|
||||
local function debug(...)
|
||||
local function toReadableString(val)
|
||||
if type(val) == "table" then
|
||||
local str = "{ "
|
||||
for k, v in pairs(val) do
|
||||
str = str .. "[" .. tostring(k) .. "] = " .. toReadableString(v) .. ", "
|
||||
end
|
||||
return str .. "}"
|
||||
elseif type(val) == "Url" then
|
||||
return "Url:" .. tostring(val)
|
||||
else
|
||||
return tostring(val)
|
||||
end
|
||||
end
|
||||
local args = { ... }
|
||||
local processed_args = {}
|
||||
for _, arg in pairs(args) do
|
||||
table.insert(processed_args, toReadableString(arg))
|
||||
end
|
||||
ya.dbg("BUNNY.YAZI", table.unpack(processed_args))
|
||||
end
|
||||
local function fail(s, ...) ya.notify { title = "bunny.yazi", content = string.format(s, ...), timeout = 4, level = "error" } end
|
||||
local function info(s, ...) ya.notify { title = "bunny.yazi", content = string.format(s, ...), timeout = 2, level = "info" } end
|
||||
|
||||
local get_state = ya.sync(function(state, attr)
|
||||
return state[attr]
|
||||
end)
|
||||
|
||||
local set_state = ya.sync(function(state, attr, value)
|
||||
state[attr] = value
|
||||
end)
|
||||
|
||||
local get_cwd = ya.sync(function(state)
|
||||
return tostring(cx.active.current.cwd) -- Url objects are evil >.<"
|
||||
end)
|
||||
|
||||
local get_current_tab_idx = ya.sync(function(state)
|
||||
return cx.tabs.idx
|
||||
end)
|
||||
|
||||
local get_tabs_as_paths = ya.sync(function(state)
|
||||
local tabs = cx.tabs
|
||||
local active_tab_idx = tabs.idx
|
||||
local result = {}
|
||||
for idx = 1, #tabs, 1 do
|
||||
if idx ~= active_tab_idx and tabs[idx] then
|
||||
result[idx] = tostring(tabs[idx].current.cwd)
|
||||
end
|
||||
end
|
||||
return result
|
||||
end)
|
||||
|
||||
local some = function(...)
|
||||
for _, v in ipairs({ ... }) do
|
||||
if v ~= nil then
|
||||
return v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function filename(pathstr)
|
||||
if pathstr == "/" then return pathstr end
|
||||
local url_name = Url(pathstr):name()
|
||||
if url_name then
|
||||
return tostring(url_name)
|
||||
else
|
||||
return pathstr
|
||||
end
|
||||
end
|
||||
|
||||
local function path_to_desc(path, strategy)
|
||||
if strategy == "filename" then
|
||||
return filename(path)
|
||||
end
|
||||
local home = os.getenv("HOME")
|
||||
if home and home ~= "" then
|
||||
local startPos, endPos = string.find(path, home)
|
||||
-- Only substitute if the match is from the start of path, very important
|
||||
if startPos == 1 then
|
||||
return "~" .. path:sub(endPos + 1)
|
||||
end
|
||||
end
|
||||
return tostring(path)
|
||||
end
|
||||
|
||||
local function sort_hops(hops)
|
||||
local function convert_key(key)
|
||||
local t = type(key)
|
||||
if t == "table" then
|
||||
-- If table, sort by first key string
|
||||
return key[1]
|
||||
elseif t == "string" and string.lower(key) ~= key then
|
||||
-- If uppercase letter (signifying an ephemeral hop), prepend "z"
|
||||
-- so it gets sorted after lowercase persistent hops
|
||||
return "z" .. key
|
||||
end
|
||||
return key
|
||||
end
|
||||
table.sort(hops, function(x, y)
|
||||
return convert_key(x.key) < convert_key(y.key)
|
||||
end)
|
||||
return hops
|
||||
end
|
||||
|
||||
local create_special_hops = function(config)
|
||||
local hops = {}
|
||||
local desc_strategy, tabs, ephemeral = config.desc_strategy, config.tabs, config.ephemeral
|
||||
if ephemeral then
|
||||
table.insert(hops, { key = "<Enter>", desc = "Create hop", path = "__MARK__" })
|
||||
end
|
||||
table.insert(hops, { key = "<Space>", desc = "Fuzzy search", path = "__FUZZY__" })
|
||||
local tabhist = get_state("tabhist")
|
||||
local tab = get_current_tab_idx()
|
||||
if tabhist[tab] and tabhist[tab][2] then
|
||||
local previous_dir = tabhist[tab][2]
|
||||
table.insert(hops, {
|
||||
key = "<Backspace>",
|
||||
path = previous_dir,
|
||||
desc = path_to_desc(previous_dir, desc_strategy)
|
||||
})
|
||||
end
|
||||
if tabs then
|
||||
for idx, tab_path in pairs(get_tabs_as_paths()) do
|
||||
table.insert(hops, {
|
||||
key = tostring(idx),
|
||||
path = tab_path,
|
||||
desc = path_to_desc(tab_path, desc_strategy)
|
||||
})
|
||||
end
|
||||
end
|
||||
return hops
|
||||
end
|
||||
|
||||
local function validate_options(options)
|
||||
local function validate_key(key)
|
||||
local kt = type(key)
|
||||
if not key then
|
||||
return false, "key is missing"
|
||||
elseif kt == "string" then
|
||||
if utf8.len(key) ~= 1 then
|
||||
return false, "key must be single character"
|
||||
end
|
||||
elseif kt == "table" then
|
||||
if #key == 0 then
|
||||
return false, "key cannot be empty table"
|
||||
end
|
||||
for _, char in pairs(key) do
|
||||
if utf8.len(char) ~= 1 then
|
||||
return false, "key list must contain single characters"
|
||||
end
|
||||
end
|
||||
else
|
||||
return false, "key must be string or table"
|
||||
end
|
||||
return true, ""
|
||||
end
|
||||
if type(options) ~= "table" then
|
||||
return "Invalid config"
|
||||
end
|
||||
local hops, desc_strategy, tabs, ephemeral, fuzzy_cmd, notify =
|
||||
options.hops, options.desc_strategy, options.tabs,
|
||||
options.ephemeral, options.fuzzy_cmd, options.notify
|
||||
-- Validate hops
|
||||
if type(hops) == "table" then
|
||||
local used_keys = {}
|
||||
for idx, hop in pairs(hops) do
|
||||
hop.desc = hop.desc or hop.tag or nil -- used to be tag, allow for backwards compat
|
||||
local key_is_validated, key_err = validate_key(hop.key)
|
||||
local err = 'Invalid "hops" config value: #' .. idx .. " "
|
||||
if not key_is_validated then
|
||||
return err .. key_err
|
||||
elseif not hop.path then
|
||||
return err .. 'path is missing'
|
||||
elseif type(hop.path) ~= "string" or string.len(hop.path) == 0 then
|
||||
return err .. 'path must be non-empty string'
|
||||
elseif hop.desc and (type(hop.desc) ~= "string" or string.len(hop.path) == 0) then
|
||||
return err .. 'desc must be non-empty string'
|
||||
end
|
||||
-- Check for duplicate keys
|
||||
if used_keys[hop.key] then
|
||||
return err .. 'needs unique key'
|
||||
end
|
||||
-- Insert hop keys as table keys to easily check for set membership
|
||||
used_keys[hop.key] = true
|
||||
end
|
||||
else
|
||||
return 'Invalid "hops" config value'
|
||||
end
|
||||
-- Validate other options
|
||||
if desc_strategy ~= nil and desc_strategy ~= "path" and desc_strategy ~= "filename" then
|
||||
return 'Invalid "desc_strategy" config value'
|
||||
elseif tabs ~= nil and type(notify) ~= "boolean" then
|
||||
return 'Invalid "tabs" config value'
|
||||
elseif ephemeral ~= nil and type(notify) ~= "boolean" then
|
||||
return 'Invalid "ephemeral" config value'
|
||||
elseif fuzzy_cmd ~= nil and type(fuzzy_cmd) ~= "string" then
|
||||
return 'Invalid "fuzzy_cmd" config value'
|
||||
elseif notify ~= nil and type(notify) ~= "boolean" then
|
||||
return 'Invalid "notify" config value'
|
||||
end
|
||||
end
|
||||
|
||||
-- https://github.com/sxyazi/yazi/blob/main/yazi-plugin/preset/plugins/fzf.lua
|
||||
-- https://github.com/sxyazi/yazi/blob/main/yazi-plugin/src/process/child.rs
|
||||
-- Returns nil if fzf command failed or user exited with escape key
|
||||
local select_fuzzy = function(hops, config)
|
||||
local permit = ui.hide()
|
||||
local child, spawn_err =
|
||||
Command(config.fuzzy_cmd):stdin(Command.PIPED):stdout(Command.PIPED):stderr(Command.INHERIT):spawn()
|
||||
if not child then
|
||||
fail("Command `%s` failed with code %s. Do you have it installed?", config.fuzzy_cmd, spawn_err.code)
|
||||
return
|
||||
end
|
||||
local fuzzy_entries = {}
|
||||
for _, hop in pairs(hops) do
|
||||
local existing_desc = fuzzy_entries[hop.path]
|
||||
if not existing_desc or existing_desc == "" then
|
||||
local fuzzy_desc = hop.desc
|
||||
-- Avoid repeating path in description
|
||||
if fuzzy_desc == hop.path then
|
||||
fuzzy_desc = ""
|
||||
end
|
||||
fuzzy_entries[hop.path] = fuzzy_desc
|
||||
end
|
||||
end
|
||||
-- Build fzf input string
|
||||
local input_lines = {}
|
||||
for entry_path, entry_desc in pairs(fuzzy_entries) do
|
||||
local line = entry_desc .. string.rep(" ", 23 - #entry_desc) .. "\t" .. entry_path
|
||||
table.insert(input_lines, line)
|
||||
end
|
||||
child:write_all(table.concat(input_lines, "\n"))
|
||||
child:flush()
|
||||
local output, output_err = child:wait_with_output()
|
||||
permit:drop()
|
||||
if not output.status.success then
|
||||
if output.status.code ~= 130 then -- user pressed escape to quit
|
||||
fail("Command `%s` failed with code %s", config.fuzzy_cmd, output_err.code)
|
||||
end
|
||||
return
|
||||
end
|
||||
-- Parse fzf output, remove right padded spaces from desc
|
||||
local desc, path = string.match(output.stdout, "^(.-) *\t(.-)\n$")
|
||||
if not desc or not path or path == "" then
|
||||
fail("Failed to parse fuzzy searcher result")
|
||||
return
|
||||
end
|
||||
if desc == "" then
|
||||
desc = path_to_desc(path, config.desc_strategy)
|
||||
end
|
||||
return { desc = desc, path = path }
|
||||
end
|
||||
|
||||
local cd = function(selected_hop, config)
|
||||
local _, dir_list_err = fs.read_dir(Url(selected_hop.path), { limit = 1, resolve = true })
|
||||
if dir_list_err then
|
||||
fail("Invalid directory " .. path_to_desc(selected_hop.path))
|
||||
return
|
||||
end
|
||||
-- Assuming that if I can fs.read_dir, then this will also succeed
|
||||
ya.emit("cd", { selected_hop.path })
|
||||
if config.notify then
|
||||
info('Hopped to ' .. selected_hop.desc)
|
||||
end
|
||||
end
|
||||
|
||||
local attempt_hop = function(hops, config)
|
||||
local cands = {}
|
||||
for _, hop in pairs(create_special_hops(config)) do
|
||||
table.insert(cands, { desc = hop.desc, on = hop.key, path = hop.path })
|
||||
end
|
||||
for _, hop in pairs(hops) do
|
||||
table.insert(cands, { desc = hop.desc, on = hop.key, path = hop.path })
|
||||
end
|
||||
local hops_idx = ya.which { cands = cands }
|
||||
if not hops_idx then return end
|
||||
local selected_hop = cands[hops_idx]
|
||||
-- Handle special hops
|
||||
if selected_hop.path == "__MARK__" then
|
||||
local mark_cands = {};
|
||||
-- Ideally any unicode character would be supported, but yazi.which
|
||||
-- requies a concrete list of candidates
|
||||
local candidate_chars = {
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
|
||||
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
|
||||
'W', 'X', 'Y', 'Z', '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
|
||||
'-', '_', '=', '+', '[', '{', ']', '}', '\\', '|', ';', ':', "'", '"', ',', '<',
|
||||
'.', '>', '/', '?', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
|
||||
}
|
||||
for _, char in pairs(candidate_chars) do
|
||||
table.insert(mark_cands, { on = char })
|
||||
end
|
||||
info("Press a key to create new hop")
|
||||
local char_idx = ya.which { cands = mark_cands, silent = true }
|
||||
if char_idx ~= nil then
|
||||
local selected_char = mark_cands[char_idx].on
|
||||
local cwd = get_cwd()
|
||||
table.insert(hops, { key = selected_char, path = cwd, desc = path_to_desc(cwd, config.desc_strategy) })
|
||||
set_state("hops", sort_hops(hops))
|
||||
end
|
||||
return
|
||||
elseif selected_hop.path == "__FUZZY__" then
|
||||
local fuzzy_hop = select_fuzzy(hops, config)
|
||||
if not fuzzy_hop then return end
|
||||
selected_hop = fuzzy_hop
|
||||
end
|
||||
cd(selected_hop, config)
|
||||
end
|
||||
|
||||
local function init()
|
||||
local options = get_state("options")
|
||||
local err = validate_options(options)
|
||||
if err then
|
||||
set_state("init_error", err)
|
||||
fail(err)
|
||||
return
|
||||
end
|
||||
-- Set default config values
|
||||
local desc_strategy = options.desc_strategy or "path"
|
||||
set_state("config", {
|
||||
desc_strategy = desc_strategy,
|
||||
fuzzy_cmd = some(options.fuzzy_cmd, "fzf"),
|
||||
notify = some(options.notify, false),
|
||||
ephemeral = some(options.ephemeral, true),
|
||||
tabs = some(options.tabs, true),
|
||||
})
|
||||
-- Set hops after ensuring they all have a description
|
||||
local hops = {}
|
||||
for _, hop in pairs(options.hops) do
|
||||
hop.desc = hop.desc or path_to_desc(hop.path, desc_strategy)
|
||||
-- Manually replace ~ from users so we can make a valid Url later to check if dir exists
|
||||
if string.sub(hop.path, 1, 1) == "~" then
|
||||
hop.path = os.getenv("HOME") .. hop.path:sub(2)
|
||||
end
|
||||
table.insert(hops, hop)
|
||||
end
|
||||
set_state("hops", sort_hops(hops))
|
||||
set_state("init", true)
|
||||
end
|
||||
|
||||
return {
|
||||
setup = function(state, options)
|
||||
state.options = options
|
||||
ps.sub("cd", function(body)
|
||||
-- Note: This callback is sync and triggered at startup!
|
||||
local tab = body.tab -- type number
|
||||
-- Very important to turn this into a string because Url has ownership issues
|
||||
-- when passed to standard utility functions >.<'
|
||||
-- https://github.com/sxyazi/yazi/issues/2159
|
||||
local cwd = tostring(cx.active.current.cwd)
|
||||
-- Upon startup this will be nil so initialize if necessary
|
||||
local tabhist = state.tabhist or {}
|
||||
-- tabhist structure:{ <tab_index> = { <current_dir>, <previous_dir?> }, ... }
|
||||
if not tabhist[tab] then
|
||||
-- If fresh tab, initialize tab history table
|
||||
tabhist[tab] = { cwd }
|
||||
else
|
||||
-- Otherwise, shift history table to the right and add cwd to the front
|
||||
tabhist[tab] = { cwd, tabhist[tab][1] }
|
||||
end
|
||||
state.tabhist = tabhist
|
||||
end)
|
||||
end,
|
||||
entry = function(self, job)
|
||||
if not get_state("init") then
|
||||
init()
|
||||
end
|
||||
local init_error = get_state("init_error")
|
||||
if init_error then
|
||||
fail(init_error)
|
||||
return
|
||||
end
|
||||
local hops, config = get_state("hops"), get_state("config")
|
||||
if job.args[1] == "fuzzy" then
|
||||
local fuzzy_hop = select_fuzzy(hops, config)
|
||||
if fuzzy_hop then
|
||||
cd(fuzzy_hop, config)
|
||||
end
|
||||
else
|
||||
attempt_hop(hops, config)
|
||||
end
|
||||
end,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Rolv Apneseth
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,50 +0,0 @@
|
||||
# bypass.yazi
|
||||
|
||||
[Yazi](https://github.com/sxyazi/yazi) plugin for bidirectional skipping of directories which only have a single subdirectory
|
||||
|
||||
<https://github.com/Rolv-Apneseth/bypass.yazi/assets/69486699/5487537b-fe01-40e9-bc34-4d1116b491b5>
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Yazi](https://github.com/sxyazi/yazi) v25.3.2+
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
ya pkg add Rolv-Apneseth/bypass
|
||||
```
|
||||
|
||||
### Manual
|
||||
|
||||
```sh
|
||||
# Linux / MacOS
|
||||
git clone https://github.com/Rolv-Apneseth/bypass.yazi.git ~/.config/yazi/plugins/bypass.yazi
|
||||
# Windows
|
||||
git clone https://github.com/Rolv-Apneseth/bypass.yazi.git %AppData%\yazi\config\plugins\bypass.yazi
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `keymap.toml`:
|
||||
|
||||
```toml
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "L" ]
|
||||
run = "plugin bypass"
|
||||
desc = "Recursively enter child directory, skipping children with only a single subdirectory"
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "H" ]
|
||||
run = "plugin bypass reverse"
|
||||
desc = "Recursively enter parent directory, skipping parents with only a single subdirectory"
|
||||
```
|
||||
|
||||
And that's it. You can bind any key you like, including overriding the default `enter` and `leave` bindings by setting them to `l` and `h` respectively.
|
||||
|
||||
Note that if you're using the [smart enter tip](https://yazi-rs.github.io/docs/tips#smart-enter) from the documentation, this plugin can replace that entirely by using this keybind instead:
|
||||
|
||||
```toml
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "l" ]
|
||||
run = "plugin bypass smart-enter"
|
||||
desc = "Open a file, or recursively enter child directory, skipping children with only a single subdirectory"
|
||||
```
|
||||
@@ -1,106 +0,0 @@
|
||||
---@since 25.3.2
|
||||
|
||||
-- For development
|
||||
--[[ local function notify(message) ]]
|
||||
--[[ ya.notify({ title = "Bypass", content = message, timeout = 5 }) ]]
|
||||
--[[ end ]]
|
||||
|
||||
-- `ya.emit` as of 25.5.28
|
||||
local emit = ya.emit or ya.manager_emit
|
||||
|
||||
---Returns the loading state of the current directory
|
||||
---@type fun(): boolean
|
||||
local is_directory_loading = ya.sync(function(_)
|
||||
-- Try to use the v25.4.8 interface
|
||||
local ok, res = pcall(cx.active.current.stage)
|
||||
if ok then
|
||||
return not res
|
||||
-- Fallback to the v25.3.2 interface
|
||||
else
|
||||
return cx.active.current.stage.is_loading
|
||||
end
|
||||
end)
|
||||
|
||||
---Enter hovered item if it is a directory
|
||||
---@type fun(use_smart_enter: boolean): boolean
|
||||
local initial = ya.sync(function(_, use_smart_enter)
|
||||
local hovered = cx.active.current.hovered
|
||||
if hovered == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
if not hovered.cha.is_dir then
|
||||
-- Open file if using "smart enter"
|
||||
if use_smart_enter then
|
||||
emit("escape", { visual = true, select = true })
|
||||
emit("open", { hovered = true })
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
emit("escape", { visual = true, select = true })
|
||||
emit("enter", { hovered = true })
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
---Enter hovered item if it is a directory and is the only item in the parent
|
||||
---@type fun(): boolean
|
||||
local bypass = ya.sync(function(_)
|
||||
local hovered = cx.active.current.hovered
|
||||
|
||||
if hovered == nil or not hovered.cha.is_dir or #cx.active.current.files > 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
emit("enter", { hovered = true })
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
---Leave the CWD, if the CWD has a parent
|
||||
---@type fun(): boolean
|
||||
local initial_rev = ya.sync(function(_)
|
||||
if cx.active.parent == nil then
|
||||
return false
|
||||
end
|
||||
|
||||
emit("escape", { visual = true, select = true })
|
||||
emit("leave", {})
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
---Leave the CWD, if the CWD has a parent and contains only one item
|
||||
---@type fun(): boolean
|
||||
local bypass_rev = ya.sync(function(_)
|
||||
if cx.active.parent == nil or #cx.active.current.files > 1 then
|
||||
return false
|
||||
end
|
||||
|
||||
emit("leave", {})
|
||||
|
||||
return true
|
||||
end)
|
||||
|
||||
return {
|
||||
entry = function(_, job)
|
||||
-- old version of Yazi will pass args directly, new version passes job. Below code ensures we derive args in both 0.3 and 0.4 Yazi API versions.
|
||||
local args = job.args or job
|
||||
local use_smart_enter = args and args[1] == "smart-enter"
|
||||
local is_reverse = args and args[1] == "reverse"
|
||||
|
||||
-- Initial run, should behave like a regular enter/smart-enter/leave
|
||||
local run = is_reverse and initial_rev() or initial(use_smart_enter)
|
||||
|
||||
while run do
|
||||
-- Wait for directory to have loaded
|
||||
while is_directory_loading() do
|
||||
ya.sleep(0.002)
|
||||
end
|
||||
|
||||
-- Conditional enter/smart-enter/leave
|
||||
run = is_reverse and bypass_rev() or bypass()
|
||||
end
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# chmod.yazi
|
||||
|
||||
Execute `chmod` on the selected files to change their mode. This plugin is only available on Unix platforms since it relies on [`chmod(2)`](https://man7.org/linux/man-pages/man2/chmod.2.html).
|
||||
|
||||
https://github.com/yazi-rs/plugins/assets/17523360/7aa3abc2-d057-498c-8473-a6282c59c464
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
ya pkg add yazi-rs/plugins:chmod
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `~/.config/yazi/keymap.toml`:
|
||||
|
||||
```toml
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "c", "m" ]
|
||||
run = "plugin chmod"
|
||||
desc = "Chmod on selected files"
|
||||
```
|
||||
|
||||
Note that, the keybindings above are just examples, please tune them up as needed to ensure they don't conflict with your other actions/plugins.
|
||||
|
||||
## License
|
||||
|
||||
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
|
||||
@@ -0,0 +1,65 @@
|
||||
--- @since 26.8.15
|
||||
|
||||
local selected_or_hovered = ya.sync(function()
|
||||
local tab, urls, paths = cx.active, {}, {}
|
||||
for i, f in pairs(tab.selected) do
|
||||
urls[i], paths[i] = f.url, tostring(f.path)
|
||||
end
|
||||
if #paths == 0 and tab.current.hovered then
|
||||
urls[1], paths[1] = tab.current.hovered.url, tostring(tab.current.hovered.path)
|
||||
end
|
||||
return urls, paths
|
||||
end)
|
||||
|
||||
local function fail(s, ...)
|
||||
ya.notify {
|
||||
title = "Chmod",
|
||||
content = string.format(s, ...),
|
||||
level = "error",
|
||||
timeout = 5,
|
||||
}
|
||||
end
|
||||
|
||||
local function report(urls)
|
||||
local ops = {}
|
||||
for _, url in ipairs(urls) do
|
||||
local file = fs.file(url)
|
||||
if file then
|
||||
ops[url.parent] = ops[url.parent] or {}
|
||||
ops[url.parent][url.key] = file
|
||||
end
|
||||
end
|
||||
for parent, files in pairs(ops) do
|
||||
ya.emit("update_files", {
|
||||
op = fs.op("upsert", { url = parent, files = files }),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
entry = function()
|
||||
ya.emit("escape", { visual = true })
|
||||
|
||||
local urls, paths = selected_or_hovered()
|
||||
if #paths == 0 then
|
||||
return ya.notify { title = "Chmod", content = "No file selected", level = "warn", timeout = 5 }
|
||||
end
|
||||
|
||||
local value, event = ya.input {
|
||||
title = "Chmod:",
|
||||
pos = { "top-center", y = 3, w = 40 },
|
||||
}
|
||||
if event ~= 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local output, err = Command("chmod"):arg(value):arg(paths):output()
|
||||
if not output then
|
||||
fail("Failed to run chmod: %s", err)
|
||||
elseif output.status.success then
|
||||
pcall(report, urls) -- TODO: remove `pcall`
|
||||
else
|
||||
fail("Chmod failed with stderr:\n%s", output.stderr:gsub("^chmod:%s*", ""))
|
||||
end
|
||||
end,
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
@@ -1,221 +0,0 @@
|
||||
# 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)
|
||||
@@ -1,600 +0,0 @@
|
||||
--- 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
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Mintass
|
||||
Copyright (c) 2025 Victor Aremu <me@victorare.mu>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -0,0 +1,108 @@
|
||||
# eza-preview.yazi
|
||||
|
||||
[Yazi](https://github.com/sxyazi/yazi) plugin to preview directories using [eza](https://github.com/eza-community/eza), can be switched between list and tree modes.
|
||||
|
||||
List mode:
|
||||

|
||||
|
||||
Tree mode:
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
- [yazi (26.1.4+) or nightly](https://github.com/sxyazi/yazi)
|
||||
- [eza (0.20+)](https://github.com/eza-community/eza)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
ya pkg add ahkohd/eza-preview
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
|
||||
Add `eza-preview` to previewers in `yazi.toml`:
|
||||
|
||||
```toml
|
||||
[[plugin.prepend_previewers]]
|
||||
url = "*/"
|
||||
run = "eza-preview"
|
||||
```
|
||||
|
||||
### Key Bindings
|
||||
|
||||
Set key bindings to control the preview in `keymap.toml`:
|
||||
|
||||
```toml
|
||||
[mgr]
|
||||
prepend_keymap = [
|
||||
{ on = [ "e", "t" ], run = "plugin eza-preview", desc = "Toggle tree/list dir preview" },
|
||||
{ on = [ "e", "-" ], run = "plugin eza-preview inc-level", desc = "Increment tree level" },
|
||||
{ on = [ "e", "_" ], run = "plugin eza-preview dec-level", desc = "Decrement tree level" },
|
||||
{ on = [ "e", "$" ], run = "plugin eza-preview toggle-follow-symlinks", desc = "Toggle tree follow symlinks" },
|
||||
{ on = [ "e", "*" ], run = "plugin eza-preview toggle-hidden", desc = "Toggle hidden files" },
|
||||
{ on = [ "e", "g", "i" ], run = "plugin eza-preview toggle-git-ignore", desc = "Toggle .gitignore files" },
|
||||
{ on = [ "e", "g", "s" ], run = "plugin eza-preview toggle-git-status", desc = "Toggle showing git status" },
|
||||
]
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
Configure the plugin in `init.lua`:
|
||||
|
||||
```lua
|
||||
require("eza-preview"):setup({
|
||||
-- Set the tree preview to be default (default: true)
|
||||
default_tree = true,
|
||||
|
||||
-- Directory depth level for tree preview (default: 3)
|
||||
level = 3,
|
||||
|
||||
-- Show file icons
|
||||
icons = true,
|
||||
|
||||
-- Follow symlinks when previewing directories (default: true)
|
||||
follow_symlinks = true,
|
||||
|
||||
-- Show target file info instead of symlink info (default: false)
|
||||
dereference = false,
|
||||
|
||||
-- Show hidden files (default: true)
|
||||
all = true,
|
||||
|
||||
-- Ignore files matching patterns (default: {})
|
||||
-- ignore_glob = "*.log"
|
||||
-- ignore_glob = { "*.tmp", "node_modules", ".git", ".DS_Store" }
|
||||
-- SEE: https://www.linuxjournal.com/content/pattern-matching-bash to learn about glob patterns
|
||||
ignore_glob = {},
|
||||
|
||||
-- Ignore files mentioned in '.gitignore' (default: true)
|
||||
git_ignore = true,
|
||||
|
||||
-- Show git status (default: false)
|
||||
git_status = false
|
||||
})
|
||||
|
||||
-- Or use default settings
|
||||
require("eza-preview"):setup({})
|
||||
```
|
||||
|
||||
## Available Commands
|
||||
|
||||
- `plugin eza-preview` - Toggle between tree and list modes
|
||||
- `plugin eza-preview inc-level` - Increase tree depth level
|
||||
- `plugin eza-preview dec-level` - Decrease tree depth level
|
||||
- `plugin eza-preview toggle-follow-symlinks` - Toggle symlink following
|
||||
- `plugin eza-preview toggle-hidden` - Toggle hidden file visibility
|
||||
- `plugin eza-preview toggle-git-ignore` - Toggle ignore files mentioned in '.gitignore'
|
||||
- `plugin eza-preview toggle-git-status` - Toggle showing git status
|
||||
|
||||
## Contributing
|
||||
|
||||
Feel free to contribute by opening issues or submitting pull requests!
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
@@ -0,0 +1,203 @@
|
||||
--- @since 25.12.29
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Batch getter - returns multiple simple values (avoids multiple sync boundary crossings)
|
||||
local get_all_opts = ya.sync(function(st)
|
||||
return st.tree ~= false,
|
||||
st.level or 3,
|
||||
st.follow_symlinks ~= false,
|
||||
st.dereference == true,
|
||||
st.all ~= false,
|
||||
st.git_ignore ~= false,
|
||||
st.git_status == true,
|
||||
st.icons ~= false,
|
||||
st.ignore_glob or ""
|
||||
end)
|
||||
|
||||
-- Individual getters for entry point (need current value before toggling)
|
||||
local get_tree = ya.sync(function(st) return st.tree ~= false end)
|
||||
local get_level = ya.sync(function(st) return st.level or 3 end)
|
||||
local get_follow_symlinks = ya.sync(function(st) return st.follow_symlinks ~= false end)
|
||||
local get_all = ya.sync(function(st) return st.all ~= false end)
|
||||
local get_git_ignore = ya.sync(function(st) return st.git_ignore ~= false end)
|
||||
local get_git_status = ya.sync(function(st) return st.git_status == true end)
|
||||
|
||||
-- Sync setters (also trigger preview refresh)
|
||||
local set_tree = ya.sync(function(st, val)
|
||||
st.tree = val
|
||||
local h = cx.active.current.hovered
|
||||
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||
end)
|
||||
local set_level = ya.sync(function(st, val)
|
||||
st.level = val
|
||||
local h = cx.active.current.hovered
|
||||
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||
end)
|
||||
local set_follow_symlinks = ya.sync(function(st, val)
|
||||
st.follow_symlinks = val
|
||||
local h = cx.active.current.hovered
|
||||
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||
end)
|
||||
local set_all = ya.sync(function(st, val)
|
||||
st.all = val
|
||||
local h = cx.active.current.hovered
|
||||
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||
end)
|
||||
local set_git_ignore = ya.sync(function(st, val)
|
||||
st.git_ignore = val
|
||||
local h = cx.active.current.hovered
|
||||
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||
end)
|
||||
local set_git_status = ya.sync(function(st, val)
|
||||
st.git_status = val
|
||||
local h = cx.active.current.hovered
|
||||
if h then ya.emit("peek", { 0, only_if = h.url, force = true }) end
|
||||
end)
|
||||
|
||||
-- Setup from user config
|
||||
local apply_config = ya.sync(function(st, cfg)
|
||||
cfg = cfg or {}
|
||||
if cfg.default_tree ~= nil then st.tree = cfg.default_tree end
|
||||
if cfg.level ~= nil then st.level = cfg.level end
|
||||
if cfg.follow_symlinks ~= nil then st.follow_symlinks = cfg.follow_symlinks end
|
||||
if cfg.dereference ~= nil then st.dereference = cfg.dereference end
|
||||
if cfg.all ~= nil then st.all = cfg.all end
|
||||
if cfg.git_ignore ~= nil then st.git_ignore = cfg.git_ignore end
|
||||
if cfg.git_status ~= nil then st.git_status = cfg.git_status end
|
||||
if cfg.icons ~= nil then st.icons = cfg.icons end
|
||||
if cfg.ignore_glob ~= nil then
|
||||
if type(cfg.ignore_glob) == "table" then
|
||||
st.ignore_glob = table.concat(cfg.ignore_glob, "|")
|
||||
else
|
||||
st.ignore_glob = cfg.ignore_glob
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function M:setup(cfg)
|
||||
apply_config(cfg)
|
||||
end
|
||||
|
||||
function M:entry(job)
|
||||
local args = string.gsub(job.args[1] or "", "^%s*(.-)%s*$", "%1")
|
||||
if args == "inc-level" then
|
||||
set_level(get_level() + 1)
|
||||
elseif args == "dec-level" then
|
||||
local lvl = get_level()
|
||||
if lvl > 1 then set_level(lvl - 1) end
|
||||
elseif args == "toggle-follow-symlinks" then
|
||||
set_follow_symlinks(not get_follow_symlinks())
|
||||
elseif args == "toggle-hidden" then
|
||||
set_all(not get_all())
|
||||
elseif args == "toggle-git-ignore" then
|
||||
set_git_ignore(not get_git_ignore())
|
||||
elseif args == "toggle-git-status" then
|
||||
set_git_status(not get_git_status())
|
||||
else
|
||||
set_tree(not get_tree())
|
||||
end
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
-- Single sync call to get all options (avoids 9 separate boundary crossings)
|
||||
local is_tree, level, follow_symlinks, dereference, all, git_ignore, git_status, icons, ignore_glob = get_all_opts()
|
||||
|
||||
local args = {
|
||||
"--color=always",
|
||||
"--group-directories-first",
|
||||
"--no-quotes",
|
||||
tostring(job.file.url),
|
||||
}
|
||||
if is_tree then
|
||||
table.insert(args, "--tree")
|
||||
table.insert(args, string.format("--level=%d", level))
|
||||
end
|
||||
if icons then
|
||||
table.insert(args, "--icons=always")
|
||||
end
|
||||
if follow_symlinks then
|
||||
table.insert(args, "--follow-symlinks")
|
||||
end
|
||||
if all then
|
||||
table.insert(args, "--all")
|
||||
end
|
||||
if dereference then
|
||||
table.insert(args, "--dereference")
|
||||
end
|
||||
if git_status then
|
||||
table.insert(args, "--long")
|
||||
table.insert(args, "--no-permissions")
|
||||
table.insert(args, "--no-user")
|
||||
table.insert(args, "--no-time")
|
||||
table.insert(args, "--no-filesize")
|
||||
table.insert(args, "--git")
|
||||
table.insert(args, "--git-repos")
|
||||
end
|
||||
if git_ignore then
|
||||
table.insert(args, "--git-ignore")
|
||||
end
|
||||
if ignore_glob ~= "" then
|
||||
table.insert(args, "-I")
|
||||
table.insert(args, ignore_glob)
|
||||
end
|
||||
|
||||
local child, err = Command("eza"):arg(args):stdout(Command.PIPED):stderr(Command.PIPED):spawn()
|
||||
if not child then
|
||||
return ya.preview_widget(job, ui.Text("eza: " .. (err or "spawn failed")):area(job.area))
|
||||
end
|
||||
|
||||
local limit = job.area.h
|
||||
local lines = ""
|
||||
local line_count = 0
|
||||
local skipped = 0
|
||||
|
||||
repeat
|
||||
local line, event = child:read_line()
|
||||
if event == 1 then
|
||||
-- stderr, skip
|
||||
elseif event ~= 0 then
|
||||
break
|
||||
elseif skipped < job.skip then
|
||||
skipped = skipped + 1
|
||||
else
|
||||
lines = lines .. line
|
||||
line_count = line_count + 1
|
||||
end
|
||||
until line_count >= limit
|
||||
|
||||
child:start_kill()
|
||||
|
||||
-- tree mode outputs dir name as first line, so empty = 1 line; list mode empty = 0 lines
|
||||
local empty_output = (is_tree and line_count <= 1) or (not is_tree and line_count == 0)
|
||||
|
||||
if job.skip > 0 and line_count < limit then
|
||||
ya.emit("peek", {
|
||||
math.max(0, job.skip - (limit - line_count)),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
elseif empty_output then
|
||||
ya.preview_widget(job, {
|
||||
ui.Text({ ui.Line("No items") }):area(job.area):align(ui.Align.CENTER),
|
||||
})
|
||||
else
|
||||
ya.preview_widget(job, {
|
||||
ui.Text.parse(lines):area(job.area),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M:seek(job)
|
||||
local h = cx.active.current.hovered
|
||||
if h and h.url == job.file.url then
|
||||
local step = math.floor(job.units * job.area.h / 10)
|
||||
ya.emit("peek", {
|
||||
math.max(0, cx.active.preview.skip + step),
|
||||
only_if = job.file.url,
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Shallow-Seek
|
||||
|
||||
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.
|
||||
@@ -1,183 +0,0 @@
|
||||
fazif allows you to search over selected items in Yazi with your configuration of search and fuzzy filter command.
|
||||
|
||||
You can config search command ([fd](https://github.com/sharkdp/fd), [rg](https://github.com/BurntSushi/ripgrep), [rga](https://github.com/phiresky/ripgrep-all), [ag](https://github.com/ggreer/the_silver_searcher), [astgrep](https://github.com/ast-grep/ast-grep) ...), fuzzy filter command ([fzf](https://github.com/junegunn/fzf), [fzy](https://github.com/jhawthorn/fzy), [zf](https://github.com/natecraddock/zf), [skim](https://github.com/lotabout/skim)), and options in a script and spawn all your scripts easily into yazi. Here are some use cases.
|
||||
|
||||
1. Search within the current working directory when none are selected.
|
||||
|
||||
For example, a simple directory jumper. Simply create a file in `~/.config/yazi/plugins/fazif.yazi` with one of the following, make it executable, and set a keybinding.
|
||||
|
||||
```
|
||||
#!/usr/bin/env zsh
|
||||
fd -H -t d|fzf
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
#!/usr/bin/env zsh
|
||||
fd -H -t d|zf
|
||||
```
|
||||
|
||||
or jump to a dir. outside CWD by applying your query to a upper dir. with a shortcut key.
|
||||
|
||||
```
|
||||
#!/usr/bin/env zsh
|
||||
fd -H -t d|fzf \
|
||||
--bind 'ctrl-e:transform:echo "change-prompt(Home_Dir> )+reload(fd . ~ --hidden --type d)"' \
|
||||
```
|
||||
|
||||
2. Search within selected files and directories
|
||||
|
||||
Example 1: to jump to some dir. in the selected folders, simply create a file in `~/.config/yazi/plugins/fazif.yazi` with one of the following, make it executable, and set a keybinding.
|
||||
|
||||
|
||||
```
|
||||
#!/usr/bin/env zsh
|
||||
fd -H -t d "$@"|fzf
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
#!/usr/bin/env zsh
|
||||
fd -H -t d "$@"|zf
|
||||
```
|
||||
|
||||
Example 2: use `fazifrga` script to search for a pattern in selected PDFs and preview the matching pages that have the pattern with fzf.
|
||||
|
||||
3. Reveal the selected file or enter the selected directory in a new yazi tab; if multiple items are selected in fzf (using the `-multi` option in fzf), then show the selected items in the search result view.
|
||||
|
||||
For example, find relevant books using `faziffd` and reveal them in yazi's search result view, then use `fazifrga` to search for a pattern in these books.
|
||||
|
||||
4. show recent modified files.
|
||||
```
|
||||
#!/usr/bin/env zsh
|
||||
fd -H -t f --changed-within 1d|fzf
|
||||
```
|
||||
## How It Works
|
||||
|
||||
This plugin acts as a bridge between Yazi and standalone scripts. The main.lua file serves as a generic wrapper that:
|
||||
1. Passes the current working directory and selected files/directories from Yazi (`"$@"` in the script) to the script
|
||||
2. Executes the specified script (passed as an argument in the keymap)
|
||||
3. Send results in Yazi search pane if multiple selected or reveal/enter the selected item in a yazi new tab
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install via ya:
|
||||
|
||||
```bash
|
||||
ya pkg -a Shallow-Seek/fazif
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Shallow-Seek/fazif.yazi.git ~/.config/yazi/plugins/fazif.yazi
|
||||
```
|
||||
|
||||
2. The 3 default scripts `faziffd`, `fazifrg`, and `fazifrga` provided are examples with features shown in the next section. The plugin still works with your scripts without them.
|
||||
|
||||
Simple put your script in `~/.config/yazi/plugins/fazif.yazi` and set a keybinding to use them. To make your script search on selected items, just add `"$@"` to the search command. Check `faziffd` to see that.
|
||||
|
||||
Also you need change your `rg` or `rga`'s delimiter to `U+00A0` by adding the option `--field-match-separator $'\u00a0'`
|
||||
to your `rg` or `rga` commmand and `--delimiter $'\u00a0'` option to `fzf`. See the default `fazifrg` as an example.
|
||||
|
||||
but you can modify the following line in `main.lua` to use your prefered delimiter.
|
||||
|
||||
```lua
|
||||
local file = line:find("\xC2\xA0") and line:sub(1, line:find("\xC2\xA0") - 1) or line
|
||||
```
|
||||
|
||||
> UTF-8 encoding of `U+00A0` is `\xC2\xA0`
|
||||
|
||||
Make sure your scripts are executable:
|
||||
|
||||
```bash
|
||||
chmod +x ~/.config/yazi/plugins/fazif.yazi/faziffd
|
||||
chmod +x ~/.config/yazi/plugins/fazif.yazi/fazifrg
|
||||
chmod +x ~/.config/yazi/plugins/fazif.yazi/fazifrga
|
||||
chmod +x ~/.config/yazi/plugins/fazif.yazi/yourscript1
|
||||
...
|
||||
```
|
||||
You may need to update the shebang (`#!`) in the example scripts.
|
||||
|
||||
## Add Keymaps to Your Script
|
||||
|
||||
The plugin can be configured to run any of your scripts by passing the script name as an argument. Add the following to your `~/.config/yazi/keymap.toml` to bind each script to a key combination:
|
||||
|
||||
### Setting up all scripts:
|
||||
|
||||
```toml
|
||||
# File/Directory finder using fd + fzf
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "b", "d" ]
|
||||
run = "plugin fazif faziffd"
|
||||
desc = "Find files/directories with fd and fzf"
|
||||
|
||||
# Content finder using ripgrep + fzf
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "b", "r" ]
|
||||
run = "plugin fazif fazifrg"
|
||||
desc = "Find content in files with ripgrep and fzf"
|
||||
|
||||
# Document content finder using ripgrep-all + fzf
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "b", "a" ]
|
||||
run = "plugin fazif fazifrga"
|
||||
desc = "Find content in documents with ripgrep-all and fzf"
|
||||
...
|
||||
```
|
||||
|
||||
That's it. is not the default `:`, you will need to change the delimiter in main.lua.
|
||||
|
||||
---
|
||||
## Example scripts
|
||||
Read this section if you use the default scripts `faziffd`, `fazifrg`, and `fazifrga`.
|
||||
|
||||
### Features
|
||||
|
||||
1. **faziffd** - File/directory jumper using `fd`. Txt preview with `bat` and Directory preview with `eza`
|
||||
2. **fazifrg** - `ripgrep` on selected. Preview shows file content with `bat` when started with no input. With any input, `rg` kicks in, and the preview highlights the matching line.
|
||||
3. **fazifrga** - `ripgrep-all` search in selected PDFs and DjVu. Preview shows the first page of a document when started with no input. With any input, `rga` kicks in, and the preview shows the matching page in the document.
|
||||
|
||||
The 3 scripts use the following tools:
|
||||
|
||||
- [fzf](https://github.com/junegunn/fzf)
|
||||
- [fd](https://github.com/sharkdp/fd) - Used by `faziffd`
|
||||
- [ripgrep](https://github.com/BurntSushi/ripgrep) - Used by `fazifrg`
|
||||
- [ripgrep-all](https://github.com/phiresky/ripgrep-all) - Used by `fazifrga`
|
||||
- [rga djvu adaptor](https://github.com/phiresky/ripgrep-all/discussions/166) - Used by `fazifrga` to search in djvu
|
||||
- Additional tools for document previews:
|
||||
- [kitty](https://sw.kovidgoyal.net/kitty/) Image preview uses kitty icat. You can also use other terminals that support image or use ueberzugpp.
|
||||
- [bat](https://github.com/sharkdp/bat) - Used by `faziffd`, `fazifrg`
|
||||
- [eza](https://github.com/eza-community/eza) - Used by `faziffd`
|
||||
- `pdftoppm` from [poppler-utils](https://github.com/freedesktop/poppler) - Used by `faziffd`, `fazifrga` for pdf prevew.
|
||||
- `ddjvu` from [djvulibre](https://github.com/DjVuLibre/djvulibre) - Used by `faziffd`, `fazifrga` for djvu preview.
|
||||
- [libreoffice](https://github.com/LibreOffice/core) for office documents - Used by `faziffd` for office file preview.
|
||||
|
||||
### Usage
|
||||
|
||||
#### faziffd - File/Directory jumper
|
||||
|
||||
- `Ctrl-w`: Search files in the home directory
|
||||
- `Ctrl-e`: Search directories in the home directory
|
||||
- `Alt-c`: Search directories in the current working directory
|
||||
- `Ctrl-t`: Search files in the current working directory
|
||||
- `Ctrl-f`: Search directories from the root
|
||||
- `Ctrl-p`: Toggle the preview window/position
|
||||
- `Ctrl-x`: Open in Yazi (new instance)(if `setsid` is not available, use `nohup`)
|
||||
|
||||
|
||||
#### fazifrg, fazifrga
|
||||
|
||||
- `Ctrl-y`: Switch between rga search mode and fzf filtering mode
|
||||
- `Ctrl-p`: Toggle the preview window/position
|
||||
- `Ctrl-o`: `fazifrg` open file in Neovim at the matched line. `fazifrga` open document in Zathura at the matched page and highlight the query.
|
||||
|
||||
## License
|
||||
|
||||
This plugin is released under the MIT License.
|
||||
|
||||
## Acknowledgement
|
||||
Plugin is based on the default [Yazi](https://github.com/sxyazi/yazi) fzf plugin and vcs-files plugin. Fzf scripts grow out of [fzf](https://github.com/junegunn/fzf)'s wiki and examples.
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/usr/bin/env zsh
|
||||
|
||||
fd . -H "$@" |fzf \
|
||||
--info=inline \
|
||||
--multi \
|
||||
--layout=reverse \
|
||||
--prompt='ctl-wetcrfp_CWD> ' \
|
||||
--bind 'ctrl-w:transform:echo "change-prompt(ctl-wetcrfp_Home_File> )+reload(fd . ~ --hidden --type f)"' \
|
||||
--bind 'ctrl-e:transform:echo "change-prompt(ctl-wetcrfp_Home_Dir> )+reload(fd . ~ --hidden --type d)"' \
|
||||
--bind 'alt-c:transform:echo "change-prompt(ctl-wetcrfp_CWD_Dir> )+reload(fd --hidden --type d)"' \
|
||||
--bind 'ctrl-t:transform:echo "change-prompt(ctl-wetcrfp_CWD_File> )+reload(fd --hidden --type f)"' \
|
||||
--bind 'ctrl-f:transform:echo "change-prompt(ctl-wetcrfp_root_Dir> )+reload(fd . / --hidden --type d)"' \
|
||||
--bind 'ctrl-r:transform:echo "change-prompt(ctl-wetcrfp_Root_File> )+reload(fd . / --hidden --type f)"' \
|
||||
--bind 'focus:transform-preview-label:echo {}' \
|
||||
--bind 'ctrl-p:change-preview-window(right|down|hidden)' \
|
||||
--bind 'focus:transform-preview-label:echo {}' \
|
||||
--bind 'ctrl-o:execute-silent(xdg-open-nvim {} &)' \
|
||||
--preview-window 'hidden' \
|
||||
--preview '
|
||||
printf "\033_Ga=d,q=1\033\\"
|
||||
[ -d {} ] && eza -aTL 2 --group-directories-first --color=always --icons=always {} ||
|
||||
tmp="/tmp/fzf_preview"
|
||||
case $(basename {}) in
|
||||
*.pdf)
|
||||
pdftoppm -q -f 1 -l 1 -jpeg -jpegopt quality=60 -scale-to-x 1000 -scale-to-y -1 -singlefile {} $tmp && \
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp.jpg
|
||||
;;
|
||||
*.doc|*.docx|*.xls|*.xlsx|*.ppt|*.pptx|*.odt|*.ods|*.odp)
|
||||
libreoffice --convert-to jpg --outdir /tmp {} >/dev/null 2>&1
|
||||
mv "/tmp/${$(basename {})%.*}.jpg" $tmp
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp
|
||||
;;
|
||||
*.djvu)
|
||||
ddjvu -format=ppm -page=1 -size 1000x-1 -aspect=yes {} $tmp 2>/dev/null && \
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp
|
||||
;;
|
||||
*)
|
||||
if file --mime-type {} | grep -qP "image/(?!vnd\.djvu)"; then
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {}
|
||||
else
|
||||
bat -p {} --color=always 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
'
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
FD_COMMAND5="fd --changed-within 5d"
|
||||
FD_COMMAND1="fd --changed-within 1d"
|
||||
|
||||
|
||||
$FD_COMMAND5 -t f . "$@" |fzf \
|
||||
--info=inline \
|
||||
--multi \
|
||||
--layout=reverse \
|
||||
--prompt='ctl-wetcrfp_CWD5d> ' \
|
||||
--bind "ctrl-w:transform:echo 'change-prompt(ctl-wetcrfp_HOME5d> )+reload($FD_COMMAND5 -t f . ~ )'" \
|
||||
--bind "ctrl-e:transform:echo 'change-prompt(ctl-wetcrfp_HOME1d)+reload($FD_COMMAND1 -t f . ~)'" \
|
||||
--bind "ctrl-t:transform:echo 'change-prompt(ctl-wetcrfp_CWD5d> )+reload($FD_COMMAND5 -t f)'" \
|
||||
--bind "alt-c:transform:echo 'change-prompt(ctl-wetcrfp_CWD1d> )+reload($FD_COMMAND1 -t f)'" \
|
||||
--bind "ctrl-r:transform:echo 'change-prompt(ctl-wetcrfp_HOMEA> )+reload($FD_COMMAND5 . ~)'" \
|
||||
--bind "ctrl-f:transform:echo 'change-prompt(ctl-wetcrfp_HOMEA_WithH> )+reload($FD_COMMAND1 -H . ~)'" \
|
||||
--bind 'focus:transform-preview-label:echo {}' \
|
||||
--bind 'ctrl-p:change-preview-window(right|down|hidden)' \
|
||||
--bind 'focus:transform-preview-label:echo {}' \
|
||||
--bind 'ctrl-x:execute-silent(setsid kitty nnn {} &)' \
|
||||
--bind 'ctrl-a:execute-silent(setsid kitty yazi {} &)' \
|
||||
--bind 'ctrl-s:execute-silent(setsid thunar {} &)' \
|
||||
--preview-window 'hidden' \
|
||||
--preview '
|
||||
printf "\033_Ga=d,q=1\033\\"
|
||||
[ -d {} ] && eza -aTL 2 --group-directories-first --color=always --icons=always {} ||
|
||||
tmp="/tmp/fzf_preview"
|
||||
case $(basename {}) in
|
||||
*.pdf)
|
||||
pdftoppm -q -f 1 -l 1 -jpeg -jpegopt quality=60 -scale-to-x 1000 -scale-to-y -1 -singlefile {} $tmp && \
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp.jpg
|
||||
;;
|
||||
*.doc|*.docx|*.xls|*.xlsx|*.ppt|*.pptx|*.odt|*.ods|*.odp)
|
||||
libreoffice --convert-to jpg --outdir /tmp {} >/dev/null 2>&1
|
||||
mv "/tmp/${$(basename {})%.*}.jpg" $tmp
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp
|
||||
;;
|
||||
*.djvu)
|
||||
ddjvu -format=ppm -page=1 -size 1000x-1 -aspect=yes {} $tmp 2>/dev/null && \
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp
|
||||
;;
|
||||
*)
|
||||
if file --mime-type {} | grep -qP "image/(?!vnd\.djvu)"; then
|
||||
kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 {}
|
||||
else
|
||||
bat -p {} --color=always 2>/dev/null
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
'
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env zsh
|
||||
rm -f /tmp/rg-fzf-{r,f} 2>/dev/null
|
||||
RG_PREFIX="rg --with-filename --column --line-number --color=always --smart-case --field-match-separator $'\u00a0'"
|
||||
|
||||
FILES_TO_ADD=""
|
||||
for item in "$@"; do
|
||||
if [ -f "$item" ]; then
|
||||
FILES_TO_ADD="$FILES_TO_ADD$item"$'\n'
|
||||
fi
|
||||
done
|
||||
|
||||
(fd . -H -t f "$@" 2>/dev/null & printf "%s" "$FILES_TO_ADD") | fzf \
|
||||
--ansi \
|
||||
--delimiter $'\u00a0' \
|
||||
--disabled \
|
||||
--prompt '1. ripgrep> ' \
|
||||
--header 'CTRL-Y: Switch between ripgrep/fzf' \
|
||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} $([ $# -gt 0 ] && printf "%q " "$@") || true" \
|
||||
--bind 'ctrl-y:transform:[[ ! $FZF_PROMPT =~ ripgrep ]] &&
|
||||
echo "rebind(change)+change-prompt(1. ripgrep> )+disable-search+transform-query:echo \{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r" ||
|
||||
echo "unbind(change)+change-prompt(2. fzf> )+enable-search+transform-query:echo \{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f"' \
|
||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||
--preview '[ -n "{2}" ] && bat --color=always {1} --style=numbers --highlight-line {2} || bat --color=always {} --style=numbers' \
|
||||
--preview-window 'down,60%,+{2}+3/3,~3' \
|
||||
--bind 'ctrl-p:change-preview-window(right,60%|hidden|down,60%)' \
|
||||
--bind 'focus:transform-preview-label:echo $(basename {1})' \
|
||||
--bind 'ctrl-o:execute(kitty -1 sh -c "nvim {1} +{2}" & )' \
|
||||
--bind 'ctrl-x:execute-silent(setsid kitty -1 nnn {1} &)' \
|
||||
--bind 'ctrl-a:execute-silent(setsid kitty -1 yazi {1} &)' \
|
||||
--bind 'ctrl-s:execute-silent(setsid thunar {1} &)' \
|
||||
--color 'label:117:bold'
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env zsh
|
||||
rm -f /tmp/rg-fzf-{r,f} 2>/dev/null
|
||||
RG_PREFIX="rga --glob='*.pdf' --glob='*.djvu' --glob='*.djv' --with-filename --color=always --no-heading --smart-case --field-match-separator $'\u00a0'"
|
||||
|
||||
FILES_TO_ADD=""
|
||||
for item in "$@"; do
|
||||
if [ -f "$item" ]; then
|
||||
FILES_TO_ADD="$FILES_TO_ADD$item"$'\n'
|
||||
fi
|
||||
done
|
||||
|
||||
(fd . -e pdf -e djvu -e djv "$@" 2>/dev/null & printf "%s" "$FILES_TO_ADD") | fzf \
|
||||
--ansi \
|
||||
--delimiter $'\u00a0' \
|
||||
--disabled \
|
||||
--with-nth 2,1 \
|
||||
--prompt="rga> " \
|
||||
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||
--bind "change:reload:sleep 0.1; $RG_PREFIX {q} $([ $# -gt 0 ] && printf "%q " "$@") || true" \
|
||||
--bind 'focus:transform-preview-label:echo $(basename {1})' \
|
||||
--bind 'ctrl-p:change-preview-window(right,50%|hidden,50%|down)' \
|
||||
--bind 'ctrl-x:execute-silent(setsid kitty -1 nnn {1} &)' \
|
||||
--bind 'ctrl-a:execute-silent(setsid kitty -1 yazi {1} &)' \
|
||||
--bind 'ctrl-s:execute-silent(setsid thunar {1} &)' \
|
||||
--preview-window 'down' \
|
||||
--bind 'ctrl-y:transform:[[ ! $FZF_PROMPT =~ rga ]] &&
|
||||
echo "rebind(change)+change-prompt(rga> )+disable-search+transform-query:echo \{q} > /tmp/rg-fzf-f; cat /tmp/rg-fzf-r" ||
|
||||
echo "unbind(change)+change-prompt(fzf> )+enable-search+transform-query:echo \{q} > /tmp/rg-fzf-r; cat /tmp/rg-fzf-f"' \
|
||||
--bind 'ctrl-o:execute(
|
||||
page_info={2}
|
||||
pagenumber=${page_info#Page }
|
||||
pagenumber=${pagenumber%%: *}
|
||||
context=${page_info#*: }
|
||||
setsid zathura --page=$pagenumber --find=$context {1} &)' \
|
||||
--preview '
|
||||
printf "\033_Ga=d,q=1\033\\"
|
||||
[ -n {2} ] && pagenumber=$(echo {2} | cut -d: -f1 | sed "s/Page\ //") || pagenumber=1
|
||||
tmp="/tmp/fzf_preview"
|
||||
case {1} in
|
||||
*.pdf)
|
||||
pdftoppm -q -f $pagenumber -l $pagenumber -jpeg -jpegopt quality=60 -scale-to-x 800 -scale-to-y -1 -singlefile {1} $tmp \
|
||||
&& kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp.jpg
|
||||
;;
|
||||
*.djvu)
|
||||
ddjvu -format=ppm -page=$pagenumber -size=800x-1 -aspect=yes {1} $tmp 2>/dev/null \
|
||||
&& kitty icat --transfer-mode=memory --stdin=no --place=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}@0x0 $tmp
|
||||
;;
|
||||
esac
|
||||
' \
|
||||
# --bind "start:reload:$RG_PREFIX {q} || true" \
|
||||
@@ -1,107 +0,0 @@
|
||||
local state = ya.sync(function()
|
||||
local selected = {}
|
||||
for _, url in pairs(cx.active.selected) do
|
||||
selected[#selected + 1] = url
|
||||
end
|
||||
return cx.active.current.cwd, selected
|
||||
end)
|
||||
|
||||
local function split_urls(cwd, output)
|
||||
if not output or output == "" then return {} end
|
||||
|
||||
local t = {}
|
||||
for line in output:gmatch("[^\r\n]+") do
|
||||
if line ~= "" then
|
||||
-- Get everything before first colon, or entire line if no colon
|
||||
local file = line:find("\xC2\xA0") and line:sub(1, line:find("\xC2\xA0") - 1) or line
|
||||
|
||||
if file and file ~= "" then
|
||||
local u = Url(file)
|
||||
if u.is_absolute then
|
||||
t[#t + 1] = u
|
||||
else
|
||||
t[#t + 1] = cwd:join(u)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
local function run_with(cwd, selected, script_name)
|
||||
local script_path = os.getenv("HOME") .. "/.config/yazi/plugins/fazif.yazi/" .. script_name
|
||||
|
||||
local sepaths = {}
|
||||
if #selected > 0 then
|
||||
for _, u in ipairs(selected) do
|
||||
table.insert(sepaths, tostring(u))
|
||||
end
|
||||
end
|
||||
|
||||
local child
|
||||
if #selected == 0 then
|
||||
child = Command(script_path):cwd(tostring(cwd)):stdin(Command.INHERIT):stdout(Command.PIPED):spawn()
|
||||
else
|
||||
child = Command(script_path):arg(sepaths):stdout(Command.PIPED):spawn()
|
||||
end
|
||||
|
||||
if not child then
|
||||
return nil, "Failed to start script"
|
||||
end
|
||||
|
||||
local output, err = child:wait_with_output()
|
||||
if not output then
|
||||
return nil, "Cannot read output: " .. tostring(err)
|
||||
elseif not output.status.success and output.status.code ~= 130 then
|
||||
return nil, "Script exited with code " .. tostring(output.status.code)
|
||||
end
|
||||
return output.stdout, nil
|
||||
end
|
||||
|
||||
local function entry(_, job)
|
||||
local script_name = job.args[1]
|
||||
|
||||
local permit = ui.hide()
|
||||
local cwd, selected = state()
|
||||
|
||||
local output, err = run_with(cwd, selected, script_name)
|
||||
|
||||
if permit then
|
||||
permit:drop()
|
||||
end
|
||||
|
||||
if not output then
|
||||
return ya.notify { title = "Fazif", content = tostring(err), timeout = 5, level = "error" }
|
||||
end
|
||||
|
||||
local urls = split_urls(cwd, output)
|
||||
if #urls == 0 then
|
||||
return
|
||||
elseif #urls == 1 then
|
||||
ya.emit("tab_create", {})
|
||||
ya.emit(fs.cha(urls[1]).is_dir and "cd" or "reveal", { urls[1], raw = true })
|
||||
else
|
||||
ya.emit("tab_create", {})
|
||||
local id = ya.id("ft")
|
||||
local virtual_cwd = cwd:into_search("FZF Results")
|
||||
ya.emit("cd", { Url(virtual_cwd) })
|
||||
ya.emit("update_files", { op = fs.op("part", { id = id, url = Url(virtual_cwd), files = {} }) })
|
||||
|
||||
local files = {}
|
||||
for i = 1, #urls do
|
||||
local url = urls[i]
|
||||
local cha = fs.cha(url, true)
|
||||
if cha then
|
||||
files[#files + 1] = File { url = url, cha = cha }
|
||||
else
|
||||
files[#files + 1] = File { url = url }
|
||||
end
|
||||
end
|
||||
|
||||
ya.emit("update_files", { op = fs.op("part", { id = id, url = Url(virtual_cwd), files = files }) })
|
||||
ya.emit("update_files", { op = fs.op("done", { id = id, url = virtual_cwd, cha = Cha { kind = 16 } }) })
|
||||
end
|
||||
end
|
||||
|
||||
return { entry = entry }
|
||||
|
||||
@@ -15,15 +15,6 @@ ya pkg add AminurAlam/yazi-plugins:fchar
|
||||
|
||||
# Usage
|
||||
|
||||
in `~/.config/yazi/keymap.toml`
|
||||
|
||||
```toml
|
||||
[mgr]
|
||||
prepend_keymap = [
|
||||
{ on = "f", run = "plugin fchar", desc = "Jump to char" },
|
||||
]
|
||||
```
|
||||
|
||||
in `~/.config/yazi/init.lua`
|
||||
|
||||
```lua
|
||||
@@ -35,11 +26,18 @@ require('fchar'):setup {
|
||||
skip_symbols = true,
|
||||
-- if {"yazi-"}: f -> file, yazi-file
|
||||
skip_prefix = {},
|
||||
-- start: f -> file
|
||||
-- word: f -> file, also-file
|
||||
-- all: f -> file, also-file, alsofile, elf
|
||||
search_location = 'start',
|
||||
aliases = {},
|
||||
keys = {},
|
||||
}
|
||||
|
||||
-- setup keymaps
|
||||
require('fchar'):setup {
|
||||
keys = {
|
||||
start = 'f', -- ff -> file
|
||||
ext = 'e', -- ef -> name.fs
|
||||
word = 'F', -- Ff -> file, also-file
|
||||
all = '<C-f>', -- ^ff -> file, also-file, twofile, elf
|
||||
},
|
||||
}
|
||||
|
||||
-- aliases for German
|
||||
@@ -79,8 +77,37 @@ require('fchar'):setup {
|
||||
},
|
||||
}
|
||||
|
||||
-- you may want to turn off the search regex from
|
||||
-- showing up in the header by doing this
|
||||
-- aliases for Chinese, taken from https://github.com/DreamMaoMao/sjch.yazi
|
||||
require('fchar'):setup {
|
||||
aliases = {
|
||||
a = "暗暧謸硙碍磑澳癌凹阿翱聱肮礙骜骯揞敖昂鏊鏖案桉菴獒瑷胺螯铵锕锿庵廒闇罯砹熬爱犴鼇黯鹌鶕鵪鴱鰲鮟驁隘隩霭靄靉鞍懊艾捱傲騃嗄嗌嗳嗷坳埃埯吖呵哀哎唉啊谙鳌鑀安矮皑盎盦盫韽俺奥媪嫒袄襖挨按拗蔼薆岙岸遨氨",
|
||||
b = "泊波泵浜颁颩飆飑飙飚晡暴本杓杯杷板柄柏綼編緶縪繃繴绊绑绷飽餅渤饱饼饽駜變诐碑碚碥碧磅磦滗滨濒濞瀑灞炮炳焙笾筆筚箅箔甭畀畚疤病痹瘢瘪癍禀禆秉秕秘稖稗稟窆窇茇荜荸莂刨别剥办勃包匕北匾半卑博卜卞卟变叭躄軷輩輽闭阪陂陛翉耙聛背胞醭釆釛釟鈵鈸鈽鉍鉼鋇礡礴驳骠骳髀髆髉髌髕髟髲鬂鬓鬢魃搏搬摆摈摒播擘敝斌斑絆絣砏不丙逼遍錃鏢鏰鄙便标梆棒榜槟檗菝菠菢萆葆狴玢玻班璧瓣瓿甏钚钡钣钯钵钸钹铂铋脖脿箥箯篦篰篳簸簿螕蠙补表锛镈并庇庳弁闆缏编缤缽罢罷罼砭煲煸爆爸版狈龅鼻鼥鼊鼈麃鹎鹁鸨鷩佰鳪邲邴邶鳊鲾鲍鲌鲅鲃鱉鮊鮅鮁驆雹霸靶鞛憋舨般舭舶艊艑艕芭臏臕苾捭掰八兵駮饆餔竝笆笔笣笨鞴鞭閟閉镳嘣坂坌坝埔埠堡镚镖镔镑邉邊吡吧呗哔哺啵邠邦邫汴谤豰豳豹貏貝賁賓賲贁贆贝败贬贲赑鳔鳖鐴鐾鑣鑮鵏詖誖諘伴伯亳宾宝蝙悲藵惫步傧傍悖怖忭必彼彬彪弼弊蓖蓓白百睥臂粑粊粃頒倍俾保扒扁趵跁跋跛跸踄踣蹦蹩蹳壁备夯奔妣婊婢嬖孛孢冰粺膀膊膑膘蒡袌袐袚被補裨裱褊褒褓褙襞襬覇覍觱愎捕捌拨拜拔拌拂抱报把扳扮蔀蔔蔽蕃薄薜薭藣藨蚌蜌屏岜崩巴币布帛帮螁苯苞苄部辟辦辨辩辫辯边迸逋避殡比毕毖毙辈",
|
||||
c = "沈沉沧测涌涔衬衰颤飭晁晟晨曹曾朝杈材村杵枞枨縩繟纏纒纔纯绌绰飺餈淙淬淳饬馇馋馳謥謿词诚诧谄谌硟碀碜碴磁磋磣滁漕潮潺澄澈澶灿炊炒炽焯筂策筴筹䌷畅畜畴疮疵痤痴瘁瘛瘥瘳祠禅禇禪秅秤称程稠稱穪穿窗窜茈茌茐茝茞茨茬茶茺荈草出刍创初刹刺剿匆匙卒厂厕厝厨参叉叱吃蹿躇躔躦車軙輳輴车闡闯阊阐陈陙除羼翀翅翆翠耖耡耻聡聦聪聰肏肞肠絺絾綝綴綵酢酬酲醇醊醋醕醜醻采重鈂鉏鋋磪磭礎礤驰骋骖骢骣鬯魑掺插揣搀搋搐搓搽摧撑撤撮撺操擦攒敕敞斥昌春昶純紣紬絒砗䲠丑丛丞串逞遄鋹錞錯鍐鍤鎚鏟鏦鏿郴鄛鄽酁酂侪促查柴柽棰椎椽椿楂楚楮榇榱槌槎槽樗樘橙橱檫次莼莿菖菙菜萃萗葱蒇猖猜猝猹琛琮璀璁璨瓷钗钞铖铛胵脆脞脣脨脭脺腄箠箣篡篨篪篸簇籌瞠螥螬螭蟬蟲蟾蠆蠢蠶衝衩铲铳锄锉错锤锸幢床廛閳閶绸缀缞缠础爨龊龀齿齼齣齜齒鼂黪黜麨麤麎麄鹾鹚鹑鸱鸧鸀鷘鶒鴟鴜鲿鲳鯙鯈驄驂陲陳雌雏雔雠憧憷舛舡舱船艖艙艚艟艸臎臣臭臿舂捶掣措催傺充冁册騁餐倡窾竄竌竾笞笧长長镵镲镩镡嗔嗤嘈嘬嘲噌嚓囱场坻坼垂城埕塍墀侈侧吵吹呈呲哧唇唱啐啜啻邨池汊汆谗谶豉豠豺貙財賜賨賰贂财赐鑱钏詞詧詫誠誴諂伺伧伥传仓从仇产残殂乘岔尝寸察宸宠蝉悴惆惝惨惩惭此储偿矬矗恻怵怊怆怅忱忡忖忏徜徂彻彩弛蓯蓛喳皴盛眵睉睬瞅瞋臅喘粣粚粗粎粋韂頳顀顣顫倘俦才赤赪趁超趡趩跐踀踅踆踌踔踟踧踩踸踹蹅蹉蹖蹙蹭蹰蹴戳尺尘处姹娼婵媸嫦存孱冲凑粲粹糍糙成腸膗膥膪膬膵蒭蒼袃裁裎裯裳褚褫觇触觸愁慈挫持拆抽抻抄承扯蓴蓸蔟蔡蔥蔯薋藏處虘虫虿蚕蚩蛏蜍层岑崇崔崭嵯巛川巢差帱常蝽腠苍苌苁芻郕郗鋤辍辏辙辞辭辰辵迟遲毳氅氚",
|
||||
d = "沌沓洞涤淀顿颠朵杜柁緞締繨纛绐绖绦淡渎渡饤饳饾諦讀讜讟订诋诞读调谍酊酖硐碇碉碓碘碟碠碡碭碲滇滴澄澹灯炖点焘煅等答电甸疔疸痘瘅瘩癜祋祶祷禘禫禱秺稲稻窎窦荅荙荡荳荻凳凼刀刁到剁动单叠叨叮叼吊躉躭躱躲軃軇軧轪闍阇阘队阽陡翟翢耊耋耑耵耷耽聃聜聸肚胆胨釘釣釱鈍鈿磴磸磹磾礅礑驮骀骶髑髧鬄魛揲搭敌敦斗断旦糴絛石砀砘丁东丢逮逿遁道達錠鍍鍛鎝鐓郸都鄧柢栋档棣椟椴菂菄菧菪萏董葮蒂玎玳玷瓞甙钝钿铎铛铞胴脦脰箪箽篤篴篼簜簞簟簤簦簹瞪蠹衟铤铥铫锝锭锻镀底店度缍缎缔羝砥爹牍牒犊狄独齻鼑鼎黮黩黨點黛鸫鸟鶫鶇鵰鴭邸鲽鲷鱽鯛鮘陮隄隊隝隥雕電霘霮靛靮靼鞑憝懂舵艜芏掂掇掉掸儋兑党兜典駳饏餖竇端竺笃笗笛笪第籴鞮閗镻镫镦嗒嗲嘀嘟噔囤地坫坻垌垛垤垫埭堆堕堞堤堵镝侗邓吨呆呔咄咚哆哒哚啖啶喋鐤谛谠豆貂貸贉贷赌赕鑟钉钓詄誕調低但佃代仃殚殆歹丹对定宕蝀蝶悼惦惮惰傣短恫怼怠怛忉德得待当弹弟蓧蓞癫登的盗盜盪盯盹盾眈眣眰睇督睹膽导韥頂頓顛顶倒打趤趸跌跶跺踮踱蹀蹈蹎蹬蹲蹾戴墩多大夺奠妒妲娣嫡冬冻凋戥蒧袋袛裆裰褋褡襌襶覩觌觝訂訋捣挡担抵抖蔸虰蚪蛋蜑蜔蜨岛岱岽峒嶝巅帝带帱腣腚达迨迪迭追逇递逓逗遞遯段殿毒氐氘氡氮",
|
||||
e = "洱颚额餌饵饿譌讍讹诶厄軛輀轭阏阨阸阿而耳鈪礘摁遏鍔鄂莪萼珥钶铒胹锇锷鹗鹅鸸鶚鵝佴鳄鲕鰪隲儿餓嗯噩垩邇呃哦谔貮贰詻恶恩睋二粫頋頞額顎俄趰尔娥婀腭蒽訛愕扼蕚薾蚵蛾屙峨苊迩",
|
||||
f = "沣沸法泛浮涪風风飛飞服枋枫緋緐縛縫繁繙纷纺绂绋绯飯飰淝饭馥馮讣讽访诽酆酚碸滏烦烽焚筏甫畈番疯痱祓祔福禣稃茯茷凫分副匐匚匪反发軓軬輔輻轓阀阜防附羵翂翡翻肤肥肪肺綍綒酦醱釜釡鈇鈖驸髣髪髮髴魵放敷斐斧方紛矾丰逢鍑鐇鄜鄷桴梵棼榧樊莩菔菲葍葑玢珐钫脯範篚簠蠜蠭锋镄幞幡府废缚缝缶缻罘罚罰砜砝砩燔父犯狒鼢鼖黼黻黺麸麩鷭鶝鳳鳆鲼鲱鲋鲂鰟鯡鯆鮄騛雰霏非舫艂艴芙芬芳茀范符笰笲籓坊坟垡吠否吩呋呒咐唪啡邡鐨汾豊豐豧負販費賦负贩费赋赗赙钒佛伐伏仿份付乏富蝮蝠悱傅偾怫忿彷弗盕盽眆瞂封粪粉韍韨頫俸俯俘扉房赴趺趽跗蹯复夫奉奋妃妇妨孚孵冯凡凤糞腹膚膰膹袚袝袱裶複褔襆覂覅覆訃訉訜訪愤拊拂抚扶蕃蕜蕟蕡蕧薠藩蘩虙蚡蚨蜂蜉蜚峰帆幅腓腑腐苻芾郙郛鋒辐返氛氟辅",
|
||||
g = "沟沩沽泔涡涫衮顾晷更杆杠杲构果枸柑緄緱緺縆縞纥纲绀给绠绲淦馃馆馉馘謴诂诖诟诡该诰酐碽磙港溉滚澉灌炅炔筈筦筶筸筻箇箍㭎疙疳痼祪祮祰祻祼禞禬秆稈稿穀窤茖茛荄刚刮刽刿剐割功勾匦匮卦古句各合躬軌輥輨輵轂轕轧轨關闺阁陔羮羹耇耈耕耿聒聝肛肝股肱胍綱綶酤釭鈣鉤銧骨骼骾高鬲鬶鬼掼搁搞搿擀改攻故敢旮旰紇紺絓給矸丐个過遘鋼錧鍋鎘鏆郭柜栝根格桂桄桧梏梗棍棺椁概槁槔橄莞莻菇菒菓菰葛葢蒄猓珙瑰瓜甘钙钢钩钴胱胳脵箛管篙篝簋簳鲠鲧瞡螝铬锅锆锢干广庋庚缑缟缸罁罆罐罟罡罣羔羙硅硌龟龜龚牯牿犷狗龔鼓鹳鹘鹒鹄鸽鸹鸪鷱鶊鴰鴣鴐鳤鳡鳜鲴鲑鱥鱤鱖鯝鯁鯀鮭鮌騧騔隔雇雊雚革鞏戆戈舡舸艮芥臌臯苽掴光公共关冈館竿笐笟笱鞲閤閣関嗝嘎嘏噶固国圪圭坩垓垢埂埚塥镐镉供告呙呱呷咕咣咯哏哥哽哿汩汞邽谷貢貫貴賅賈賡購贡购贯贵贾赅赓鳏鑵钆鴿訽詬詭該詿誥佝估仡乾乖寡宫官宄蝈蜾藳惯傀矔瞽冠恭怪归彀弓蓕歌癸皈皋盖盥盬睴睾亘粓韟顧倌扛赣赶趕跟跪尴尬尜尕够妫姑媾孤粿糓糕戤膈膏膕膭蓇蓋袞袧袬袼袿裓裹褂褠襘規覯觀观规觏觚觥感挂拱拐藁虢虼蚣蛄蛊蛤岗岣崞崮工巩帼腘苷苟郜辜过逛毂轱辊",
|
||||
h = "沆沪河泓洄洪洹活浍浑浒浣浩海涣涵涸颃颌颔颢晃晖晗晦曷杭緩縠繉繣繯红纥纮绗绘餀淮混饸馄諱譁譓護讙讧讳诃诙话诨诲湖湟溷滑滹漶潢濠瀚灏火灰烀烘烩焊焓焕煌画痕痪瘊癀祜祸禍禾秳秽穢穫茴荒荟荤荭荷凰函划劐劾化卉华厂厚号合后軣輝轋轘轟闳阂阍阓阖阚阛翃翙翚翬翭翮翯翰耗肓肣胡綄綔酣酼醐醢釪銲鋎磺礉礐驩骅骇骸骺鬟魂魟魱魺魽撼擐攉斛旱昊昏紅紇紘紭絙砉逭遑鍧鍸鏸鐄鄗鄠侯核桁桓桦槐槲横欢获菏菡華萈萑葔葟葫葷猢猴猾獾环珩珲琥瑚璜瓠钬胲脝篁篌篲簧籇鲩瞣螖蟥蟪蠔蠖蠚行衡铧铪锪锽锾幻閽缋缓缳罕龢煳狐狠龁齁鼾黒黑黉黄黃麾麧鹱鹮鹤鹘鹖鹕鹄鸿鸻鸖鶴鶡鴴鳠邯鳇鲎鲄鱯鰉鯶鮰驊隍隳隺雘霍靃鞎鞨憨憾航艎艧花芴臛餱餬餫竑竤笏笐閧閤镮镬嗥嗨嗬嘿嚆嚎嚯回囫圜坏堠镐還邗吓含吭吼呵呼和咳咴哄哈哏哕哗哼唤唬唿喉喊鐬汞汗汉汇郈郇郃谎谼谽豁豗豢豪貉貋貛貨賀賄货贺贿鑊鴻詤詥詪詯話詼何会伙亨亥乎寰寒害宦宏蝴蝦蝗惑惚惠蘅藿惶患悔悍恨恢恚恒恍怙怀忽徽徨後徊很彗弧弘皇皓盇盉盍盒睅睆睧睺喙喝互粐韓韩頜頦頷顪顸候扈戽赫趪踝户壑壕壶夥夯奂好婚孩冱粭糀糇糊或膴蒿袆袔褐褱覈觳訇訌慌慧换捍挥护蔊蕙蕻薃薅薨藧虎虖虝號虹虺虾蚘蚝蚢蚵蚶蛔蛤岵幌郝鋐迒还迴逅毁毫氦轰轷辉",
|
||||
j = "沮泾洁洎洚津浃浅浆浇济浚浸涓涧衸颈颉颊颎飓飢晋景晶暨机杰极枧架枷枸緊緝績繋繝繫繳繼纠级纪经结给绛绝绞绢继绩飷餃渐渠饥饯饺馂馑駃駉駕謇講謹謽警计讥讦记讲讵诀诘诫酒硷碅碣碱湔湫溅激灸炅炬炯烬焦煎筊筋筥简箋箕甲界畸畿疆疖疚疥疽疾痂痉瘕瘠祭祲禁禨秔秬积秸稘稭稷稼稽穄穊積究穽窌窖窘窭茄茍茎茧茭荆荊荐荚荠荩击刭剂剑剞剧剪剿劂加劫劲匠即卷卺厥厩及句叫叽吉躩車軍輂較轇轎车间阄阱阶际降羯翦耩聚肌肩肼胛胫絭絳絶絹經綗酱酵醮醵金釿鉀鉅鉴磯礀礁礓驥驹驾骄骏骑骥骱髻鬋鬏揪揭搅搛撅攫救教敫敬斤旌既旧糺系紀級紤紧絅結絕絜絞給矶䴔䴖䴗進鋸鋻錈錦鍓鍳鍵鎸鏡鐍鐎鄄鄿柜柩柬校桀桔桕桨桷检棘椐椒楗楫楷榉榘槛槿橘橛莒莖莙莢菁菅菊菌菨菫菹葌葏葭狷獍獗玑玖珈珏琚瑾钜钧钾铗胶脊脚脛脧脨脻腈箘箭箿節篯簡簥簴籍鲣蟜蟨蟭蟼蠒蠲街铰锏锔锦锩键锯廑建閰缄缉缙缣缰缴罝罥罽羁羇羈砛爝爵犄犋犍犟狙狡龃齽齑齏鼱麚麖麕麏麉麇麂鹼鹫鹪鹣鹡鹃鸡鸠鷲鷦鶺鶄鵳佳佶佼鴃鴂鳩鳉鲸鲫鲪鲛鲚鲒鱾鱂鱀鰿鰹鰜鯽鯨鯚鮈驚驕騎階際隦隽集雋雎雞霁靓靖静靜靳鞊鞠鞫憬戋舉舊舰艍艦艰艽节芥芨芰芵臮臶臼舅据捷掎掘接掬僦僬僭僵儆兢具兼冀駶饑借倦竞竟竣竭競竸笄笈笕笳笺鞿鞯鞬間閒镢嗟嘉嘏噍噘噤噱嚼囝圈圾均坚基堇境镜镓镌侥君咀咎咭哜唧啾喈鐧鐖汲江郏郊谏谨谫谲谻豇豭貑貗賈賮賷贱贾赆赍鳒鐻鑑钁钅钑鵁鵋詨詰誡諓伽伎价件介今仅京交亟歼乩九久举尖寄寂家蝍悸惊惧蘔矯矫矩矡矜矍瞿瞼瞷恝急忌徼径强蓟䝼䦆皆皎皲监盡監眷睊睑睛睠睫臇臄井军将粔韭韮韲頰頸倔俱俭俊扃赳赽趄趌趝足趹趼跂跏跔距跡跤践跻跼跽踐踖踘踞踦踺踽蹇蹐蹟蹶蹷蹻就墼夹奇奖奸妓妗姐姜姣姬娇娟婕婧嫁嫉孑孓季决净减几粳粷精糨戬截戢戟戛戒倨腱腳腵膠膲蒋蒟蒹蒺衿袀袈袓袷裥裾褧褯襟覊見覐覚覠覵覺见觉觊觐角觔觖解觭觼訆計訐記訣健偈假捡捐捃挤挢挟拮拣拘拒抉技蓳蓵蔣蕉蕝蕨蕳蕺薑薦藉藠虀虃虡虮蚧蛟蛣蛱蛶蜐蜠尽局居届屐屦岌螀岬峤峻崛嵇嵴巨己巾苴苣郟郡辑近进迥迦迳迹逕遽殛毽轿较",
|
||||
k = "浍颏颗暌枯纩绔馈馗駃诓诳课硱碦磕磡渴溃溘炕烤筈筐筘筷㧟疴科秙稇稛稞空窟窠窥凯刊刳刻剀勘匡匮卡口叩可躻躿軠軻轗闚闶闿阃阔阚考聧聩聭聵肯肻胩綑酷醌釦鈧銙礊礦骒骙骷骻髁髋髠髡魁揆揩旷昆絓矻矿砍逵鄶鄺侩柯栲框棵楷槛葵蒈狯珂琨钪钶铐铠胯脍腃箜篑簻籄铿锎锞锟锴库康廓开闊缂砢硁硄龛犒狂龈齦黋鹍鷇鵼鵟佧侃鲲鲙鲓鱇騍隗霩靠鞚鞟慷艐芤臗控克饋餽倥窾竷開嗑困圹坎坑块坤坷垦垮垲堀堪侉吭咔咖咳哐哙哭啃喀邝郀邼豈贶鑛誇課佝会伉亢寇宽客蝌蝰傀矙瞰悝悃恺恳恪恐忾快款喹喾盔看眍眖眶睏睽瞆喟亏頍頦顆顑顝扣扛趶趷跍跨跬蹞壳夔夸夼奎孔况糠戡蒉蒯袴裈裉裤裩褌褲愦愧慨捆挎拷括抠抗扩蔲蔻薖虧蛞蜫尻岂岢岿崆嵌苦苛瞌郐适氪轲",
|
||||
l = "沥沦泐泠泪泷泸泺洌洛流浏浪涝涞涟淋颅领飀飗晾朗李来林枥柃練縷繂繆繚繿纍纑纶练络绫渌饹馏馿謧謱讈讕论诔谅硫碌磊磠磥溜溧滤滥滦漉漏漓漤漯潋潞潦澜澧濂濑灵炉炼烂烈烙笿筣箓箖䁖留略疗疠疬痢痨瘌瘘瘤瘰癃癞祾祿禄禮离秢稂稆稑稜茏茢荔荖荦莅列刘利剌力劣励劳勒卢卤卵历厉厘另叻吏魿躏躐躒躘躶躼輘輪轥轮闌闾阆阑陆陇陋羷羸翎老耒耢耧耮聆聊聋联聫聯聾肋胧胪絽綟綠綡綹酪酹醁醨醪醴醽里量釐釠鈩鈴銮磷磿礌礨礱礼驡驢驴骆骊骝骡髅髏鬑鬛鬣鬲鬸魉揽搂摞撂撩撸擂敛斓料旅旒旯糷累絡絫䴕两临連逦逯逻遛錄録鍊鎌鎏鎘鎯鏈鏍鏐鏕鏤鏻鐐鄰酃柳栊栌栎栏栗栳栾梁梨棂棱椋椤楝楞楼榄榈榔榴橹檑檩莉莨莱莲菉菕菞菱萊萝落葎葻狸狼猁猎猡獠率玲珑珞琅理琉琏琳璃璐瓴铃铑铝脔脟脶脷脸脼腀腂箩篓篥篭篮篱簍簏簕簬簼簾籁鲡鲢鲤瞜螺蟧蠃蠊蠝蠟蠡蠣蠪衖铹铼链锂锊锍锒锣镂庐廉廊廖廪弄閬閭绺绿缆缕缡缧缪缭罍罗罱罶罹羀羅羉羚砢砬砺砻砾裏熘燎牢犁龙龓龒龍龄齢齡黧黎麟麜麗麓麐鹿鹭鹩鹨鹠鹂鸾鸰鸬鸓鸁鷺鶆鵣佬鴒鳢鳞鲮鲈鲁鱺鱲鱧鱗鰡鯬鯩鯏驘驑驎騾騼陵陸隆隣隶隸雒離雳零雷霖霝霤露霳靈靓鞡懒懔舲舻艆艣艫良芦臘臚臨捩掠掳僚儡六兰騄駵窿立笠笭笼籙籠籣籬鞻镴镰镭镧镥镣镠嘞嘹噜囵囹坜垃垄垅垆垒埒塄镙镏例侣邋邌邏吕吝呖呤咙咧咯哩唠唳啉啦啷喇鐳鐮郎邻谰豂豊貍賂賚賴赁赂赉赖鳓鑘鑢鑨鑪鑾钌鴷鴼詅詈諒論伶伦令仑仂亮殓乱乐丽尥寮寥蜽蝼蘆藺藶傈矝瞵瞭悝恋怜徕律录蓮蓠蓝蓏喱喽盝盠眬睐睖睙睞睩臉臈臁了粩粦粝粚粒类韷領類顟顱俪俩俚俐戾赲趔跉跞路踉蹓蹽墚奁姥娄娈娌婪嫘嫠孪冷冽凉凌凛粮粱粴粶粼糧戮膂膋膟膦膫蒌蒗蒞袊裂裏裡裢裣裲裸褛褴褵褸襕襱襽覙览愣偻倮捞捋挛拦拢拎拉抡蓼蓾蔂蔞蔺蕌蕗蕾薐藍藘藜藞藰蘭蘿虏虑虜蛉蛎蛚蛯蜊蜡屡履岚岭峦崂崃嵝嶙帘螂腊苙苓苈郘郞郦鋫鋰辌辘辚辢辣辽连遴氇氯轳轹辂辆",
|
||||
m = "沐沔没沫泌泖泯浼颟暝暮曼朦木末杩杪枚某綿緍緜緡緲縻繆绵淼渑馍馒馬駡謀謨謩謬谋硭碼磨渺湄湎溟满漠漫漭灭焖瘼祕祙祢禖禡禰秒秘秣穆穈茅茆茉茗茫茻荬劢勉勐募卖卯名门闵闷闽陌耄耱酩酶醚醾醿鈱銘鋂礞祃马骂髦髳鬘鬽魅魔描摩摸摹敉敏旄明昧昴码万錨鍪鎇郿鄍鄚鄤鄮鄳梅梦棉楣模檬莓莫莽莾萌葞猕猛猫猸玛玟玫珉瑁甍钼铆铓脈脉脒篃篾簢瞐瞑瞒瞞瞢螞螟螨蟆蟊蟒蠎蠓蠛蠻铭锚锰镁镅镆幕庙缅缈缗缦缪罙罵美砪煤熳牟牡牦牧犸鼏黾黴默麿麽麼麻麵麰麦麥麋麊鹛鹋鸣鸍鶜鶓鴓鳴鳘鰻鰵驀雮霂霉霾靡面靣靦靺鞔鞪懋懑懵艋艒艨芈芒茂免駹饝饅笀米閔門嘛嘧坶埋墁镘侔邁邈吗命咩咪哞唛邙汨谜谟谧谩谬貉貊貌貓貘買貿賣贸鳗钔鴾詸詺们仫亩殁买乜么寞寐密宓蝥悯蘑矛矒矊矇矀忙弭弥蓦喵皿盟目盲盿眀眄眇眉眊眜眠眫眯眳眸睦瞀瞄瞇冕冒粖跊墓墨妈妙妹姆姥娩媒媚嫫嬷孟冥糜糢腼膜蒙蒾蓂袂袤覓覛觅愍慕慢拇抿抹扪蔄蔑蔓蔴蕒藐藦蘪蘼虋虻蚂蛑蛖蛮蜜蜢岷峁嵋帽幂幔苠苜苗芼迈迷母每毛民氓",
|
||||
n = "泞泥浓涅淖衲颞暖曩纳纽馁馕讷诺碾溺甯男疟秾稬茑努匿南輗輦闹陧羺耐耨聂聍聣聶聹聻肭酿醲釀釢鈉鈕驽鬧搦撵攮旎昵糵納紐錗鍩侬柠柰楠菍萘猊猱瑙钕钠钮铌铙胬能脑脓脮脲脳籋螚蟯蠥衂衄锘年廿弄砮硇秊牛狃狞齯齧鼰鼐黏麑鸟鲶鲵鲇鯘鯓鮾鮎难難霓靵靹懦艿臑臬捺捻傩内餒嗫囊囔囝囡坭垴埝镎镍镊呐呢呶咛哝哪啮喃那貎鑷誽你佞伲乃宁孽蝻矃您恼恧恁怩怒忸念弩眲睨膿喏农粘顳赧跈蹑蹨奈女奴奶妞妮娘娜嫩嬲孥孬凝糯糱倪腻膩袅袦褦襛捏挪挠拿拧拟拗拈扭蔦蔫薿蘖虐蛲蜺尼尿腩腦苶苧辗農迺逆氖辇",
|
||||
o = "沤讴筽禺区耦鏂欧瓯鸥噢呕哦怄喔偶蕅藕殴",
|
||||
p = "沛泊泡泮泼派浦颇频颦飘普曝朋朴杷板枇枰縹纀纄纰淠馮譜譬评配硼碰磐磞湃湓溥滂漂潘澎濮瀑炮烹筢畔番疱疲痞癖票秠穙凭判刨剖剽劈匍匏匹匾叛叵魾軿輧闢陂陠翩翸耙耪聘聠肧胖胚酦酺醅醥醱釙磻礮骈骗骠骿髲髼鬅魄魒魸撇擗攀攴攵旁旆砒䴙丕錋鄱便棚榀榜莆莩菐菩萍萢葡葩珀琵琶璞瓢瓶甓钯钷铍胼脬脯脾篇篷篺簰瞟瞥螵蟛蟠蠙铺锫平庀庖庞闆缥缾罴砰砲破爬片牌牝犏狍龎鼙麅麃鹏鸊鷿鵬佩邳鲏鲆鰟鯆鮍騙陪陴雱霈霹鞄舖般芃芘捧掊排僻竂笸閛镨镤嘌嘭噗噼圃圮坡坪坯埔埤培堋堡否呸咆品哌啤啪谝谱貔貧贌贫赔鳑钋評仳仆殍乓乒蘋怦怕徘彷彭蓬喷皤皮盆盘盤盼眅睥粕韼頗頻俳俜扒扑扁趴跁跑跘跰踫蹁蹒蹣蹼姘娉婆媲嫔嫖冯膀膨蒪蒲袍袢裒裨裴褜襻偏拼拚拍披抨抛批扳蓱蚍蚾蛢蜱屁屏帔帕螃腗苹苤郫鋪鋬辔辟迫逄毗氆氕",
|
||||
q = "沁沏泅泉泣洽浅淇衹颀颧晴曲朐期权杞枪柒緝繑纃纤绮清渠謦讫诎诠诮请酋硗硚确碁碃碕碛碶確碻磜磩溱漆潜炔炝筇筌签箐䓖畎畦痊瘸祛祺禥禽秋秌秦稽穐穷穹窃窍窮茄茕茜荃荞荠切券前劁劝劬勤区千卡却卿去取躯軀軡輕闕阒阕阙阡陗群翑翘翹耆聺肍肷胊胠絿綅綦綮醛釥鈆鈙鉛銎銓銶磬磲磽礄礐祁祇祈驱骎骐骑骞骹髂鬈鬐鬿揿搴撬擎擒敲旗絟矵砌䲡七且丘逡遒錆錢鍥鍬鎗鏘鐑鄡鄿侨侵栖桤桥棋椠楸榷槭樯樵橇檎檠欠菃菣菦菬菳萁萋萩葋葜葲葺球琦琪琴琼璩钤钦钱钳铅脐箝箞箧篟篬簯簽瞏瞧螓螧蟿蠄蠷蠼衢铨锓锖锜锲锵锹庆廑弃閴闃绻缉缱缲缺罄羌羗羟硂硈龝牵犬犰龋齲齐齊鼩鼜黥黢黔麹麴麯麡麒鹙鹐鹊鸲鶈鵲佥邱鳈鳅鲯鲭鰽鰸鰬鰜鰌鰁鯖鯕鮼鮨驅騫騎雀青鞒鞘鞦鞧憔憩艢艩芊芍芎芑芡芩芪芹臍臞掐掮全其駸竅竏竒笉籤镪嗪嘁噙器囚圈圊圻堑邔吣启呛邛鐰汽汔求郄谦谯谴豈赇鳍钎詮誳諆請企仟仇亲亟乾乞乔寝蝺蝤悭情惬愀瞿悫悛悄恰怯强歉欺䞍癯睘亓将韆頃頎顣顴顷俟俏俅扦起赹趄趋趣趥趨趬跂跄跒跫跷踆踡踥蹊蹌蹡蹻墙壳奇契妻妾娶嫱凄粸糗戚戗戕倩蒨衾袪裘裙褰襁親觑觓訄訅愆慊倾挈拳抢歧蔷蕁蕉蕖蕲薔藑藭藮蘧虔虬虯蚈蚑蚯蛆蛐蛣蛩蛬蛴蛷蜞蜣蜷蜸蜻屈屺岂岍岐岖峭崎嵌巧巯腔苘郪迁迄逑遣遷氍气氢氰轻辁",
|
||||
r = "洳润颥飪朊枘染柔緌緛繞纕纫纴绒绕餁饪饶讓认让讱溶溽濡热然禳秹稔穃穰茸茹荏荛荣刃軔軟軠轫闰阮耎肉肜肰鈤銳礽髥髯魜揉攘日紝絍絨鎔桡榕葇葚瑞瓤箬篛融蠕铷锐缛熔燃狨鶸鴧鳰靭靱鞣戎芮儒入冉駥饒閏嚅嚷镕汝鴽鵀認任仞仍仁人乳容蘂惹忍弱蓐睿喏冗韌韧顬扔蹂壤壬女如妊娆孺糅腬膶蒘蒻蓉衽袇袵袽褣褥襦訒偌扰蕊蕋蕘蕤薷蘘蚋蚺嵘蝾若苒鋭辱込遶软",
|
||||
s = "沈沙沭泗泷洒涉涑涩涮淑衫衰颂颡颵颼飒飔飕食飧晌晒晟暑曙朔术杀杉杓束松枢緦縮縿繀繌繐繕繖繩繬繸繺纱纾绅绍绥绱绳绶飼飾淞深渑渖渗饰饲馊馓首駛諡謏謚謪譅識譢讪讼设识诉试诗诜说诵谁谂谇谉硕碎碩磃磉湿溯溲滠漱潲潸澌澍濉炻烁烧筍筛筭筮筲㩳甩申畲疏疝痧瘙瘦祏祘神祟祳禅禗禠禩禪禭私秫稅稍税稣穑穗穟穯窣荪荽删刷刹剡剩劭势勺匙十卅升单厍厕厦厮叁参双叔受叟召史司身輋輸闍闩闪阇陕陝陞羧羴羶翛翜翣耍耜耸聖聲肃肅肆肾胂胜絲綀綏酥酸酾释釋釤鈰鈶鉂鉃鉐鉽銫示社祀驦驶驷骕骚骟髓髞髿鬆鬖鬙魫掺揲搔搜搠搡摄摅摔撒撕擅擞收散数斯施时是紓紗素索紳紹絁石砂䴓三上世丝丧逝速逤遂遈鍦鎍鎖鎪鏁鏉鄯柿树栓桑桫梢梭梳森椹榫槊莎莘莤莳菘菽萨葚葠葰葹蒁狲狻猞率珊琐瑟甚生甥铄铈脎脽算箾篩簌簑簨簺籂鲥鲨瞍瞓瞫螦螫蟀蟮術铩铯锁锶锼庶閯閷缌缩缫缮缲署砷煞煽熟熵燧爽牲狩狮鼶鼫鼪鼠黍麝鸶鸘鷫鷥鶳鶐鵨使鳾鳣鳝邵鳋鳃鲹鱣鱓鰰鰤鰠鯵鯋鯊鯅鯂鮻驌騻騷隃隋随隧隨隼雖雙雟霎霜霰靸鞖鞝戍舌舍舐舒舜舢艄艏艘艭色芍芟苼授傻僧僳兕兽騇駷餗竖竢竦笇笋笙笥籔籶閃嗄嗉嗍嗓嗖嗜嗣嗦嗽嗾嘶噬噻四圣垧埏埘埽塑塞塾侍邃吮呻咝哂哨唆唢售唰唼商啥啬善邥鐩汤汜汕邿谡谥豉豎豕賞賸贳赊赎赏赛钃钐設訴試詩詵誓說説誰佘似伺伸伤伞仨仕什殊殇书乘寺宿室审实宋守蝕蝨蘓蘇藷矧矢矟矂瞬悚恕恃思怂忪彡弑式蓡蓑蓍歙歃䥇䦂臊盛省眎眘眚眭睃睄睒睡睢事寿射粟粛韶順頌顺倏俟俞俗手扇所赡赦跚跾尸尚少墅墒士声夙失奢妁始姒姗姝娑娠婶嫂嬗孀孙孰凇糁糂腮膄膆膳膻蒐蒜蒴蓃袑裋裟裳褨襫視覗视觞觫訕訟愫慎慑损捎挲拾拴拭折抒扫死蔌蔎蔘蔬蔱蕂蕣蕵蕼薮薯藗虒虱虵虽蚀蛇蛥蛳蛸蜀蜃尿屎属山岁崧嵊嵩巳市帅师螋腨腧腎苫苕苏输辻述送适遡適殳毵毹氏水轼",
|
||||
t = "沓沱沲泰洮涂涕涛淌淘颋颓题颱飥飩暾替条柁緰緹縚縢縧统绦绨飳飸餂餇添饧饨駝駞謄謟讨讬调谈硐碳碵磄湍溏溻滔滕滩漯潭潼澹炭炱烃烫筒筩㳠甬田町畋疃疼痛痰瘫祧祹禔禢禫禵禿秃秱稌穜突窕茼荑荡荼凸剃剔厅叨台叹同躺軆軘闒闛闼阗阘阤陀陁羰耥聴聽肽胎統綂綈綎酞酡酮酴醄醍醣醰鈦鈿鉄銅磹祂驮驼骀骰體髫鬀鬄魋提搪摊昙紏絛䴘逷遆鋵鋾鍎鍩鍮鎝鎲郯柝桃桐桶梃梯棠椭榻樘橐檀莛莵菟菭萄萚萜葖葶獭瑭甜钛钭钽钿铁铊铜脫脱腆箨鲦螗螣螳衕铤铽锑锬庭庹廷閮绹缇罈砣砤砼煺特龆鼮鼞鼗鼍鼉黮黇鹈鸵鷵鷉鶟鶗佻鴫邰鳀鲖鲐鲀鰷鰨鰧鯈鮀驖騰騠騊陶隤霆靦鞓鞗舔舦艇艼臋臺掏探推掭僮兔駣饕餮童笤笹籐籜鞺镡嗒嗵嚏团囤图土坍坛坦坨堂堍塌塔塘填镗侗邆吐吞听呔唐唾啕啼汰汤汀谭豚貒貣貪貼贉贪贴鳎鐵钍鴺詷誊誻調談佟佗体佃仝他亭殄乇它惕傥矘瞳悌恸恬态忝忒忑忐徒彤彖弹弟蓨蓚盪眮眺臀粡粜粏韬頭頹頺題倜倘托趟趧趯趿跅跆跎跳跶踏踢蹄蹆蹋蹚蹹天太头套她妥婷糖糰腾腿膛膯袉袒袥裼褪襢覃覥觍討託慝偷停捅挺挞挑拖拓抬抟投蓷蕛薙薹藤藫藬蚒蛻蜓蜕蜩蜪屉屠屯峒帑帖苕苔迏迢迯退逃透途逖通遝遢毯氽",
|
||||
w = "沃沩洧洼浯涠涡顽晚晤望未杌枉緯纨纬纹维渥温謂讏诬误诿硙硪碔碗磈磑渭湾潍炜烷焐畏畹痦痿瘟祦禑稳穩穵窊窝窩窪茣莁凹刎剜务勿午卧卫危躛軎輐輓问闱闻阌阢翁翫聞聬肟胃維網鋈骛骩骫魍魏魰握文斡於无旺紊紋万丸逶違鋺鍏鏏鄬侮桅梧莞莴菀菵萎萬萵葦葨葳猥猬王玟玩玮琬璺瓦瓮钨脕脗脘螐螡衛铻庑閺閿闅绾罋网罔硊煨物牾龌齆鼯鼤鹟鹜鹉鹀鶲鶩佤鴮鳚邬鳂鳁鲔鰞鮪鮠鮇隈隗雘雯雾霚霧霨靰慰戊舞艉芄芜芴臒臥苿兀免餵嗡围圩圬坞吴吻吾呙呜味哇唔唯喂汶汪污谓豌貦誤諉位伪伟伍仵亡乌为寤宛完蝄蜿蝸蝟惋惘惟蘁悟恶怃忤忘微往弯䦟皖盌盳睕五尉韈韦韪頠顐尢外妄妩委威娃娓娩娲婉婺我腽蓊袜襪偎倭捂挽挝挖武歪蔚蔿蕰蕹薇薳藯蚊蛙蜈蜗蜼尾屋崴嵬巍巫帏帷幄腕苇辒迋迕违毋辋",
|
||||
x = "泄泫泶泻洗洫洵浔浠消涎淅淆须顼颉飨晓晰暄暇暹曛曦朽杏析枭枵緒緗線緤縀縣縼繋繫繻纁續纕纖纤线绁细绚绡绣绤绪续餉饧饩饷饻馅馐香馨馴諧謏謝譃譞训讯许诇询详诩诶酗硒硖硝硣渫渲湘溆溪溴漩潇瀣炫烯煊筅筊筱䜣畜痃痫癣祥祫禊禤禧秀稀稄稧稴稸穴穸窨茜荀荇荥荨凶刑削勋勖勰匈匣协卸厢厦县叙叶吁躞軐軒轩闟闲阋陉降限陜羡羲翈習翔翕翖翛翧聟肖肸胁胥絮綊酰醎醑醒醯醺銊銑銒銜銝銷鋅礂祆驯骁骍骧骱髤髹鬚魆魈魖魣揎携撷擤效斜新旋旬旭昔昕星显系紃細絃絏絜矽砉下遐錫鍟鎋鎴鏭鏽鄉鄦鄩柙校栩械楔楦榍榭樨橡檄欣欷莕莘莶菥萧萱葙葸猃猩献獬獯玄现玺瑕璇钘铉铏胸脅脙脩箫箱篂篠簘簫鲞鲟瞎瞓螑蟂蟋蟓蟢蟳蟹蠍蠏血衅衋行衒衔衖衘铣铦销锈锌锡锨幸序庠庥廨闀缃缬缷罅羞硎龤煦熄熊熏熙熹燮燹牺犀狎狭齛鼷黠鹹鹇鸺鸴鸮鸂鷽鷼鷴鷳鴞鳛鲜鱻鱮鱌鰕鯗鮮騽险陷隙險隰雄雟雪需霄霞霫霰靴鞋懈戌興舋舷舾芎芗芯臐臖臭舃舄掀像僖儇兄先兮兴駨饟饈餼窸笑閒閑镶嗅嘘嘻嚣囟圩型埙镟侠邂向吓吸呷咸咻响哓哮唏啸邢邪汹汛汐郋郇郄谐谑谖谞谢谿豀象豨貅賢賯贤鳕鑫鑲鑴鴵鵂訮許訹詗詢詨詳詾諝諠休仙亵享殉乡习宿宵宪宣蝑蝎蝦悻惜想惺矎矄悬悉息恤恂性忻心循徙徐徇形弦歇歆皙盢盨盱相盻省県眩睍睎睱睻喜喧些写寻小粞籼韰韱響項須頊顕顨顯项修信跣跹踃踅蹊墟夏夕奚姓娴婿媳嫌嬉孝学冼粯糈糦糮戏腺膝膮蓄蓆袕袖袨袭裼褎褏褶襄襲西見见觋解觽觿訊訏訓訙訢偕挟蓰蓿蔙蕁蕈蕦蕿薂薛薢薤薪薰藇藓藖藚藛虈虗虚虛虲虾蚬蚿蛸蜁蜆蜥屑屣岘岫峋峡崤巡巷巽希席螅腥腊苋郗郤辖辛迅选逊逍遜選氙",
|
||||
y = "沂沅油沿泱泳洇洋浴涌涯液预颍颐颖颙颜颺颻飏飖晏晔晕曜曰曳月有杨杳緣緰緷緼縁縈縕縜縯繇繧纡约纭绎飮飲餆養淤淫淹渊渔渝饫饮饴馀馧馻駚諹諺謚謠謡譯議譻譽讉讌讛讞议讶译诒诣语诱谀谊酉硏硬硽碒游湮源溢滟滢演漪漾潆瀛瀹炀炎烊烟烨焉焰焱煜筠筵筼䜩甬由畲疑疟疡疣疫痈痍痒痖瘀瘐瘗瘾瘿癔祎祐祤禋禐禜禦禹禺秧秵移穻窅窈窑窔窨窫窬窯窳茔茚茵荑荢荧荫药刈刖剡劓勇匀医卣印压厌原厣又友右叶吁吆躍軅軈軉軏輰輶輿轝轧闫阅阈阉阎阏阳阴院羠義羭羽羿翊翌翳翼耀耘耰耴耶聿肀肄肊肙育肴胤綖酝酳酽醃醖醞醫醼釉野釔釪鈆鈨鈺鈾銀銦鋆鋊礇礒礜礿祅驛驭驿骃验鬱鬳鬻魇魚魣掾揄揖揠援揶摇撄攸於旖昀易映昱約紻絪砑研䲟䲣䶮一与业严丫逸逾遇遊運遗遙鋺錏錥鍚鍱鎰郵郾鄘鄞鄢酀柚样桠椅椰楹榆樱樾橼檐欤欲欹莚莜莠莸莹莺菸萤营萦萸葉葯葾狱狳狺猗猷猿玉珧琊琰瑗瑛瑜瑶璎用钖钥钰钺铀铔铕铟胭胰脜箢箷箹簃簷籆螘螠螢蟫蟻蠅衍衙衣铫铱银锳幺幼幽应庸庾延异閱閹閻閾闄闉缊缘缢缨罂罃罨羊羑羕砚龠熠熨燕燠爰爷爻牖牙犹狁龑龉龈龂齾齸齮齦齗鼹鼬鼘鼋黿黶黡黟黝麣麀鹽鹰鹬鹦鹞鹓鹆鸳鸯鸭鸦鸢鷹鷧鷣鷖鷃鷁鶢鶠鶍鶃佯佾鴳鴪鴨鴦鴥鴢鴉鳿鳶鳙邮鲬鲉鱿鱼鰫鯒鮨鮣鮋驗驈陨陰陻陽隂隃隅隐隩隱雁雅雍雨雩雲霠霣霪靥靷靿鞅慵懿舣艈艞艤艳艶艷艺艾芋芫芸臾舀舁舆與掖掩允元兖养駰饜饔饐餫餚餘餍竽笎笖籞籥籲镱嘤噎噫因园囿圄圆圉圜圯垠垣垭垸埏域埸堙堰塬镛镒侑依邑邕邘吖吟听吲呀呓员呦咏咦咬咽咿哑哕哟唁唷喁郁邺谒谕谚谣谥谳豫豷貐貤貽賱賹贀贇贋贏贠贻赝赟鳐鐷鐿鑰钇鴹詇詒詠詣誉誘語誾諛佣佚余佑伢伛优伊仰仪以仡亿亦殒殃也乙乐义么寓寅宴宥宜宇蝓蝇蝹蝣蝝蝘蘌藴矣矞矅悦悠悒恿恽恹恙怿怨怡怏忧忆徭御徉役影彦彝引弋弈蓥蓣蓔喻䥺盂盈益盐盓眃眏眙眢眼睚睪臆臃喑亚云于予粥粤粌韗韫音韵韺韻頁頉預頤頨頵顏顒顔顗願顩页倚俨俣俞俑赢越趯跃踊踦踰踴尹尧尤尢墉壅壹夜夤夭央夷奄奕妍妖妤妪姚姨姻娅娱婴媛媵嫣嬴孕冤冶粵腰腴膺蒑蒕蒷袁袘袣裀裔裕裛裺褑褹襾要觎言訑訔訚訝訞訡愈愉意愚愠愿偃挹拽拥押抑扬蓺蔚蕰蕴蕷薀薏薗藀藙藝藥虞蚁蚓蚖蚜蚰蚴蛘蛜蛡蛹蜎蜏蜒蜮蜴尾屹屿岈岩岳峄峪崖崦崾嵛嶷已蝿螈腪腌腋英苡苑苅苃芽郓郢郧辕迂迆迎运迓远迤迻逌遠遥遹遺邀殪殷毅毓氤氧氩氲永轶轺",
|
||||
z = "治沼沾注泽洙洲浊浙浞涨涿淄衷衹衼颛飐飦智暂曾最朕朝札术朱杂杖杼枕枝枣枳緅緵緻縂縋縡縱總繇繒織繜繤纂纉纗纘纣纵纸纻组织终绉渍渚渣饡馔駋駐諥諮諸謺譄證譔譗識譟譫譸讚讝证诅识诈诊诌诏诛诤诪诸诹诼谆酇酌酎酖碡磔磚湛溱滋滓滞漳潴澡濯灶灼灾炙炷炸烛筑筝筰箏箒甾畛疰疹痄症痔痣瘴瘵祖祗祚祝祩祭祯禃禇禌禎禔禛种租秩秭秲秶秼稓稙稚稡種稹穉穛穜穱窀窄窋窒茈茊茱茲茽茿荘荮凿则制助匝卒卓占卮只召躁躅躜躦転軸輈載輒輖輾轉轧转闸阤阯阵阻阼陟陣翐翟翥者耑职職肇肘肢肫肿胀胄胑胔胗胙胝絷絼綜綴酢酨酯醆醉醊醡重針鈡鉁鉄鉔銌磫磳礈礋祇祉驟驵驺驻骓骔骘骤髒髭髽鬃鬉魳揍揸搌摘摭摺撙撞撮撰擢攒攥支政整斋斟斩斫旃族旨早昃昝昨昭昼紂紖紙紩紫紮紵紾終組矺砖䦶丈专中造週鋷錐錣錾鍐鍼鎭鎮鏱鐏鄑鄟鄣鄫鄹酂柘柞查柱栀栅栈栉株栽桌桎桢桩梓棕棹植椎楂楱榛榨槠樟樽橥莊菆菑菚菹著葘葬葴蒃狰猪獐珍珠琢璋瓒甄甑钟钲钻铚胾脂脏脹箤箦箴箸篆築篜簪簻籀鲝瞕瞩螤螽蟅蟑蠈蠋蠌蠗蠩蠾術铡铢铮铸锃锗锥锧锱锺镃镇幛幢庄座综绽缀缁缒缜缯缵罇罩罪置罾羜砟砦砧砫砸硃照煮燥爪状龇齺齰齜齋齄鼅鼄黹麞麈麆鹯鹧鸼鸷鸩鸇鷷鷟鶎鴲鴙鳷鳣鳟邹鲻鲰鲗鲊鱵鱣鱄鯽鯮鯖鯔鯐鮡驙驇騿騺騣陬陼障隹隻雉雑雜霅震霑鞝憎戆舟舯舳舴艁芖芝芷臓臜臟臢臧自至致臻茁掌掷僮兆兹饘饌餦餟窼竈站竚章竹竺笊笍笜笫笮籒长長镯嘬嘱嘲嘴噪在圳址坐坠埴镞侄侏侦侧邅吒吱周呲咂咋咒咤咨咫咱哉哲哳唑唣啁啄啧啭鐲鐣鐘汁郅邾谘谪谮谵豬貞責貯貲資賊質賳賺贄贅贈贊贞责账质贮贼贽赀赃资赈赒赘赚赜赞赠鑄鑕鑽鑿针钊鵄診註証訾詀詐詛詝詟詹誌諄諈諏諑作佐住伫传众仲仗仔从仉仄殖乙乍之乇主寨宰宙宗宅宁藻惴止正矰知矚瞻恣总怔怎怍忮忪忠志徵征彰彘张蓙蓗蓔喳䦃皂皱盅盏盞盩直眕眞真眥眦眨眹眾着睁膼争再尊粧粥粢粘粍籽韴顓俎俊执扎赭走赵趑趙趦趮趱足趾跂跓跖跱踨踪踬踯踵蹔蹠蹤蹧蹱增壮奏奘妆妯姊姿嫜子字孜孳冢准粻粽糉糌糟战倬腫膱蒸蓁袗袟袠袩装裝製褚褶襍襢覟觜觯偬做值债捉振挣挝挚指拽拶拯择招拙拄折抓找蔁蔗蕞薝藏藢蘸蚤蚱蛀蛛蛭蛰蜇蜘展属屯峙峥崭崽嵫嶂州左帐帙帚帜帧帻腙苲苧苎郑鋕辎辗辙迍这迮追逐這遭遮遵毡氏轴轵轸载轾辀辄"
|
||||
},
|
||||
}
|
||||
|
||||
--[[ you may want to turn off the search regex from showing up
|
||||
in the header by putting this in `~/.config/yazi/init.lua` ]]
|
||||
function Header:flags()
|
||||
local cwd = self._current.cwd
|
||||
local filter = self._current.files.filter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
--- @since 25.5.31
|
||||
--- @since 26.8.15
|
||||
|
||||
local M = {}
|
||||
|
||||
@@ -23,11 +23,9 @@ local get_config = ya.sync(function(st)
|
||||
skip_symbols = true,
|
||||
-- if {"yazi-"}: f -> file, yazi-file
|
||||
skip_prefix = {},
|
||||
-- start: f -> file
|
||||
-- word: f -> file, also-file
|
||||
-- all: f -> file, also-file, twofile, elf
|
||||
search_location = 'start',
|
||||
fallback = true,
|
||||
aliases = {},
|
||||
keys = {},
|
||||
}
|
||||
end)
|
||||
|
||||
@@ -47,10 +45,19 @@ end
|
||||
---@type fun(self, config: FCharConf): nil
|
||||
function M:setup(config)
|
||||
set_config(tbl_deep_extend(get_config(), config))
|
||||
|
||||
local opts = get_config()
|
||||
for action, key in pairs(opts.keys) do
|
||||
km.mgr.rules:insert(1, { on = key, run = 'plugin fchar ' .. action })
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: process `--flags`
|
||||
function M:entry()
|
||||
---@param job Job
|
||||
function M:entry(job)
|
||||
if not job.args[1] then
|
||||
ya.err('type of search not specified')
|
||||
end
|
||||
|
||||
local cands = {
|
||||
{ on = '0' },
|
||||
{ on = '1' },
|
||||
@@ -132,6 +139,7 @@ function M:entry()
|
||||
|
||||
local loc = {
|
||||
start = '^',
|
||||
ext = '\\.',
|
||||
word = '(^|\\W)',
|
||||
all = '',
|
||||
}
|
||||
@@ -142,13 +150,15 @@ function M:entry()
|
||||
prefixes = prefixes .. '(' .. prefix .. ')?'
|
||||
end
|
||||
|
||||
local re = loc[opts.search_location]
|
||||
.. ((opts.search_location == 'start' and opts.skip_symbols) and [[\W?]] or '')
|
||||
local re = loc[job.args[1]]
|
||||
.. ((job.args[1] == 'start' and opts.skip_symbols) and [[\W?]] or '')
|
||||
.. prefixes
|
||||
.. '['
|
||||
.. cands[idx].on
|
||||
.. (opts.aliases[cands[idx].on] or '')
|
||||
.. ']'
|
||||
|
||||
-- TODO: fallback to word/all if no match was found
|
||||
if changed(re) then
|
||||
ya.emit('find_do', { re, insensitive = opts.insensitive })
|
||||
else
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
Copyright (c) 2024 boydaihungst
|
||||
|
||||
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.
|
||||
@@ -1,110 +0,0 @@
|
||||
# file-extra-metadata
|
||||
|
||||
<!--toc:start-->
|
||||
|
||||
- [file-extra-metadata](#file-extra-metadata)
|
||||
- [Preview](#preview)
|
||||
- [Before:](#before)
|
||||
- [After:](#after)
|
||||
- [Requirements](#requirements)
|
||||
- [Installation](#installation)
|
||||
- [For developer](#for-developer)
|
||||
<!--toc:end-->
|
||||
|
||||
This is a Yazi plugin that replaces the default file previewer and spotter with extra information.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Minimum version: yazi v25.5.28
|
||||
|
||||
## Preview
|
||||
|
||||
### Before:
|
||||
|
||||
- Previewer
|
||||
|
||||

|
||||
|
||||
- Spotter
|
||||
|
||||

|
||||
|
||||
### After:
|
||||
|
||||
- Previewer
|
||||
|
||||

|
||||
|
||||
- Spotter
|
||||
|
||||

|
||||
|
||||
## Requirements
|
||||
|
||||
- [yazi >= 25.5.28](https://github.com/sxyazi/yazi)
|
||||
- Tested on Linux. For MacOS, Windows: some fields will shows empty values.
|
||||
|
||||
## Installation
|
||||
|
||||
Install the plugin:
|
||||
|
||||
```sh
|
||||
ya pkg add boydaihungst/file-extra-metadata
|
||||
```
|
||||
|
||||
Create `~/.config/yazi/yazi.toml` and add:
|
||||
For yazi < v25.12.29 (29/12/2025) replace `url` with `name`
|
||||
|
||||
```toml
|
||||
[plugin]
|
||||
append_previewers = [
|
||||
{ url = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
|
||||
# Setup keybind for spotter: https://github.com/sxyazi/yazi/pull/1802
|
||||
append_spotters = [
|
||||
{ url = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```toml
|
||||
[plugin]
|
||||
previewers = [
|
||||
# ... the rest
|
||||
# disable default file plugin { name = "*", run = "file" },
|
||||
{ url = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
|
||||
# Setup keybind for spotter: https://github.com/sxyazi/yazi/pull/1802
|
||||
spotters = [
|
||||
# ... the rest
|
||||
# Fallback
|
||||
# { name = "*", run = "file" },
|
||||
{ url = "*", run = "file-extra-metadata" },
|
||||
]
|
||||
```
|
||||
|
||||
### Custom theme
|
||||
|
||||
Read more: https://github.com/sxyazi/yazi/pull/2391
|
||||
|
||||
Edit or add `yazi/theme.toml`:
|
||||
|
||||
```toml
|
||||
[spot]
|
||||
border = { fg = "#4fa6ed" }
|
||||
title = { fg = "#4fa6ed" }
|
||||
|
||||
# Table.
|
||||
tbl_cell = { fg = "#4fa6ed", reversed = true }
|
||||
tbl_col = { fg = "#4fa6ed" }
|
||||
```
|
||||
|
||||
## For developer
|
||||
|
||||
If you want to combine this with other spotter/previewer:
|
||||
|
||||
```lua
|
||||
require("file-extra-metadata"):render_table(job, { show_plugins_section = true })
|
||||
```
|
||||
@@ -1,371 +0,0 @@
|
||||
--- @since 25.5.28
|
||||
|
||||
local M = {}
|
||||
|
||||
local STATE_KEY = {
|
||||
ALTER_DF_COMMAND = "ALTER_DF_COMMAND",
|
||||
}
|
||||
|
||||
local set_state = ya.sync(function(state, key, value)
|
||||
state[key] = value
|
||||
end)
|
||||
|
||||
local get_state = ya.sync(function(state, key)
|
||||
return state[key]
|
||||
end)
|
||||
|
||||
local function permission(file)
|
||||
local h = file
|
||||
if not h then
|
||||
return ""
|
||||
end
|
||||
|
||||
local perm = h.cha:perm()
|
||||
if not perm then
|
||||
return ""
|
||||
end
|
||||
|
||||
local spans = ""
|
||||
for i = 1, #perm do
|
||||
local c = perm:sub(i, i)
|
||||
spans = spans .. c
|
||||
end
|
||||
return spans
|
||||
end
|
||||
|
||||
local function link_count(file)
|
||||
local h = file
|
||||
if h == nil or ya.target_family() ~= "unix" then
|
||||
return ""
|
||||
end
|
||||
|
||||
return h.cha.nlink
|
||||
end
|
||||
|
||||
local function owner_group(file)
|
||||
local h = file
|
||||
if h == nil or ya.target_family() ~= "unix" then
|
||||
return ""
|
||||
end
|
||||
return (ya.user_name(h.cha.uid) or tostring(h.cha.uid)) .. "/" .. (ya.group_name(h.cha.gid) or tostring(h.cha.gid))
|
||||
end
|
||||
|
||||
local file_size_and_folder_childs = function(file)
|
||||
local h = file
|
||||
if not h then
|
||||
return ""
|
||||
end
|
||||
|
||||
return h.cha.len and ya.readable_size(h.cha.len) or ""
|
||||
end
|
||||
|
||||
--- get file timestamp
|
||||
---@param file any
|
||||
---@param type "mtime" | "atime" | "btime"
|
||||
---@return any
|
||||
local function fileTimestamp(file, type)
|
||||
local h = file
|
||||
if not h then
|
||||
return ""
|
||||
end
|
||||
local time = math.floor(h.cha[type] or 0)
|
||||
if time == 0 then
|
||||
return ""
|
||||
else
|
||||
return os.date("%Y-%m-%d %H:%M", time)
|
||||
end
|
||||
end
|
||||
|
||||
-- Function to split a string by spaces (considering multiple spaces as one delimiter)
|
||||
local function split_by_whitespace(input)
|
||||
local result = {}
|
||||
for word in string.gmatch(input, "%S+") do
|
||||
table.insert(result, word)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function get_filesystem_extra(file)
|
||||
local result = {
|
||||
filesystem = "",
|
||||
device = "",
|
||||
type = "",
|
||||
used_space = "",
|
||||
avail_space = "",
|
||||
total_space = "",
|
||||
used_space_percent = "",
|
||||
avail_space_percent = "",
|
||||
error = nil,
|
||||
is_virtual = false,
|
||||
}
|
||||
local h = file
|
||||
local file_url = h.url
|
||||
local is_virtual = file_url.scheme and file_url.scheme.is_virtual
|
||||
file_url = is_virtual and (file.path or Url(file_url.scheme.cache .. tostring(file_url.path)))
|
||||
or (file_url.path or file_url.url)
|
||||
if not h or ya.target_family() ~= "unix" then
|
||||
return result
|
||||
end
|
||||
local output, child
|
||||
|
||||
local alter_df_command = get_state(STATE_KEY.ALTER_DF_COMMAND)
|
||||
if not alter_df_command then
|
||||
child, _ = Command("df"):arg({ "-P", "-T", "-h", tostring(file_url) }):stdout(Command.PIPED):spawn()
|
||||
|
||||
if child then
|
||||
-- Ignore header
|
||||
local _, event = child:read_line()
|
||||
if event == 0 then
|
||||
output, _ = child:read_line()
|
||||
end
|
||||
child:start_kill()
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback for macOS/BSD if -T failed (empty output or error)
|
||||
if not output or output == "" then
|
||||
if not alter_df_command then
|
||||
set_state(STATE_KEY.ALTER_DF_COMMAND, true)
|
||||
end
|
||||
child, _ = Command("df"):arg({ "-P", "-h", tostring(file_url) }):stdout(Command.PIPED):spawn()
|
||||
if child then
|
||||
local _, event = child:read_line() -- skip header
|
||||
if event == 0 then
|
||||
output, _ = child:read_line()
|
||||
end
|
||||
child:start_kill()
|
||||
end
|
||||
end
|
||||
|
||||
if output and output ~= "" then
|
||||
-- Splitting the data
|
||||
local parts = split_by_whitespace(output)
|
||||
|
||||
-- Display the result
|
||||
if #parts >= 7 then
|
||||
result.filesystem = is_virtual and (parts[7] .. " (vfs)") or parts[7]
|
||||
result.device = is_virtual and (parts[1] .. " (vfs)") or parts[1]
|
||||
result.total_space = parts[3]
|
||||
result.used_space = parts[4]
|
||||
result.avail_space = parts[5]
|
||||
result.used_space_percent = parts[6]
|
||||
result.avail_space_percent = 100 - tonumber((string.match(parts[6], "%d+") or "0"))
|
||||
result.type = is_virtual and (parts[2] .. " (vfs)") or parts[2]
|
||||
else
|
||||
result.filesystem = is_virtual and (parts[6] .. " (vfs)") or parts[6]
|
||||
-- result.device (Type) is missing in df output, fetch from mount
|
||||
result.total_space = parts[2]
|
||||
result.used_space = parts[3]
|
||||
result.avail_space = parts[4]
|
||||
result.used_space_percent = parts[5]
|
||||
result.avail_space_percent = 100 - tonumber((string.match(parts[5], "%d+") or "0"))
|
||||
result.device = is_virtual and (parts[1] .. " (vfs)") or parts[1]
|
||||
|
||||
local mount_child = Command("mount"):stdout(Command.PIPED):spawn()
|
||||
if mount_child then
|
||||
while true do
|
||||
local line, event = mount_child:read_line()
|
||||
if not line or event ~= 0 then
|
||||
break
|
||||
end
|
||||
-- Check if line starts with filesystem (e.g. /dev/disk1s1 on ...)
|
||||
local s, e = line:find(parts[1] .. " on ", 1, true)
|
||||
if s == 1 then
|
||||
local fstype = line:match("%(([^,]+)")
|
||||
if fstype then
|
||||
result.type = is_virtual and (fstype .. " (vfs)") or fstype
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
result.is_virtual = is_virtual
|
||||
else
|
||||
result.error = "df error: check install"
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function attributes(file)
|
||||
local h = file
|
||||
local file_url = h.url
|
||||
if h.cha.is_link then
|
||||
file_url = Url(h.link_to)
|
||||
end
|
||||
local is_virtual = file_url.scheme and file_url.scheme.is_virtual
|
||||
file_url = is_virtual and (file.path or Url(file_url.scheme.cache .. tostring(file_url.path))) or file_url
|
||||
|
||||
if not h or ya.target_family() ~= "unix" then
|
||||
return ""
|
||||
end
|
||||
|
||||
local output, _ = Command("lsattr"):arg({ "-d", tostring(file_url) }):stdout(Command.PIPED):output()
|
||||
|
||||
if output then
|
||||
-- Splitting the data
|
||||
local parts = split_by_whitespace(output.stdout)
|
||||
|
||||
-- Display the result
|
||||
for i, part in ipairs(parts) do
|
||||
if i == 1 then
|
||||
return part
|
||||
end
|
||||
end
|
||||
return ""
|
||||
else
|
||||
return "lsattr error: check install"
|
||||
end
|
||||
end
|
||||
|
||||
---shorten string
|
||||
---@param _s string string
|
||||
---@param _t string tail
|
||||
---@param _w number max characters
|
||||
---@return string
|
||||
local shorten = function(_s, _t, _w)
|
||||
local s = _s or utf8.len(_s)
|
||||
local t = _t or ""
|
||||
local ellipsis = "…" .. t
|
||||
local w = _w < utf8.len(ellipsis) and utf8.len(ellipsis) or _w
|
||||
local n_ellipsis = utf8.len(ellipsis) or 0
|
||||
if utf8.len(s) > w then
|
||||
return s:sub(1, (utf8.offset(s, w - n_ellipsis + 1) or 2) - 1) .. ellipsis
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
function M:render_table(job, opts)
|
||||
local styles = {
|
||||
header = th.spot.title or ui.Style():fg("green"),
|
||||
row_label = ui.Style():fg("reset"),
|
||||
row_value = th.spot.tbl_col or ui.Style():fg("blue"),
|
||||
}
|
||||
local filesystem_extra = get_filesystem_extra(job.file)
|
||||
local prefix = " "
|
||||
local rows = {}
|
||||
local label_max_length = 15
|
||||
local file_name_extension = job.file.cha.is_dir and "…" or ("." .. (job.file.url.ext or ""))
|
||||
|
||||
local row = function(key, value)
|
||||
local h = type(value) == "table" and #value or 1
|
||||
rows[#rows + 1] = ui.Row({ ui.Line(key):style(styles.row_label), ui.Text(value):style(styles.row_value) })
|
||||
:height(h)
|
||||
end
|
||||
|
||||
local file_name = shorten(
|
||||
job.file.name,
|
||||
file_name_extension,
|
||||
math.floor(job.area.w - label_max_length - utf8.len(file_name_extension))
|
||||
)
|
||||
local location = shorten(
|
||||
tostring((job.file.url.path or job.file.url).parent),
|
||||
"",
|
||||
math.floor(job.area.w - label_max_length - utf8.len(prefix))
|
||||
)
|
||||
local filesystem_error = filesystem_extra.error
|
||||
and shorten(filesystem_extra.error, "", math.floor(job.area.w - label_max_length - utf8.len(prefix)))
|
||||
or nil
|
||||
local filesystem =
|
||||
shorten(filesystem_extra.filesystem, "", math.floor(job.area.w - label_max_length - utf8.len(prefix)))
|
||||
|
||||
rows[#rows + 1] = ui.Row({ "Metadata", "" }):style(styles.header)
|
||||
row(prefix .. "File:", file_name)
|
||||
row(prefix .. "Mimetype:", job.mime)
|
||||
row(prefix .. "Location:", location)
|
||||
if job.file.cache then
|
||||
row(prefix .. "Cached:", tostring(job.file.path or job.file.cache))
|
||||
end
|
||||
row(prefix .. "Mode:", permission(job.file))
|
||||
row(prefix .. "Attributes:", attributes(job.file))
|
||||
row(prefix .. "Links:", tostring(link_count(job.file)))
|
||||
if job.file.cha.is_link then
|
||||
row(prefix .. "Linked:", tostring(job.file.cha.is_link and job.file.link_to))
|
||||
end
|
||||
row(prefix .. "Owner:", owner_group(job.file))
|
||||
row(prefix .. "Size:", file_size_and_folder_childs(job.file))
|
||||
row(prefix .. "Created:", fileTimestamp(job.file, "btime"))
|
||||
row(prefix .. "Modified:", fileTimestamp(job.file, "mtime"))
|
||||
row(prefix .. "Accessed:", fileTimestamp(job.file, "atime"))
|
||||
row(prefix .. "Filesystem:", filesystem_error or filesystem)
|
||||
row(prefix .. "Device:", filesystem_error or filesystem_extra.device)
|
||||
row(prefix .. "Type:", filesystem_error or filesystem_extra.type)
|
||||
row(
|
||||
prefix .. "Free space:",
|
||||
filesystem_error
|
||||
or (
|
||||
(
|
||||
filesystem_extra.avail_space
|
||||
and filesystem_extra.total_space
|
||||
and filesystem_extra.avail_space_percent
|
||||
)
|
||||
and (filesystem_extra.avail_space .. " / " .. filesystem_extra.total_space .. " (" .. filesystem_extra.avail_space_percent .. "%)")
|
||||
or ""
|
||||
)
|
||||
)
|
||||
if opts and opts.show_plugins_section then
|
||||
-- TODO: Remove this after the next release
|
||||
local is_yazi_nightly, _ = pcall(require, "mime.dir")
|
||||
local spotter = rt.plugin.spotter(is_yazi_nightly and job.file or job.file.url, job.mime)
|
||||
local previewer = rt.plugin.previewer(is_yazi_nightly and job.file or job.file.url, job.mime)
|
||||
local fetchers = rt.plugin.fetchers(job.file, job.mime)
|
||||
local preloaders = rt.plugin.preloaders(is_yazi_nightly and job.file or job.file.url, job.mime)
|
||||
|
||||
for i, v in ipairs(fetchers) do
|
||||
fetchers[i] = v.cmd
|
||||
end
|
||||
for i, v in ipairs(preloaders) do
|
||||
preloaders[i] = v.cmd
|
||||
end
|
||||
|
||||
rows[#rows + 1] = ui.Row({ { "", "Plugins" }, "" }):height(2):style(styles.header)
|
||||
row(prefix .. "Spotter:", spotter and spotter.cmd or "(none)")
|
||||
row(prefix .. "Previewer:", previewer and previewer.cmd or "(none)")
|
||||
row(prefix .. "Fetchers:", #fetchers ~= 0 and fetchers or "(none)")
|
||||
row(prefix .. "Preloaders:", #preloaders ~= 0 and preloaders or "(none)")
|
||||
end
|
||||
|
||||
return ui.Table(rows):area(job.area):row(1):col(1):col_style(styles.row_value):widths({
|
||||
ui.Constraint.Length(label_max_length),
|
||||
ui.Constraint.Fill(1),
|
||||
})
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
local start, cache = os.clock(), ya.file_cache(job)
|
||||
if not cache or not self:preload(job) then
|
||||
return 1
|
||||
end
|
||||
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
|
||||
ya.preview_widget(job, { self:render_table(job) })
|
||||
end
|
||||
|
||||
function M:seek(job)
|
||||
local h = cx.active.current.hovered
|
||||
if h and h.url == job.file.url then
|
||||
local step = math.floor(job.units * job.area.h / 10)
|
||||
ya.emit("peek", {
|
||||
tostring(math.max(0, cx.active.preview.skip + step)),
|
||||
only_if = tostring(job.file.url),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cache = ya.file_cache(job)
|
||||
if not cache or fs.cha(cache) then
|
||||
return true
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
function M:spot(job)
|
||||
job.area = ui.Pos({ "center", w = 80, h = 25 })
|
||||
ya.spot_table(
|
||||
job,
|
||||
self:render_table(job, { show_plugins_section = true })
|
||||
:cell_style(th.spot.tbl_cell or ui.Style():fg("blue"):reverse())
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 yazi-rs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,32 +0,0 @@
|
||||
# full-border.yazi
|
||||
|
||||
Add a full border to Yazi to make it look fancier.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
ya pkg add yazi-rs/plugins:full-border
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `init.lua` to enable the plugin:
|
||||
|
||||
```lua
|
||||
require("full-border"):setup()
|
||||
```
|
||||
|
||||
Or you can customize the border type:
|
||||
|
||||
```lua
|
||||
require("full-border"):setup {
|
||||
-- Available values: ui.Border.PLAIN, ui.Border.ROUNDED
|
||||
type = ui.Border.ROUNDED,
|
||||
}
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
|
||||
@@ -1,43 +0,0 @@
|
||||
--- @since 25.2.26
|
||||
|
||||
local function setup(_, opts)
|
||||
local type = opts and opts.type or ui.Border.ROUNDED
|
||||
local old_build = Tab.build
|
||||
|
||||
Tab.build = function(self, ...)
|
||||
local bar = function(c, x, y)
|
||||
if x <= 0 or x == self._area.w - 1 or th.mgr.border_symbol ~= "│" then
|
||||
return ui.Bar(ui.Edge.TOP)
|
||||
end
|
||||
|
||||
return ui.Bar(ui.Edge.TOP)
|
||||
:area(
|
||||
ui.Rect { x = x, y = math.max(0, y), w = ya.clamp(0, self._area.w - x, 1), h = math.min(1, self._area.h) }
|
||||
)
|
||||
:symbol(c)
|
||||
end
|
||||
|
||||
local c = self._chunks
|
||||
self._chunks = {
|
||||
c[1]:pad(ui.Pad.y(1)),
|
||||
c[2]:pad(ui.Pad(1, c[3].w > 0 and 0 or 1, 1, c[1].w > 0 and 0 or 1)),
|
||||
c[3]:pad(ui.Pad.y(1)),
|
||||
}
|
||||
|
||||
local style = th.mgr.border_style
|
||||
self._base = ya.list_merge(self._base or {}, {
|
||||
ui.Border(ui.Edge.ALL):area(self._area):type(type):style(style),
|
||||
ui.Bar(ui.Edge.RIGHT):area(self._chunks[1]):style(style),
|
||||
ui.Bar(ui.Edge.LEFT):area(self._chunks[3]):style(style),
|
||||
|
||||
bar("┬", c[1].right - 1, c[1].y),
|
||||
bar("┴", c[1].right - 1, c[1].bottom - 1),
|
||||
bar("┬", c[2].right, c[2].y),
|
||||
bar("┴", c[2].right, c[2].bottom - 1),
|
||||
})
|
||||
|
||||
old_build(self, ...)
|
||||
end
|
||||
end
|
||||
|
||||
return { setup = setup }
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 yazi-rs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,78 +0,0 @@
|
||||
# git.yazi
|
||||
|
||||
Show the status of Git file changes as linemode in the file list.
|
||||
|
||||
https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
ya pkg add yazi-rs/plugins:git
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
Add the following to your `~/.config/yazi/init.lua`:
|
||||
|
||||
```lua
|
||||
require("git"):setup()
|
||||
```
|
||||
|
||||
And register it as fetchers in your `~/.config/yazi/yazi.toml`:
|
||||
|
||||
```toml
|
||||
[[plugin.prepend_fetchers]]
|
||||
id = "git"
|
||||
url = "*"
|
||||
run = "git"
|
||||
|
||||
[[plugin.prepend_fetchers]]
|
||||
id = "git"
|
||||
url = "*/"
|
||||
run = "git"
|
||||
```
|
||||
|
||||
## Advanced
|
||||
|
||||
> [!NOTE]
|
||||
> The following configuration must be put before `require("git"):setup()`
|
||||
|
||||
You can customize the [Style](https://yazi-rs.github.io/docs/plugins/layout#style) of the status sign with:
|
||||
|
||||
- `th.git.modified`
|
||||
- `th.git.added`
|
||||
- `th.git.untracked`
|
||||
- `th.git.ignored`
|
||||
- `th.git.deleted`
|
||||
- `th.git.updated`
|
||||
|
||||
For example:
|
||||
|
||||
```lua
|
||||
-- ~/.config/yazi/init.lua
|
||||
th.git = th.git or {}
|
||||
th.git.modified = ui.Style():fg("blue")
|
||||
th.git.deleted = ui.Style():fg("red"):bold()
|
||||
```
|
||||
|
||||
You can also customize the text of the status sign with:
|
||||
|
||||
- `th.git.modified_sign`
|
||||
- `th.git.added_sign`
|
||||
- `th.git.untracked_sign`
|
||||
- `th.git.ignored_sign`
|
||||
- `th.git.deleted_sign`
|
||||
- `th.git.updated_sign`
|
||||
|
||||
For example:
|
||||
|
||||
```lua
|
||||
-- ~/.config/yazi/init.lua
|
||||
th.git = th.git or {}
|
||||
th.git.modified_sign = "M"
|
||||
th.git.deleted_sign = "D"
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
|
||||
@@ -1,255 +0,0 @@
|
||||
--- @since 25.12.29
|
||||
|
||||
local WINDOWS = ya.target_family() == "windows"
|
||||
|
||||
-- The code of supported git status,
|
||||
-- also used to determine which status to show for directories when they contain different statuses
|
||||
-- see `bubble_up`
|
||||
---@enum CODES
|
||||
local CODES = {
|
||||
excluded = 100, -- ignored directory
|
||||
ignored = 6, -- ignored file
|
||||
untracked = 5,
|
||||
modified = 4,
|
||||
added = 3,
|
||||
deleted = 2,
|
||||
updated = 1,
|
||||
unknown = 0,
|
||||
}
|
||||
|
||||
local PATTERNS = {
|
||||
{ "!$", CODES.ignored },
|
||||
{ "?$", CODES.untracked },
|
||||
{ "[MT]", CODES.modified },
|
||||
{ "[AC]", CODES.added },
|
||||
{ "D", CODES.deleted },
|
||||
{ "U", CODES.updated },
|
||||
{ "[AD][AD]", CODES.updated },
|
||||
}
|
||||
|
||||
---@param line string
|
||||
---@return CODES, string
|
||||
local function match(line)
|
||||
local signs = line:sub(1, 2)
|
||||
for _, p in ipairs(PATTERNS) do
|
||||
local path, pattern, code = nil, p[1], p[2]
|
||||
if signs:find(pattern) then
|
||||
path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4)
|
||||
path = WINDOWS and path:gsub("/", "\\") or path
|
||||
end
|
||||
if not path then
|
||||
elseif path:find("[/\\]$") then
|
||||
-- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down`
|
||||
return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2)
|
||||
else
|
||||
return code, path
|
||||
end
|
||||
---@diagnostic disable-next-line: missing-return
|
||||
end
|
||||
end
|
||||
|
||||
---@param cwd Url
|
||||
---@return string?
|
||||
local function root(cwd)
|
||||
local is_worktree = function(url)
|
||||
local file, head = io.open(tostring(url)), nil
|
||||
if file then
|
||||
head = file:read(8)
|
||||
file:close()
|
||||
end
|
||||
return head == "gitdir: "
|
||||
end
|
||||
|
||||
repeat
|
||||
local next = cwd:join(".git")
|
||||
local cha = fs.cha(next)
|
||||
if cha and (cha.is_dir or is_worktree(next)) then
|
||||
return tostring(cwd)
|
||||
end
|
||||
cwd = cwd.parent
|
||||
until not cwd
|
||||
end
|
||||
|
||||
---@param changed Changes
|
||||
---@return Changes
|
||||
local function bubble_up(changed)
|
||||
local new, empty = {}, Url("")
|
||||
for path, code in pairs(changed) do
|
||||
if code ~= CODES.ignored then
|
||||
local url = Url(path).parent
|
||||
while url and url ~= empty do
|
||||
local s = tostring(url)
|
||||
new[s] = (new[s] or CODES.unknown) > code and new[s] or code
|
||||
url = url.parent
|
||||
end
|
||||
end
|
||||
end
|
||||
return new
|
||||
end
|
||||
|
||||
---@param excluded string[]
|
||||
---@param cwd Url
|
||||
---@param repo Url
|
||||
---@return Changes
|
||||
local function propagate_down(excluded, cwd, repo)
|
||||
local new, rel = {}, cwd:strip_prefix(repo)
|
||||
for _, path in ipairs(excluded) do
|
||||
if rel:starts_with(path) then
|
||||
-- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded`
|
||||
new[tostring(cwd)] = CODES.excluded
|
||||
elseif cwd == repo:join(path).parent then
|
||||
-- If `path` is a direct subdirectory of `cwd`, mark it as `ignored`
|
||||
new[path] = CODES.ignored
|
||||
else
|
||||
-- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance
|
||||
end
|
||||
end
|
||||
return new
|
||||
end
|
||||
|
||||
---@param cwd string
|
||||
---@param repo string
|
||||
---@param changed Changes
|
||||
local add = ya.sync(function(st, cwd, repo, changed)
|
||||
---@cast st State
|
||||
|
||||
st.dirs[cwd] = repo
|
||||
st.repos[repo] = st.repos[repo] or {}
|
||||
for path, code in pairs(changed) do
|
||||
if code == CODES.unknown then
|
||||
st.repos[repo][path] = nil
|
||||
elseif code == CODES.excluded then
|
||||
-- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering
|
||||
st.dirs[path] = CODES.excluded
|
||||
else
|
||||
st.repos[repo][path] = code
|
||||
end
|
||||
end
|
||||
ui.render()
|
||||
end)
|
||||
|
||||
---@param cwd string
|
||||
local remove = ya.sync(function(st, cwd)
|
||||
---@cast st State
|
||||
|
||||
local repo = st.dirs[cwd]
|
||||
if not repo then
|
||||
return
|
||||
end
|
||||
|
||||
ui.render()
|
||||
st.dirs[cwd] = nil
|
||||
if not st.repos[repo] then
|
||||
return
|
||||
end
|
||||
|
||||
for _, r in pairs(st.dirs) do
|
||||
if r == repo then
|
||||
return
|
||||
end
|
||||
end
|
||||
st.repos[repo] = nil
|
||||
end)
|
||||
|
||||
---@param st State
|
||||
---@param opts Options
|
||||
local function setup(st, opts)
|
||||
st.dirs = {}
|
||||
st.repos = {}
|
||||
|
||||
opts = opts or {}
|
||||
opts.order = opts.order or 1500
|
||||
|
||||
local t = th.git or {}
|
||||
local styles = {
|
||||
[CODES.ignored] = t.ignored and ui.Style(t.ignored) or ui.Style():fg("darkgray"),
|
||||
[CODES.untracked] = t.untracked and ui.Style(t.untracked) or ui.Style():fg("magenta"),
|
||||
[CODES.modified] = t.modified and ui.Style(t.modified) or ui.Style():fg("yellow"),
|
||||
[CODES.added] = t.added and ui.Style(t.added) or ui.Style():fg("green"),
|
||||
[CODES.deleted] = t.deleted and ui.Style(t.deleted) or ui.Style():fg("red"),
|
||||
[CODES.updated] = t.updated and ui.Style(t.updated) or ui.Style():fg("yellow"),
|
||||
}
|
||||
local signs = {
|
||||
[CODES.ignored] = t.ignored_sign or " ",
|
||||
[CODES.untracked] = t.untracked_sign or "? ",
|
||||
[CODES.modified] = t.modified_sign or " ",
|
||||
[CODES.added] = t.added_sign or " ",
|
||||
[CODES.deleted] = t.deleted_sign or " ",
|
||||
[CODES.updated] = t.updated_sign or " ",
|
||||
}
|
||||
|
||||
Linemode:children_add(function(self)
|
||||
if not self._file.in_current then
|
||||
return ""
|
||||
end
|
||||
|
||||
local url = self._file.url
|
||||
local repo = st.dirs[tostring(url.base or url.parent)]
|
||||
local code
|
||||
if repo then
|
||||
code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)]
|
||||
end
|
||||
|
||||
if not code or signs[code] == "" then
|
||||
return ""
|
||||
elseif self._file.is_hovered then
|
||||
return ui.Line { " ", signs[code] }
|
||||
else
|
||||
return ui.Line { " ", ui.Span(signs[code]):style(styles[code]) }
|
||||
end
|
||||
end, opts.order)
|
||||
end
|
||||
|
||||
---@type UnstableFetcher
|
||||
local function fetch(_, job)
|
||||
local cwd = job.files[1].url.base or job.files[1].url.parent
|
||||
local repo = root(cwd)
|
||||
if not repo then
|
||||
remove(tostring(cwd))
|
||||
return true
|
||||
end
|
||||
|
||||
local paths = {}
|
||||
for _, file in ipairs(job.files) do
|
||||
paths[#paths + 1] = tostring(file.url)
|
||||
end
|
||||
|
||||
-- stylua: ignore
|
||||
local output, err = Command("git")
|
||||
:cwd(tostring(cwd))
|
||||
:arg({ "--no-optional-locks", "-c", "core.quotePath=", "status", "--porcelain", "-unormal", "--no-renames", "--ignored=matching" })
|
||||
:arg(paths)
|
||||
:stdout(Command.PIPED)
|
||||
:output()
|
||||
if not output then
|
||||
return true, Err("Cannot spawn `git` command, error: %s", err)
|
||||
end
|
||||
|
||||
local changed, excluded = {}, {}
|
||||
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||
local code, path = match(line)
|
||||
if code == CODES.excluded then
|
||||
excluded[#excluded + 1] = path
|
||||
else
|
||||
changed[path] = code
|
||||
end
|
||||
end
|
||||
|
||||
if job.files[1].cha.is_dir then
|
||||
ya.dict_merge(changed, bubble_up(changed))
|
||||
end
|
||||
ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo)))
|
||||
|
||||
-- Reset the status of any files that don't appear in the output of `git status` to `unknown`,
|
||||
-- so that cleaning up outdated statuses from `st.repos`
|
||||
for _, path in ipairs(paths) do
|
||||
local s = path:sub(#repo + 2)
|
||||
changed[s] = changed[s] or CODES.unknown
|
||||
end
|
||||
|
||||
add(tostring(cwd), repo, changed)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
return { setup = setup, fetch = fetch }
|
||||
@@ -1,12 +0,0 @@
|
||||
---@class State
|
||||
---@field dirs table<string, string|CODES> Mapping between a directory and its corresponding repository
|
||||
---@field repos table<string, Changes> Mapping between a repository and the status of each of its files
|
||||
|
||||
---@class Options
|
||||
---@field order number The order in which the status is displayed
|
||||
---@field renamed boolean Whether to include renamed files in the status (or treat them as modified)
|
||||
|
||||
-- TODO: move this to `types.yazi` once it's get stable
|
||||
---@alias UnstableFetcher fun(self: unknown, job: { files: File[] }): boolean, Error?
|
||||
|
||||
---@alias Changes table<string, CODES>
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Darius
|
||||
|
||||
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.
|
||||
@@ -1,31 +0,0 @@
|
||||
# lazygit.yazi
|
||||
Plugin for [Yazi](https://github.com/sxyazi/yazi) to manage git repos with [lazygit](https://github.com/jesseduffield/lazygit)
|
||||
## Dependencies
|
||||
Make sure [lazygit](https://github.com/jesseduffield/lazygit) is installed and in your `PATH`.
|
||||
## Installation
|
||||
|
||||
### Using `ya pkg`
|
||||
```
|
||||
ya pkg add Lil-Dank/lazygit
|
||||
```
|
||||
|
||||
### Manual
|
||||
**Linux/macOS**
|
||||
```
|
||||
git clone https://github.com/Lil-Dank/lazygit.yazi.git ~/.config/yazi/plugins/lazygit.yazi
|
||||
```
|
||||
**Windows**
|
||||
```
|
||||
git clone https://github.com/Lil-Dank/lazygit.yazi.git %AppData%\yazi\config\plugins\lazygit.yazi
|
||||
```
|
||||
## Configuration
|
||||
add this to your **keymap.toml** file
|
||||
```toml
|
||||
[[mgr.prepend_keymap]]
|
||||
on = [ "g", "i" ]
|
||||
run = "plugin lazygit"
|
||||
desc = "run lazygit"
|
||||
```
|
||||
you can customize the keybinding however you like. Please refer to the [keymap.toml](https://yazi-rs.github.io/docs/configuration/keymap) documentation
|
||||
## Disclaimer
|
||||
**THIS PLUGIN IS FOR THE STABLE RELEASE OF YAZI, DON'T PROPOSE ANY CHANGES BASED ON PRE-RELEASES OF YAZI**
|
||||
@@ -1,34 +0,0 @@
|
||||
return {
|
||||
entry = function()
|
||||
local output = Command("git"):arg("status"):stderr(Command.PIPED):output()
|
||||
if output.stderr ~= "" then
|
||||
ya.notify({
|
||||
title = "lazygit",
|
||||
content = "Not in a git directory\nError: " .. output.stderr,
|
||||
level = "warn",
|
||||
timeout = 5,
|
||||
})
|
||||
else
|
||||
permit = ui.hide and ui.hide() or ya.hide()
|
||||
local output, err_code = Command("lazygit"):stdin(Command.INHERIT):stdout(Command.INHERIT):stderr(Command.PIPED):spawn()
|
||||
if output and not err_code then
|
||||
output, err_code = output:wait_with_output()
|
||||
end
|
||||
if err_code ~= nil then
|
||||
ya.notify({
|
||||
title = "Failed to run lazygit command",
|
||||
content = "Status: " .. err_code,
|
||||
level = "error",
|
||||
timeout = 5,
|
||||
})
|
||||
elseif not output.status.success then
|
||||
ya.notify({
|
||||
title = "lazygit in" .. cwd .. "failed, exit code " .. output.status.code,
|
||||
content = output.stderr,
|
||||
level = "error",
|
||||
timeout = 5,
|
||||
})
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
Copyright (c) 2024 Lauri Niskanen
|
||||
Copyright (c) 2025 Huy Hoang
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -13,7 +13,8 @@ This is a Yazi plugin for previewing media files. The preview shows thumbnail
|
||||
using `ffmpeg` if available and media metadata using `mediainfo`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Minimum version: yazi v25.5.31.
|
||||
> Minimum version: yazi v26.1.22.
|
||||
> Check it via command `yazi --debug`
|
||||
|
||||
## Preview
|
||||
|
||||
@@ -70,29 +71,58 @@ Config folder for each OS: https://yazi-rs.github.io/docs/configuration/overview
|
||||
|
||||
Create `.../yazi/yazi.toml` and add:
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> For yazi (>=v25.12.29) replace `name` with `url`
|
||||
`prepend_preloaders` are optional, but highly recommended for faster preview.
|
||||
|
||||
```toml
|
||||
[plugin]
|
||||
prepend_preloaders = [
|
||||
# Replace magick, image, video with mediainfo
|
||||
{ mime = "{audio,video,image}/*", run = "mediainfo" },
|
||||
{ mime = "application/subrip", run = "mediainfo" },
|
||||
# Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above
|
||||
{ mime = "application/postscript", run = "mediainfo" },
|
||||
# .srt subtitle, adobe illustrator, adobe photoshop file
|
||||
{ mime = "application/{subrip,postscript,illustrator,dvb.ait,vnd.adobe.illustrator,eps}", run = "mediainfo" },
|
||||
|
||||
# Sometimes AI file is recognized as "application/pdf". Lmao.
|
||||
# In this case use file extension instead:
|
||||
{ url = "*.{ai,eps,ait}", run = "mediainfo" },
|
||||
|
||||
# Hide metadata by default.
|
||||
# Example for image mimetype:
|
||||
{ mime = "{image}/*", run = "mediainfo --no-metadata" },
|
||||
|
||||
# Hide image preview by default.
|
||||
# Example for video mimetype:
|
||||
{ mime = "{video}/*", run = "mediainfo --no-preview" },
|
||||
|
||||
# NOTE: Use both --no-metadata and --no-preview will display nothing. :)
|
||||
# Make sure both of your previewers and preloaders has the same arguments (--no-metadata and --no-preview)
|
||||
]
|
||||
|
||||
prepend_previewers = [
|
||||
# Replace magick, image, video with mediainfo
|
||||
{ mime = "{audio,video,image}/*", run = "mediainfo"},
|
||||
{ mime = "application/subrip", run = "mediainfo" },
|
||||
# Adobe Illustrator, Adobe Photoshop is image/adobe.photoshop, already handled above
|
||||
{ mime = "application/postscript", run = "mediainfo" },
|
||||
# .srt subtitle, adobe illustrator, adobe photoshop file
|
||||
{ mime = "application/{subrip,postscript,illustrator,dvb.ait,vnd.adobe.illustrator,eps}", run = "mediainfo" },
|
||||
|
||||
# Sometimes AI file is recognized as "application/pdf". Lmao.
|
||||
# In this case use file extension instead:
|
||||
{ url = "*.{ai,eps,ait}", run = "mediainfo" },
|
||||
|
||||
# Hide metadata by default.
|
||||
# Example for image mimetype:
|
||||
{ mime = "{image}/*", run = "mediainfo --no-metadata" },
|
||||
|
||||
# Hide image preview by default.
|
||||
# Example for video mimetype:
|
||||
{ mime = "{video}/*", run = "mediainfo --no-preview" },
|
||||
|
||||
# NOTE: Use both --no-metadata and --no-preview will display nothing. :)
|
||||
# Make sure both of your previewers and preloaders has the same arguments (--no-metadata and --no-preview)
|
||||
]
|
||||
# There are more extensions which are supported by mediainfo.
|
||||
|
||||
# There are more extensions, mime types which are supported by mediainfo.
|
||||
# Just add file's MIME type to `previewers`, `preloaders` above.
|
||||
# https://mediaarea.net/en/MediaInfo/Support/Formats
|
||||
# If it's not working, file an issue at https://github.com/boydaihungst/mediainfo.yazi/issues
|
||||
|
||||
# For a large file like Adobe Illustrator, Adobe Photoshop, etc
|
||||
# you may need to increase the memory limit if no image is rendered.
|
||||
@@ -102,9 +132,9 @@ Create `.../yazi/yazi.toml` and add:
|
||||
|
||||
```
|
||||
|
||||
## Custom theme
|
||||
## (Optional) Custom theme
|
||||
|
||||
Using the same style with spotter. [Read more](https://github.com/sxyazi/yazi/pull/2391)
|
||||
Using the same style with spotter windows. [Read more](https://github.com/sxyazi/yazi/pull/2391)
|
||||
|
||||
Edit or add `yazi/theme.toml`:
|
||||
|
||||
@@ -119,7 +149,39 @@ title = { fg = "green" }
|
||||
tbl_col = { fg = "blue" }
|
||||
```
|
||||
|
||||
## (Optional) Keymaps to hide metadata and to preview images in full screen
|
||||
## (Optional) Hide labels or section labels
|
||||
|
||||
This `setup` function and all of its options are optional.
|
||||
Modify your `~/.config/yazi/init.lua` to include:
|
||||
|
||||
```lua
|
||||
require("mediainfo"):setup({
|
||||
-- Auto hide the lines with these labels
|
||||
-- Labels are the text with white color in preview images above (without colon ":")
|
||||
-- Example: To hide `Format: FLAC` => "Format"
|
||||
-- Default value:
|
||||
skip_labels = {
|
||||
"Complete name",
|
||||
"CompleteName_Last",
|
||||
"Unique ID",
|
||||
"File size",
|
||||
"Format/Info",
|
||||
"Codec ID/Info",
|
||||
"MD5 of the unencoded content"
|
||||
-- "Format" -- Hide all lines with "Format" label
|
||||
},
|
||||
-- skip_labels = false, -- Disable auto hide labels
|
||||
|
||||
|
||||
-- Auto hide the section labels
|
||||
-- Section labels are the text with green color in preview images above
|
||||
-- Example: To hide `Image` => "Image"
|
||||
-- Default value: {}
|
||||
skip_section_labels = { "General", "Image", "Text" },
|
||||
})
|
||||
```
|
||||
|
||||
## (Optional) Keymaps to toggle/show/hide/reset metadata and preview image
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Use any key you want, but make sure there is no conflicts with [default Keybindings](https://github.com/sxyazi/yazi/blob/main/yazi-config/preset/keymap-default.toml).
|
||||
@@ -130,6 +192,23 @@ Or you can use `keymap` to replace all other keys
|
||||
```toml
|
||||
[mgr]
|
||||
prepend_keymap = [
|
||||
{ on = "<F9>", run = "plugin mediainfo -- toggle-metadata", desc = "Toggle media preview metadata" },
|
||||
{ on = "<F3>", run = "plugin mediainfo -- toggle-metadata", desc = "Toggle media preview metadata" },
|
||||
{ on = "<F4>", run = "plugin mediainfo -- toggle-preview", desc = "Toggle media preview image" },
|
||||
|
||||
# Hide and show metadata, image
|
||||
{ on = "<F6>", run = "plugin mediainfo -- hide-metadata", desc = "Hide media preview metadata" },
|
||||
{ on = "<F7>", run = "plugin mediainfo -- hide-preview", desc = "Hide media preview image" },
|
||||
|
||||
{ on = "<F8>", run = "plugin mediainfo -- show-metadata", desc = "Show media preview metadata" },
|
||||
{ on = "<F9>", run = "plugin mediainfo -- show-preview", desc = "Show media preview image" },
|
||||
|
||||
# Reset to defautl settings in yazi.toml file
|
||||
{ on = "<F5>", run = "plugin mediainfo -- reset", desc = "Reset media preview to default settings" },
|
||||
|
||||
# Use multiple actions
|
||||
{ on = "<F10>", run = "plugin mediainfo -- --toggle-preview --toggle-metadata", desc = "Toggle both media preview image and metadata" },
|
||||
{ on = "<F11>", run = "plugin mediainfo -- --show-preview --hide-metadata", desc = "Show media preview image and hide metadata" },
|
||||
# You can also use all of them together (7 actions above). Priority is reset > toggle > hide > show
|
||||
{ on = "<F12>", run = "plugin mediainfo -- --show-preview --hide-metadata --reset --show-metadata --hide-preview", desc = "Show media preview image and hide metadata" },
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,302 @@
|
||||
--- @since 26.1.22
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
local function image_layer_count(job)
|
||||
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||
if not cache then
|
||||
return 0
|
||||
end
|
||||
local layer_count = utils.get_state("f" .. tostring(cache))
|
||||
if layer_count then
|
||||
return layer_count
|
||||
end
|
||||
local output, err = Command("identify")
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
if err or not output then
|
||||
return 0
|
||||
end
|
||||
layer_count = 0
|
||||
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||
if line:match("%S") then
|
||||
layer_count = layer_count + 1
|
||||
end
|
||||
end
|
||||
utils.set_state("f" .. tostring(cache), layer_count)
|
||||
return layer_count
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local no_metadata = job.args.no_metadata
|
||||
local mediainfo_job_skip = job.skip
|
||||
::recalc_mediainfo_job_skip::
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
if not no_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = ui.Line({
|
||||
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||
})
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local line_height = ui.height
|
||||
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||
if next_str == nil and line_height == 1 then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if (last_line + line_height) > mediainfo_job_skip then
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if (last_line + line_height) >= mediainfo_job_skip + limit then
|
||||
last_line = mediainfo_job_skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
last_line = last_line + line_height
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if not no_metadata then
|
||||
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||
if
|
||||
image_layer_count(job)
|
||||
< (
|
||||
1
|
||||
+ math.floor(
|
||||
math.max(
|
||||
0,
|
||||
utils.get_state(const.STATE_KEY.units)
|
||||
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||
or 0
|
||||
)
|
||||
)
|
||||
)
|
||||
then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
else
|
||||
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||
|
||||
goto recalc_mediainfo_job_skip
|
||||
end
|
||||
else
|
||||
utils.set_state(
|
||||
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||
if
|
||||
old_metadata_area.win_x == job.area.x
|
||||
and old_metadata_area.win_y == job.area.y
|
||||
and old_metadata_area.win_w == job.area.w
|
||||
and old_metadata_area.win_h == job.area.h
|
||||
then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect({
|
||||
x = old_metadata_area.x,
|
||||
y = old_metadata_area.y,
|
||||
w = old_metadata_area.w,
|
||||
h = old_metadata_area.h,
|
||||
})),
|
||||
})
|
||||
end
|
||||
end
|
||||
utils.force_render()
|
||||
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Preload image
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||
|
||||
-- NOTE: Only generate preview image when cache image is not exist
|
||||
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||
local cache_img_status, image_preload_err
|
||||
local layer_index = 0
|
||||
local units = utils.get_state(const.STATE_KEY.units)
|
||||
if units ~= nil then
|
||||
local max_layer = image_layer_count(job)
|
||||
layer_index = math.floor(math.max(0, math.abs(job.skip / units)))
|
||||
if layer_index + 1 > max_layer then
|
||||
layer_index = math.max(0, max_layer - 1)
|
||||
end
|
||||
end
|
||||
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||
if fs.cha(cache_img_url_tmp) then
|
||||
fs.remove("file", cache_img_url_tmp)
|
||||
end
|
||||
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||
or fs.unique_name(cache_img_url_tmp)
|
||||
cache_img_status, image_preload_err = require("magick")
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-background",
|
||||
"none",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) .. "[" .. tostring(
|
||||
layer_index
|
||||
) .. "]",
|
||||
"-auto-orient",
|
||||
"-strip",
|
||||
"-resize",
|
||||
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||
"-quality",
|
||||
rt.preview.image_quality,
|
||||
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||
})
|
||||
:status()
|
||||
if cache_img_status then
|
||||
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||
end
|
||||
|
||||
if not cache_img_status and image_preload_err then
|
||||
ya.dbg("mediainfo", image_preload_err)
|
||||
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,338 @@
|
||||
--- @since 26.5.6
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
local function image_layer_count(job)
|
||||
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||
if not cache then
|
||||
return 0
|
||||
end
|
||||
local layer_count = utils.get_state("f" .. tostring(cache))
|
||||
if layer_count then
|
||||
return layer_count
|
||||
end
|
||||
local output, err = Command("identify")
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
if err or not output then
|
||||
return 0
|
||||
end
|
||||
layer_count = 0
|
||||
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||
if line:match("%S") then
|
||||
layer_count = layer_count + 1
|
||||
end
|
||||
end
|
||||
utils.set_state("f" .. tostring(cache), layer_count)
|
||||
return layer_count
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local no_metadata = job.args.no_metadata
|
||||
local mediainfo_job_skip = job.skip
|
||||
::recalc_mediainfo_job_skip::
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
if not no_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
local opt = { ansi = true, tab_size = rt.preview.tab_size, wrap = rt.preview.wrap, width = max_width }
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = label .. ": " .. value
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = str
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local wrapped = ui.lines(line, opt)
|
||||
local line_height = #wrapped
|
||||
local from = 1
|
||||
local to = math.min(line_height, mediainfo_job_skip + limit - last_line)
|
||||
|
||||
local total_rendered_text_len = 1
|
||||
local total_label_rendered_len = 1
|
||||
local label_total_len = label and utf8.len(label .. ": ") or 0
|
||||
for j = from, to do
|
||||
local current_line_components = {}
|
||||
local wrapped_line_len = wrapped[j]:width() or 0
|
||||
local wrapped_raw =
|
||||
utils.utf8_sub(line, total_rendered_text_len, total_rendered_text_len + wrapped_line_len)
|
||||
wrapped_line_len = utf8.len(wrapped_raw)
|
||||
total_rendered_text_len = total_rendered_text_len + wrapped_line_len
|
||||
|
||||
if last_line + 1 > mediainfo_job_skip then
|
||||
local label_raw = label_total_len - total_label_rendered_len <= 0 and ""
|
||||
or utils.utf8_sub(wrapped_raw, 1, label_total_len - total_label_rendered_len)
|
||||
local label_raw_len = utf8.len(label_raw)
|
||||
if label_raw_len > 0 then
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(label_raw):style(ui.Style():fg("reset"):bold())
|
||||
)
|
||||
total_label_rendered_len = total_label_rendered_len + label_raw_len
|
||||
wrapped_raw = wrapped_raw:gsub("^" .. utils.is_literal_string(label_raw), "", 1)
|
||||
end
|
||||
if total_label_rendered_len >= label_total_len then
|
||||
local value_raw = wrapped_raw
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(value_raw or ""):style(
|
||||
label and (th.spot.tbl_col or ui.Style():fg("blue"))
|
||||
or (th.spot.title or ui.Style():fg("green"))
|
||||
)
|
||||
)
|
||||
end
|
||||
table.insert(lines, ui.Line(current_line_components))
|
||||
-- last_line = last_line + 1
|
||||
else
|
||||
total_label_rendered_len = total_label_rendered_len + total_rendered_text_len
|
||||
end
|
||||
last_line = last_line + 1
|
||||
if next_str == nil and not wrapped[j + 1] then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if last_line >= mediainfo_job_skip + limit then
|
||||
last_line = mediainfo_job_skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if not no_metadata then
|
||||
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||
if
|
||||
image_layer_count(job)
|
||||
< (
|
||||
1
|
||||
+ math.floor(
|
||||
math.max(
|
||||
0,
|
||||
utils.get_state(const.STATE_KEY.units)
|
||||
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||
or 0
|
||||
)
|
||||
)
|
||||
)
|
||||
then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
else
|
||||
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||
|
||||
goto recalc_mediainfo_job_skip
|
||||
end
|
||||
else
|
||||
utils.set_state(
|
||||
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||
if
|
||||
old_metadata_area.win_x == job.area.x
|
||||
and old_metadata_area.win_y == job.area.y
|
||||
and old_metadata_area.win_w == job.area.w
|
||||
and old_metadata_area.win_h == job.area.h
|
||||
then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect({
|
||||
x = old_metadata_area.x,
|
||||
y = old_metadata_area.y,
|
||||
w = old_metadata_area.w,
|
||||
h = old_metadata_area.h,
|
||||
})),
|
||||
})
|
||||
end
|
||||
end
|
||||
utils.force_render()
|
||||
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Preload image
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||
|
||||
-- NOTE: Only generate preview image when cache image is not exist
|
||||
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||
local cache_img_status, image_preload_err
|
||||
local layer_index = 0
|
||||
local units = utils.get_state(const.STATE_KEY.units)
|
||||
if units ~= nil then
|
||||
local max_layer = image_layer_count(job)
|
||||
layer_index = math.floor(math.max(0, math.abs(job.skip / units)))
|
||||
if layer_index + 1 > max_layer then
|
||||
layer_index = math.max(0, max_layer - 1)
|
||||
end
|
||||
end
|
||||
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||
if fs.cha(cache_img_url_tmp) then
|
||||
fs.remove("file", cache_img_url_tmp)
|
||||
end
|
||||
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||
or fs.unique_name(cache_img_url_tmp)
|
||||
cache_img_status, image_preload_err = require("magick")
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-background",
|
||||
"none",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) .. "[" .. tostring(
|
||||
layer_index
|
||||
) .. "]",
|
||||
"-auto-orient",
|
||||
"-strip",
|
||||
"-resize",
|
||||
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||
"-quality",
|
||||
rt.preview.image_quality,
|
||||
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||
})
|
||||
:status()
|
||||
if cache_img_status then
|
||||
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||
end
|
||||
|
||||
if not cache_img_status and image_preload_err then
|
||||
ya.dbg("mediainfo", image_preload_err)
|
||||
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,345 @@
|
||||
--- @since 26.1.22
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
local function get_cover_layers(job)
|
||||
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||
if not cache then
|
||||
return {}
|
||||
end
|
||||
local covers = utils.get_state("f" .. tostring(cache))
|
||||
if covers and type(covers) == "table" then
|
||||
return covers
|
||||
end
|
||||
local output, err = Command("ffprobe"):arg({
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v",
|
||||
"-show_entries",
|
||||
"stream=index:stream_disposition=attached_pic",
|
||||
"-of",
|
||||
"json",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
}):output()
|
||||
if err or not output then
|
||||
return {}
|
||||
end
|
||||
covers = {}
|
||||
local data = ya.json_decode(output.stdout)
|
||||
if type(data.streams) == "table" then
|
||||
for _, stream in ipairs(data.streams) do
|
||||
if stream.disposition and stream.disposition.attached_pic then
|
||||
covers[#covers + 1] = stream.index
|
||||
end
|
||||
end
|
||||
end
|
||||
utils.set_state("f" .. tostring(cache), covers)
|
||||
return covers
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local no_metadata = job.args.no_metadata
|
||||
local mediainfo_job_skip = job.skip
|
||||
::recalc_mediainfo_job_skip::
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
if not no_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = ui.Line({
|
||||
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||
})
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local line_height = ui.height
|
||||
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||
if next_str == nil and line_height == 1 then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if (last_line + line_height) > mediainfo_job_skip then
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if (last_line + line_height) >= mediainfo_job_skip + limit then
|
||||
last_line = mediainfo_job_skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
last_line = last_line + line_height
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if not no_metadata then
|
||||
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||
local covers = get_cover_layers(job)
|
||||
local cover_index = math.floor(
|
||||
math.max(
|
||||
1,
|
||||
utils.get_state(const.STATE_KEY.units)
|
||||
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||
or 0
|
||||
)
|
||||
)
|
||||
|
||||
if not covers[cover_index] then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
else
|
||||
-- NOTE: Recalculate mediainfo using cached latest valid skip value when reach the end of mediainfo output
|
||||
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||
|
||||
goto recalc_mediainfo_job_skip
|
||||
end
|
||||
else
|
||||
utils.set_state(
|
||||
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||
if
|
||||
old_metadata_area.win_x == job.area.x
|
||||
and old_metadata_area.win_y == job.area.y
|
||||
and old_metadata_area.win_w == job.area.w
|
||||
and old_metadata_area.win_h == job.area.h
|
||||
then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect({
|
||||
x = old_metadata_area.x,
|
||||
y = old_metadata_area.y,
|
||||
w = old_metadata_area.w,
|
||||
h = old_metadata_area.h,
|
||||
})),
|
||||
})
|
||||
end
|
||||
end
|
||||
utils.force_render()
|
||||
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Preload image
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||
|
||||
-- NOTE: Only generate preview image when cache image is not exist
|
||||
if cache_img_url and (not cache_img_url_cha or cache_img_url_cha.len <= 0) then
|
||||
local units = utils.get_state(const.STATE_KEY.units)
|
||||
local covers = get_cover_layers(job)
|
||||
local cover_index = covers[1] or 0
|
||||
if units ~= nil then
|
||||
cover_index = math.floor(math.max(1, math.abs(job.skip / units)))
|
||||
if not covers[cover_index] then
|
||||
cover_index = math.max(0, #covers - 1)
|
||||
end
|
||||
end
|
||||
local qv = 31 - math.floor(rt.preview.image_quality * 0.3)
|
||||
local audio_preload_output, audio_preload_err = Command("ffmpeg"):arg({
|
||||
"-v",
|
||||
"error",
|
||||
"-threads",
|
||||
1,
|
||||
"-i",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
"-map",
|
||||
string.format("0:v:%d?", cover_index),
|
||||
"-an",
|
||||
"-sn",
|
||||
"-dn",
|
||||
"-vframes",
|
||||
1,
|
||||
"-q:v",
|
||||
qv,
|
||||
"-vf",
|
||||
string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2),
|
||||
"-f",
|
||||
"image2",
|
||||
"-y",
|
||||
tostring(cache_img_url),
|
||||
}):output()
|
||||
-- NOTE: Some audio types doesn't have cover image -> error ""
|
||||
if audio_preload_err then
|
||||
ya.dbg("mediainfo", audio_preload_err)
|
||||
err_msg = err_msg
|
||||
.. string.format(
|
||||
"Failed to start `%s`.\n Error: %s\n",
|
||||
"ffmpeg",
|
||||
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||
)
|
||||
elseif
|
||||
audio_preload_output
|
||||
and type(audio_preload_output.stderr) == "string"
|
||||
and audio_preload_output.stderr:find("does not contain any stream")
|
||||
then
|
||||
ya.dbg("mediainfo", audio_preload_output and audio_preload_output.stderr)
|
||||
cache_img_url_cha, _ = fs.cha(cache_img_url)
|
||||
if cache_img_url_cha then
|
||||
fs.remove("file", Url(cache_img_url))
|
||||
end
|
||||
-- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image
|
||||
audio_preload_output, audio_preload_err = require("magick")
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-size",
|
||||
"1x1",
|
||||
"canvas:none",
|
||||
string.format("PNG32:%s", cache_img_url),
|
||||
})
|
||||
:output()
|
||||
if
|
||||
(audio_preload_output and audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "")
|
||||
or audio_preload_err
|
||||
then
|
||||
ya.dbg("mediainfo", audio_preload_err or (audio_preload_output and audio_preload_output.stderr))
|
||||
err_msg = err_msg
|
||||
.. string.format(
|
||||
"Failed to start `%s`.\n Error: %s\n",
|
||||
"magick",
|
||||
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,381 @@
|
||||
--- @since 26.5.6
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
local function get_cover_layers(job)
|
||||
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||
if not cache then
|
||||
return {}
|
||||
end
|
||||
local covers = utils.get_state("f" .. tostring(cache))
|
||||
if covers and type(covers) == "table" then
|
||||
return covers
|
||||
end
|
||||
local output, err = Command("ffprobe"):arg({
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v",
|
||||
"-show_entries",
|
||||
"stream=index:stream_disposition=attached_pic",
|
||||
"-of",
|
||||
"json",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
}):output()
|
||||
if err or not output then
|
||||
return {}
|
||||
end
|
||||
covers = {}
|
||||
local data = ya.json_decode(output.stdout)
|
||||
if type(data.streams) == "table" then
|
||||
for _, stream in ipairs(data.streams) do
|
||||
if stream.disposition and stream.disposition.attached_pic then
|
||||
covers[#covers + 1] = stream.index
|
||||
end
|
||||
end
|
||||
end
|
||||
utils.set_state("f" .. tostring(cache), covers)
|
||||
return covers
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local no_metadata = job.args.no_metadata
|
||||
local mediainfo_job_skip = job.skip
|
||||
::recalc_mediainfo_job_skip::
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
if not no_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
local opt = { ansi = true, tab_size = rt.preview.tab_size, wrap = rt.preview.wrap, width = max_width }
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = label .. ": " .. value
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = str
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local wrapped = ui.lines(line, opt)
|
||||
local line_height = #wrapped
|
||||
local from = 1
|
||||
local to = math.min(line_height, mediainfo_job_skip + limit - last_line)
|
||||
|
||||
local total_rendered_text_len = 1
|
||||
local total_label_rendered_len = 1
|
||||
local label_total_len = label and utf8.len(label .. ": ") or 0
|
||||
for j = from, to do
|
||||
local current_line_components = {}
|
||||
local wrapped_line_len = wrapped[j]:width() or 0
|
||||
local wrapped_raw =
|
||||
utils.utf8_sub(line, total_rendered_text_len, total_rendered_text_len + wrapped_line_len)
|
||||
wrapped_line_len = utf8.len(wrapped_raw)
|
||||
total_rendered_text_len = total_rendered_text_len + wrapped_line_len
|
||||
|
||||
if last_line + 1 > mediainfo_job_skip then
|
||||
local label_raw = label_total_len - total_label_rendered_len <= 0 and ""
|
||||
or utils.utf8_sub(wrapped_raw, 1, label_total_len - total_label_rendered_len)
|
||||
local label_raw_len = utf8.len(label_raw)
|
||||
if label_raw_len > 0 then
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(label_raw):style(ui.Style():fg("reset"):bold())
|
||||
)
|
||||
total_label_rendered_len = total_label_rendered_len + label_raw_len
|
||||
wrapped_raw = wrapped_raw:gsub("^" .. utils.is_literal_string(label_raw), "", 1)
|
||||
end
|
||||
if total_label_rendered_len >= label_total_len then
|
||||
local value_raw = wrapped_raw
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(value_raw or ""):style(
|
||||
label and (th.spot.tbl_col or ui.Style():fg("blue"))
|
||||
or (th.spot.title or ui.Style():fg("green"))
|
||||
)
|
||||
)
|
||||
end
|
||||
table.insert(lines, ui.Line(current_line_components))
|
||||
-- last_line = last_line + 1
|
||||
else
|
||||
total_label_rendered_len = total_label_rendered_len + total_rendered_text_len
|
||||
end
|
||||
last_line = last_line + 1
|
||||
if next_str == nil and not wrapped[j + 1] then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if last_line >= mediainfo_job_skip + limit then
|
||||
last_line = mediainfo_job_skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if not no_metadata then
|
||||
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||
local covers = get_cover_layers(job)
|
||||
local cover_index = math.floor(
|
||||
math.max(
|
||||
1,
|
||||
utils.get_state(const.STATE_KEY.units)
|
||||
and (math.abs(job.skip / utils.get_state(const.STATE_KEY.units)))
|
||||
or 0
|
||||
)
|
||||
)
|
||||
|
||||
if not covers[cover_index] then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
else
|
||||
-- NOTE: Recalculate mediainfo using cached latest valid skip value when reach the end of mediainfo output
|
||||
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||
|
||||
goto recalc_mediainfo_job_skip
|
||||
end
|
||||
else
|
||||
utils.set_state(
|
||||
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||
if
|
||||
old_metadata_area.win_x == job.area.x
|
||||
and old_metadata_area.win_y == job.area.y
|
||||
and old_metadata_area.win_w == job.area.w
|
||||
and old_metadata_area.win_h == job.area.h
|
||||
then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect({
|
||||
x = old_metadata_area.x,
|
||||
y = old_metadata_area.y,
|
||||
w = old_metadata_area.w,
|
||||
h = old_metadata_area.h,
|
||||
})),
|
||||
})
|
||||
end
|
||||
end
|
||||
utils.force_render()
|
||||
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Preload image
|
||||
|
||||
local cache_img_url = ya.file_cache(job)
|
||||
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||
|
||||
-- NOTE: Only generate preview image when cache image is not exist
|
||||
if cache_img_url and (not cache_img_url_cha or cache_img_url_cha.len <= 0) then
|
||||
local units = utils.get_state(const.STATE_KEY.units)
|
||||
local covers = get_cover_layers(job)
|
||||
local cover_index = covers[1] or 0
|
||||
if units ~= nil then
|
||||
cover_index = math.floor(math.max(1, math.abs(job.skip / units)))
|
||||
if not covers[cover_index] then
|
||||
cover_index = math.max(0, #covers - 1)
|
||||
end
|
||||
end
|
||||
local qv = 31 - math.floor(rt.preview.image_quality * 0.3)
|
||||
local audio_preload_output, audio_preload_err = Command("ffmpeg"):arg({
|
||||
"-v",
|
||||
"error",
|
||||
"-threads",
|
||||
1,
|
||||
"-i",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
"-map",
|
||||
string.format("0:v:%d?", cover_index),
|
||||
"-an",
|
||||
"-sn",
|
||||
"-dn",
|
||||
"-vframes",
|
||||
1,
|
||||
"-q:v",
|
||||
qv,
|
||||
"-vf",
|
||||
string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2),
|
||||
"-f",
|
||||
"image2",
|
||||
"-y",
|
||||
tostring(cache_img_url),
|
||||
}):output()
|
||||
-- NOTE: Some audio types doesn't have cover image -> error ""
|
||||
if audio_preload_err then
|
||||
ya.dbg("mediainfo", audio_preload_err)
|
||||
err_msg = err_msg
|
||||
.. string.format(
|
||||
"Failed to start `%s`.\n Error: %s\n",
|
||||
"ffmpeg",
|
||||
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||
)
|
||||
elseif
|
||||
audio_preload_output
|
||||
and type(audio_preload_output.stderr) == "string"
|
||||
and audio_preload_output.stderr:find("does not contain any stream")
|
||||
then
|
||||
ya.dbg("mediainfo", audio_preload_output and audio_preload_output.stderr)
|
||||
cache_img_url_cha, _ = fs.cha(cache_img_url)
|
||||
if cache_img_url_cha then
|
||||
fs.remove("file", Url(cache_img_url))
|
||||
end
|
||||
-- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image
|
||||
audio_preload_output, audio_preload_err = require("magick")
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-size",
|
||||
"1x1",
|
||||
"canvas:none",
|
||||
string.format("PNG32:%s", cache_img_url),
|
||||
})
|
||||
:output()
|
||||
if
|
||||
(audio_preload_output and audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "")
|
||||
or audio_preload_err
|
||||
then
|
||||
ya.dbg("mediainfo", audio_preload_err or (audio_preload_output and audio_preload_output.stderr))
|
||||
err_msg = err_msg
|
||||
.. string.format(
|
||||
"Failed to start `%s`.\n Error: %s\n",
|
||||
"magick",
|
||||
tostring(audio_preload_err or (audio_preload_output and audio_preload_output.stderr or ""))
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,68 @@
|
||||
--- @since 26.1.22
|
||||
|
||||
local M = {}
|
||||
|
||||
M.skip_labels = {
|
||||
["Complete name"] = true,
|
||||
["CompleteName_Last"] = true,
|
||||
["Unique ID"] = true,
|
||||
["File size"] = true,
|
||||
["Format/Info"] = true,
|
||||
["Codec ID/Info"] = true,
|
||||
["MD5 of the unencoded content"] = true,
|
||||
}
|
||||
|
||||
M.ENTRY_ACTION = {
|
||||
toggle_metadata = "toggle-metadata",
|
||||
toggle_preview = "toggle-preview",
|
||||
hide_metadata = "hide-metadata",
|
||||
hide_preview = "hide-preview",
|
||||
show_metadata = "show-metadata",
|
||||
show_preview = "show-preview",
|
||||
reset = "reset",
|
||||
}
|
||||
|
||||
M.STATE_KEY = {
|
||||
skip_labels = "skip_labels",
|
||||
skip_section_labels = "skip_section_labels",
|
||||
units = "units",
|
||||
no_metadata = "no_metadata",
|
||||
no_preview = "no_preview",
|
||||
prev_metadata_area = "prev_metadata_area",
|
||||
prev_image_height = "prev_image_height",
|
||||
last_valid_mediainfo_skip = "last_valid_mediainfo_skip",
|
||||
cached_mediainfo = "cached_mediainfo",
|
||||
cached_job_args = "cached_job_args",
|
||||
}
|
||||
|
||||
M.magick_image_mimes = {
|
||||
avif = true,
|
||||
hei = true,
|
||||
heic = true,
|
||||
heif = true,
|
||||
["heif-sequence"] = true,
|
||||
["heic-sequence"] = true,
|
||||
jxl = true,
|
||||
tiff = true,
|
||||
xml = true,
|
||||
-- ["svg+xml"] = true,
|
||||
["canon-cr2"] = true,
|
||||
}
|
||||
|
||||
M.seekable_mimes = {
|
||||
-- NOTE: Adobe illustrator photoshop mimetypes
|
||||
["application/postscript"] = true,
|
||||
["application/dvb.ait"] = true,
|
||||
["application/illustrator"] = true,
|
||||
["application/vnd.adobe.illustrator"] = true,
|
||||
["image/x-eps"] = true,
|
||||
["application/eps"] = true,
|
||||
["application/pdf"] = true,
|
||||
|
||||
["image/adobe.photoshop"] = true,
|
||||
}
|
||||
|
||||
M.suffix = "_mediainfo"
|
||||
M.SHELL = os.getenv("SHELL") or ""
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,253 @@
|
||||
--- @since 26.1.22
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url = ya.file_cache({
|
||||
skip = 0,
|
||||
args = job.args,
|
||||
file = job.file,
|
||||
area = job.area,
|
||||
})
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local no_metadata = job.args.no_metadata
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
if not no_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = ui.Line({
|
||||
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||
})
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local line_height = ui.height
|
||||
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||
if next_str == nil and line_height == 1 then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if (last_line + line_height) > job.skip then
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if (last_line + line_height) >= job.skip + limit then
|
||||
last_line = job.skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
last_line = last_line + line_height
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if not no_metadata and EOF_mediainfo and #lines == 0 and job.skip > 0 then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
utils.force_render()
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||
if
|
||||
old_metadata_area.win_x == job.area.x
|
||||
and old_metadata_area.win_y == job.area.y
|
||||
and old_metadata_area.win_w == job.area.w
|
||||
and old_metadata_area.win_h == job.area.h
|
||||
then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect({
|
||||
x = old_metadata_area.x,
|
||||
y = old_metadata_area.y,
|
||||
w = old_metadata_area.w,
|
||||
h = old_metadata_area.h,
|
||||
})),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Preload image
|
||||
|
||||
local mime = job.mime:match(".*/(.*)$")
|
||||
local is_svg = mime == "svg+xml"
|
||||
local is_magick = const.magick_image_mimes[mime]
|
||||
local no_skip_job = { skip = 0, file = job.file, args = job.args, area = job.area }
|
||||
local cache_img_url = ya.file_cache(no_skip_job)
|
||||
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||
|
||||
-- NOTE: Only generate preview image when cache image is not exist
|
||||
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||
local cache_img_status, image_preload_err
|
||||
if not is_valid_utf8_path then
|
||||
-- NOTE: Case not valid utf8 path, use trick to generate preview image
|
||||
if is_svg then
|
||||
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||
if fs.cha(cache_img_url_tmp) then
|
||||
fs.remove("file", cache_img_url_tmp)
|
||||
end
|
||||
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||
or fs.unique_name(cache_img_url_tmp)
|
||||
-- svg under invalid utf8 path
|
||||
cache_img_status, image_preload_err = require("magick")
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-background",
|
||||
"none",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
"-auto-orient",
|
||||
"-strip",
|
||||
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||
"-quality",
|
||||
rt.preview.image_quality,
|
||||
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||
})
|
||||
:status()
|
||||
if cache_img_status then
|
||||
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||
end
|
||||
end
|
||||
else
|
||||
-- NOTE: Case valid utf8 path, use image, svg, or magick module
|
||||
local image_module = is_svg and "svg" or (is_magick and "magick" or "image")
|
||||
cache_img_status, image_preload_err = require(image_module):preload(no_skip_job)
|
||||
end
|
||||
|
||||
if not cache_img_status and image_preload_err then
|
||||
ya.dbg("mediainfo", image_preload_err)
|
||||
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,290 @@
|
||||
--- @since 26.5.6
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url = ya.file_cache({
|
||||
skip = 0,
|
||||
args = job.args,
|
||||
file = job.file,
|
||||
area = job.area,
|
||||
})
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local no_metadata = job.args.no_metadata
|
||||
local mediainfo_job_skip = job.skip
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
if not no_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
local opt = { ansi = true, tab_size = rt.preview.tab_size, wrap = rt.preview.wrap, width = max_width }
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = label .. ": " .. value
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = str
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local wrapped = ui.lines(line, opt)
|
||||
local line_height = #wrapped
|
||||
local from = 1
|
||||
local to = math.min(line_height, mediainfo_job_skip + limit - last_line)
|
||||
|
||||
local total_rendered_text_len = 1
|
||||
local total_label_rendered_len = 1
|
||||
local label_total_len = label and utf8.len(label .. ": ") or 0
|
||||
for j = from, to do
|
||||
local current_line_components = {}
|
||||
local wrapped_line_len = wrapped[j]:width() or 0
|
||||
local wrapped_raw =
|
||||
utils.utf8_sub(line, total_rendered_text_len, total_rendered_text_len + wrapped_line_len)
|
||||
wrapped_line_len = utf8.len(wrapped_raw)
|
||||
total_rendered_text_len = total_rendered_text_len + wrapped_line_len
|
||||
|
||||
if last_line + 1 > mediainfo_job_skip then
|
||||
local label_raw = label_total_len - total_label_rendered_len <= 0 and ""
|
||||
or utils.utf8_sub(wrapped_raw, 1, label_total_len - total_label_rendered_len)
|
||||
local label_raw_len = utf8.len(label_raw)
|
||||
if label_raw_len > 0 then
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(label_raw):style(ui.Style():fg("reset"):bold())
|
||||
)
|
||||
total_label_rendered_len = total_label_rendered_len + label_raw_len
|
||||
wrapped_raw = wrapped_raw:gsub("^" .. utils.is_literal_string(label_raw), "", 1)
|
||||
end
|
||||
if total_label_rendered_len >= label_total_len then
|
||||
local value_raw = wrapped_raw
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(value_raw or ""):style(
|
||||
label and (th.spot.tbl_col or ui.Style():fg("blue"))
|
||||
or (th.spot.title or ui.Style():fg("green"))
|
||||
)
|
||||
)
|
||||
end
|
||||
table.insert(lines, ui.Line(current_line_components))
|
||||
-- last_line = last_line + 1
|
||||
else
|
||||
total_label_rendered_len = total_label_rendered_len + total_rendered_text_len
|
||||
end
|
||||
last_line = last_line + 1
|
||||
if next_str == nil and not wrapped[j + 1] then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if last_line >= mediainfo_job_skip + limit then
|
||||
last_line = mediainfo_job_skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if not no_metadata and EOF_mediainfo and #lines == 0 and job.skip > 0 then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
utils.force_render()
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||
if
|
||||
old_metadata_area.win_x == job.area.x
|
||||
and old_metadata_area.win_y == job.area.y
|
||||
and old_metadata_area.win_w == job.area.w
|
||||
and old_metadata_area.win_h == job.area.h
|
||||
then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect({
|
||||
x = old_metadata_area.x,
|
||||
y = old_metadata_area.y,
|
||||
w = old_metadata_area.w,
|
||||
h = old_metadata_area.h,
|
||||
})),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Preload image
|
||||
|
||||
local mime = job.mime:match(".*/(.*)$")
|
||||
local is_svg = mime == "svg+xml"
|
||||
local is_magick = const.magick_image_mimes[mime]
|
||||
local no_skip_job = { skip = 0, file = job.file, args = job.args, area = job.area }
|
||||
local cache_img_url = ya.file_cache(no_skip_job)
|
||||
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||
|
||||
-- NOTE: Only generate preview image when cache image is not exist
|
||||
if not cache_img_url_cha or cache_img_url_cha.len <= 0 then
|
||||
local cache_img_status, image_preload_err
|
||||
if not is_valid_utf8_path then
|
||||
-- NOTE: Case not valid utf8 path, use trick to generate preview image
|
||||
if is_svg then
|
||||
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||
if fs.cha(cache_img_url_tmp) then
|
||||
fs.remove("file", cache_img_url_tmp)
|
||||
end
|
||||
local tmp_file_path, _ = type(fs.unique) == "function" and fs.unique("file", cache_img_url_tmp)
|
||||
or fs.unique_name(cache_img_url_tmp)
|
||||
-- svg under invalid utf8 path
|
||||
cache_img_status, image_preload_err = require("magick")
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-background",
|
||||
"none",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
"-auto-orient",
|
||||
"-strip",
|
||||
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||
"-quality",
|
||||
rt.preview.image_quality,
|
||||
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||
})
|
||||
:status()
|
||||
if cache_img_status then
|
||||
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||
end
|
||||
end
|
||||
else
|
||||
-- NOTE: Case valid utf8 path, use image, svg, or magick module
|
||||
local image_module = is_svg and "svg" or (is_magick and "magick" or "image")
|
||||
cache_img_status, image_preload_err = require(image_module):preload(no_skip_job)
|
||||
end
|
||||
|
||||
if not cache_img_status and image_preload_err then
|
||||
ya.dbg("mediainfo", image_preload_err)
|
||||
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,497 +1,233 @@
|
||||
--- @since 25.5.31
|
||||
|
||||
local skip_labels = {
|
||||
["Complete name"] = true,
|
||||
["CompleteName_Last"] = true,
|
||||
["Unique ID"] = true,
|
||||
["File size"] = true,
|
||||
["Format/Info"] = true,
|
||||
["Codec ID/Info"] = true,
|
||||
["MD5 of the unencoded content"] = true,
|
||||
}
|
||||
|
||||
local ENTRY_ACTION = {
|
||||
toggle_metadata = "toggle-metadata",
|
||||
}
|
||||
|
||||
local STATE_KEY = {
|
||||
units = "units",
|
||||
hide_metadata = "hide_metadata",
|
||||
prev_metadata_area = "prev_metadata_area",
|
||||
prev_peek_data = "prev_peek_data",
|
||||
}
|
||||
|
||||
local magick_image_mimes = {
|
||||
avif = true,
|
||||
hei = true,
|
||||
heic = true,
|
||||
heif = true,
|
||||
["heif-sequence"] = true,
|
||||
["heic-sequence"] = true,
|
||||
jxl = true,
|
||||
xml = true,
|
||||
["svg+xml"] = true,
|
||||
["canon-cr2"] = true,
|
||||
}
|
||||
|
||||
local seekable_mimes = {
|
||||
["application/postscript"] = true,
|
||||
["image/adobe.photoshop"] = true,
|
||||
}
|
||||
--- @since 26.1.22
|
||||
|
||||
local M = {}
|
||||
local suffix = "_mediainfo"
|
||||
local SHELL = os.getenv("SHELL") or ""
|
||||
local using_new_height_api = ui.lines ~= nil
|
||||
local const
|
||||
local utils
|
||||
local adobe
|
||||
local audio
|
||||
local image
|
||||
local video
|
||||
local none_media_preview
|
||||
|
||||
local function is_valid_utf8(str)
|
||||
return utf8.len(str) ~= nil
|
||||
end
|
||||
const = require(".const")
|
||||
utils = require(".utils")
|
||||
|
||||
local function path_quote(path)
|
||||
if not path or tostring(path) == "" then
|
||||
return path
|
||||
end
|
||||
local result = "'" .. string.gsub(tostring(path), "'", "'\\''") .. "'"
|
||||
return result
|
||||
end
|
||||
|
||||
local function read_mediainfo_cached_file(file_path)
|
||||
-- Open the file in read mode
|
||||
local file = io.open(file_path, "r")
|
||||
|
||||
if file then
|
||||
-- Read the entire file content
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
return content
|
||||
end
|
||||
end
|
||||
|
||||
local set_state = ya.sync(function(state, key, value)
|
||||
state[key] = value
|
||||
end)
|
||||
|
||||
local get_state = ya.sync(function(state, key)
|
||||
return state[key]
|
||||
end)
|
||||
|
||||
local force_render = ya.sync(function(_, _)
|
||||
(ui.render or ya.render)()
|
||||
end)
|
||||
|
||||
local function image_layer_count(job)
|
||||
local cache = ya.file_cache({ file = job.file, skip = 0 })
|
||||
if not cache then
|
||||
return 0
|
||||
end
|
||||
local layer_count = get_state("f" .. tostring(cache))
|
||||
if layer_count then
|
||||
return layer_count
|
||||
end
|
||||
local output, err = Command("identify")
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
if err then
|
||||
return 0
|
||||
end
|
||||
layer_count = 0
|
||||
for line in output.stdout:gmatch("[^\r\n]+") do
|
||||
if line:match("%S") then
|
||||
layer_count = layer_count + 1
|
||||
end
|
||||
end
|
||||
set_state("f" .. tostring(cache), layer_count)
|
||||
return layer_count
|
||||
if using_new_height_api then
|
||||
ya.dbg("mediainfo", "Using yazi version >= 26.5.6")
|
||||
adobe = require(".adobe-stable")
|
||||
audio = require(".audio-stable")
|
||||
image = require(".image-stable")
|
||||
video = require(".video-stable")
|
||||
none_media_preview = require(".none-media-preview-stable")
|
||||
else
|
||||
ya.dbg("mediainfo", "Using yazi version <= 26.1.22")
|
||||
adobe = require(".adobe-old")
|
||||
audio = require(".audio-old")
|
||||
image = require(".image-old")
|
||||
video = require(".video-old")
|
||||
none_media_preview = require(".none-media-preview-old")
|
||||
end
|
||||
|
||||
function M:peek(job)
|
||||
-- debounce peek
|
||||
local start = os.clock()
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
|
||||
|
||||
-- Need mime to decide which module to use
|
||||
if not job.mime then
|
||||
return
|
||||
end
|
||||
set_state(STATE_KEY.prev_peek_data, {
|
||||
file = tostring(job.file.path or job.file.cache or job.file.url),
|
||||
mime = job.mime,
|
||||
area = {
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = job.area.h,
|
||||
},
|
||||
args = job.args,
|
||||
skip = job.skip,
|
||||
})
|
||||
|
||||
local no_metadata_user_cfg = utils.get_state(const.STATE_KEY.no_metadata)
|
||||
if no_metadata_user_cfg ~= nil then
|
||||
job.args.no_metadata = no_metadata_user_cfg
|
||||
end
|
||||
|
||||
local no_preview_user_cfg = utils.get_state(const.STATE_KEY.no_preview)
|
||||
if no_preview_user_cfg ~= nil then
|
||||
job.args.no_preview = no_preview_user_cfg
|
||||
end
|
||||
|
||||
utils.set_states(const.STATE_KEY.cached_job_args, tostring(job.file.url), job.args)
|
||||
if job.args.no_preview and job.args.no_metadata then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(job.area),
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if job.args.no_preview then
|
||||
return none_media_preview:peek(job)
|
||||
end
|
||||
|
||||
local is_video = string.find(job.mime, "^video/")
|
||||
local is_audio = string.find(job.mime, "^audio/")
|
||||
local is_image = string.find(job.mime, "^image/")
|
||||
local is_seekable = seekable_mimes[job.mime] or is_video
|
||||
local cache_img_url = (is_audio or is_image) and cache_img_url_no_skip
|
||||
local is_adobe = const.seekable_mimes[job.mime]
|
||||
|
||||
if is_seekable then
|
||||
cache_img_url = ya.file_cache(job)
|
||||
end
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
ya.sleep(math.max(0, rt.preview.image_delay / 1000 + start - os.clock()))
|
||||
local hide_metadata = get_state(STATE_KEY.hide_metadata)
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local is_wrap = rt.preview.wrap == "yes"
|
||||
if not hide_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. suffix
|
||||
local output = read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
for str in output:gsub("\n+$", ""):gmatch("[^\n]*") do
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
local line
|
||||
if label then
|
||||
if not skip_labels[label] then
|
||||
line = ui.Line({
|
||||
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||
})
|
||||
end
|
||||
elseif str ~= "General" then
|
||||
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||
end
|
||||
|
||||
if line then
|
||||
local line_height = math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1)
|
||||
if (last_line + line_height) > job.skip then
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if (last_line + line_height) >= job.skip + limit then
|
||||
last_line = job.skip + limit
|
||||
break
|
||||
end
|
||||
last_line = last_line + line_height
|
||||
end
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if
|
||||
(job.skip > 0 and #lines == 0 and not hide_metadata)
|
||||
and (
|
||||
not is_seekable
|
||||
or (is_video and job.skip >= 90)
|
||||
or (
|
||||
(job.mime == "image/adobe.photoshop" or job.mime == "application/postscript")
|
||||
and image_layer_count(job)
|
||||
< (1 + math.floor(
|
||||
math.max(0, get_state(STATE_KEY.units) and (job.skip / get_state(STATE_KEY.units)) or 0)
|
||||
))
|
||||
)
|
||||
)
|
||||
then
|
||||
ya.emit("peek", {
|
||||
math.max(0, job.skip - (get_state(STATE_KEY.units) or limit)),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
end
|
||||
force_render()
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if hide_metadata and get_state(STATE_KEY.prev_metadata_area) then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect(get_state(STATE_KEY.prev_metadata_area))),
|
||||
})
|
||||
end
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image
|
||||
if is_audio and image_height == 1 then
|
||||
local info = ya.image_info(cache_img_url)
|
||||
if not info or (info.w == 1 and info.h == 1) then
|
||||
image_height = 0
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Workaround case video.lua doesn't doesn't generate preview image because of `skip` overflow video duration
|
||||
if is_video and not rendered_img_rect then
|
||||
image_height = math.max(job.area.h - mediainfo_height, 0)
|
||||
end
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if not hide_metadata then
|
||||
set_state(STATE_KEY.prev_metadata_area, {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
})
|
||||
if is_adobe then
|
||||
return adobe:peek(job)
|
||||
elseif is_image then
|
||||
return image:peek(job)
|
||||
elseif is_video then
|
||||
return video:peek(job)
|
||||
elseif is_audio then
|
||||
return audio:peek(job)
|
||||
else
|
||||
return none_media_preview:peek(job)
|
||||
end
|
||||
end
|
||||
|
||||
function M:seek(job)
|
||||
local h = cx.active.current.hovered
|
||||
if h and h.url == job.file.url then
|
||||
set_state(STATE_KEY.units, job.units)
|
||||
utils.set_state(const.STATE_KEY.units, job.units)
|
||||
ya.emit("peek", {
|
||||
math.max(0, cx.active.preview.skip + job.units),
|
||||
only_if = job.file.url,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function M:re_peek()
|
||||
local prev_peek_data = get_state(STATE_KEY.prev_peek_data)
|
||||
if prev_peek_data then
|
||||
prev_peek_data.file = File({
|
||||
url = Url(prev_peek_data.file),
|
||||
cha = fs.cha(Url(prev_peek_data.file)),
|
||||
})
|
||||
prev_peek_data.area = ui.area and ui.area("preview") or ui.Rect(prev_peek_data.area)
|
||||
|
||||
self:peek(prev_peek_data)
|
||||
end
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cache_img_url = ya.file_cache({ file = job.file, skip = 0 })
|
||||
if not cache_img_url then
|
||||
ya.dbg("mediainfo", "Can't access yazi cache folder")
|
||||
return true
|
||||
end
|
||||
local cache_mediainfo_url = Url(tostring(cache_img_url) .. suffix)
|
||||
cache_img_url = seekable_mimes[job.mime] and ya.file_cache(job) or cache_img_url
|
||||
local cache_img_url_cha = cache_img_url and fs.cha(cache_img_url)
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
-- video mimetype
|
||||
if job.mime then
|
||||
if string.find(job.mime, "^video/") then
|
||||
local cache_img_status, video_preload_err = require("video"):preload(job)
|
||||
if not cache_img_status and video_preload_err then
|
||||
err_msg = err_msg
|
||||
.. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg")
|
||||
end
|
||||
-- audo and image mimetype
|
||||
elseif cache_img_url and (not cache_img_url_cha or cache_img_url_cha.len <= 0) then
|
||||
-- audio
|
||||
if string.find(job.mime, "^audio/") then
|
||||
local qv = 31 - math.floor(rt.preview.image_quality * 0.3)
|
||||
local audio_preload_output, audio_preload_err = Command("ffmpeg"):arg({
|
||||
"-v",
|
||||
"error",
|
||||
"-threads",
|
||||
1,
|
||||
"-skip_frame",
|
||||
"nokey",
|
||||
"-an",
|
||||
"-sn",
|
||||
"-dn",
|
||||
"-i",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
"-vframes",
|
||||
1,
|
||||
"-q:v",
|
||||
qv,
|
||||
"-vf",
|
||||
string.format("scale=-1:'min(%d,ih)':flags=fast_bilinear", rt.preview.max_height / 2),
|
||||
"-f",
|
||||
"image2",
|
||||
"-y",
|
||||
tostring(cache_img_url),
|
||||
}):output()
|
||||
-- NOTE: Some audio types doesn't have cover image -> error ""
|
||||
if
|
||||
(audio_preload_output and audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "")
|
||||
or audio_preload_err
|
||||
then
|
||||
err_msg = err_msg
|
||||
.. string.format("Failed to start `%s`, Do you have `%s` installed?\n", "ffmpeg", "ffmpeg")
|
||||
else
|
||||
cache_img_url_cha, _ = fs.cha(cache_img_url)
|
||||
if not cache_img_url_cha then
|
||||
-- NOTE: Workaround case audio has no cover image. Prevent regenerate preview image
|
||||
audio_preload_output, audio_preload_err = require("magick")
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-size",
|
||||
"1x1",
|
||||
"canvas:none",
|
||||
string.format("PNG32:%s", cache_img_url),
|
||||
})
|
||||
:output()
|
||||
if
|
||||
(audio_preload_output.stderr ~= nil and audio_preload_output.stderr ~= "")
|
||||
or audio_preload_err
|
||||
then
|
||||
err_msg = err_msg
|
||||
.. string.format(
|
||||
"Failed to start `%s`, Do you have `%s` installed?\n",
|
||||
"magick",
|
||||
"magick"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- image
|
||||
elseif string.find(job.mime, "^image/") or job.mime == "application/postscript" then
|
||||
local svg_plugin_ok, svg_plugin = pcall(require, "svg")
|
||||
local _, magick_plugin = pcall(require, "magick")
|
||||
local mime = job.mime:match(".*/(.*)$")
|
||||
|
||||
local image_plugin = magick_image_mimes[mime]
|
||||
and ((mime == "svg+xml" and svg_plugin_ok) and svg_plugin or magick_plugin)
|
||||
or require("image")
|
||||
|
||||
local cache_img_status, image_preload_err
|
||||
-- psd, ai, eps
|
||||
if mime == "adobe.photoshop" or job.mime == "application/postscript" then
|
||||
local layer_index = 0
|
||||
local units = get_state(STATE_KEY.units)
|
||||
if units ~= nil then
|
||||
local max_layer = image_layer_count(job)
|
||||
layer_index = math.floor(math.max(0, job.skip / units))
|
||||
if layer_index + 1 > max_layer then
|
||||
layer_index = max_layer - 1
|
||||
end
|
||||
end
|
||||
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||
if fs.cha(cache_img_url_tmp) then
|
||||
fs.remove("file", cache_img_url_tmp)
|
||||
end
|
||||
local tmp_file_path, _ = fs.unique_name(cache_img_url_tmp)
|
||||
cache_img_status, image_preload_err = magick_plugin
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-background",
|
||||
"none",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url)
|
||||
.. "["
|
||||
.. tostring(layer_index)
|
||||
.. "]",
|
||||
"-auto-orient",
|
||||
"-strip",
|
||||
"-resize",
|
||||
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||
"-quality",
|
||||
rt.preview.image_quality,
|
||||
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||
})
|
||||
:status()
|
||||
if cache_img_status then
|
||||
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||
end
|
||||
elseif mime == "svg+xml" and not is_valid_utf8_path then
|
||||
local cache_img_url_tmp = Url(cache_img_url .. ".tmp")
|
||||
if fs.cha(cache_img_url_tmp) then
|
||||
fs.remove("file", cache_img_url_tmp)
|
||||
end
|
||||
local tmp_file_path, _ = fs.unique_name(cache_img_url_tmp)
|
||||
-- svg under invalid utf8 path
|
||||
cache_img_status, image_preload_err = magick_plugin
|
||||
.with_limit()
|
||||
:arg({
|
||||
"-background",
|
||||
"none",
|
||||
tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url),
|
||||
"-auto-orient",
|
||||
"-strip",
|
||||
"-flatten",
|
||||
"-resize",
|
||||
string.format("%dx%d>", rt.preview.max_width, rt.preview.max_height),
|
||||
"-quality",
|
||||
rt.preview.image_quality,
|
||||
string.format("PNG32:%s", tostring(tmp_file_path)),
|
||||
})
|
||||
:status()
|
||||
if cache_img_status then
|
||||
os.rename(tostring(tmp_file_path), tostring(cache_img_url))
|
||||
end
|
||||
else
|
||||
-- other image
|
||||
local no_skip_job = { skip = 0, file = job.file, args = {} }
|
||||
cache_img_status, image_preload_err = image_plugin:preload(no_skip_job)
|
||||
end
|
||||
if not cache_img_status then
|
||||
err_msg = err_msg .. (image_preload_err and (tostring(image_preload_err)) or "")
|
||||
end
|
||||
end
|
||||
end
|
||||
if not job.mime then
|
||||
return false
|
||||
end
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
|
||||
local no_metadata_user_cfg = utils.get_state(const.STATE_KEY.no_metadata)
|
||||
if no_metadata_user_cfg ~= nil then
|
||||
job.args.no_metadata = no_metadata_user_cfg
|
||||
end
|
||||
local cmd = "mediainfo"
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
|
||||
local no_preview_user_cfg = utils.get_state(const.STATE_KEY.no_preview)
|
||||
if no_preview_user_cfg ~= nil then
|
||||
job.args.no_preview = no_preview_user_cfg
|
||||
end
|
||||
|
||||
if job.args.no_preview and job.args.no_metadata then
|
||||
return false, nil
|
||||
end
|
||||
if job.args.no_preview then
|
||||
return none_media_preview:preload(job)
|
||||
end
|
||||
|
||||
local is_video = string.find(job.mime, "^video/")
|
||||
local is_audio = string.find(job.mime, "^audio/")
|
||||
local is_image = string.find(job.mime, "^image/")
|
||||
local is_adobe = const.seekable_mimes[job.mime]
|
||||
|
||||
if is_adobe then
|
||||
return adobe:preload(job)
|
||||
elseif is_image then
|
||||
return image:preload(job)
|
||||
elseif is_video then
|
||||
return video:preload(job)
|
||||
elseif is_audio then
|
||||
return audio:preload(job)
|
||||
else
|
||||
cmd = "cd "
|
||||
.. path_quote(job.file.path or job.file.cache or (job.file.url.path or job.file.url).parent)
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. path_quote(tostring(job.file.path or job.file.cache or job.file.url.name))
|
||||
output, err = Command(SHELL)
|
||||
:arg({ "-c", cmd })
|
||||
:arg({ tostring(job.file.path or job.file.cache or (job.file.url.path or job.file.url)) })
|
||||
:output()
|
||||
return none_media_preview:preload(job)
|
||||
end
|
||||
if err then
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`, Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
function M:setup(opts)
|
||||
if not opts or type(opts) ~= "table" then
|
||||
return
|
||||
end
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
|
||||
if type(opts.skip_labels) == "table" then
|
||||
opts.skip_labels = utils.tbl_to_set(utils.tbl_unique_strings(opts.skip_labels))
|
||||
end
|
||||
|
||||
if type(opts.skip_section_labels) == "table" then
|
||||
opts.skip_section_labels = utils.tbl_to_set(utils.tbl_unique_strings(opts.skip_section_labels))
|
||||
end
|
||||
|
||||
utils.set_state(
|
||||
const.STATE_KEY.skip_labels,
|
||||
type(opts.skip_labels) == "table" and opts.skip_labels
|
||||
or (opts.skip_labels == false and {} or const.skip_labels)
|
||||
)
|
||||
|
||||
utils.set_state(
|
||||
const.STATE_KEY.skip_section_labels,
|
||||
type(opts.skip_section_labels) == "table" and opts.skip_section_labels or {}
|
||||
)
|
||||
end
|
||||
|
||||
function M:entry(job)
|
||||
local action = job.args[1]
|
||||
if action == const.ENTRY_ACTION.reset or job.args.reset then
|
||||
utils.set_state(const.STATE_KEY.no_preview, nil)
|
||||
utils.set_state(const.STATE_KEY.no_metadata, nil)
|
||||
ya.emit("peek", {
|
||||
force = true,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
if action == ENTRY_ACTION.toggle_metadata then
|
||||
set_state(STATE_KEY.hide_metadata, not get_state(STATE_KEY.hide_metadata))
|
||||
M:re_peek()
|
||||
if action == const.ENTRY_ACTION.show_metadata or job.args.show_metadata then
|
||||
utils.set_state(const.STATE_KEY.no_metadata, false)
|
||||
ya.emit("peek", {
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
|
||||
if action == const.ENTRY_ACTION.show_preview or job.args.show_preview then
|
||||
utils.set_state(const.STATE_KEY.no_preview, false)
|
||||
ya.emit("peek", {
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
|
||||
if action == const.ENTRY_ACTION.hide_metadata or job.args.hide_metadata then
|
||||
utils.set_state(const.STATE_KEY.no_metadata, true)
|
||||
ya.emit("peek", {
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
if action == const.ENTRY_ACTION.hide_preview or job.args.hide_preview then
|
||||
utils.set_state(const.STATE_KEY.no_preview, true)
|
||||
ya.emit("peek", {
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
|
||||
job.retry = job.retry or 0
|
||||
if job.retry > 5 then
|
||||
utils.error("Preview data is loading, try again later")
|
||||
return
|
||||
end
|
||||
|
||||
local current_file = utils.current_file()
|
||||
if not current_file then
|
||||
ya.sleep(0.1)
|
||||
job.retry = job.retry + 1
|
||||
return self:entry(job)
|
||||
end
|
||||
|
||||
local job_args = utils.get_states(const.STATE_KEY.cached_job_args, current_file)
|
||||
if not job_args then
|
||||
ya.sleep(0.1)
|
||||
job.retry = job.retry + 1
|
||||
return self:entry(job)
|
||||
end
|
||||
|
||||
if action == const.ENTRY_ACTION.toggle_metadata or job.args.toggle_metadata then
|
||||
utils.set_state(const.STATE_KEY.no_metadata, not job_args.no_metadata)
|
||||
ya.emit("peek", {
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
if action == const.ENTRY_ACTION.toggle_preview or job.args.toggle_preview then
|
||||
utils.set_state(const.STATE_KEY.no_preview, not job_args.no_preview)
|
||||
ya.emit("peek", {
|
||||
force = true,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
--- @since 26.1.22
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = ui.Line({
|
||||
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||
})
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local line_height = ui.height
|
||||
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||
if next_str == nil and line_height == 1 then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if (last_line + line_height) > job.skip then
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if (last_line + line_height) >= job.skip + limit then
|
||||
last_line = job.skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
last_line = last_line + line_height
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
|
||||
if EOF_mediainfo and #lines == 0 and job.skip > 0 then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
utils.force_render()
|
||||
-- Handle preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(job.area),
|
||||
ui.Text(lines):area(job.area):wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, {
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = job.area.h,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,185 @@
|
||||
--- @since 26.5.6
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
local opt = { ansi = true, tab_size = rt.preview.tab_size, wrap = rt.preview.wrap, width = max_width }
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = label .. ": " .. value
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = str
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local wrapped = ui.lines(line, opt)
|
||||
local line_height = #wrapped
|
||||
local from = 1
|
||||
local to = math.min(line_height, job.skip + limit - last_line)
|
||||
|
||||
local total_rendered_text_len = 1
|
||||
local total_label_rendered_len = 1
|
||||
local label_total_len = label and utf8.len(label .. ": ") or 0
|
||||
for j = from, to do
|
||||
local current_line_components = {}
|
||||
local wrapped_line_len = wrapped[j]:width() or 0
|
||||
local wrapped_raw =
|
||||
utils.utf8_sub(line, total_rendered_text_len, total_rendered_text_len + wrapped_line_len)
|
||||
wrapped_line_len = utf8.len(wrapped_raw)
|
||||
total_rendered_text_len = total_rendered_text_len + wrapped_line_len
|
||||
|
||||
if last_line + 1 > job.skip then
|
||||
local label_raw = label_total_len - total_label_rendered_len <= 0 and ""
|
||||
or utils.utf8_sub(wrapped_raw, 1, label_total_len - total_label_rendered_len)
|
||||
local label_raw_len = utf8.len(label_raw)
|
||||
if label_raw_len > 0 then
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(label_raw):style(ui.Style():fg("reset"):bold())
|
||||
)
|
||||
total_label_rendered_len = total_label_rendered_len + label_raw_len
|
||||
wrapped_raw = wrapped_raw:gsub("^" .. utils.is_literal_string(label_raw), "", 1)
|
||||
end
|
||||
if total_label_rendered_len >= label_total_len then
|
||||
local value_raw = wrapped_raw
|
||||
table.insert(
|
||||
current_line_components,
|
||||
ui.Span(value_raw or ""):style(
|
||||
label and (th.spot.tbl_col or ui.Style():fg("blue"))
|
||||
or (th.spot.title or ui.Style():fg("green"))
|
||||
)
|
||||
)
|
||||
end
|
||||
table.insert(lines, ui.Line(current_line_components))
|
||||
-- last_line = last_line + 1
|
||||
else
|
||||
total_label_rendered_len = total_label_rendered_len + total_rendered_text_len
|
||||
end
|
||||
last_line = last_line + 1
|
||||
if next_str == nil and not wrapped[j + 1] then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if last_line >= job.skip + limit then
|
||||
last_line = job.skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
|
||||
if EOF_mediainfo and #lines == 0 and job.skip > 0 then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
utils.force_render()
|
||||
-- Handle preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(job.area),
|
||||
ui.Text(lines):area(job.area):wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, {
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = job.area.h,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,127 @@
|
||||
--- @since 26.1.22
|
||||
|
||||
local const = require(".const")
|
||||
|
||||
local M = {}
|
||||
local MAX_ITEMS_IN_CACHE = 10
|
||||
|
||||
function M.is_valid_utf8(str)
|
||||
return utf8.len(str) ~= nil
|
||||
end
|
||||
|
||||
function M.utf8_sub(str, start_char, end_char)
|
||||
local start_byte = utf8.offset(str, start_char) -- Expects start_char to be a character index
|
||||
local end_byte = end_char and (utf8.offset(str, end_char + 1) or (#str + 1)) - 1 -- Expects end_char
|
||||
if not start_byte then
|
||||
return ""
|
||||
end
|
||||
return str:sub(start_byte, end_byte)
|
||||
end
|
||||
function M.is_literal_string(str)
|
||||
return str and str:gsub("([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
|
||||
end
|
||||
function M.path_quote(path)
|
||||
if not path or tostring(path) == "" then
|
||||
return path
|
||||
end
|
||||
local result = "'" .. string.gsub(tostring(path), "'", "'\\''") .. "'"
|
||||
return result
|
||||
end
|
||||
|
||||
M.force_render = ya.sync(function(_, _)
|
||||
(ui.render or ya.render)()
|
||||
end)
|
||||
|
||||
M.set_state = ya.sync(function(state, key, value)
|
||||
state[key] = value
|
||||
end)
|
||||
|
||||
M.get_state = ya.sync(function(state, key)
|
||||
return state[key]
|
||||
end)
|
||||
|
||||
M.set_states = ya.sync(function(state, namespace, key, value, limit_cached_items)
|
||||
if not limit_cached_items then
|
||||
limit_cached_items = MAX_ITEMS_IN_CACHE
|
||||
end
|
||||
if not state[namespace] then
|
||||
state[namespace] = {}
|
||||
end
|
||||
local storage = state[namespace]
|
||||
if limit_cached_items and #storage > limit_cached_items then
|
||||
table.remove(storage, 1)
|
||||
end
|
||||
storage[key] = value
|
||||
end)
|
||||
|
||||
M.get_states = ya.sync(function(state, namespace, key)
|
||||
local storage = state[namespace]
|
||||
if not storage then
|
||||
return nil
|
||||
end
|
||||
|
||||
if storage then
|
||||
return storage[key]
|
||||
end
|
||||
end)
|
||||
function M.read_mediainfo_cached_file(file_path)
|
||||
local cached = M.get_states(const.STATE_KEY.cached_mediainfo, file_path)
|
||||
if cached then
|
||||
return cached
|
||||
end
|
||||
-- Open the file in read mode
|
||||
local file = io.open(file_path, "r")
|
||||
|
||||
if file then
|
||||
-- Read the entire file content
|
||||
local content = file:read("*all")
|
||||
file:close()
|
||||
M.set_states(const.STATE_KEY.cached_mediainfo, file_path, content)
|
||||
return content
|
||||
end
|
||||
end
|
||||
|
||||
M.current_file = ya.sync(function()
|
||||
local h = cx.active.current.hovered
|
||||
if not h then
|
||||
return
|
||||
end
|
||||
return tostring(h.url)
|
||||
end)
|
||||
|
||||
function M.error(s, ...)
|
||||
ya.notify({ title = "mediainfo", content = string.format(s, ...), timeout = 3, level = "error" })
|
||||
end
|
||||
|
||||
function M.info(s, ...)
|
||||
ya.notify({ title = "mediainfo", content = string.format(s, ...), timeout = 3, level = "info" })
|
||||
end
|
||||
|
||||
--- return a string array with unique value
|
||||
---@param tbl string[]
|
||||
---@return string[] table with only unique strings
|
||||
function M.tbl_unique_strings(tbl)
|
||||
local unique_table = {}
|
||||
local seen = {}
|
||||
|
||||
for _, str in ipairs(tbl) do
|
||||
if not seen[str] then
|
||||
seen[str] = true
|
||||
table.insert(unique_table, str)
|
||||
end
|
||||
end
|
||||
|
||||
return unique_table
|
||||
end
|
||||
|
||||
function M.tbl_to_set(t1)
|
||||
local set = {}
|
||||
|
||||
for _, v in ipairs(t1) do
|
||||
set[v] = true
|
||||
end
|
||||
|
||||
return set
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,241 @@
|
||||
--- @since 26.1.22
|
||||
|
||||
local M = {}
|
||||
local const = require(".const")
|
||||
local utils = require(".utils")
|
||||
|
||||
function M:peek(job)
|
||||
local preload_status, preload_err = self:preload(job)
|
||||
-- Stop if preload failed
|
||||
if not preload_status then
|
||||
return
|
||||
end
|
||||
|
||||
local cache_img_url = ya.file_cache({
|
||||
skip = job.skip > 90 and 90 or job.skip,
|
||||
args = job.args,
|
||||
file = job.file,
|
||||
area = job.area,
|
||||
})
|
||||
local cache_img_url_no_skip = ya.file_cache({ file = job.file, skip = 0 })
|
||||
|
||||
local no_metadata = job.args.no_metadata
|
||||
local mediainfo_job_skip = job.skip
|
||||
::recalc_mediainfo_job_skip::
|
||||
local mediainfo_height = 0
|
||||
local lines = {}
|
||||
local limit = job.area.h
|
||||
local last_line = 0
|
||||
local EOF_mediainfo = true
|
||||
local is_wrap = rt.preview.wrap == "yes" or rt.preview.wrap == ui.Wrap.YES
|
||||
|
||||
if not no_metadata then
|
||||
local cache_mediainfo_path = tostring(cache_img_url_no_skip) .. const.suffix
|
||||
local output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
if output then
|
||||
local max_width = math.max(1, job.area.w)
|
||||
if output:match("^Error:") then
|
||||
job.args.force_reload_mediainfo = true
|
||||
preload_status, preload_err = self:preload(job)
|
||||
if not preload_status or preload_err then
|
||||
return
|
||||
end
|
||||
output = utils.read_mediainfo_cached_file(cache_mediainfo_path)
|
||||
end
|
||||
|
||||
output = output:gsub("\n+$", "")
|
||||
|
||||
local iter = output:gmatch("[^\n]*")
|
||||
local str = iter()
|
||||
|
||||
while str ~= nil do
|
||||
local next_str = iter()
|
||||
local label, value = str:match("(.*[^ ]) +: (.*)")
|
||||
local line
|
||||
if label then
|
||||
if not (utils.get_state(const.STATE_KEY.skip_labels) or const.skip_labels)[label] then
|
||||
line = ui.Line({
|
||||
ui.Span(label .. ": "):style(ui.Style():fg("reset"):bold()),
|
||||
ui.Span(value):style(th.spot.tbl_col or ui.Style():fg("blue")),
|
||||
})
|
||||
end
|
||||
else
|
||||
if not (utils.get_state(const.STATE_KEY.skip_section_labels) or {})[str] then
|
||||
line = ui.Line({ ui.Span(str):style(th.spot.title or ui.Style():fg("green")) })
|
||||
end
|
||||
end
|
||||
|
||||
if line then
|
||||
local line_height = ui.height
|
||||
and ui.height(str, { width = max_width, ansi = true, wrap = rt.preview.wrap })
|
||||
or (math.max(1, is_wrap and math.ceil(ui.width(line) / max_width) or 1))
|
||||
if next_str == nil and line_height == 1 then
|
||||
EOF_mediainfo = true
|
||||
end
|
||||
if (last_line + line_height) > mediainfo_job_skip then
|
||||
table.insert(lines, line)
|
||||
end
|
||||
if (last_line + line_height) >= mediainfo_job_skip + limit then
|
||||
last_line = mediainfo_job_skip + limit
|
||||
EOF_mediainfo = false
|
||||
break
|
||||
end
|
||||
last_line = last_line + line_height
|
||||
end
|
||||
str = next_str
|
||||
end
|
||||
end
|
||||
mediainfo_height = math.min(limit, last_line)
|
||||
end
|
||||
|
||||
if not no_metadata then
|
||||
if EOF_mediainfo and #lines == 0 and mediainfo_job_skip > 0 then
|
||||
if job.skip > 90 then
|
||||
ya.emit("peek", {
|
||||
math.max(0, (job.skip - (utils.get_state(const.STATE_KEY.units) or 0))),
|
||||
only_if = job.file.url,
|
||||
upper_bound = true,
|
||||
})
|
||||
return
|
||||
else
|
||||
-- NOTE: Recalculate mediainfo using cached latest valid skip value when reach the end of mediainfo output
|
||||
local last_valid_mediainfo_skip = utils.get_state(const.STATE_KEY.last_valid_mediainfo_skip)
|
||||
mediainfo_job_skip = last_valid_mediainfo_skip
|
||||
and last_valid_mediainfo_skip[tostring(cache_img_url_no_skip)]
|
||||
or math.max(0, mediainfo_job_skip - (utils.get_state(const.STATE_KEY.units) or 0))
|
||||
|
||||
goto recalc_mediainfo_job_skip
|
||||
end
|
||||
else
|
||||
utils.set_state(
|
||||
const.STATE_KEY.last_valid_mediainfo_skip,
|
||||
{ [tostring(cache_img_url_no_skip)] = mediainfo_job_skip }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
if utils.get_state(const.STATE_KEY.prev_metadata_area) then
|
||||
local old_metadata_area = utils.get_state(const.STATE_KEY.prev_metadata_area)
|
||||
if
|
||||
old_metadata_area.win_x == job.area.x
|
||||
and old_metadata_area.win_y == job.area.y
|
||||
and old_metadata_area.win_w == job.area.w
|
||||
and old_metadata_area.win_h == job.area.h
|
||||
then
|
||||
ya.preview_widget(job, {
|
||||
ui.Clear(ui.Rect({
|
||||
x = old_metadata_area.x,
|
||||
y = old_metadata_area.y,
|
||||
w = old_metadata_area.w,
|
||||
h = old_metadata_area.h,
|
||||
})),
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
utils.force_render()
|
||||
local rendered_img_rect = cache_img_url
|
||||
and fs.cha(cache_img_url)
|
||||
and ya.image_show(
|
||||
cache_img_url,
|
||||
ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y,
|
||||
w = job.area.w,
|
||||
h = mediainfo_height > 0 and math.max(job.area.h - mediainfo_height, job.area.h / 2) or job.area.h,
|
||||
})
|
||||
)
|
||||
or nil
|
||||
local image_height = rendered_img_rect and rendered_img_rect.h or 0
|
||||
|
||||
-- NOTE: Workaround case video.lua doesn't doesn't generate preview image because of `skip` overflow video duration
|
||||
if not rendered_img_rect then
|
||||
local prev_image_height = utils.get_state(const.STATE_KEY.prev_image_height)
|
||||
image_height = prev_image_height and prev_image_height[tostring(cache_img_url_no_skip)] or 0
|
||||
else
|
||||
utils.set_state(const.STATE_KEY.prev_image_height, { [tostring(cache_img_url_no_skip)] = image_height })
|
||||
end
|
||||
|
||||
-- Handle image preload error
|
||||
if preload_err then
|
||||
table.insert(lines, ui.Line(tostring(preload_err)):style(th.spot.title or ui.Style():fg("red")))
|
||||
end
|
||||
|
||||
ya.preview_widget(job, {
|
||||
ui.Text(lines)
|
||||
:area(ui.Rect({
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
}))
|
||||
:wrap(is_wrap and ui.Wrap.YES or ui.Wrap.NO),
|
||||
})
|
||||
|
||||
-- NOTE: Hacky way to prevent image overlap with old metadata area
|
||||
utils.set_state(const.STATE_KEY.prev_metadata_area, not no_metadata and {
|
||||
x = job.area.x,
|
||||
y = job.area.y + image_height,
|
||||
w = job.area.w,
|
||||
h = job.area.h - image_height,
|
||||
win_x = job.area.x,
|
||||
win_y = job.area.y,
|
||||
win_w = job.area.w,
|
||||
win_h = job.area.h,
|
||||
} or nil)
|
||||
end
|
||||
|
||||
function M:preload(job)
|
||||
local cmd = "mediainfo"
|
||||
local err_msg = ""
|
||||
|
||||
-- NOTE: Preload image from video
|
||||
|
||||
local cache_img_status, video_preload_err = require("video"):preload({
|
||||
skip = job.skip > 90 and 90 or job.skip,
|
||||
args = job.args,
|
||||
file = job.file,
|
||||
area = job.area,
|
||||
})
|
||||
|
||||
if not cache_img_status and video_preload_err then
|
||||
ya.dbg("mediainfo", video_preload_err)
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`.\n Do you have `%s` installed?\n", "ffmpeg", "ffmpeg")
|
||||
end
|
||||
|
||||
-- NOTE: Get mediainfo and save to cache folder
|
||||
local cache_mediainfo_url = Url(tostring(ya.file_cache({ file = job.file, skip = 0 })) .. const.suffix)
|
||||
local cache_mediainfo_cha = fs.cha(cache_mediainfo_url)
|
||||
-- Case peek function called preload to refetch mediainfo
|
||||
if cache_mediainfo_cha and not job.args.force_reload_mediainfo then
|
||||
return true, err_msg ~= "" and ("Error: " .. err_msg) or nil
|
||||
end
|
||||
|
||||
local output, err
|
||||
local is_valid_utf8_path = utils.is_valid_utf8(tostring(job.file.path or job.file.cache or job.file.url))
|
||||
if is_valid_utf8_path then
|
||||
output, err = Command(cmd)
|
||||
:arg({ tostring(job.file.path or job.file.cache or job.file.url.path or job.file.url) })
|
||||
:output()
|
||||
else
|
||||
cmd = "cd "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url.path or job.file.url).parent))
|
||||
.. " && "
|
||||
.. cmd
|
||||
.. " "
|
||||
.. utils.path_quote(tostring((job.file.path or job.file.cache or job.file.url).name))
|
||||
output, err = Command(const.SHELL):arg({ "-c", cmd }):output()
|
||||
end
|
||||
if err then
|
||||
ya.dbg("mediainfo", tostring(err))
|
||||
err_msg = err_msg .. string.format("Failed to start `%s`. \n Do you have `%s` installed?\n", cmd, cmd)
|
||||
end
|
||||
|
||||
return fs.write(
|
||||
cache_mediainfo_url,
|
||||
(err_msg ~= "" and ("Error: " .. err_msg) or "") .. (output and output.stdout or "")
|
||||
)
|
||||
end
|
||||
|
||||
return M
|
||||