restertet yazi setup

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

No files matched your search

+1
View File
@@ -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" }
]
# : }}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 KiB

File diff suppressed because it is too large. Load diff
Submodule yazi/.config/yazi/flavors/dracula.yazi deleted from 1b1a8d1083.
@@ -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" }
]
# : }}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

File diff suppressed because it is too large. Load diff
@@ -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
![gruvbox-material](./preview.png)
## 🎨 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" },
]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

File diff suppressed because it is too large. Load diff
@@ -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" },
]
Binary file not shown.

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>
Whitespace-only changes.
Whitespace-only changes.
@@ -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 },
]
Binary file not shown.

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" }
]
# : }}}
Binary file not shown.

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&#x2f;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" },
]
# : }}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 712 KiB

File diff suppressed because it is too large. Load diff
+62 -269
View File
@@ -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
-- },
},
})
Regular → Executable
+616 -495
View File
File diff suppressed because it is too large. Load diff
-163
View File
@@ -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
}
-103
View File
@@ -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"
-157
View File
@@ -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
-103
View File
@@ -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"
+24 -156
View File
@@ -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:
![list.png](list.png)
Tree mode:
![tree.png](tree.png)
## 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 }
+42 -15
View File
@@ -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
+19 -9
View File
@@ -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
![Before preview](statics/2024-11-17-12-06-24.png)
- Spotter
![Before spot](statics/2025-12-29-before_spotter.png)
### After:
- Previewer
![After previewer](statics/2024-11-21-05-27-48.png)
- Spotter
![After spotter](statics/2025-12-29-after_spotter.png)
## 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.
![full-border](https://github.com/yazi-rs/plugins/assets/17523360/ef81b560-2465-4d36-abf2-5d21dcb7b987)
## 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.
-255
View 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
+187 -451
View File
@@ -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
Loaded 100 of 226 files, more files were not shown because too many files have changed in this diff. Show more